]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs
New upstream version 1.65.0+dfsg1
[rustc.git] / src / tools / clippy / 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, Visitor};
12 use rustc_hir::{BinOpKind, BorrowKind, Closure, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath};
13 use rustc_lint::LateContext;
14 use rustc_middle::middle::region;
15 use rustc_middle::ty::{self, Ty};
16 use rustc_span::symbol::{sym, Symbol};
17 use std::iter::{self, Iterator};
18 use std::mem;
19
20 /// Checks for looping over a range and then indexing a sequence with it.
21 /// The iteratee must be a range literal.
22 #[expect(clippy::too_many_lines)]
23 pub(super) fn check<'tcx>(
24 cx: &LateContext<'tcx>,
25 pat: &'tcx Pat<'_>,
26 arg: &'tcx Expr<'_>,
27 body: &'tcx Expr<'_>,
28 expr: &'tcx Expr<'_>,
29 ) {
30 if let Some(higher::Range {
31 start: Some(start),
32 ref end,
33 limits,
34 }) = higher::Range::hir(arg)
35 {
36 // the var must be a single name
37 if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
38 let mut visitor = VarVisitor {
39 cx,
40 var: canonical_id,
41 indexed_mut: FxHashSet::default(),
42 indexed_indirectly: FxHashMap::default(),
43 indexed_directly: FxHashMap::default(),
44 referenced: FxHashSet::default(),
45 nonindex: false,
46 prefer_mutable: false,
47 };
48 walk_expr(&mut visitor, body);
49
50 // linting condition: we only indexed one variable, and indexed it directly
51 if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
52 let (indexed, (indexed_extent, indexed_ty)) = visitor
53 .indexed_directly
54 .into_iter()
55 .next()
56 .expect("already checked that we have exactly 1 element");
57
58 // ensure that the indexed variable was declared before the loop, see #601
59 if let Some(indexed_extent) = indexed_extent {
60 let parent_def_id = cx.tcx.hir().get_parent_item(expr.hir_id);
61 let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id);
62 let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
63 if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) {
64 return;
65 }
66 }
67
68 // don't lint if the container that is indexed does not have .iter() method
69 let has_iter = has_iter_method(cx, indexed_ty);
70 if has_iter.is_none() {
71 return;
72 }
73
74 // don't lint if the container that is indexed into is also used without
75 // indexing
76 if visitor.referenced.contains(&indexed) {
77 return;
78 }
79
80 let starts_at_zero = is_integer_const(cx, start, 0);
81
82 let skip = if starts_at_zero {
83 String::new()
84 } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) {
85 return;
86 } else {
87 format!(".skip({})", snippet(cx, start.span, ".."))
88 };
89
90 let mut end_is_start_plus_val = false;
91
92 let take = if let Some(end) = *end {
93 let mut take_expr = end;
94
95 if let ExprKind::Binary(ref op, left, right) = end.kind {
96 if op.node == BinOpKind::Add {
97 let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
98 let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
99
100 if start_equal_left {
101 take_expr = right;
102 } else if start_equal_right {
103 take_expr = left;
104 }
105
106 end_is_start_plus_val = start_equal_left | start_equal_right;
107 }
108 }
109
110 if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
111 String::new()
112 } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
113 return;
114 } else {
115 match limits {
116 ast::RangeLimits::Closed => {
117 let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
118 format!(".take({})", take_expr + sugg::ONE)
119 },
120 ast::RangeLimits::HalfOpen => {
121 format!(".take({})", snippet(cx, take_expr.span, ".."))
122 },
123 }
124 }
125 } else {
126 String::new()
127 };
128
129 let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) {
130 ("mut ", "iter_mut")
131 } else {
132 ("", "iter")
133 };
134
135 let take_is_empty = take.is_empty();
136 let mut method_1 = take;
137 let mut method_2 = skip;
138
139 if end_is_start_plus_val {
140 mem::swap(&mut method_1, &mut method_2);
141 }
142
143 if visitor.nonindex {
144 span_lint_and_then(
145 cx,
146 NEEDLESS_RANGE_LOOP,
147 arg.span,
148 &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
149 |diag| {
150 multispan_sugg(
151 diag,
152 "consider using an iterator",
153 vec![
154 (pat.span, format!("({}, <item>)", ident.name)),
155 (
156 arg.span,
157 format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
158 ),
159 ],
160 );
161 },
162 );
163 } else {
164 let repl = if starts_at_zero && take_is_empty {
165 format!("&{}{}", ref_mut, indexed)
166 } else {
167 format!("{}.{}(){}{}", indexed, method, method_1, method_2)
168 };
169
170 span_lint_and_then(
171 cx,
172 NEEDLESS_RANGE_LOOP,
173 arg.span,
174 &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
175 |diag| {
176 multispan_sugg(
177 diag,
178 "consider using an iterator",
179 vec![(pat.span, "<item>".to_string()), (arg.span, repl)],
180 );
181 },
182 );
183 }
184 }
185 }
186 }
187 }
188
189 fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
190 if_chain! {
191 if let ExprKind::MethodCall(method, recv, [], _) = expr.kind;
192 if method.ident.name == sym::len;
193 if let ExprKind::Path(QPath::Resolved(_, path)) = recv.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
267 .tcx
268 .region_scope_tree(parent_def_id)
269 .var_scope(hir_id.local_id)
270 .unwrap();
271 if index_used_directly {
272 self.indexed_directly.insert(
273 seqvar.segments[0].ident.name,
274 (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
275 );
276 } else {
277 self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
278 }
279 return false; // no need to walk further *on the variable*
280 }
281 Res::Def(DefKind::Static (_)| DefKind::Const, ..) => {
282 if index_used_directly {
283 self.indexed_directly.insert(
284 seqvar.segments[0].ident.name,
285 (None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
286 );
287 } else {
288 self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
289 }
290 return false; // no need to walk further *on the variable*
291 }
292 _ => (),
293 }
294 }
295 }
296 true
297 }
298 }
299
300 impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
301 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
302 if_chain! {
303 // a range index op
304 if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind;
305 if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
306 || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
307 if !self.check(args_1, args_0, expr);
308 then { return }
309 }
310
311 if_chain! {
312 // an index op
313 if let ExprKind::Index(seqexpr, idx) = expr.kind;
314 if !self.check(idx, seqexpr, expr);
315 then { return }
316 }
317
318 if_chain! {
319 // directly using a variable
320 if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind;
321 if let Res::Local(local_id) = path.res;
322 then {
323 if local_id == self.var {
324 self.nonindex = true;
325 } else {
326 // not the correct variable, but still a variable
327 self.referenced.insert(path.segments[0].ident.name);
328 }
329 }
330 }
331
332 let old = self.prefer_mutable;
333 match expr.kind {
334 ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
335 self.prefer_mutable = true;
336 self.visit_expr(lhs);
337 self.prefer_mutable = false;
338 self.visit_expr(rhs);
339 },
340 ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
341 if mutbl == Mutability::Mut {
342 self.prefer_mutable = true;
343 }
344 self.visit_expr(expr);
345 },
346 ExprKind::Call(f, args) => {
347 self.visit_expr(f);
348 for expr in args {
349 let ty = self.cx.typeck_results().expr_ty_adjusted(expr);
350 self.prefer_mutable = false;
351 if let ty::Ref(_, _, mutbl) = *ty.kind() {
352 if mutbl == Mutability::Mut {
353 self.prefer_mutable = true;
354 }
355 }
356 self.visit_expr(expr);
357 }
358 },
359 ExprKind::MethodCall(_, receiver, args, _) => {
360 let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
361 for (ty, expr) in iter::zip(
362 self.cx.tcx.fn_sig(def_id).inputs().skip_binder(),
363 std::iter::once(receiver).chain(args.iter()),
364 ) {
365 self.prefer_mutable = false;
366 if let ty::Ref(_, _, mutbl) = *ty.kind() {
367 if mutbl == Mutability::Mut {
368 self.prefer_mutable = true;
369 }
370 }
371 self.visit_expr(expr);
372 }
373 },
374 ExprKind::Closure(&Closure { body, .. }) => {
375 let body = self.cx.tcx.hir().body(body);
376 self.visit_expr(body.value);
377 },
378 _ => walk_expr(self, expr),
379 }
380 self.prefer_mutable = old;
381 }
382 }