1
1
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
2
use clippy_utils:: source:: snippet_with_applicability;
3
3
use 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 } ;
5
5
use rustc_errors:: Applicability ;
6
6
use rustc_hir as hir;
7
7
use 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 ;
9
13
10
14
use super :: USELESS_ASREF ;
11
15
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
+
12
41
/// Checks for the `USELESS_ASREF` lint.
13
42
pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > , call_name : & str , recvr : & hir:: Expr < ' _ > ) {
14
43
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
15
44
// 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 ) {
17
50
// check if the type after `as_ref` or `as_mut` is the same as before
18
51
let rcv_ty = cx. typeck_results ( ) . expr_ty ( recvr) ;
19
52
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,
39
72
applicability,
40
73
) ;
41
74
}
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
42
106
}
43
107
}
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