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::type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{get_path_from_caller_to_method_type, is_adjusted, path_to_local, path_to_local_id}; use rustc_errors::Applicability; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, TyCtxt, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; use rustc_target::spec::abi::Abi; use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; declare_clippy_lint! { /// ### What it does /// Checks for closures which just call another function where /// the function can be called directly. `unsafe` functions, calls where types /// get adjusted or where the callee is marked `#[track_caller]` are ignored. /// /// ### Why is this bad? /// Needlessly creating a closure adds code for no benefit /// and gives the optimizer more work. /// /// ### Known problems /// If creating the closure inside the closure has a side- /// effect then moving the closure creation out will change when that side- /// effect runs. /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details. /// /// ### Example /// ```rust,ignore /// xs.map(|x| foo(x)) /// ``` /// /// Use instead: /// ```rust,ignore /// // where `foo(_)` is a plain function that takes the exact argument type of `x`. /// xs.map(foo) /// ``` #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE, style, "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)" } declare_clippy_lint! { /// ### What it does /// Checks for closures which only invoke a method on the closure /// argument and can be replaced by referencing the method directly. /// /// ### Why is this bad? /// It's unnecessary to create the closure. /// /// ### Example /// ```rust,ignore /// Some('a').map(|s| s.to_uppercase()); /// ``` /// may be rewritten as /// ```rust,ignore /// Some('a').map(char::to_uppercase); /// ``` #[clippy::version = "1.35.0"] pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS, pedantic, "redundant closures for method calls" } declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]); impl<'tcx> LateLintPass<'tcx> for EtaReduction { #[allow(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let body = if let ExprKind::Closure(c) = expr.kind && c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) && matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_)) && !expr.span.from_expansion() { cx.tcx.hir().body(c.body) } else { return; }; if body.value.span.from_expansion() { if body.params.is_empty() { if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { // replace `|| vec![]` with `Vec::new` span_lint_and_sugg( cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", "replace the closure with `Vec::new`", "std::vec::Vec::new".into(), Applicability::MachineApplicable, ); } } // skip `foo(|| macro!())` return; } let typeck = cx.typeck_results(); let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() { closure_subs.as_closure() } else { return; }; if is_adjusted(cx, body.value) { return; } match body.value.kind { ExprKind::Call(callee, args) if matches!( callee.kind, ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..)) ) => { let callee_ty = typeck.expr_ty(callee).peel_refs(); if matches!(type_diagnostic_name(cx, callee_ty), Some(sym::Arc | sym::Rc)) || !check_inputs(typeck, body.params, None, args) { return; } let callee_ty_adjusted = typeck .expr_adjustments(callee) .last() .map_or(callee_ty, |a| a.target.peel_refs()); let sig = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => { // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location` if cx.tcx.has_attr(*def, sym::track_caller) { return; } cx.tcx.fn_sig(def).skip_binder().skip_binder() }, ty::FnPtr(sig) => sig.skip_binder(), ty::Closure(_, subs) => cx .tcx .signature_unclosure(subs.as_closure().sig(), Safety::Safe) .skip_binder(), _ => { if typeck.type_dependent_def_id(body.value.hir_id).is_some() && let subs = typeck.node_args(body.value.hir_id) && let output = typeck.expr_ty(body.value) && let ty::Tuple(tys) = *subs.type_at(1).kind() { cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) } else { return; } }, }; if check_sig(cx, closure, sig) && let generic_args = typeck.node_args(callee.hir_id) // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result // in a type which is `'static`. // 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 let Ok((ClosureKind::FnMut, _)) = cx.tcx.infer_ctxt().build().type_implements_fn_trait( cx.param_env, Binder::bind_with_vars(callee_ty_adjusted, List::empty()), ty::PredicatePolarity::Positive, ) && path_to_local(callee).map_or(false, |l| { local_used_in(cx, l, args) || 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, "replace the closure with the function itself", snippet, Applicability::MachineApplicable, ); } }); } }, ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) && !cx.tcx.has_attr(method_def_id, sym::track_caller) && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) { span_lint_and_then( cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| { let args = typeck.node_args(body.value.hir_id); let caller = self_.hir_id.owner.def_id; let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); diag.span_suggestion( expr.span, "replace the closure with the method itself", format!("{}::{}", type_name, path.ident.name), Applicability::MachineApplicable, ); }, ); } }, _ => (), } } } fn check_inputs( typeck: &TypeckResults<'_>, params: &[Param<'_>], self_arg: Option<&Expr<'_>>, args: &[Expr<'_>], ) -> bool { params.len() == self_arg.map_or(0, |_| 1) + args.len() && params.iter().zip(self_arg.into_iter().chain(args)).all(|(p, arg)| { matches!( p.pat.kind, PatKind::Binding(BindingMode::NONE, id, _, None) if path_to_local_id(arg, id) ) // Only allow adjustments which change regions (i.e. re-borrowing). && typeck .expr_adjustments(arg) .last() .map_or(true, |a| a.target == typeck.expr_ty(arg)) }) } fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs>, call_sig: FnSig<'_>) -> bool { call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions( cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(), call_sig, ) } /// This walks through both signatures and checks for any time a late-bound region is expected by an /// `impl Fn` type, but the target signature does not have a late-bound region in the same position. /// /// This is needed because rustc is unable to late bind early-bound regions in a function signature. fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool { fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool { matches!(from_region.kind(), RegionKind::ReBound(..)) && !matches!(to_region.kind(), RegionKind::ReBound(..)) } fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool { if from_subs.len() != to_subs.len() { return true; } for (from_arg, to_arg) in to_subs.iter().zip(from_subs) { match (from_arg.unpack(), to_arg.unpack()) { (GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => { if check_region(from_region, to_region) { return true; } }, (GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) => { if check_ty(from_ty, to_ty) { return true; } }, (GenericArgKind::Const(_), GenericArgKind::Const(_)) => (), _ => return true, } } false } fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool { match (from_ty.kind(), to_ty.kind()) { (&ty::Adt(_, from_subs), &ty::Adt(_, to_subs)) => check_subs(from_subs, to_subs), (&ty::Array(from_ty, _), &ty::Array(to_ty, _)) | (&ty::Slice(from_ty), &ty::Slice(to_ty)) => { check_ty(from_ty, to_ty) }, (&ty::Ref(from_region, from_ty, _), &ty::Ref(to_region, to_ty, _)) => { check_region(from_region, to_region) || check_ty(from_ty, to_ty) }, (&ty::Tuple(from_tys), &ty::Tuple(to_tys)) => { from_tys.len() != to_tys.len() || from_tys .iter() .zip(to_tys) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) }, _ => from_ty.has_bound_regions(), } } assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len()); from_sig .inputs_and_output .iter() .zip(to_sig.inputs_and_output) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) }