Skip to content

Commit 2e9172a

Browse files
committed
Check for known array length in needless_range_loop
1 parent b1d0343 commit 2e9172a

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

clippy_lints/src/loops.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ fn check_for_loop_range<'a, 'tcx>(
10681068

10691069
// linting condition: we only indexed one variable, and indexed it directly
10701070
if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 {
1071-
let (indexed, indexed_extent) = visitor
1071+
let (indexed, (indexed_extent, indexed_ty)) = visitor
10721072
.indexed_directly
10731073
.into_iter()
10741074
.next()
@@ -1119,7 +1119,7 @@ fn check_for_loop_range<'a, 'tcx>(
11191119
}
11201120
}
11211121

1122-
if is_len_call(end, indexed) {
1122+
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
11231123
String::new()
11241124
} else {
11251125
match limits {
@@ -1207,6 +1207,28 @@ fn is_len_call(expr: &Expr, var: Name) -> bool {
12071207
false
12081208
}
12091209

1210+
fn is_end_eq_array_len(
1211+
cx: &LateContext<'_, '_>,
1212+
end: &Expr,
1213+
limits: ast::RangeLimits,
1214+
indexed_ty: Ty<'_>,
1215+
) -> bool {
1216+
if_chain! {
1217+
if let ExprKind::Lit(ref lit) = end.node;
1218+
if let ast::LitKind::Int(end_int, _) = lit.node;
1219+
if let ty::TyKind::Array(_, arr_len_const) = indexed_ty.sty;
1220+
if let Some(arr_len) = arr_len_const.assert_usize(cx.tcx);
1221+
then {
1222+
return match limits {
1223+
ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(),
1224+
ast::RangeLimits::HalfOpen => end_int >= arr_len.into(),
1225+
};
1226+
}
1227+
}
1228+
1229+
false
1230+
}
1231+
12101232
fn check_for_loop_reverse_range<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, arg: &'tcx Expr, expr: &'tcx Expr) {
12111233
// if this for loop is iterating over a two-sided range...
12121234
if let Some(higher::Range {
@@ -1678,7 +1700,7 @@ struct VarVisitor<'a, 'tcx: 'a> {
16781700
indexed_indirectly: FxHashMap<Name, Option<region::Scope>>,
16791701
/// subset of `indexed` of vars that are indexed directly: `v[i]`
16801702
/// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
1681-
indexed_directly: FxHashMap<Name, Option<region::Scope>>,
1703+
indexed_directly: FxHashMap<Name, (Option<region::Scope>, Ty<'tcx>)>,
16821704
/// Any names that are used outside an index operation.
16831705
/// Used to detect things like `&mut vec` used together with `vec[i]`
16841706
referenced: FxHashSet<Name>,
@@ -1725,7 +1747,10 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
17251747
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
17261748
}
17271749
if index_used_directly {
1728-
self.indexed_directly.insert(seqvar.segments[0].ident.name, Some(extent));
1750+
self.indexed_directly.insert(
1751+
seqvar.segments[0].ident.name,
1752+
(Some(extent), self.cx.tables.node_id_to_type(seqexpr.hir_id)),
1753+
);
17291754
}
17301755
return false; // no need to walk further *on the variable*
17311756
}
@@ -1734,7 +1759,10 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
17341759
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
17351760
}
17361761
if index_used_directly {
1737-
self.indexed_directly.insert(seqvar.segments[0].ident.name, None);
1762+
self.indexed_directly.insert(
1763+
seqvar.segments[0].ident.name,
1764+
(None, self.cx.tables.node_id_to_type(seqexpr.hir_id)),
1765+
);
17381766
}
17391767
return false; // no need to walk further *on the variable*
17401768
}

tests/ui/needless_range_loop.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn calc_idx(i: usize) -> usize {
1313
}
1414

1515
fn main() {
16-
let ns = [2, 3, 5, 7];
16+
let ns = vec![2, 3, 5, 7];
1717

1818
for i in 3..10 {
1919
println!("{}", ns[i]);
@@ -76,4 +76,18 @@ fn main() {
7676
for i in x..=x + 4 {
7777
vec[i] += 1;
7878
}
79+
80+
let arr = [1,2,3];
81+
82+
for i in 0..3 {
83+
println!("{}", arr[i]);
84+
}
85+
86+
for i in 0..2 {
87+
println!("{}", arr[i]);
88+
}
89+
90+
for i in 1..3 {
91+
println!("{}", arr[i]);
92+
}
7993
}

tests/ui/needless_range_loop.stderr

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,35 @@ help: consider using an iterator
5050
76 | for <item> in vec.iter_mut().skip(x).take(4 + 1) {
5151
| ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5252

53-
error: aborting due to 5 previous errors
53+
error: the loop variable `i` is only used to index `arr`.
54+
--> $DIR/needless_range_loop.rs:82:14
55+
|
56+
82 | for i in 0..3 {
57+
| ^^^^
58+
help: consider using an iterator
59+
|
60+
82 | for <item> in &arr {
61+
| ^^^^^^ ^^^^
62+
63+
error: the loop variable `i` is only used to index `arr`.
64+
--> $DIR/needless_range_loop.rs:86:14
65+
|
66+
86 | for i in 0..2 {
67+
| ^^^^
68+
help: consider using an iterator
69+
|
70+
86 | for <item> in arr.iter().take(2) {
71+
| ^^^^^^ ^^^^^^^^^^^^^^^^^^
72+
73+
error: the loop variable `i` is only used to index `arr`.
74+
--> $DIR/needless_range_loop.rs:90:14
75+
|
76+
90 | for i in 1..3 {
77+
| ^^^^
78+
help: consider using an iterator
79+
|
80+
90 | for <item> in arr.iter().skip(1) {
81+
| ^^^^^^ ^^^^^^^^^^^^^^^^^^
82+
83+
error: aborting due to 8 previous errors
5484

0 commit comments

Comments
 (0)