diff --git a/CHANGELOG.md b/CHANGELOG.md index 993406b692c..87a96bdeba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5561,6 +5561,7 @@ Released 2018-09-13 [`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice [`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async [`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect +[`unused_enumerate_index`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index [`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index d9c97a8cc97..1a646ba38c3 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -274,6 +274,7 @@ crate::loops::NEVER_LOOP_INFO, crate::loops::SAME_ITEM_PUSH_INFO, crate::loops::SINGLE_ELEMENT_LOOP_INFO, + crate::loops::UNUSED_ENUMERATE_INDEX_INFO, crate::loops::WHILE_IMMUTABLE_CONDITION_INFO, crate::loops::WHILE_LET_LOOP_INFO, crate::loops::WHILE_LET_ON_ITERATOR_INFO, diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 575a84c8c63..c2b07920932 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -14,6 +14,7 @@ mod never_loop; mod same_item_push; mod single_element_loop; +mod unused_enumerate_index; mod utils; mod while_immutable_condition; mod while_let_loop; @@ -577,6 +578,33 @@ "manual implementation of `Iterator::find`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `for (_, v) in a.iter().enumerate()` + /// + /// ### Why is this bad? + /// The index from `.enumerate()` is immediately dropped. + /// + /// ### Example + /// ```rust + /// let v = vec![1, 2, 3, 4]; + /// for (_, x) in v.iter().enumerate() { + /// print!("{x}") + /// } + /// ``` + /// Use instead: + /// ```rust + /// let v = vec![1, 2, 3, 4]; + /// for x in v.iter() { + /// print!("{x}") + /// } + /// ``` + #[clippy::version = "1.69.0"] + pub UNUSED_ENUMERATE_INDEX, + style, + "using .enumerate() and immediately dropping the index" +} + declare_clippy_lint! { /// ### What it does /// Looks for loops that check for emptiness of a `Vec` in the condition and pop an element @@ -619,6 +647,7 @@ pub fn new(msrv: Msrv, enforce_iter_loop_reborrow: bool) -> Self { } } } + impl_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -638,7 +667,8 @@ pub fn new(msrv: Msrv, enforce_iter_loop_reborrow: bool) -> Self { SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, MANUAL_FIND, - MANUAL_WHILE_LET_SOME + MANUAL_WHILE_LET_SOME, + UNUSED_ENUMERATE_INDEX, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -717,6 +747,7 @@ fn check_for_loop<'tcx>( same_item_push::check(cx, pat, arg, body, expr); manual_flatten::check(cx, pat, arg, body, span); manual_find::check(cx, pat, arg, body, span, expr); + unused_enumerate_index::check(cx, pat, arg, body); } fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) { diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs new file mode 100644 index 00000000000..33c29499ee9 --- /dev/null +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -0,0 +1,75 @@ +use super::UNUSED_ENUMERATE_INDEX; +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::source::snippet; +use clippy_utils::sugg; +use clippy_utils::visitors::is_local_used; +use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { + let pat_span = pat.span; + + let PatKind::Tuple(pat, _) = pat.kind else { + return; + }; + + if pat.len() != 2 { + return; + } + + let arg_span = arg.span; + + let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind else { + return; + }; + + if method.ident.as_str() != "enumerate" { + return; + } + + let ty = cx.typeck_results().expr_ty(arg); + + if !pat_is_wild(cx, &pat[0].kind, body) { + return; + } + + let new_pat_span = pat[1].span; + + let name = match *ty.kind() { + ty::Adt(base, _substs) => cx.tcx.def_path_str(base.did()), + _ => return, + }; + + if name != "std::iter::Enumerate" && name != "core::iter::Enumerate" { + return; + } + + span_lint_and_then( + cx, + UNUSED_ENUMERATE_INDEX, + arg_span, + "you seem to use `.enumerate()` and immediately discard the index", + |diag| { + let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + multispan_sugg( + diag, + "remove the `.enumerate()` call", + vec![ + (pat_span, snippet(cx, new_pat_span, "value").into_owned()), + (arg_span, base_iter.to_string()), + ], + ); + }, + ); +} + +/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. +fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id), + _ => false, + } +} diff --git a/tests/ui/unused_enumerate_index.fixed b/tests/ui/unused_enumerate_index.fixed new file mode 100644 index 00000000000..3a9f89063fd --- /dev/null +++ b/tests/ui/unused_enumerate_index.fixed @@ -0,0 +1,10 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::unused_enumerate_index)] + +fn main() { + let v = [1, 2, 3]; + for x in v.iter() { + print!("{x}"); + } +} diff --git a/tests/ui/unused_enumerate_index.rs b/tests/ui/unused_enumerate_index.rs new file mode 100644 index 00000000000..d047371f0b9 --- /dev/null +++ b/tests/ui/unused_enumerate_index.rs @@ -0,0 +1,10 @@ +// run-rustfix +#![allow(unused)] +#![warn(clippy::unused_enumerate_index)] + +fn main() { + let v = [1, 2, 3]; + for (_, x) in v.iter().enumerate() { + print!("{x}"); + } +} diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr new file mode 100644 index 00000000000..7bd9e374151 --- /dev/null +++ b/tests/ui/unused_enumerate_index.stderr @@ -0,0 +1,15 @@ +error: you seem to use `.enumerate()` and immediately discard the index + --> $DIR/unused_enumerate_index.rs:7:19 + | +LL | for (_, x) in v.iter().enumerate() { + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` +help: remove the `.enumerate()` call + | +LL | for x in v.iter() { + | ~ ~~~~~~~~ + +error: aborting due to previous error +