Emit lint about redundant closure on the closure node itself (#14791)

Fixes rust-lang/rust-clippy#14789

changelog: [`redundant_closure`],
[`redundant_closure_for_method_calls`]: allow lint to be allowed or
expected on the closure expression itself
This commit is contained in:
Timo 2025-06-21 15:20:48 +00:00 committed by GitHub
commit 6330f9119b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 39 deletions

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::higher::VecArgs;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::ty::get_type_diagnostic_name;
@ -108,14 +108,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
{
let vec_crate = if is_no_std_crate(cx) { "alloc" } else { "std" };
// replace `|| vec![]` with `Vec::new`
span_lint_and_sugg(
span_lint_hir_and_then(
cx,
REDUNDANT_CLOSURE,
expr.hir_id,
expr.span,
"redundant closure",
"replace the closure with `Vec::new`",
format!("{vec_crate}::vec::Vec::new"),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion(
expr.span,
"replace the closure with `Vec::new`",
format!("{vec_crate}::vec::Vec::new"),
Applicability::MachineApplicable,
);
},
);
}
// skip `foo(|| macro!())`
@ -197,41 +203,48 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
// For now ignore all callee types which reference a type parameter.
&& !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_)))
{
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
if path_to_local(callee).is_some_and(|l| {
// FIXME: Do we really need this `local_used_in` check?
// Isn't it checking something like... `callee(callee)`?
// If somehow this check is needed, add some test for it,
// 'cuz currently nothing changes after deleting this check.
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
}) {
match cx
.tcx
.infer_ctxt()
.build(cx.typing_mode())
.err_ctxt()
.type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,
) {
// Mutable closure is used after current expr; we cannot consume it.
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
snippet = format!("&{snippet}");
},
_ => (),
span_lint_hir_and_then(
cx,
REDUNDANT_CLOSURE,
expr.hir_id,
expr.span,
"redundant closure",
|diag| {
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
if path_to_local(callee).is_some_and(|l| {
// FIXME: Do we really need this `local_used_in` check?
// Isn't it checking something like... `callee(callee)`?
// If somehow this check is needed, add some test for it,
// 'cuz currently nothing changes after deleting this check.
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
}) {
match cx
.tcx
.infer_ctxt()
.build(cx.typing_mode())
.err_ctxt()
.type_implements_fn_trait(
cx.param_env,
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
ty::PredicatePolarity::Positive,
) {
// Mutable closure is used after current expr; we cannot consume it.
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
snippet = format!("&{snippet}");
},
_ => (),
}
}
diag.span_suggestion(
expr.span,
"replace the closure with the function itself",
snippet,
Applicability::MachineApplicable,
);
}
diag.span_suggestion(
expr.span,
"replace the closure with the function itself",
snippet,
Applicability::MachineApplicable,
);
}
});
},
);
}
},
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
@ -244,9 +257,10 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
Some(span) => format!("::{}", snippet_with_applicability(cx, span, "<..>", &mut app)),
None => String::new(),
};
span_lint_and_then(
span_lint_hir_and_then(
cx,
REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
expr.hir_id,
expr.span,
"redundant closure",
|diag| {

View file

@ -543,3 +543,21 @@ mod issue_13073 {
//~^ redundant_closure
}
}
fn issue_14789() {
_ = Some(1u8).map(
#[expect(clippy::redundant_closure)]
|a| foo(a),
);
_ = Some("foo").map(
#[expect(clippy::redundant_closure_for_method_calls)]
|s| s.to_owned(),
);
let _: Vec<u8> = None.map_or_else(
#[expect(clippy::redundant_closure)]
|| vec![],
std::convert::identity,
);
}

View file

@ -543,3 +543,21 @@ mod issue_13073 {
//~^ redundant_closure
}
}
fn issue_14789() {
_ = Some(1u8).map(
#[expect(clippy::redundant_closure)]
|a| foo(a),
);
_ = Some("foo").map(
#[expect(clippy::redundant_closure_for_method_calls)]
|s| s.to_owned(),
);
let _: Vec<u8> = None.map_or_else(
#[expect(clippy::redundant_closure)]
|| vec![],
std::convert::identity,
);
}