Skip to content

Commit f99f935

Browse files
committed
New lint: unused_enumerate_value
1 parent 221ae5f commit f99f935

File tree

8 files changed

+171
-0
lines changed

8 files changed

+171
-0
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6256,6 +6256,7 @@ Released 2018-09-13
62566256
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
62576257
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
62586258
[`unused_enumerate_index`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index
6259+
[`unused_enumerate_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_value
62596260
[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs
62606261
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
62616262
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label

Diff for: clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
305305
crate::loops::SAME_ITEM_PUSH_INFO,
306306
crate::loops::SINGLE_ELEMENT_LOOP_INFO,
307307
crate::loops::UNUSED_ENUMERATE_INDEX_INFO,
308+
crate::loops::UNUSED_ENUMERATE_VALUE_INFO,
308309
crate::loops::WHILE_FLOAT_INFO,
309310
crate::loops::WHILE_IMMUTABLE_CONDITION_INFO,
310311
crate::loops::WHILE_LET_LOOP_INFO,

Diff for: clippy_lints/src/loops/mod.rs

+34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod never_loop;
1717
mod same_item_push;
1818
mod single_element_loop;
1919
mod unused_enumerate_index;
20+
mod unused_enumerate_value;
2021
mod utils;
2122
mod while_float;
2223
mod while_immutable_condition;
@@ -740,6 +741,37 @@ declare_clippy_lint! {
740741
"manually filling a slice with a value"
741742
}
742743

744+
declare_clippy_lint! {
745+
/// ### What it does
746+
/// Checks for uses of the `enumerate` method where the value is unused (`_`) on iterators
747+
/// implementing `ExactSizeIterator`.
748+
///
749+
/// ### Why is this bad?
750+
/// Just iterating a range of indices is more idiomatic and is probably faster because it
751+
/// avoids consuming the iterator.
752+
///
753+
/// ### Example
754+
/// ```no_run
755+
/// fn example(iter: impl ExactSizeIterator<Item = i32>) {
756+
/// for (i, _) in iter.enumerate() {
757+
/// ..
758+
/// }
759+
/// }
760+
/// ```
761+
/// Use instead:
762+
/// ```no_run
763+
/// fn example(iter: impl ExactSizeIterator<Item = i32>) {
764+
/// for i in 0..iter.len() {
765+
/// ..
766+
/// }
767+
/// }
768+
/// ```
769+
#[clippy::version = "1.87.0"]
770+
pub UNUSED_ENUMERATE_VALUE,
771+
nursery,
772+
"using `.enumerate()` and immediately dropping the value"
773+
}
774+
743775
pub struct Loops {
744776
msrv: Msrv,
745777
enforce_iter_loop_reborrow: bool,
@@ -777,6 +809,7 @@ impl_lint_pass!(Loops => [
777809
UNUSED_ENUMERATE_INDEX,
778810
INFINITE_LOOP,
779811
MANUAL_SLICE_FILL,
812+
UNUSED_ENUMERATE_VALUE,
780813
]);
781814

782815
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -860,6 +893,7 @@ impl Loops {
860893
manual_flatten::check(cx, pat, arg, body, span, self.msrv);
861894
manual_find::check(cx, pat, arg, body, span, expr);
862895
unused_enumerate_index::check(cx, pat, arg, body);
896+
unused_enumerate_value::check(cx, pat, arg, body);
863897
}
864898

865899
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {

Diff for: clippy_lints/src/loops/unused_enumerate_value.rs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use super::UNUSED_ENUMERATE_VALUE;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::source::snippet;
4+
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::implements_trait;
6+
use clippy_utils::{get_trait_def_id, pat_is_wild, paths};
7+
use rustc_errors::Applicability;
8+
use rustc_hir::def::DefKind;
9+
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
10+
use rustc_lint::LateContext;
11+
use rustc_middle::ty;
12+
use rustc_span::sym;
13+
14+
/// Checks for the `UNUSED_ENUMERATE_VALUE` lint.
15+
///
16+
/// TODO: Extend this lint to cover iterator chains.
17+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'tcx>) {
18+
if let PatKind::Tuple([index, elem], _) = pat.kind
19+
&& let ExprKind::MethodCall(_method, recv, [], _) = arg.kind
20+
&& pat_is_wild(cx, &elem.kind, body)
21+
&& let arg_ty = cx.typeck_results().expr_ty(arg)
22+
&& let ty::Adt(base, _) = *arg_ty.kind()
23+
&& cx.tcx.is_diagnostic_item(sym::Enumerate, base.did())
24+
&& let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id)
25+
&& cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id)
26+
&& let receiver_ty = cx.typeck_results().expr_ty(recv)
27+
// TODO: Replace with `sym` when it's available
28+
&& let Some(exact_size_iter) = get_trait_def_id(cx.tcx, &paths::ITER_EXACT_SIZE_ITERATOR)
29+
&& implements_trait(cx, receiver_ty, exact_size_iter, &[])
30+
{
31+
let recv = remove_trailing_iter(cx, recv);
32+
span_lint_and_then(
33+
cx,
34+
UNUSED_ENUMERATE_VALUE,
35+
arg.span,
36+
"you seem to use `.enumerate()` and immediately discard the value",
37+
|diag| {
38+
let mut applicability = Applicability::MachineApplicable;
39+
let range_end = Sugg::hir_with_applicability(cx, recv, "..", &mut applicability);
40+
if applicability != Applicability::MachineApplicable {
41+
diag.help(format!("consider using `0..{range_end}.len()` instead"));
42+
return;
43+
}
44+
45+
diag.multipart_suggestion(
46+
format!("replace `{}` with `0..{range_end}.len()`", snippet(cx, arg.span, "..")),
47+
vec![
48+
(pat.span, snippet(cx, index.span, "..").into_owned()),
49+
(arg.span, format!("0..{range_end}.len()")),
50+
],
51+
applicability,
52+
);
53+
},
54+
);
55+
}
56+
}
57+
58+
/// Removes trailing `.iter()`, `.iter_mut()`, or `.into_iter()` calls from the given expression if
59+
/// it's a method call on an array, slice, or a collection type.
60+
fn remove_trailing_iter<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> &'tcx Expr<'tcx> {
61+
if let ExprKind::MethodCall(iter_path, iter_recv, _, _) = expr.kind
62+
&& matches!(iter_path.ident.name, sym::iter | sym::iter_mut | sym::into_iter)
63+
&& let iter_recv_ty = cx.typeck_results().expr_ty(iter_recv).peel_refs()
64+
&& (iter_recv_ty.is_array()
65+
|| iter_recv_ty.is_slice()
66+
|| iter_recv_ty.is_array_slice()
67+
|| (matches!(*iter_recv_ty.kind(), ty::Adt(iter_base, _)
68+
if [sym::Vec, sym::VecDeque, sym::LinkedList, sym::BTreeMap, sym::BTreeSet, sym::HashMap, sym::HashSet, sym::BinaryHeap]
69+
.iter()
70+
.any(|sym| cx.tcx.is_diagnostic_item(*sym, iter_base.did())))))
71+
{
72+
return iter_recv;
73+
}
74+
75+
expr
76+
}

Diff for: clippy_utils/src/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]
3030
pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>", "is_ascii"];
3131
pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
3232
pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];
33+
pub const ITER_EXACT_SIZE_ITERATOR: [&str; 3] = ["core", "iter", "ExactSizeIterator"];
3334

3435
// Paths in clippy itself
3536
pub const MSRV_STACK: [&str; 3] = ["clippy_utils", "msrvs", "MsrvStack"];

Diff for: tests/ui/unused_enumerate_value.fixed

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#![warn(clippy::unused_enumerate_value)]
2+
3+
fn main() {
4+
let mut array = [1, 2, 3];
5+
for index in 0..array.len() {
6+
//~^ unused_enumerate_value
7+
println!("{}", index);
8+
}
9+
10+
let my_iter = vec![1, 2, 3].into_iter();
11+
for index in 0..my_iter.len() {
12+
//~^ unused_enumerate_value
13+
println!("{}", index);
14+
}
15+
}

Diff for: tests/ui/unused_enumerate_value.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#![warn(clippy::unused_enumerate_value)]
2+
3+
fn main() {
4+
let mut array = [1, 2, 3];
5+
for (index, _) in array.iter_mut().enumerate() {
6+
//~^ unused_enumerate_value
7+
println!("{}", index);
8+
}
9+
10+
let my_iter = vec![1, 2, 3].into_iter();
11+
for (index, _) in my_iter.enumerate() {
12+
//~^ unused_enumerate_value
13+
println!("{}", index);
14+
}
15+
}

Diff for: tests/ui/unused_enumerate_value.stderr

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: you seem to use `.enumerate()` and immediately discard the value
2+
--> tests/ui/unused_enumerate_value.rs:5:23
3+
|
4+
LL | for (index, _) in array.iter_mut().enumerate() {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::unused-enumerate-value` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_value)]`
9+
help: replace `array.iter_mut().enumerate()` with `0..array.len()`
10+
|
11+
LL - for (index, _) in array.iter_mut().enumerate() {
12+
LL + for index in 0..array.len() {
13+
|
14+
15+
error: you seem to use `.enumerate()` and immediately discard the value
16+
--> tests/ui/unused_enumerate_value.rs:11:23
17+
|
18+
LL | for (index, _) in my_iter.enumerate() {
19+
| ^^^^^^^^^^^^^^^^^^^
20+
|
21+
help: replace `my_iter.enumerate()` with `0..my_iter.len()`
22+
|
23+
LL - for (index, _) in my_iter.enumerate() {
24+
LL + for index in 0..my_iter.len() {
25+
|
26+
27+
error: aborting due to 2 previous errors
28+

0 commit comments

Comments
 (0)