Skip to content

Commit 087e5ea

Browse files
committed
Auto merge of #4691 - HMPerson1:suggest_iter, r=phansch
Fix suggestion of `explicit_counter_loop` changelog: In the suggestion of `explicit_counter_loop`, if the `for` loop argument doesn't implement `Iterator`, then we suggest `x.into_iter().enumerate()` (or `x.iter{_mut}()` as appropriate). Also, the span of the suggestion has been corrected. Fixes #4678
2 parents 1bce252 + a9cb2b9 commit 087e5ea

File tree

4 files changed

+92
-29
lines changed

4 files changed

+92
-29
lines changed

clippy_lints/src/loops.rs

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ use syntax_pos::{BytePos, Symbol};
2727

2828
use crate::utils::paths;
2929
use crate::utils::{
30-
get_enclosing_block, get_parent_expr, has_iter_method, higher, is_integer_const, is_refutable, last_path_segment,
31-
match_trait_method, match_type, match_var, multispan_sugg, snippet, snippet_opt, snippet_with_applicability,
32-
span_help_and_lint, span_lint, span_lint_and_sugg, span_lint_and_then, SpanlessEq,
30+
get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
31+
is_integer_const, is_refutable, last_path_segment, match_trait_method, match_type, match_var, multispan_sugg,
32+
snippet, snippet_opt, snippet_with_applicability, span_help_and_lint, span_lint, span_lint_and_sugg,
33+
span_lint_and_then, SpanlessEq,
3334
};
3435

3536
declare_clippy_lint! {
@@ -1460,27 +1461,26 @@ fn check_for_loop_explicit_counter<'a, 'tcx>(
14601461
if visitor2.state == VarState::Warn {
14611462
if let Some(name) = visitor2.name {
14621463
let mut applicability = Applicability::MachineApplicable;
1464+
1465+
// for some reason this is the only way to get the `Span`
1466+
// of the entire `for` loop
1467+
let for_span = if let ExprKind::Match(_, arms, _) = &expr.kind {
1468+
arms[0].body.span
1469+
} else {
1470+
unreachable!()
1471+
};
1472+
14631473
span_lint_and_sugg(
14641474
cx,
14651475
EXPLICIT_COUNTER_LOOP,
1466-
expr.span,
1476+
for_span.with_hi(arg.span.hi()),
14671477
&format!("the variable `{}` is used as a loop counter.", name),
14681478
"consider using",
14691479
format!(
14701480
"for ({}, {}) in {}.enumerate()",
14711481
name,
14721482
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
1473-
if higher::range(cx, arg).is_some() {
1474-
format!(
1475-
"({})",
1476-
snippet_with_applicability(cx, arg.span, "_", &mut applicability)
1477-
)
1478-
} else {
1479-
format!(
1480-
"{}",
1481-
sugg::Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).maybe_par()
1482-
)
1483-
}
1483+
make_iterator_snippet(cx, arg, &mut applicability),
14841484
),
14851485
applicability,
14861486
);
@@ -1490,6 +1490,39 @@ fn check_for_loop_explicit_counter<'a, 'tcx>(
14901490
}
14911491
}
14921492

1493+
/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
1494+
/// actual `Iterator` that the loop uses.
1495+
fn make_iterator_snippet(cx: &LateContext<'_, '_>, arg: &Expr, applic_ref: &mut Applicability) -> String {
1496+
let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR)
1497+
.map_or(false, |id| implements_trait(cx, cx.tables.expr_ty(arg), id, &[]));
1498+
if impls_iterator {
1499+
format!(
1500+
"{}",
1501+
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
1502+
)
1503+
} else {
1504+
// (&x).into_iter() ==> x.iter()
1505+
// (&mut x).into_iter() ==> x.iter_mut()
1506+
match &arg.kind {
1507+
ExprKind::AddrOf(mutability, arg_inner) if has_iter_method(cx, cx.tables.expr_ty(&arg_inner)).is_some() => {
1508+
let meth_name = match mutability {
1509+
MutMutable => "iter_mut",
1510+
MutImmutable => "iter",
1511+
};
1512+
format!(
1513+
"{}.{}()",
1514+
sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(),
1515+
meth_name,
1516+
)
1517+
},
1518+
_ => format!(
1519+
"{}.into_iter()",
1520+
sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
1521+
),
1522+
}
1523+
}
1524+
}
1525+
14931526
/// Checks for the `FOR_KV_MAP` lint.
14941527
fn check_for_loop_over_map_kv<'a, 'tcx>(
14951528
cx: &LateContext<'a, 'tcx>,

clippy_lints/src/utils/sugg.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ impl<'a> Sugg<'a> {
4646
pub fn hir_opt(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> Option<Self> {
4747
snippet_opt(cx, expr.span).map(|snippet| {
4848
let snippet = Cow::Owned(snippet);
49-
Self::hir_from_snippet(expr, snippet)
49+
Self::hir_from_snippet(cx, expr, snippet)
5050
})
5151
}
5252

@@ -84,12 +84,20 @@ impl<'a> Sugg<'a> {
8484
pub fn hir_with_macro_callsite(cx: &LateContext<'_, '_>, expr: &hir::Expr, default: &'a str) -> Self {
8585
let snippet = snippet_with_macro_callsite(cx, expr.span, default);
8686

87-
Self::hir_from_snippet(expr, snippet)
87+
Self::hir_from_snippet(cx, expr, snippet)
8888
}
8989

9090
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
9191
/// function variants of `Sugg`, since these use different snippet functions.
92-
fn hir_from_snippet(expr: &hir::Expr, snippet: Cow<'a, str>) -> Self {
92+
fn hir_from_snippet(cx: &LateContext<'_, '_>, expr: &hir::Expr, snippet: Cow<'a, str>) -> Self {
93+
if let Some(range) = higher::range(cx, expr) {
94+
let op = match range.limits {
95+
ast::RangeLimits::HalfOpen => AssocOp::DotDot,
96+
ast::RangeLimits::Closed => AssocOp::DotDotEq,
97+
};
98+
return Sugg::BinOp(op, snippet);
99+
}
100+
93101
match expr.kind {
94102
hir::ExprKind::AddrOf(..)
95103
| hir::ExprKind::Box(..)

tests/ui/explicit_counter_loop.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ fn main() {
1212
for _v in &vec {
1313
_index += 1
1414
}
15+
16+
let mut _index = 0;
17+
for _v in &mut vec {
18+
_index += 1;
19+
}
20+
21+
let mut _index = 0;
22+
for _v in vec {
23+
_index += 1;
24+
}
1525
}
1626

1727
mod issue_1219 {

tests/ui/explicit_counter_loop.stderr

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,46 @@
11
error: the variable `_index` is used as a loop counter.
2-
--> $DIR/explicit_counter_loop.rs:6:15
2+
--> $DIR/explicit_counter_loop.rs:6:5
33
|
44
LL | for _v in &vec {
5-
| ^^^^ help: consider using: `for (_index, _v) in (&vec).enumerate()`
5+
| ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
66
|
77
= note: `-D clippy::explicit-counter-loop` implied by `-D warnings`
88

99
error: the variable `_index` is used as a loop counter.
10-
--> $DIR/explicit_counter_loop.rs:12:15
10+
--> $DIR/explicit_counter_loop.rs:12:5
1111
|
1212
LL | for _v in &vec {
13-
| ^^^^ help: consider using: `for (_index, _v) in (&vec).enumerate()`
13+
| ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
14+
15+
error: the variable `_index` is used as a loop counter.
16+
--> $DIR/explicit_counter_loop.rs:17:5
17+
|
18+
LL | for _v in &mut vec {
19+
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()`
20+
21+
error: the variable `_index` is used as a loop counter.
22+
--> $DIR/explicit_counter_loop.rs:22:5
23+
|
24+
LL | for _v in vec {
25+
| ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()`
1426

1527
error: the variable `count` is used as a loop counter.
16-
--> $DIR/explicit_counter_loop.rs:51:19
28+
--> $DIR/explicit_counter_loop.rs:61:9
1729
|
1830
LL | for ch in text.chars() {
19-
| ^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
31+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
2032

2133
error: the variable `count` is used as a loop counter.
22-
--> $DIR/explicit_counter_loop.rs:62:19
34+
--> $DIR/explicit_counter_loop.rs:72:9
2335
|
2436
LL | for ch in text.chars() {
25-
| ^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
37+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
2638

2739
error: the variable `count` is used as a loop counter.
28-
--> $DIR/explicit_counter_loop.rs:120:19
40+
--> $DIR/explicit_counter_loop.rs:130:9
2941
|
3042
LL | for _i in 3..10 {
31-
| ^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()`
43+
| ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()`
3244

33-
error: aborting due to 5 previous errors
45+
error: aborting due to 7 previous errors
3446

0 commit comments

Comments
 (0)