about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDinu Blanovschi <git@dnbln.dev>2023-02-26 02:18:08 +0100
committerDinu Blanovschi <git@dnbln.dev>2023-10-31 17:53:24 +0100
commit0b90f72064cba3ced487773679aba48ae78af007 (patch)
tree200b9c5fdb5d74144cc5d9c000bd818b3bdd2151
parent7d34406015fce08031d0986dec0a4689a3cc407e (diff)
downloadrust-0b90f72064cba3ced487773679aba48ae78af007.tar.gz
rust-0b90f72064cba3ced487773679aba48ae78af007.zip
feat: unused_enumerate_index lint
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/loops/mod.rs33
-rw-r--r--clippy_lints/src/loops/unused_enumerate_index.rs75
-rw-r--r--tests/ui/unused_enumerate_index.fixed10
-rw-r--r--tests/ui/unused_enumerate_index.rs10
-rw-r--r--tests/ui/unused_enumerate_index.stderr15
7 files changed, 144 insertions, 1 deletions
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 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     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 needless_range_loop;
 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;
@@ -579,6 +580,33 @@ declare_clippy_lint! {
 
 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
     /// in the body as a separate operation.
     ///
@@ -619,6 +647,7 @@ impl Loops {
         }
     }
 }
+
 impl_lint_pass!(Loops => [
     MANUAL_MEMCPY,
     MANUAL_FLATTEN,
@@ -638,7 +667,8 @@ impl_lint_pass!(Loops => [
     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 @@ impl Loops {
         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
+