11use super :: EXPLICIT_ITER_LOOP ;
22use clippy_utils:: diagnostics:: span_lint_and_sugg;
33use clippy_utils:: is_trait_method;
4+ use clippy_utils:: msrvs:: { self , Msrv } ;
45use 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} ;
68use rustc_errors:: Applicability ;
79use rustc_hir:: { Expr , Mutability } ;
810use 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 } ;
1013use rustc_span:: sym;
1114
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 ) => {
1635 let receiver_ty = cx. typeck_results ( ) . expr_ty ( self_arg) ;
1736 let receiver_ty_adjusted = cx. typeck_results ( ) . expr_ty_adjusted ( self_arg) ;
1837 let ref_receiver_ty = cx. tcx . mk_ref (
@@ -22,54 +41,159 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m
2241 mutbl : Mutability :: Not ,
2342 } ,
2443 ) ;
25- receiver_ty_adjusted == ref_receiver_ty
44+ if receiver_ty_adjusted == ref_receiver_ty {
45+ AdjustKind :: None
46+ } else {
47+ return ;
48+ }
2649 } ,
27- _ => false ,
50+ _ => return ,
2851 } ;
2952
30- if !should_lint {
31- return ;
32- }
33-
3453 let mut applicability = Applicability :: MachineApplicable ;
3554 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+ } ;
3763 span_lint_and_sugg (
3864 cx,
3965 EXPLICIT_ITER_LOOP ,
40- arg . span ,
66+ call_expr . span ,
4167 "it is more concise to loop over references to containers instead of using explicit \
4268 iteration methods",
4369 "to write this more concisely, try" ,
44- format ! ( "&{muta }{object}" ) ,
70+ format ! ( "{prefix }{object}" ) ,
4571 applicability,
4672 ) ;
4773}
4874
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+ }
65104}
66105
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
74198 }
75199}
0 commit comments