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,89 @@ 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+ && is_calling_clone ( cx, arg)
94+ {
95+ lint_as_ref_clone ( cx, expr. span . with_hi ( parent. span . hi ( ) ) , recvr, call_name) ;
96+ }
97+ }
98+ }
99+
100+ fn check_qpath ( cx : & LateContext < ' _ > , qpath : hir:: QPath < ' _ > , hir_id : hir:: HirId ) -> bool {
101+ // We check it's calling the `clone` method of the `Clone` trait.
102+ if let Some ( path_def_id) = cx. qpath_res ( & qpath, hir_id) . opt_def_id ( ) {
103+ match_def_path ( cx, path_def_id, & paths:: CLONE_TRAIT_METHOD )
104+ } else {
105+ false
42106 }
43107}
108+
109+ fn is_calling_clone ( cx : & LateContext < ' _ > , arg : & hir:: Expr < ' _ > ) -> bool {
110+ match arg. kind {
111+ hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) => {
112+ // If it's a closure, we need to check what is called.
113+ let closure_body = cx. tcx . hir ( ) . body ( body) ;
114+ let closure_expr = peel_blocks ( closure_body. value ) ;
115+ match closure_expr. kind {
116+ hir:: ExprKind :: MethodCall ( method, obj, [ ] , _) => {
117+ if method. ident . name == sym:: clone
118+ && let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( closure_expr. hir_id )
119+ && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
120+ // We check it's the `Clone` trait.
121+ && cx. tcx . lang_items ( ) . clone_trait ( ) . map_or ( false , |id| id == trait_id)
122+ // no autoderefs
123+ && !cx. typeck_results ( ) . expr_adjustments ( obj) . iter ( )
124+ . any ( |a| matches ! ( a. kind, Adjust :: Deref ( Some ( ..) ) ) )
125+ {
126+ true
127+ } else {
128+ false
129+ }
130+ } ,
131+ hir:: ExprKind :: Call ( call, [ _] ) => {
132+ if let hir:: ExprKind :: Path ( qpath) = call. kind {
133+ check_qpath ( cx, qpath, call. hir_id )
134+ } else {
135+ false
136+ }
137+ } ,
138+ _ => false ,
139+ }
140+ } ,
141+ hir:: ExprKind :: Path ( qpath) => check_qpath ( cx, qpath, arg. hir_id ) ,
142+ _ => false ,
143+ }
144+ }
145+
146+ fn lint_as_ref_clone ( cx : & LateContext < ' _ > , span : Span , recvr : & hir:: Expr < ' _ > , call_name : & str ) {
147+ let mut applicability = Applicability :: MachineApplicable ;
148+ span_lint_and_sugg (
149+ cx,
150+ USELESS_ASREF ,
151+ span,
152+ & format ! ( "this call to `{call_name}.map(...)` does nothing" ) ,
153+ "try" ,
154+ format ! (
155+ "{}.clone()" ,
156+ snippet_with_applicability( cx, recvr. span, ".." , & mut applicability)
157+ ) ,
158+ applicability,
159+ ) ;
160+ }
0 commit comments