1
1
use super :: EXPLICIT_ITER_LOOP ;
2
2
use clippy_utils:: diagnostics:: span_lint_and_sugg;
3
3
use clippy_utils:: is_trait_method;
4
+ use clippy_utils:: msrvs:: { self , Msrv } ;
4
5
use clippy_utils:: source:: snippet_with_applicability;
5
- use clippy_utils:: ty:: is_type_diagnostic_item;
6
+ use clippy_utils:: ty:: { implements_trait_with_env, make_normalized_projection_with_regions, normalize_with_regions,
7
+ make_normalized_projection, implements_trait, is_copy} ;
6
8
use rustc_errors:: Applicability ;
7
9
use rustc_hir:: { Expr , Mutability } ;
8
10
use rustc_lint:: LateContext ;
9
- use rustc_middle:: ty:: { self , Ty } ;
11
+ use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow , AutoBorrowMutability } ;
12
+ use rustc_middle:: ty:: { self , EarlyBinder , TypeAndMut , Ty } ;
10
13
use rustc_span:: sym;
11
14
12
- pub ( super ) fn check ( cx : & LateContext < ' _ > , self_arg : & Expr < ' _ > , arg : & Expr < ' _ > , method_name : & str ) {
13
- let should_lint = match method_name {
14
- "iter" | "iter_mut" => is_ref_iterable_type ( cx, self_arg) ,
15
- "into_iter" if is_trait_method ( cx, arg, sym:: IntoIterator ) => {
15
+ pub ( super ) fn check ( cx : & LateContext < ' _ > , self_arg : & Expr < ' _ > , call_expr : & Expr < ' _ > , method_name : & str , msrv : & Msrv ) {
16
+ let borrow_kind = match method_name {
17
+ "iter" | "iter_mut" => match is_ref_iterable ( cx, self_arg, call_expr) {
18
+ Some ( ( kind, ty) ) => {
19
+ if let ty:: Array ( _, count) = * ty. peel_refs ( ) . kind ( ) {
20
+ if !ty. is_ref ( ) {
21
+ if !msrv. meets ( msrvs:: ARRAY_INTO_ITERATOR ) {
22
+ return ;
23
+ }
24
+ } else if count. try_eval_target_usize ( cx. tcx , cx. param_env ) . map_or ( true , |x| x > 32 )
25
+ && !msrv. meets ( msrvs:: ARRAY_IMPL_ANY_LEN )
26
+ {
27
+ return
28
+ }
29
+ }
30
+ kind
31
+ } ,
32
+ None => return ,
33
+ } ,
34
+ "into_iter" if is_trait_method ( cx, call_expr, sym:: IntoIterator ) => {
16
35
let receiver_ty = cx. typeck_results ( ) . expr_ty ( self_arg) ;
17
36
let receiver_ty_adjusted = cx. typeck_results ( ) . expr_ty_adjusted ( self_arg) ;
18
37
let ref_receiver_ty = cx. tcx . mk_ref (
@@ -22,54 +41,159 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m
22
41
mutbl : Mutability :: Not ,
23
42
} ,
24
43
) ;
25
- receiver_ty_adjusted == ref_receiver_ty
44
+ if receiver_ty_adjusted == ref_receiver_ty {
45
+ AdjustKind :: None
46
+ } else {
47
+ return ;
48
+ }
26
49
} ,
27
- _ => false ,
50
+ _ => return ,
28
51
} ;
29
52
30
- if !should_lint {
31
- return ;
32
- }
33
-
34
53
let mut applicability = Applicability :: MachineApplicable ;
35
54
let object = snippet_with_applicability ( cx, self_arg. span , "_" , & mut applicability) ;
36
- let muta = if method_name == "iter_mut" { "mut " } else { "" } ;
55
+ let prefix = match borrow_kind {
56
+ AdjustKind :: None => "" ,
57
+ AdjustKind :: Borrow => "&" ,
58
+ AdjustKind :: BorrowMut => "&mut " ,
59
+ AdjustKind :: Deref => "*" ,
60
+ AdjustKind :: Reborrow => "&*" ,
61
+ AdjustKind :: ReborrowMut => "&mut *" ,
62
+ } ;
37
63
span_lint_and_sugg (
38
64
cx,
39
65
EXPLICIT_ITER_LOOP ,
40
- arg . span ,
66
+ call_expr . span ,
41
67
"it is more concise to loop over references to containers instead of using explicit \
42
68
iteration methods",
43
69
"to write this more concisely, try" ,
44
- format ! ( "&{muta }{object}" ) ,
70
+ format ! ( "{prefix }{object}" ) ,
45
71
applicability,
46
72
) ;
47
73
}
48
74
49
- /// Returns `true` if the type of expr is one that provides `IntoIterator` impls
50
- /// for `&T` and `&mut T`, such as `Vec`.
51
- #[ rustfmt:: skip]
52
- fn is_ref_iterable_type ( cx : & LateContext < ' _ > , e : & Expr < ' _ > ) -> bool {
53
- // no walk_ptrs_ty: calling iter() on a reference can make sense because it
54
- // will allow further borrows afterwards
55
- let ty = cx. typeck_results ( ) . expr_ty ( e) ;
56
- is_iterable_array ( ty, cx) ||
57
- is_type_diagnostic_item ( cx, ty, sym:: Vec ) ||
58
- is_type_diagnostic_item ( cx, ty, sym:: LinkedList ) ||
59
- is_type_diagnostic_item ( cx, ty, sym:: HashMap ) ||
60
- is_type_diagnostic_item ( cx, ty, sym:: HashSet ) ||
61
- is_type_diagnostic_item ( cx, ty, sym:: VecDeque ) ||
62
- is_type_diagnostic_item ( cx, ty, sym:: BinaryHeap ) ||
63
- is_type_diagnostic_item ( cx, ty, sym:: BTreeMap ) ||
64
- is_type_diagnostic_item ( cx, ty, sym:: BTreeSet )
75
+ enum AdjustKind {
76
+ None ,
77
+ Borrow ,
78
+ BorrowMut ,
79
+ Deref ,
80
+ Reborrow ,
81
+ ReborrowMut ,
82
+ }
83
+ impl AdjustKind {
84
+ fn borrow ( mutbl : Mutability ) -> Self {
85
+ match mutbl {
86
+ Mutability :: Not => Self :: Borrow ,
87
+ Mutability :: Mut => Self :: BorrowMut ,
88
+ }
89
+ }
90
+
91
+ fn auto_borrow ( mutbl : AutoBorrowMutability ) -> Self {
92
+ match mutbl {
93
+ AutoBorrowMutability :: Not => Self :: Borrow ,
94
+ AutoBorrowMutability :: Mut { .. } => Self :: BorrowMut ,
95
+ }
96
+ }
97
+
98
+ fn reborrow ( mutbl : AutoBorrowMutability ) -> Self {
99
+ match mutbl {
100
+ AutoBorrowMutability :: Not => Self :: Reborrow ,
101
+ AutoBorrowMutability :: Mut { .. } => Self :: ReborrowMut ,
102
+ }
103
+ }
65
104
}
66
105
67
- fn is_iterable_array < ' tcx > ( ty : Ty < ' tcx > , cx : & LateContext < ' tcx > ) -> bool {
68
- // IntoIterator is currently only implemented for array sizes <= 32 in rustc
69
- match ty. kind ( ) {
70
- ty:: Array ( _, n) => n
71
- . try_eval_target_usize ( cx. tcx , cx. param_env )
72
- . map_or ( false , |val| ( 0 ..=32 ) . contains ( & val) ) ,
73
- _ => false ,
106
+ /// Checks if an `iter` or `iter_mut` call returns `IntoIterator::IntoIter`. Returns how the
107
+ /// argument needs to be adjusted.
108
+ fn is_ref_iterable < ' tcx > ( cx : & LateContext < ' tcx > , self_arg : & Expr < ' _ > , call_expr : & Expr < ' _ > ) -> Option < ( AdjustKind , Ty < ' tcx > ) > {
109
+ let typeck = cx. typeck_results ( ) ;
110
+ if let Some ( trait_id) = cx. tcx . get_diagnostic_item ( sym:: IntoIterator )
111
+ && let Some ( fn_id) = typeck. type_dependent_def_id ( call_expr. hir_id )
112
+ && let sig = cx. tcx . liberate_late_bound_regions ( fn_id, cx. tcx . fn_sig ( fn_id) . skip_binder ( ) )
113
+ && let & [ req_self_ty, req_res_ty] = & * * sig. inputs_and_output
114
+ && let param_env = cx. tcx . param_env ( fn_id)
115
+ && implements_trait_with_env ( cx. tcx , param_env, req_self_ty, trait_id, [ ] )
116
+ && let Some ( into_iter_ty) =
117
+ make_normalized_projection_with_regions ( cx. tcx , param_env, trait_id, sym ! ( IntoIter ) , [ req_self_ty] )
118
+ && let req_res_ty = normalize_with_regions ( cx. tcx , param_env, req_res_ty)
119
+ && into_iter_ty == req_res_ty
120
+ {
121
+ let adjustments = typeck. expr_adjustments ( self_arg) ;
122
+ let self_ty = typeck. expr_ty ( self_arg) ;
123
+ let self_is_copy = is_copy ( cx, self_ty) ;
124
+
125
+ if adjustments. is_empty ( ) && self_is_copy {
126
+ return Some ( ( AdjustKind :: None , self_ty) ) ;
127
+ }
128
+
129
+ let res_ty = cx. tcx . erase_regions ( EarlyBinder :: bind ( req_res_ty) . subst ( cx. tcx , typeck. node_substs ( call_expr. hir_id ) ) ) ;
130
+ if !adjustments. is_empty ( ) && self_is_copy {
131
+ if implements_trait ( cx, self_ty, trait_id, & [ ] )
132
+ && let Some ( ty) = make_normalized_projection ( cx. tcx , cx. param_env , trait_id, sym ! ( IntoIter ) , [ self_ty] )
133
+ && ty == res_ty
134
+ {
135
+ return Some ( ( AdjustKind :: None , self_ty) ) ;
136
+ }
137
+ }
138
+
139
+ let mutbl = if let ty:: Ref ( _, _, mutbl) = * req_self_ty. kind ( ) {
140
+ Some ( mutbl)
141
+ } else {
142
+ None
143
+ } ;
144
+ if let Some ( mutbl) = mutbl
145
+ && !self_ty. is_ref ( )
146
+ {
147
+ let self_ty = cx. tcx . mk_ref ( cx. tcx . lifetimes . re_erased , TypeAndMut {
148
+ ty : self_ty,
149
+ mutbl,
150
+ } ) ;
151
+ if implements_trait ( cx, self_ty, trait_id, & [ ] )
152
+ && let Some ( ty) = make_normalized_projection ( cx. tcx , cx. param_env , trait_id, sym ! ( IntoIter ) , [ self_ty] )
153
+ && ty == res_ty
154
+ {
155
+ return Some ( ( AdjustKind :: borrow ( mutbl) , self_ty) ) ;
156
+ }
157
+ }
158
+
159
+ match adjustments {
160
+ [ ] => Some ( ( AdjustKind :: None , self_ty) ) ,
161
+ & [ Adjustment { kind : Adjust :: Deref ( _) , ..} , Adjustment { kind : Adjust :: Borrow ( AutoBorrow :: Ref ( _, mutbl) ) , target } , ..] => {
162
+ if target != self_ty
163
+ && implements_trait ( cx, target, trait_id, & [ ] )
164
+ && let Some ( ty) = make_normalized_projection ( cx. tcx , cx. param_env , trait_id, sym ! ( IntoIter ) , [ target] )
165
+ && ty == res_ty
166
+ {
167
+ Some ( ( AdjustKind :: reborrow ( mutbl) , target) )
168
+ } else {
169
+ None
170
+ }
171
+ }
172
+ & [ Adjustment { kind : Adjust :: Deref ( _) , target } , ..] => {
173
+ if is_copy ( cx, target)
174
+ && implements_trait ( cx, target, trait_id, & [ ] )
175
+ && let Some ( ty) = make_normalized_projection ( cx. tcx , cx. param_env , trait_id, sym ! ( IntoIter ) , [ target] )
176
+ && ty == res_ty
177
+ {
178
+ Some ( ( AdjustKind :: Deref , target) )
179
+ } else {
180
+ None
181
+ }
182
+ }
183
+ & [ Adjustment { kind : Adjust :: Borrow ( AutoBorrow :: Ref ( _, mutbl) ) , target } , ..] => {
184
+ if self_ty. is_ref ( )
185
+ && implements_trait ( cx, target, trait_id, & [ ] )
186
+ && let Some ( ty) = make_normalized_projection ( cx. tcx , cx. param_env , trait_id, sym ! ( IntoIter ) , [ target] )
187
+ && ty == res_ty
188
+ {
189
+ Some ( ( AdjustKind :: auto_borrow ( mutbl) , target) )
190
+ } else {
191
+ None
192
+ }
193
+ }
194
+ _ => None ,
195
+ }
196
+ } else {
197
+ None
74
198
}
75
199
}
0 commit comments