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,101 @@ 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
+ {
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
+ }
42
139
}
43
140
}
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