| 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, TyCtxt, |
| TypeVisitableExt, TypeckResults, |
| }; |
| use rustc_session::declare_lint_pass; |
| use rustc_span::symbol::sym; |
| use rustc_target::spec::abi::Abi; |
| use rustc_trait_selection::error_reporting::InferCtxtErrorExt 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_raw = typeck.expr_ty(callee); |
| let callee_ty = callee_ty_raw.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 path_to_local(callee).map_or(false, |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().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, |
| ); |
| } |
| }); |
| } |
| }, |
| 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<TyCtxt<'tcx>>, 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)) |
| } |