]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/needless_range_loop.rs
Auto merge of #92805 - BoxyUwU:revert-lazy-anon-const-substs, r=lcnr
[rust.git] / clippy_lints / src / loops / needless_range_loop.rs
1 use super::NEEDLESS_RANGE_LOOP;
2 use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
3 use clippy_utils::source::snippet;
4 use clippy_utils::ty::has_iter_method;
5 use clippy_utils::visitors::is_local_used;
6 use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
7 use if_chain::if_chain;
8 use rustc_ast::ast;
9 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
10 use rustc_hir::def::{DefKind, Res};
11 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
12 use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
13 use rustc_lint::LateContext;
14 use rustc_middle::hir::map::Map;
15 use rustc_middle::middle::region;
16 use rustc_middle::ty::{self, Ty};
17 use rustc_span::symbol::{sym, Symbol};
18 use std::iter::{self, Iterator};
19 use std::mem;
20
21 /// Checks for looping over a range and then indexing a sequence with it.
22 /// The iteratee must be a range literal.
23 #[allow(clippy::too_many_lines)]
24 pub(super) fn check<'tcx>(
25     cx: &LateContext<'tcx>,
26     pat: &'tcx Pat<'_>,
27     arg: &'tcx Expr<'_>,
28     body: &'tcx Expr<'_>,
29     expr: &'tcx Expr<'_>,
30 ) {
31     if let Some(higher::Range {
32         start: Some(start),
33         ref end,
34         limits,
35     }) = higher::Range::hir(arg)
36     {
37         // the var must be a single name
38         if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
39             let mut visitor = VarVisitor {
40                 cx,
41                 var: canonical_id,
42                 indexed_mut: FxHashSet::default(),
43                 indexed_indirectly: FxHashMap::default(),
44                 indexed_directly: FxHashMap::default(),
45                 referenced: FxHashSet::default(),
46                 nonindex: false,
47                 prefer_mutable: false,
48             };
49             walk_expr(&mut visitor, body);
50
51             // linting condition: we only indexed one variable, and indexed it directly
52             if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
53                 let (indexed, (indexed_extent, indexed_ty)) = visitor
54                     .indexed_directly
55                     .into_iter()
56                     .next()
57                     .expect("already checked that we have exactly 1 element");
58
59                 // ensure that the indexed variable was declared before the loop, see #601
60                 if let Some(indexed_extent) = indexed_extent {
61                     let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id);
62                     let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
63                     let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id);
64                     if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
65                         return;
66                     }
67                 }
68
69                 // don't lint if the container that is indexed does not have .iter() method
70                 let has_iter = has_iter_method(cx, indexed_ty);
71                 if has_iter.is_none() {
72                     return;
73                 }
74
75                 // don't lint if the container that is indexed into is also used without
76                 // indexing
77                 if visitor.referenced.contains(&indexed) {
78                     return;
79                 }
80
81                 let starts_at_zero = is_integer_const(cx, start, 0);
82
83                 let skip = if starts_at_zero {
84                     String::new()
85                 } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) {
86                     return;
87                 } else {
88                     format!(".skip({})", snippet(cx, start.span, ".."))
89                 };
90
91                 let mut end_is_start_plus_val = false;
92
93                 let take = if let Some(end) = *end {
94                     let mut take_expr = end;
95
96                     if let ExprKind::Binary(ref op, left, right) = end.kind {
97                         if op.node == BinOpKind::Add {
98                             let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
99                             let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
100
101                             if start_equal_left {
102                                 take_expr = right;
103                             } else if start_equal_right {
104                                 take_expr = left;
105                             }
106
107                             end_is_start_plus_val = start_equal_left | start_equal_right;
108                         }
109                     }
110
111                     if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
112                         String::new()
113                     } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
114                         return;
115                     } else {
116                         match limits {
117                             ast::RangeLimits::Closed => {
118                                 let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
119                                 format!(".take({})", take_expr + sugg::ONE)
120                             },
121                             ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")),
122                         }
123                     }
124                 } else {
125                     String::new()
126                 };
127
128                 let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
129                     ("mut ", "iter_mut")
130                 } else {
131                     ("", "iter")
132                 };
133
134                 let take_is_empty = take.is_empty();
135                 let mut method_1 = take;
136                 let mut method_2 = skip;
137
138                 if end_is_start_plus_val {
139                     mem::swap(&mut method_1, &mut method_2);
140                 }
141
142                 if visitor.nonindex {
143                     span_lint_and_then(
144                         cx,
145                         NEEDLESS_RANGE_LOOP,
146                         arg.span,
147                         &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
148                         |diag| {
149                             multispan_sugg(
150                                 diag,
151                                 "consider using an iterator",
152                                 vec![
153                                     (pat.span, format!("({}, <item>)", ident.name)),
154                                     (
155                                         arg.span,
156                                         format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
157                                     ),
158                                 ],
159                             );
160                         },
161                     );
162                 } else {
163                     let repl = if starts_at_zero && take_is_empty {
164                         format!("&{}{}", ref_mut, indexed)
165                     } else {
166                         format!("{}.{}(){}{}", indexed, method, method_1, method_2)
167                     };
168
169                     span_lint_and_then(
170                         cx,
171                         NEEDLESS_RANGE_LOOP,
172                         arg.span,
173                         &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
174                         |diag| {
175                             multispan_sugg(
176                                 diag,
177                                 "consider using an iterator",
178                                 vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
179                             );
180                         },
181                     );
182                 }
183             }
184         }
185     }
186 }
187
188 fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
189     if_chain! {
190         if let ExprKind::MethodCall(method, _, len_args, _) = expr.kind;
191         if len_args.len() == 1;
192         if method.ident.name == sym::len;
193         if let ExprKind::Path(QPath::Resolved(_, path)) = len_args[0].kind;
194         if path.segments.len() == 1;
195         if path.segments[0].ident.name == var;
196         then {
197             return true;
198         }
199     }
200
201     false
202 }
203
204 fn is_end_eq_array_len<'tcx>(
205     cx: &LateContext<'tcx>,
206     end: &Expr<'_>,
207     limits: ast::RangeLimits,
208     indexed_ty: Ty<'tcx>,
209 ) -> bool {
210     if_chain! {
211         if let ExprKind::Lit(ref lit) = end.kind;
212         if let ast::LitKind::Int(end_int, _) = lit.node;
213         if let ty::Array(_, arr_len_const) = indexed_ty.kind();
214         if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env);
215         then {
216             return match limits {
217                 ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
218                 ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
219             };
220         }
221     }
222
223     false
224 }
225
226 struct VarVisitor<'a, 'tcx> {
227     /// context reference
228     cx: &'a LateContext<'tcx>,
229     /// var name to look for as index
230     var: HirId,
231     /// indexed variables that are used mutably
232     indexed_mut: FxHashSet<Symbol>,
233     /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global
234     indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
235     /// subset of `indexed` of vars that are indexed directly: `v[i]`
236     /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
237     indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
238     /// Any names that are used outside an index operation.
239     /// Used to detect things like `&mut vec` used together with `vec[i]`
240     referenced: FxHashSet<Symbol>,
241     /// has the loop variable been used in expressions other than the index of
242     /// an index op?
243     nonindex: bool,
244     /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar
245     /// takes `&mut self`
246     prefer_mutable: bool,
247 }
248
249 impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
250     fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
251         if_chain! {
252             // the indexed container is referenced by a name
253             if let ExprKind::Path(ref seqpath) = seqexpr.kind;
254             if let QPath::Resolved(None, seqvar) = *seqpath;
255             if seqvar.segments.len() == 1;
256             if is_local_used(self.cx, idx, self.var);
257             then {
258                 if self.prefer_mutable {
259                     self.indexed_mut.insert(seqvar.segments[0].ident.name);
260                 }
261                 let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
262                 let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
263                 match res {
264                     Res::Local(hir_id) => {
265                         let parent_def_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
266                         let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
267                         if index_used_directly {
268                             self.indexed_directly.insert(
269                                 seqvar.segments[0].ident.name,
270                                 (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
271                             );
272                         } else {
273                             self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
274                         }
275                         return false;  // no need to walk further *on the variable*
276                     }
277                     Res::Def(DefKind::Static | DefKind::Const, ..) => {
278                         if index_used_directly {
279                             self.indexed_directly.insert(
280                                 seqvar.segments[0].ident.name,
281                                 (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
282                             );
283                         } else {
284                             self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
285                         }
286                         return false;  // no need to walk further *on the variable*
287                     }
288                     _ => (),
289                 }
290             }
291         }
292         true
293     }
294 }
295
296 impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
297     type Map = Map<'tcx>;
298
299     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
300         if_chain! {
301             // a range index op
302             if let ExprKind::MethodCall(meth, _, [args_0, args_1, ..], _) = &expr.kind;
303             if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
304                 || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
305             if !self.check(args_1, args_0, expr);
306             then { return }
307         }
308
309         if_chain! {
310             // an index op
311             if let ExprKind::Index(seqexpr, idx) = expr.kind;
312             if !self.check(idx, seqexpr, expr);
313             then { return }
314         }
315
316         if_chain! {
317             // directly using a variable
318             if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
319             if let Res::Local(local_id) = path.res;
320             then {
321                 if local_id == self.var {
322                     self.nonindex = true;
323                 } else {
324                     // not the correct variable, but still a variable
325                     self.referenced.insert(path.segments[0].ident.name);
326                 }
327             }
328         }
329
330         let old = self.prefer_mutable;
331         match expr.kind {
332             ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
333                 self.prefer_mutable = true;
334                 self.visit_expr(lhs);
335                 self.prefer_mutable = false;
336                 self.visit_expr(rhs);
337             },
338             ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
339                 if mutbl == Mutability::Mut {
340                     self.prefer_mutable = true;
341                 }
342                 self.visit_expr(expr);
343             },
344             ExprKind::Call(f, args) => {
345                 self.visit_expr(f);
346                 for expr in args {
347                     let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
348                     self.prefer_mutable = false;
349                     if let ty::Ref(_, _, mutbl) = *ty.kind() {
350                         if mutbl == Mutability::Mut {
351                             self.prefer_mutable = true;
352                         }
353                     }
354                     self.visit_expr(expr);
355                 }
356             },
357             ExprKind::MethodCall(_, _, args, _) => {
358                 let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
359                 for (ty, expr) in iter::zip(self.cx.tcx.fn_sig(def_id).inputs().skip_binder(), args) {
360                     self.prefer_mutable = false;
361                     if let ty::Ref(_, _, mutbl) = *ty.kind() {
362                         if mutbl == Mutability::Mut {
363                             self.prefer_mutable = true;
364                         }
365                     }
366                     self.visit_expr(expr);
367                 }
368             },
369             ExprKind::Closure(_, _, body_id, ..) => {
370                 let body = self.cx.tcx.hir().body(body_id);
371                 self.visit_expr(&body.value);
372             },
373             _ => walk_expr(self, expr),
374         }
375         self.prefer_mutable = old;
376     }
377     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
378         NestedVisitorMap::None
379     }
380 }