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
::UsedAfterExprVisitor
;
5 use clippy_utils
::{get_enclosing_loop_or_closure, higher, 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 pub REDUNDANT_CLOSURE
,
44 "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)"
47 declare_clippy_lint
! {
49 /// Checks for closures which only invoke a method on the closure
50 /// argument and can be replaced by referencing the method directly.
52 /// ### Why is this bad?
53 /// It's unnecessary to create the closure.
57 /// Some('a').map(|s| s.to_uppercase());
59 /// may be rewritten as
61 /// Some('a').map(char::to_uppercase);
63 pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS
,
65 "redundant closures for method calls"
68 declare_lint_pass
!(EtaReduction
=> [REDUNDANT_CLOSURE
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
]);
70 impl<'tcx
> LateLintPass
<'tcx
> for EtaReduction
{
71 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
72 if expr
.span
.from_expansion() {
75 let body
= match expr
.kind
{
76 ExprKind
::Closure(_
, _
, id
, _
, _
) => cx
.tcx
.hir().body(id
),
79 if body
.value
.span
.from_expansion() {
80 if body
.params
.is_empty() {
81 if let Some(VecArgs
::Vec(&[])) = higher
::VecArgs
::hir(cx
, &body
.value
) {
82 // replace `|| vec![]` with `Vec::new`
88 "replace the closure with `Vec::new`",
89 "std::vec::Vec::new".into(),
90 Applicability
::MachineApplicable
,
94 // skip `foo(|| macro!())`
98 let closure_ty
= cx
.typeck_results().expr_ty(expr
);
101 if let ExprKind
::Call(callee
, args
) = body
.value
.kind
;
102 if let ExprKind
::Path(_
) = callee
.kind
;
103 if check_inputs(cx
, body
.params
, args
);
104 let callee_ty
= cx
.typeck_results().expr_ty_adjusted(callee
);
105 let call_ty
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
)
106 .map_or(callee_ty
, |id
| cx
.tcx
.type_of(id
));
107 if check_sig(cx
, closure_ty
, call_ty
);
108 let substs
= cx
.typeck_results().node_substs(callee
.hir_id
);
109 // This fixes some false positives that I don't entirely understand
110 if substs
.is_empty() || !cx
.typeck_results().expr_ty(expr
).has_late_bound_regions();
111 // A type param function ref like `T::f` is not 'static, however
112 // it is if cast like `T::f as fn()`. This seems like a rustc bug.
113 if !substs
.types().any(|t
| matches
!(t
.kind(), ty
::Param(_
)));
115 span_lint_and_then(cx
, REDUNDANT_CLOSURE
, expr
.span
, "redundant closure", |diag
| {
116 if let Some(mut snippet
) = snippet_opt(cx
, callee
.span
) {
118 if let ty
::Closure(_
, substs
) = callee_ty
.peel_refs().kind();
119 if substs
.as_closure().kind() == ClosureKind
::FnMut
;
120 if get_enclosing_loop_or_closure(cx
.tcx
, expr
).is_some()
121 || UsedAfterExprVisitor
::is_found(cx
, callee
);
124 // Mutable closure is used after current expr; we cannot consume it.
125 snippet
= format
!("&mut {}", snippet
);
128 diag
.span_suggestion(
130 "replace the closure with the function itself",
132 Applicability
::MachineApplicable
,
140 if let ExprKind
::MethodCall(path
, _
, args
, _
) = body
.value
.kind
;
141 if check_inputs(cx
, body
.params
, args
);
142 let method_def_id
= cx
.typeck_results().type_dependent_def_id(body
.value
.hir_id
).unwrap();
143 let substs
= cx
.typeck_results().node_substs(body
.value
.hir_id
);
144 let call_ty
= cx
.tcx
.type_of(method_def_id
).subst(cx
.tcx
, substs
);
145 if check_sig(cx
, closure_ty
, call_ty
);
147 span_lint_and_then(cx
, REDUNDANT_CLOSURE_FOR_METHOD_CALLS
, expr
.span
, "redundant closure", |diag
| {
148 let name
= get_ufcs_type_name(cx
, method_def_id
);
149 diag
.span_suggestion(
151 "replace the closure with the method itself",
152 format
!("{}::{}", name
, path
.ident
.name
),
153 Applicability
::MachineApplicable
,
161 fn check_inputs(cx
: &LateContext
<'_
>, params
: &[Param
<'_
>], call_args
: &[Expr
<'_
>]) -> bool
{
162 if params
.len() != call_args
.len() {
165 std
::iter
::zip(params
, call_args
).all(|(param
, arg
)| {
166 match param
.pat
.kind
{
167 PatKind
::Binding(_
, id
, ..) if path_to_local_id(arg
, id
) => {}
,
170 match *cx
.typeck_results().expr_adjustments(arg
) {
174 kind
: Adjust
::Deref(None
),
178 kind
: Adjust
::Borrow(AutoBorrow
::Ref(_
, mu2
)),
182 // re-borrow with the same mutability is allowed
183 let ty
= cx
.typeck_results().expr_ty(arg
);
184 matches
!(*ty
.kind(), ty
::Ref(.., mu1
) if mu1
== mu2
.into())
191 fn check_sig
<'tcx
>(cx
: &LateContext
<'tcx
>, closure_ty
: Ty
<'tcx
>, call_ty
: Ty
<'tcx
>) -> bool
{
192 let call_sig
= call_ty
.fn_sig(cx
.tcx
);
193 if call_sig
.unsafety() == Unsafety
::Unsafe
{
196 if !closure_ty
.has_late_bound_regions() {
199 let substs
= match closure_ty
.kind() {
200 ty
::Closure(_
, substs
) => substs
,
203 let closure_sig
= cx
.tcx
.signature_unclosure(substs
.as_closure().sig(), Unsafety
::Normal
);
204 cx
.tcx
.erase_late_bound_regions(closure_sig
) == cx
.tcx
.erase_late_bound_regions(call_sig
)
207 fn get_ufcs_type_name(cx
: &LateContext
<'_
>, method_def_id
: DefId
) -> String
{
208 match cx
.tcx
.associated_item(method_def_id
).container
{
209 ty
::TraitContainer(def_id
) => cx
.tcx
.def_path_str(def_id
),
210 ty
::ImplContainer(def_id
) => {
211 let ty
= cx
.tcx
.type_of(def_id
);
213 ty
::Adt(adt
, _
) => cx
.tcx
.def_path_str(adt
.did
),