diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 710fceceae5..3732410e71e 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::usage::local_used_after_expr; use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id}; use if_chain::if_chain; @@ -11,7 +11,7 @@ use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; -use rustc_middle::ty::{self, ClosureKind, Ty, TypeVisitable}; +use rustc_middle::ty::{self, Ty, TypeVisitable}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -122,15 +122,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { then { span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { if let Some(mut snippet) = snippet_opt(cx, callee.span) { - if_chain! { - if let ty::Closure(_, substs) = callee_ty.peel_refs().kind(); - if substs.as_closure().kind() == ClosureKind::FnMut; - if path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr)); - - then { + if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait() + && implements_trait(cx, callee_ty.peel_refs(), fn_mut_id, &[]) + && path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr)) + { // Mutable closure is used after current expr; we cannot consume it. snippet = format!("&mut {snippet}"); - } } diag.span_suggestion( expr.span, diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index f8d559bf226..e257b3b3afa 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -303,3 +303,16 @@ fn not_general_enough() { fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} f(|path| std::fs::remove_file(path)); } + +// https://github.com/rust-lang/rust-clippy/issues/9369 +pub fn mutable_impl_fn_mut(mut f: impl FnMut(), mut f_used_once: impl FnMut()) -> impl FnMut() { + fn takes_fn_mut(_: impl FnMut()) {} + takes_fn_mut(&mut f); + + fn takes_fn_once(_: impl FnOnce()) {} + takes_fn_once(&mut f); + + f(); + + move || takes_fn_mut(&mut f_used_once) +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index f0fb55a1e5f..486a251f30b 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -303,3 +303,16 @@ fn not_general_enough() { fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} f(|path| std::fs::remove_file(path)); } + +// https://github.com/rust-lang/rust-clippy/issues/9369 +pub fn mutable_impl_fn_mut(mut f: impl FnMut(), mut f_used_once: impl FnMut()) -> impl FnMut() { + fn takes_fn_mut(_: impl FnMut()) {} + takes_fn_mut(|| f()); + + fn takes_fn_once(_: impl FnOnce()) {} + takes_fn_once(|| f()); + + f(); + + move || takes_fn_mut(|| f_used_once()) +} diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index bf2e97e744a..434706b7e25 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -116,5 +116,23 @@ error: redundant closure LL | Some(1).map(|n| in_loop(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop` -error: aborting due to 19 previous errors +error: redundant closure + --> $DIR/eta.rs:310:18 + | +LL | takes_fn_mut(|| f()); + | ^^^^^^ help: replace the closure with the function itself: `&mut f` + +error: redundant closure + --> $DIR/eta.rs:313:19 + | +LL | takes_fn_once(|| f()); + | ^^^^^^ help: replace the closure with the function itself: `&mut f` + +error: redundant closure + --> $DIR/eta.rs:317:26 + | +LL | move || takes_fn_mut(|| f_used_once()) + | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once` + +error: aborting due to 22 previous errors