Skip to content

Commit 7790ac9

Browse files
committed
Fix needless_range_loop false positive with nested indexing
1 parent 1fb96b1 commit 7790ac9

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

clippy_lints/src/loops/needless_range_loop.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::NEEDLESS_RANGE_LOOP;
22
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::source::snippet;
44
use clippy_utils::ty::has_iter_method;
5-
use clippy_utils::visitors::is_local_used;
5+
use clippy_utils::visitors::{is_const_evaluatable, is_local_used};
66
use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_literal, peel_hir_expr_while, sugg};
77
use rustc_ast::ast;
88
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
@@ -43,6 +43,7 @@ pub(super) fn check<'tcx>(
4343
unnamed_indexed_indirectly: false,
4444
indexed_directly: FxIndexMap::default(),
4545
unnamed_indexed_directly: false,
46+
has_dynamic_indexed_container: false,
4647
referenced: FxHashSet::default(),
4748
nonindex: false,
4849
prefer_mutable: false,
@@ -53,6 +54,7 @@ pub(super) fn check<'tcx>(
5354
if visitor.indexed_indirectly.is_empty()
5455
&& !visitor.unnamed_indexed_indirectly
5556
&& !visitor.unnamed_indexed_directly
57+
&& !visitor.has_dynamic_indexed_container
5658
&& visitor.indexed_directly.len() == 1
5759
{
5860
let (indexed, (indexed_extent, indexed_ty)) = visitor
@@ -239,6 +241,9 @@ struct VarVisitor<'a, 'tcx> {
239241
indexed_directly: FxIndexMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
240242
/// directly indexed literals, like `[1, 2, 3][i]`
241243
unnamed_indexed_directly: bool,
244+
/// Whether the loop variable indexes a container selected by a non-constant expression.
245+
/// For example, in `a[sum % 5][i]`, iterating over `a` would change the program's semantics.
246+
has_dynamic_indexed_container: bool,
242247
/// Any names that are used outside an index operation.
243248
/// Used to detect things like `&mut vec` used together with `vec[i]`
244249
referenced: FxHashSet<Symbol>,
@@ -256,24 +261,39 @@ impl<'tcx> VarVisitor<'_, 'tcx> {
256261
// It is `true` if all indices are direct
257262
let mut index_used_directly = true;
258263

264+
// Track whether the loop variable has been found while walking from the outermost index
265+
// towards the base expression. Any later non-constant index selects the container indexed
266+
// by the loop variable, so it cannot be replaced by an iterator over the base expression.
267+
let mut found_loop_index = false;
268+
let mut indexed_container_is_stable = true;
269+
259270
// Handle initial index
260271
if is_local_used(self.cx, idx, self.var) {
261272
used_cnt += 1;
262273
index_used_directly &= matches!(idx.kind, ExprKind::Path(_));
274+
found_loop_index = true;
263275
}
264276
// Handle nested indices
265277
let seqexpr = peel_hir_expr_while(seqexpr, |e| {
266278
if let ExprKind::Index(e, idx, _) = e.kind {
267279
if is_local_used(self.cx, idx, self.var) {
268280
used_cnt += 1;
269281
index_used_directly &= matches!(idx.kind, ExprKind::Path(_));
282+
found_loop_index = true;
283+
} else if found_loop_index && !is_const_evaluatable(self.cx.tcx, self.cx.typeck_results(), idx) {
284+
indexed_container_is_stable = false;
270285
}
271286
Some(e)
272287
} else {
273288
None
274289
}
275290
});
276291

292+
if !indexed_container_is_stable {
293+
self.has_dynamic_indexed_container = true;
294+
return false;
295+
}
296+
277297
match used_cnt {
278298
0 => return true,
279299
n if n > 1 => self.nonindex = true, // Optimize code like `a[i][i]`

tests/ui/needless_range_loop.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,18 @@ fn issue_15068() {
237237
let _ = a[0][i];
238238
}
239239
}
240+
241+
// Regression test for #17432: the container indexed by `i` changes between iterations.
242+
fn issue_17432(a: [[usize; 5]; 5]) {
243+
let mut sum = 0;
244+
245+
for i in 0..5 {
246+
sum += a[sum % 5][i];
247+
}
248+
249+
// Keep linting when `i` selects the outer container.
250+
for i in 0..5 {
251+
//~^ needless_range_loop
252+
let _ = a[i][sum % 5];
253+
}
254+
}

tests/ui/needless_range_loop.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,17 @@ LL - for i in 0..MAX_LEN {
192192
LL + for <item> in a.iter().take(MAX_LEN) {
193193
|
194194

195-
error: aborting due to 16 previous errors
195+
error: the loop variable `i` is only used to index `a`
196+
--> tests/ui/needless_range_loop.rs:250:14
197+
|
198+
LL | for i in 0..5 {
199+
| ^^^^
200+
|
201+
help: consider using an iterator
202+
|
203+
LL - for i in 0..5 {
204+
LL + for <item> in &a {
205+
|
206+
207+
error: aborting due to 17 previous errors
196208

0 commit comments

Comments
 (0)