11use clippy_utils:: diagnostics:: span_lint_and_sugg;
22use clippy_utils:: source:: snippet_with_applicability;
33use clippy_utils:: ty:: walk_ptrs_ty_depth;
4- use clippy_utils:: { get_parent_expr, is_trait_method } ;
4+ use clippy_utils:: { get_parent_expr, is_diag_trait_item , match_def_path , paths , peel_blocks } ;
55use rustc_errors:: Applicability ;
66use rustc_hir as hir;
77use rustc_lint:: LateContext ;
8- use rustc_span:: sym;
8+ use rustc_middle:: ty:: adjustment:: Adjust ;
9+ use rustc_middle:: ty:: { Ty , TyCtxt , TypeSuperVisitable , TypeVisitable , TypeVisitor } ;
10+ use rustc_span:: { sym, Span } ;
11+
12+ use core:: ops:: ControlFlow ;
913
1014use super :: USELESS_ASREF ;
1115
16+ /// Returns the first type inside the `Option`/`Result` type passed as argument.
17+ fn get_enum_ty ( enum_ty : Ty < ' _ > ) -> Option < Ty < ' _ > > {
18+ struct ContainsTyVisitor {
19+ level : usize ,
20+ }
21+
22+ impl < ' tcx > TypeVisitor < TyCtxt < ' tcx > > for ContainsTyVisitor {
23+ type BreakTy = Ty < ' tcx > ;
24+
25+ fn visit_ty ( & mut self , t : Ty < ' tcx > ) -> ControlFlow < Self :: BreakTy > {
26+ self . level += 1 ;
27+ if self . level == 1 {
28+ t. super_visit_with ( self )
29+ } else {
30+ ControlFlow :: Break ( t)
31+ }
32+ }
33+ }
34+
35+ match enum_ty. visit_with ( & mut ContainsTyVisitor { level : 0 } ) {
36+ ControlFlow :: Break ( ty) => Some ( ty) ,
37+ ControlFlow :: Continue ( ( ) ) => None ,
38+ }
39+ }
40+
1241/// Checks for the `USELESS_ASREF` lint.
1342pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > , call_name : & str , recvr : & hir:: Expr < ' _ > ) {
1443 // when we get here, we've already checked that the call name is "as_ref" or "as_mut"
1544 // check if the call is to the actual `AsRef` or `AsMut` trait
16- if is_trait_method ( cx, expr, sym:: AsRef ) || is_trait_method ( cx, expr, sym:: AsMut ) {
45+ let Some ( def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id ) else {
46+ return ;
47+ } ;
48+
49+ if is_diag_trait_item ( cx, def_id, sym:: AsRef ) || is_diag_trait_item ( cx, def_id, sym:: AsMut ) {
1750 // check if the type after `as_ref` or `as_mut` is the same as before
1851 let rcv_ty = cx. typeck_results ( ) . expr_ty ( recvr) ;
1952 let res_ty = cx. typeck_results ( ) . expr_ty ( expr) ;
@@ -39,5 +72,101 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
3972 applicability,
4073 ) ;
4174 }
75+ } else if match_def_path ( cx, def_id, & [ "core" , "option" , "Option" , call_name] )
76+ || match_def_path ( cx, def_id, & [ "core" , "result" , "Result" , call_name] )
77+ {
78+ let rcv_ty = cx. typeck_results ( ) . expr_ty ( recvr) . peel_refs ( ) ;
79+ let res_ty = cx. typeck_results ( ) . expr_ty ( expr) . peel_refs ( ) ;
80+
81+ if let Some ( rcv_ty) = get_enum_ty ( rcv_ty)
82+ && let Some ( res_ty) = get_enum_ty ( res_ty)
83+ // If the only thing the `as_mut`/`as_ref` call is doing is adding references and not
84+ // changing the type, then we can move forward.
85+ && rcv_ty. peel_refs ( ) == res_ty. peel_refs ( )
86+ && let Some ( parent) = get_parent_expr ( cx, expr)
87+ && let hir:: ExprKind :: MethodCall ( segment, _, args, _) = parent. kind
88+ && segment. ident . span != expr. span
89+ // We check that the called method name is `map`.
90+ && segment. ident . name == sym:: map
91+ // And that it only has one argument.
92+ && let [ arg] = args
93+ {
94+ match arg. kind {
95+ hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) => {
96+ // If it's a closure, we need to check what is called.
97+ let closure_body = cx. tcx . hir ( ) . body ( body) ;
98+ let closure_expr = peel_blocks ( closure_body. value ) ;
99+ match closure_expr. kind {
100+ hir:: ExprKind :: MethodCall ( method, obj, [ ] , _) => {
101+ if method. ident . name == sym:: clone
102+ && let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( closure_expr. hir_id )
103+ && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
104+ // We check it's the `Clone` trait.
105+ && cx. tcx . lang_items ( ) . clone_trait ( ) . map_or ( false , |id| id == trait_id)
106+ // no autoderefs
107+ && !cx. typeck_results ( ) . expr_adjustments ( obj) . iter ( )
108+ . any ( |a| matches ! ( a. kind, Adjust :: Deref ( Some ( ..) ) ) )
109+ {
110+ lint_as_ref_clone ( cx, expr. span . with_hi ( parent. span . hi ( ) ) , recvr, call_name) ;
111+ }
112+ } ,
113+ hir:: ExprKind :: Call ( call, [ _] ) => {
114+ if let hir:: ExprKind :: Path ( qpath) = call. kind {
115+ check_qpath (
116+ cx,
117+ expr. span . with_hi ( parent. span . hi ( ) ) ,
118+ recvr,
119+ call_name,
120+ qpath,
121+ call. hir_id ,
122+ ) ;
123+ }
124+ } ,
125+ _ => { } ,
126+ }
127+ } ,
128+ hir:: ExprKind :: Path ( qpath) => check_qpath (
129+ cx,
130+ expr. span . with_hi ( parent. span . hi ( ) ) ,
131+ recvr,
132+ call_name,
133+ qpath,
134+ arg. hir_id ,
135+ ) ,
136+ _ => { } ,
137+ }
138+ }
42139 }
43140}
141+
142+ fn check_qpath (
143+ cx : & LateContext < ' _ > ,
144+ span : Span ,
145+ recvr : & hir:: Expr < ' _ > ,
146+ call_name : & str ,
147+ qpath : hir:: QPath < ' _ > ,
148+ hir_id : hir:: HirId ,
149+ ) {
150+ // We check it's calling the `clone` method of the `Clone` trait.
151+ if let Some ( path_def_id) = cx. qpath_res ( & qpath, hir_id) . opt_def_id ( )
152+ && match_def_path ( cx, path_def_id, & paths:: CLONE_TRAIT_METHOD )
153+ {
154+ lint_as_ref_clone ( cx, span, recvr, call_name) ;
155+ }
156+ }
157+
158+ fn lint_as_ref_clone ( cx : & LateContext < ' _ > , span : Span , recvr : & hir:: Expr < ' _ > , call_name : & str ) {
159+ let mut applicability = Applicability :: MachineApplicable ;
160+ span_lint_and_sugg (
161+ cx,
162+ USELESS_ASREF ,
163+ span,
164+ & format ! ( "this call to `{call_name}.map(...)` does nothing" ) ,
165+ "try" ,
166+ format ! (
167+ "{}.clone()" ,
168+ snippet_with_applicability( cx, recvr. span, ".." , & mut applicability)
169+ ) ,
170+ applicability,
171+ ) ;
172+ }
0 commit comments