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
::usage
::local_used_after_expr
;
5 use clippy_utils
::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id}
;
6 use if_chain
::if_chain
;
7 use rustc_errors
::Applicability
;
8 use rustc_hir
::def_id
::DefId
;
9 use rustc_hir
::{Expr, ExprKind, Param, PatKind, Unsafety}
;
10 use rustc_lint
::{LateContext, LateLintPass}
;
11 use rustc_middle
::ty
::adjustment
::{Adjust, Adjustment, AutoBorrow}
;
12 use rustc_middle
::ty
::subst
::Subst
;
13 use rustc_middle
::ty
::{self, ClosureKind, Ty, TypeFoldable}
;
14 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 declare_clippy_lint
! {
18 /// Checks for closures which just call another function where
19 /// the function can be called directly. `unsafe` functions or calls where types
20 /// get adjusted are ignored.
22 /// ### Why is this bad?
23 /// Needlessly creating a closure adds code for no benefit
24 /// and gives the optimizer more work.
26 /// ### Known problems
27 /// If creating the closure inside the closure has a side-
28 /// effect then moving the closure creation out will change when that side-
30 /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
35 /// xs.map(|x| foo(x))
40 /// where `foo(_)` is a plain function that takes the exact argument type of
42 #[clippy::version = "pre 1.29.0"]
43 pub REDUNDANT_CLOSURE
,
45 "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)"
48 declare_clippy_lint
! {
50 /// Checks for closures which only invoke a method on the closure
51 /// argument and can be replaced by referencing the method directly.
53 /// ### Why is this bad?
54 /// It's unnecessary to create the closure.
58 /// Some('a').map(|s| s.to_uppercase());
60 /// may be rewritten as
62 /// Some('a').map(char::to_uppercase);
64 #[clippy::version = "1.35.0"]
65 pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS
,
67 "redundant closures for method calls"
70 declare_lint_pass
!(EtaReduction
=> [REDUNDANT_CLOSURE
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
]);
72 impl<'tcx
> LateLintPass
<'tcx
> for EtaReduction
{
73 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
74 if expr
.span
.from_expansion() {
77 let body
= match expr
.kind
{
78 ExprKind
::Closure(_
, _
, id
, _
, _
) => cx
.tcx
.hir().body(id
),
81 if body
.value
.span
.from_expansion() {
82 if body
.params
.is_empty() {
83 if let Some(VecArgs
::Vec(&[])) = higher
::VecArgs
::hir(cx
, &body
.value
) {
84 // replace `|| vec![]` with `Vec::new`
90 "replace the closure with `Vec::new`",
91 "std::vec::Vec::new".into(),
92 Applicability
::MachineApplicable
,
96 // skip `foo(|| macro!())`
100 let closure_ty
= cx
.typeck_results().expr_ty(expr
);
103 if let ExprKind
::Call(callee
, args
) = body
.value
.kind
;
104 if let ExprKind
::Path(_
) = callee
.kind
;
105 if check_inputs(cx
, body
.params
, args
);
106 let callee_ty
= cx
.typeck_results().expr_ty_adjusted(callee
);
107 let call_ty
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
)
108 .map_or(callee_ty
, |id
| cx
.tcx
.type_of(id
));
109 if check_sig(cx
, closure_ty
, call_ty
);
110 let substs
= cx
.typeck_results().node_substs(callee
.hir_id
);
111 // This fixes some false positives that I don't entirely understand
112 if substs
.is_empty() || !cx
.typeck_results().expr_ty(expr
).has_late_bound_regions();
113 // A type param function ref like `T::f` is not 'static, however
114 // it is if cast like `T::f as fn()`. This seems like a rustc bug.
115 if !substs
.types().any(|t
| matches
!(t
.kind(), ty
::Param(_
)));
117 span_lint_and_then(cx
, REDUNDANT_CLOSURE
, expr
.span
, "redundant closure", |diag
| {
118 if let Some(mut snippet
) = snippet_opt(cx
, callee
.span
) {
120 if let ty
::Closure(_
, substs
) = callee_ty
.peel_refs().kind();
121 if substs
.as_closure().kind() == ClosureKind
::FnMut
;
122 if get_enclosing_loop_or_closure(cx
.tcx
, expr
).is_some()
123 || path_to_local(callee
).map_or(false, |l
| local_used_after_expr(cx
, l
, callee
));
126 // Mutable closure is used after current expr; we cannot consume it.
127 snippet
= format
!("&mut {}", snippet
);
130 diag
.span_suggestion(
132 "replace the closure with the function itself",
134 Applicability
::MachineApplicable
,
142 if let ExprKind
::MethodCall(path
, _
, args
, _
) = body
.value
.kind
;
143 if check_inputs(cx
, body
.params
, args
);
144 let method_def_id
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
).unwrap();
145 let substs
= cx
.typeck_results().node_substs(body
.value
.hir_id
);
146 let call_ty
= cx
.tcx
.type_of(method_def_id
).subst(cx
.tcx
, substs
);
147 if check_sig(cx
, closure_ty
, call_ty
);
149 span_lint_and_then(cx
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
, expr
.span
, "redundant closure", |diag
| {
150 let name
= get_ufcs_type_name(cx
, method_def_id
);
151 diag
.span_suggestion(
153 "replace the closure with the method itself",
154 format
!("{}::{}", name
, path
.ident
.name
),
155 Applicability
::MachineApplicable
,
163 fn check_inputs(cx
: &LateContext
<'_
>, params
: &[Param
<'_
>], call_args
: &[Expr
<'_
>]) -> bool
{
164 if params
.len() != call_args
.len() {
167 std
::iter
::zip(params
, call_args
).all(|(param
, arg
)| {
168 match param
.pat
.kind
{
169 PatKind
::Binding(_
, id
, ..) if path_to_local_id(arg
, id
) => {}
,
172 match *cx
.typeck_results().expr_adjustments(arg
) {
176 kind
: Adjust
::Deref(None
),
180 kind
: Adjust
::Borrow(AutoBorrow
::Ref(_
, mu2
)),
184 // re-borrow with the same mutability is allowed
185 let ty
= cx
.typeck_results().expr_ty(arg
);
186 matches
!(*ty
.kind(), ty
::Ref(.., mu1
) if mu1
== mu2
.into())
193 fn check_sig
<'tcx
>(cx
: &LateContext
<'tcx
>, closure_ty
: Ty
<'tcx
>, call_ty
: Ty
<'tcx
>) -> bool
{
194 let call_sig
= call_ty
.fn_sig(cx
.tcx
);
195 if call_sig
.unsafety() == Unsafety
::Unsafe
{
198 if !closure_ty
.has_late_bound_regions() {
201 let substs
= match closure_ty
.kind() {
202 ty
::Closure(_
, substs
) => substs
,
205 let closure_sig
= cx
.tcx
.signature_unclosure(substs
.as_closure().sig(), Unsafety
::Normal
);
206 cx
.tcx
.erase_late_bound_regions(closure_sig
) == cx
.tcx
.erase_late_bound_regions(call_sig
)
209 fn get_ufcs_type_name(cx
: &LateContext
<'_
>, method_def_id
: DefId
) -> String
{
210 match cx
.tcx
.associated_item(method_def_id
).container
{
211 ty
::TraitContainer(def_id
) => cx
.tcx
.def_path_str(def_id
),
212 ty
::ImplContainer(def_id
) => {
213 let ty
= cx
.tcx
.type_of(def_id
);
215 ty
::Adt(adt
, _
) => cx
.tcx
.def_path_str(adt
.did
),