Skip to content

Commit fcf0a98

Browse files
committed
New lint: unused_enumerate_value
1 parent d28d234 commit fcf0a98

10 files changed

+221
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6259,6 +6259,7 @@ Released 2018-09-13
62596259
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
62606260
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
62616261
[`unused_enumerate_index`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index
6262+
[`unused_enumerate_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_value
62626263
[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs
62636264
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
62646265
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
308308
crate::loops::SAME_ITEM_PUSH_INFO,
309309
crate::loops::SINGLE_ELEMENT_LOOP_INFO,
310310
crate::loops::UNUSED_ENUMERATE_INDEX_INFO,
311+
crate::loops::UNUSED_ENUMERATE_VALUE_INFO,
311312
crate::loops::WHILE_FLOAT_INFO,
312313
crate::loops::WHILE_IMMUTABLE_CONDITION_INFO,
313314
crate::loops::WHILE_LET_LOOP_INFO,

clippy_lints/src/loops/mod.rs

+34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod never_loop;
1818
mod same_item_push;
1919
mod single_element_loop;
2020
mod unused_enumerate_index;
21+
mod unused_enumerate_value;
2122
mod utils;
2223
mod while_float;
2324
mod while_immutable_condition;
@@ -784,6 +785,37 @@ declare_clippy_lint! {
784785
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"
785786
}
786787

788+
declare_clippy_lint! {
789+
/// ### What it does
790+
/// Checks for uses of the `enumerate` method where the value is unused (`_`) on iterators
791+
/// implementing `ExactSizeIterator`.
792+
///
793+
/// ### Why is this bad?
794+
/// Just iterating a range of indices is more idiomatic and is probably faster because it
795+
/// avoids consuming the iterator.
796+
///
797+
/// ### Example
798+
/// ```no_run
799+
/// fn example(iter: impl ExactSizeIterator<Item = i32>) {
800+
/// for (i, _) in iter.enumerate() {
801+
/// ..;
802+
/// }
803+
/// }
804+
/// ```
805+
/// Use instead:
806+
/// ```no_run
807+
/// fn example(iter: impl ExactSizeIterator<Item = i32>) {
808+
/// for i in 0..iter.len() {
809+
/// ..;
810+
/// }
811+
/// }
812+
/// ```
813+
#[clippy::version = "1.87.0"]
814+
pub UNUSED_ENUMERATE_VALUE,
815+
nursery,
816+
"using `.enumerate()` and immediately dropping the value"
817+
}
818+
787819
pub struct Loops {
788820
msrv: Msrv,
789821
enforce_iter_loop_reborrow: bool,
@@ -822,6 +854,7 @@ impl_lint_pass!(Loops => [
822854
INFINITE_LOOP,
823855
MANUAL_SLICE_FILL,
824856
CHAR_INDICES_AS_BYTE_INDICES,
857+
UNUSED_ENUMERATE_VALUE,
825858
]);
826859

827860
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -906,6 +939,7 @@ impl Loops {
906939
manual_find::check(cx, pat, arg, body, span, expr);
907940
unused_enumerate_index::check(cx, pat, arg, body);
908941
char_indices_as_byte_indices::check(cx, pat, arg, body);
942+
unused_enumerate_value::check(cx, pat, arg, body);
909943
}
910944

911945
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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::{get_adt_inherent_method, 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::{self, 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, applicability) = 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 range_end = Sugg::hir(cx, recv, "..");
39+
if applicability != Applicability::MachineApplicable {
40+
diag.help(format!("consider using `0..{range_end}.len()` instead"));
41+
return;
42+
}
43+
44+
diag.multipart_suggestion(
45+
format!("replace `{}` with `0..{range_end}.len()`", snippet(cx, arg.span, "..")),
46+
vec![
47+
(pat.span, snippet(cx, index.span, "..").into_owned()),
48+
(arg.span, format!("0..{range_end}.len()")),
49+
],
50+
applicability,
51+
);
52+
},
53+
);
54+
}
55+
}
56+
57+
/// Removes trailing `.iter()`, `.iter_mut()`, or `.into_iter()` calls from the given expression if
58+
/// `len` can be called directly on the receiver. Note that this may be incorrect if the receiver is
59+
/// a user-defined type whose `len` method has a different meaning than the standard library.
60+
fn remove_trailing_iter<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> (&'tcx Expr<'tcx>, Applicability) {
61+
let mut app = Applicability::MachineApplicable;
62+
if let ExprKind::MethodCall(iter_path, iter_recv, _, _) = expr.kind
63+
&& matches!(iter_path.ident.name, sym::iter | sym::iter_mut | sym::into_iter)
64+
&& let iter_recv_ty = cx.typeck_results().expr_ty(iter_recv).peel_refs()
65+
&& let ty_is_builtin_container = is_builtin_container(cx, iter_recv_ty)
66+
&& (ty_is_builtin_container || get_adt_inherent_method(cx, iter_recv_ty, sym::len).is_some())
67+
{
68+
if !ty_is_builtin_container {
69+
app = Applicability::MaybeIncorrect;
70+
}
71+
72+
return (iter_recv, app);
73+
}
74+
75+
(expr, app)
76+
}
77+
78+
/// Return `true` if the given type is a built-in container type (array, slice, or collection).
79+
fn is_builtin_container(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
80+
ty.is_array()
81+
|| ty.is_slice()
82+
|| ty.is_array_slice()
83+
|| (matches!(*ty.kind(), ty::Adt(iter_base, _)
84+
if [sym::Vec, sym::VecDeque, sym::LinkedList, sym::BTreeMap, sym::BTreeSet, sym::HashMap, sym::HashSet, sym::BinaryHeap]
85+
.iter()
86+
.any(|sym| cx.tcx.is_diagnostic_item(*sym, iter_base.did()))))
87+
}

clippy_utils/src/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>",
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"];
3333
pub const ALIGN_OF: [&str; 3] = ["core", "mem", "align_of"];
34+
pub const ITER_EXACT_SIZE_ITERATOR: [&str; 3] = ["core", "iter", "ExactSizeIterator"];
3435

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

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+
todo!();
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+
todo!();
14+
}
15+
}

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+
todo!();
8+
}
9+
10+
let my_iter = vec![1, 2, 3].into_iter();
11+
for (index, _) in my_iter.enumerate() {
12+
//~^ unused_enumerate_value
13+
todo!();
14+
}
15+
}
+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+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//@no-rustfix
2+
#![warn(clippy::unused_enumerate_value)]
3+
4+
fn main() {
5+
struct Length(usize);
6+
7+
impl IntoIterator for Length {
8+
type Item = usize;
9+
type IntoIter = std::iter::Once<usize>;
10+
11+
fn into_iter(self) -> Self::IntoIter {
12+
std::iter::once(self.0)
13+
}
14+
}
15+
16+
impl Length {
17+
fn len(&self) -> usize {
18+
self.0
19+
}
20+
}
21+
22+
let length = Length(3);
23+
for (index, _) in length.into_iter().enumerate() {
24+
//~^ unused_enumerate_value
25+
todo!();
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error: you seem to use `.enumerate()` and immediately discard the value
2+
--> tests/ui/unused_enumerate_value_unfixable.rs:23:23
3+
|
4+
LL | for (index, _) in length.into_iter().enumerate() {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: consider using `0..length.len()` instead
8+
= note: `-D clippy::unused-enumerate-value` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_value)]`
10+
11+
error: aborting due to 1 previous error
12+

0 commit comments

Comments
 (0)