1 use clippy_utils
::diagnostics
::{span_lint_and_sugg, span_lint_and_then}
;
2 use clippy_utils
::higher
::VecArgs
;
3 use clippy_utils
::source
::snippet_opt
;
4 use clippy_utils
::ty
::is_type_diagnostic_item
;
5 use clippy_utils
::usage
::local_used_after_expr
;
6 use clippy_utils
::{higher, is_adjusted, path_to_local, path_to_local_id}
;
7 use if_chain
::if_chain
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::def_id
::DefId
;
10 use rustc_hir
::{Expr, ExprKind, Param, PatKind, Unsafety}
;
11 use rustc_lint
::{LateContext, LateLintPass}
;
12 use rustc_middle
::ty
::adjustment
::{Adjust, Adjustment, AutoBorrow}
;
13 use rustc_middle
::ty
::binding
::BindingMode
;
14 use rustc_middle
::ty
::subst
::Subst
;
15 use rustc_middle
::ty
::{self, ClosureKind, Ty, TypeFoldable}
;
16 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
17 use rustc_span
::symbol
::sym
;
19 declare_clippy_lint
! {
21 /// Checks for closures which just call another function where
22 /// the function can be called directly. `unsafe` functions or calls where types
23 /// get adjusted are ignored.
25 /// ### Why is this bad?
26 /// Needlessly creating a closure adds code for no benefit
27 /// and gives the optimizer more work.
29 /// ### Known problems
30 /// If creating the closure inside the closure has a side-
31 /// effect then moving the closure creation out will change when that side-
33 /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
37 /// xs.map(|x| foo(x))
42 /// // where `foo(_)` is a plain function that takes the exact argument type of `x`.
45 #[clippy::version = "pre 1.29.0"]
46 pub REDUNDANT_CLOSURE
,
48 "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)"
51 declare_clippy_lint
! {
53 /// Checks for closures which only invoke a method on the closure
54 /// argument and can be replaced by referencing the method directly.
56 /// ### Why is this bad?
57 /// It's unnecessary to create the closure.
61 /// Some('a').map(|s| s.to_uppercase());
63 /// may be rewritten as
65 /// Some('a').map(char::to_uppercase);
67 #[clippy::version = "1.35.0"]
68 pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS
,
70 "redundant closures for method calls"
73 declare_lint_pass
!(EtaReduction
=> [REDUNDANT_CLOSURE
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
]);
75 impl<'tcx
> LateLintPass
<'tcx
> for EtaReduction
{
76 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
77 if expr
.span
.from_expansion() {
80 let body
= match expr
.kind
{
81 ExprKind
::Closure { body, .. }
=> cx
.tcx
.hir().body(body
),
84 if body
.value
.span
.from_expansion() {
85 if body
.params
.is_empty() {
86 if let Some(VecArgs
::Vec(&[])) = higher
::VecArgs
::hir(cx
, &body
.value
) {
87 // replace `|| vec![]` with `Vec::new`
93 "replace the closure with `Vec::new`",
94 "std::vec::Vec::new".into(),
95 Applicability
::MachineApplicable
,
99 // skip `foo(|| macro!())`
103 let closure_ty
= cx
.typeck_results().expr_ty(expr
);
106 if !is_adjusted(cx
, &body
.value
);
107 if let ExprKind
::Call(callee
, args
) = body
.value
.kind
;
108 if let ExprKind
::Path(_
) = callee
.kind
;
109 if check_inputs(cx
, body
.params
, args
);
110 let callee_ty
= cx
.typeck_results().expr_ty_adjusted(callee
);
111 let call_ty
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
)
112 .map_or(callee_ty
, |id
| cx
.tcx
.type_of(id
));
113 if check_sig(cx
, closure_ty
, call_ty
);
114 let substs
= cx
.typeck_results().node_substs(callee
.hir_id
);
115 // This fixes some false positives that I don't entirely understand
116 if substs
.is_empty() || !cx
.typeck_results().expr_ty(expr
).has_late_bound_regions();
117 // A type param function ref like `T::f` is not 'static, however
118 // it is if cast like `T::f as fn()`. This seems like a rustc bug.
119 if !substs
.types().any(|t
| matches
!(t
.kind(), ty
::Param(_
)));
120 let callee_ty_unadjusted
= cx
.typeck_results().expr_ty(callee
).peel_refs();
121 if !is_type_diagnostic_item(cx
, callee_ty_unadjusted
, sym
::Arc
);
122 if !is_type_diagnostic_item(cx
, callee_ty_unadjusted
, sym
::Rc
);
124 span_lint_and_then(cx
, REDUNDANT_CLOSURE
, expr
.span
, "redundant closure", |diag
| {
125 if let Some(mut snippet
) = snippet_opt(cx
, callee
.span
) {
127 if let ty
::Closure(_
, substs
) = callee_ty
.peel_refs().kind();
128 if substs
.as_closure().kind() == ClosureKind
::FnMut
;
129 if path_to_local(callee
).map_or(false, |l
| local_used_after_expr(cx
, l
, expr
));
132 // Mutable closure is used after current expr; we cannot consume it.
133 snippet
= format
!("&mut {}", snippet
);
136 diag
.span_suggestion(
138 "replace the closure with the function itself",
140 Applicability
::MachineApplicable
,
148 if !is_adjusted(cx
, &body
.value
);
149 if let ExprKind
::MethodCall(path
, args
, _
) = body
.value
.kind
;
150 if check_inputs(cx
, body
.params
, args
);
151 let method_def_id
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
).unwrap();
152 let substs
= cx
.typeck_results().node_substs(body
.value
.hir_id
);
153 let call_ty
= cx
.tcx
.bound_type_of(method_def_id
).subst(cx
.tcx
, substs
);
154 if check_sig(cx
, closure_ty
, call_ty
);
156 span_lint_and_then(cx
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
, expr
.span
, "redundant closure", |diag
| {
157 let name
= get_ufcs_type_name(cx
, method_def_id
);
158 diag
.span_suggestion(
160 "replace the closure with the method itself",
161 format
!("{}::{}", name
, path
.ident
.name
),
162 Applicability
::MachineApplicable
,
170 fn check_inputs(cx
: &LateContext
<'_
>, params
: &[Param
<'_
>], call_args
: &[Expr
<'_
>]) -> bool
{
171 if params
.len() != call_args
.len() {
174 let binding_modes
= cx
.typeck_results().pat_binding_modes();
175 std
::iter
::zip(params
, call_args
).all(|(param
, arg
)| {
176 match param
.pat
.kind
{
177 PatKind
::Binding(_
, id
, ..) if path_to_local_id(arg
, id
) => {}
,
180 // checks that parameters are not bound as `ref` or `ref mut`
181 if let Some(BindingMode
::BindByReference(_
)) = binding_modes
.get(param
.pat
.hir_id
) {
185 match *cx
.typeck_results().expr_adjustments(arg
) {
189 kind
: Adjust
::Deref(None
),
193 kind
: Adjust
::Borrow(AutoBorrow
::Ref(_
, mu2
)),
197 // re-borrow with the same mutability is allowed
198 let ty
= cx
.typeck_results().expr_ty(arg
);
199 matches
!(*ty
.kind(), ty
::Ref(.., mu1
) if mu1
== mu2
.into())
206 fn check_sig
<'tcx
>(cx
: &LateContext
<'tcx
>, closure_ty
: Ty
<'tcx
>, call_ty
: Ty
<'tcx
>) -> bool
{
207 let call_sig
= call_ty
.fn_sig(cx
.tcx
);
208 if call_sig
.unsafety() == Unsafety
::Unsafe
{
211 if !closure_ty
.has_late_bound_regions() {
214 let substs
= match closure_ty
.kind() {
215 ty
::Closure(_
, substs
) => substs
,
218 let closure_sig
= cx
.tcx
.signature_unclosure(substs
.as_closure().sig(), Unsafety
::Normal
);
219 cx
.tcx
.erase_late_bound_regions(closure_sig
) == cx
.tcx
.erase_late_bound_regions(call_sig
)
222 fn get_ufcs_type_name(cx
: &LateContext
<'_
>, method_def_id
: DefId
) -> String
{
223 match cx
.tcx
.associated_item(method_def_id
).container
{
224 ty
::TraitContainer(def_id
) => cx
.tcx
.def_path_str(def_id
),
225 ty
::ImplContainer(def_id
) => {
226 let ty
= cx
.tcx
.type_of(def_id
);
228 ty
::Adt(adt
, _
) => cx
.tcx
.def_path_str(adt
.did()),