From 0b90f72064cba3ced487773679aba48ae78af007 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Sun, 26 Feb 2023 02:18:08 +0100 Subject: [PATCH 1/3] feat: unused_enumerate_index lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/loops/mod.rs | 33 +++++++- .../src/loops/unused_enumerate_index.rs | 75 +++++++++++++++++++ tests/ui/unused_enumerate_index.fixed | 10 +++ tests/ui/unused_enumerate_index.rs | 10 +++ tests/ui/unused_enumerate_index.stderr | 15 ++++ 7 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 clippy_lints/src/loops/unused_enumerate_index.rs create mode 100644 tests/ui/unused_enumerate_index.fixed create mode 100644 tests/ui/unused_enumerate_index.rs create mode 100644 tests/ui/unused_enumerate_index.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 993406b692cd..87a96bdeba65 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 d9c97a8cc97d..1a646ba38c35 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 575a84c8c636..c2b07920932e 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; @@ -577,6 +578,33 @@ declare_clippy_lint! { "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 @@ 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 000000000000..33c29499ee97 --- /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 000000000000..3a9f89063fd1 --- /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 000000000000..d047371f0b96 --- /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 000000000000..7bd9e3741515 --- /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 + From 14b82909b02b14c5b0c475bfbb5e507ddbd8f7c0 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Thu, 12 Oct 2023 19:05:22 +0200 Subject: [PATCH 2/3] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alejandra González --- clippy_lints/src/loops/mod.rs | 10 ++-- .../src/loops/unused_enumerate_index.rs | 37 ++++++------- tests/ui/unused_enumerate_index.fixed | 52 ++++++++++++++++++- tests/ui/unused_enumerate_index.rs | 52 ++++++++++++++++++- tests/ui/unused_enumerate_index.stderr | 15 +++++- 5 files changed, 135 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index c2b07920932e..b99e0fd814ff 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -580,7 +580,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `for (_, v) in a.iter().enumerate()` + /// Checks for uses of the `enumerate` method where the index is unused (`_`) /// /// ### Why is this bad? /// The index from `.enumerate()` is immediately dropped. @@ -589,20 +589,20 @@ declare_clippy_lint! { /// ```rust /// let v = vec![1, 2, 3, 4]; /// for (_, x) in v.iter().enumerate() { - /// print!("{x}") + /// println!("{x}"); /// } /// ``` /// Use instead: /// ```rust /// let v = vec![1, 2, 3, 4]; /// for x in v.iter() { - /// print!("{x}") + /// println!("{x}"); /// } /// ``` - #[clippy::version = "1.69.0"] + #[clippy::version = "1.75.0"] pub UNUSED_ENUMERATE_INDEX, style, - "using .enumerate() and immediately dropping the index" + "using `.enumerate()` and immediately dropping the index" } declare_clippy_lint! { diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 33c29499ee97..18b73f77e81e 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -3,40 +3,27 @@ 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::def::DefKind; 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 { + let PatKind::Tuple(tuple, _) = pat.kind else { return; }; - if pat.len() != 2 { - return; - } - - let arg_span = arg.span; - - let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind else { + 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) { + if !pat_is_wild(cx, &tuple[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, @@ -46,10 +33,20 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx return; } + let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) else { + return; + }; + + let call_name = cx.tcx.def_path_str(call_id); + + if call_name != "std::iter::Iterator::enumerate" && call_name != "core::iter::Iterator::enumerate" { + return; + } + span_lint_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg_span, + arg.span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); @@ -57,8 +54,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx diag, "remove the `.enumerate()` call", vec![ - (pat_span, snippet(cx, new_pat_span, "value").into_owned()), - (arg_span, base_iter.to_string()), + (pat.span, snippet(cx, tuple[1].span, "..").into_owned()), + (arg.span, base_iter.to_string()), ], ); }, diff --git a/tests/ui/unused_enumerate_index.fixed b/tests/ui/unused_enumerate_index.fixed index 3a9f89063fd1..d079807ab587 100644 --- a/tests/ui/unused_enumerate_index.fixed +++ b/tests/ui/unused_enumerate_index.fixed @@ -1,10 +1,58 @@ -// run-rustfix #![allow(unused)] #![warn(clippy::unused_enumerate_index)] +use std::iter::Enumerate; + fn main() { let v = [1, 2, 3]; for x in v.iter() { - print!("{x}"); + println!("{x}"); + } + + struct Dummy1; + impl Dummy1 { + fn enumerate(self) -> Vec { + vec![] + } + } + let dummy = Dummy1; + for x in dummy.enumerate() { + println!("{x}"); + } + + struct Dummy2; + impl Dummy2 { + fn enumerate(self) -> Enumerate> { + vec![1, 2].into_iter().enumerate() + } + } + let dummy = Dummy2; + for (_, x) in dummy.enumerate() { + println!("{x}"); + } + + let mut with_used_iterator = [1, 2, 3].into_iter().enumerate(); + with_used_iterator.next(); + for (_, x) in with_used_iterator { + println!("{x}"); + } + + struct Dummy3(std::vec::IntoIter); + + impl Iterator for Dummy3 { + type Item = usize; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + } + + let dummy = Dummy3(vec![1, 2, 3].into_iter()); + for x in dummy { + println!("{x}"); } } diff --git a/tests/ui/unused_enumerate_index.rs b/tests/ui/unused_enumerate_index.rs index d047371f0b96..2d524da76327 100644 --- a/tests/ui/unused_enumerate_index.rs +++ b/tests/ui/unused_enumerate_index.rs @@ -1,10 +1,58 @@ -// run-rustfix #![allow(unused)] #![warn(clippy::unused_enumerate_index)] +use std::iter::Enumerate; + fn main() { let v = [1, 2, 3]; for (_, x) in v.iter().enumerate() { - print!("{x}"); + println!("{x}"); + } + + struct Dummy1; + impl Dummy1 { + fn enumerate(self) -> Vec { + vec![] + } + } + let dummy = Dummy1; + for x in dummy.enumerate() { + println!("{x}"); + } + + struct Dummy2; + impl Dummy2 { + fn enumerate(self) -> Enumerate> { + vec![1, 2].into_iter().enumerate() + } + } + let dummy = Dummy2; + for (_, x) in dummy.enumerate() { + println!("{x}"); + } + + let mut with_used_iterator = [1, 2, 3].into_iter().enumerate(); + with_used_iterator.next(); + for (_, x) in with_used_iterator { + println!("{x}"); + } + + struct Dummy3(std::vec::IntoIter); + + impl Iterator for Dummy3 { + type Item = usize; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } + } + + let dummy = Dummy3(vec![1, 2, 3].into_iter()); + for (_, x) in dummy.enumerate() { + println!("{x}"); } } diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 7bd9e3741515..b575fbbc4e61 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,5 +1,5 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> $DIR/unused_enumerate_index.rs:7:19 + --> $DIR/unused_enumerate_index.rs:8:19 | LL | for (_, x) in v.iter().enumerate() { | ^^^^^^^^^^^^^^^^^^^^ @@ -11,5 +11,16 @@ help: remove the `.enumerate()` call LL | for x in v.iter() { | ~ ~~~~~~~~ -error: aborting due to previous error +error: you seem to use `.enumerate()` and immediately discard the index + --> $DIR/unused_enumerate_index.rs:55:19 + | +LL | for (_, x) in dummy.enumerate() { + | ^^^^^^^^^^^^^^^^^ + | +help: remove the `.enumerate()` call + | +LL | for x in dummy { + | ~ ~~~~~ + +error: aborting due to 2 previous errors From bb9cc6d47c11d33087d5776ecc261b95c6dace74 Mon Sep 17 00:00:00 2001 From: Dinu Blanovschi Date: Wed, 1 Nov 2023 14:10:42 +0100 Subject: [PATCH 3/3] refactor: extract common pat_is_wild to clippy_utils This function was previously defined for the iter_kv_map, for_kw_map, and unused_enumerate_index lints. This commit extracts it into clippy_utils. --- clippy_lints/src/loops/for_kv_map.rs | 12 +----------- clippy_lints/src/loops/unused_enumerate_index.rs | 12 +----------- clippy_lints/src/methods/iter_kv_map.rs | 13 +------------ clippy_utils/src/lib.rs | 12 ++++++++++++ 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index ed620460dbe6..94c951fc10a6 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -1,9 +1,8 @@ use super::FOR_KV_MAP; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::source::snippet; -use clippy_utils::sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::is_local_used; +use clippy_utils::{pat_is_wild, sugg}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -55,12 +54,3 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx } } } - -/// 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/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index 18b73f77e81e..62a2ab1ccb4c 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,8 +1,7 @@ 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 clippy_utils::{pat_is_wild, sugg}; use rustc_hir::def::DefKind; use rustc_hir::{Expr, ExprKind, Pat, PatKind}; use rustc_lint::LateContext; @@ -61,12 +60,3 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx }, ); } - -/// 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/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index b44a2716dde1..625325d4cf5d 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -3,9 +3,8 @@ use super::ITER_KV_MAP; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::is_local_used; +use clippy_utils::{pat_is_wild, sugg}; use rustc_hir::{BindingAnnotation, Body, BorrowKind, ByRef, Expr, ExprKind, Mutability, Pat, PatKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; @@ -84,13 +83,3 @@ pub(super) fn check<'tcx>( } } } - -/// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_` -/// that is not locally used. -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/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 11467138a589..7497d4b2cf11 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2960,3 +2960,15 @@ op_utils! { Shl ShlAssign Shr ShrAssign } + +/// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_` +/// that is not locally used. +pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { + !visitors::is_local_used(cx, body, id) + }, + _ => false, + } +}