]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/eta_reduction.rs
suggest `&mut` for redundant FnMut closures
[rust.git] / clippy_lints / src / eta_reduction.rs
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::{implements_trait, type_is_unsafe_function};
5 use clippy_utils::usage::UsedAfterExprVisitor;
6 use clippy_utils::{get_enclosing_loop_or_closure, higher};
7 use clippy_utils::{is_adjusted, iter_input_pats};
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
10 use rustc_hir::{def_id, Expr, ExprKind, Param, PatKind, QPath};
11 use rustc_lint::{LateContext, LateLintPass, LintContext};
12 use rustc_middle::lint::in_external_macro;
13 use rustc_middle::ty::{self, ClosureKind, Ty};
14 use rustc_session::{declare_lint_pass, declare_tool_lint};
15
16 declare_clippy_lint! {
17     /// **What it does:** Checks for closures which just call another function where
18     /// the function can be called directly. `unsafe` functions or calls where types
19     /// get adjusted are ignored.
20     ///
21     /// **Why is this bad?** Needlessly creating a closure adds code for no benefit
22     /// and gives the optimizer more work.
23     ///
24     /// **Known problems:** If creating the closure inside the closure has a side-
25     /// effect then moving the closure creation out will change when that side-
26     /// effect runs.
27     /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
28     ///
29     /// **Example:**
30     /// ```rust,ignore
31     /// // Bad
32     /// xs.map(|x| foo(x))
33     ///
34     /// // Good
35     /// xs.map(foo)
36     /// ```
37     /// where `foo(_)` is a plain function that takes the exact argument type of
38     /// `x`.
39     pub REDUNDANT_CLOSURE,
40     style,
41     "redundant closures, i.e., `|a| foo(a)` (which can be written as just `foo`)"
42 }
43
44 declare_clippy_lint! {
45     /// **What it does:** Checks for closures which only invoke a method on the closure
46     /// argument and can be replaced by referencing the method directly.
47     ///
48     /// **Why is this bad?** It's unnecessary to create the closure.
49     ///
50     /// **Known problems:** [#3071](https://github.com/rust-lang/rust-clippy/issues/3071),
51     /// [#3942](https://github.com/rust-lang/rust-clippy/issues/3942),
52     /// [#4002](https://github.com/rust-lang/rust-clippy/issues/4002)
53     ///
54     ///
55     /// **Example:**
56     /// ```rust,ignore
57     /// Some('a').map(|s| s.to_uppercase());
58     /// ```
59     /// may be rewritten as
60     /// ```rust,ignore
61     /// Some('a').map(char::to_uppercase);
62     /// ```
63     pub REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
64     pedantic,
65     "redundant closures for method calls"
66 }
67
68 declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]);
69
70 impl<'tcx> LateLintPass<'tcx> for EtaReduction {
71     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
72         if in_external_macro(cx.sess(), expr.span) {
73             return;
74         }
75
76         match expr.kind {
77             ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => {
78                 for arg in args {
79                     // skip `foo(macro!())`
80                     if arg.span.ctxt() == expr.span.ctxt() {
81                         check_closure(cx, arg);
82                     }
83                 }
84             },
85             _ => (),
86         }
87     }
88 }
89
90 fn check_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
91     if let ExprKind::Closure(_, decl, eid, _, _) = expr.kind {
92         let body = cx.tcx.hir().body(eid);
93         let ex = &body.value;
94
95         if ex.span.ctxt() != expr.span.ctxt() {
96             if decl.inputs.is_empty() {
97                 if let Some(VecArgs::Vec(&[])) = higher::vec_macro(cx, ex) {
98                     // replace `|| vec![]` with `Vec::new`
99                     span_lint_and_sugg(
100                         cx,
101                         REDUNDANT_CLOSURE,
102                         expr.span,
103                         "redundant closure",
104                         "replace the closure with `Vec::new`",
105                         "std::vec::Vec::new".into(),
106                         Applicability::MachineApplicable,
107                     );
108                 }
109             }
110             // skip `foo(|| macro!())`
111             return;
112         }
113
114         if_chain!(
115             if let ExprKind::Call(caller, args) = ex.kind;
116
117             if let ExprKind::Path(_) = caller.kind;
118
119             // Not the same number of arguments, there is no way the closure is the same as the function return;
120             if args.len() == decl.inputs.len();
121
122             // Are the expression or the arguments type-adjusted? Then we need the closure
123             if !(is_adjusted(cx, ex) || args.iter().any(|arg| is_adjusted(cx, arg)));
124
125             let fn_ty = cx.typeck_results().expr_ty(caller);
126
127             if matches!(fn_ty.kind(), ty::FnDef(_, _) | ty::FnPtr(_) | ty::Closure(_, _));
128
129             if !type_is_unsafe_function(cx, fn_ty);
130
131             if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter());
132
133             then {
134                 span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
135                     if let Some(mut snippet) = snippet_opt(cx, caller.span) {
136                         if_chain! {
137                             if let ty::Closure(_, substs) = fn_ty.kind();
138                             if let ClosureKind::FnMut = substs.as_closure().kind();
139                             if UsedAfterExprVisitor::is_found(cx, caller)
140                                 || get_enclosing_loop_or_closure(cx.tcx, expr).is_some();
141
142                             then {
143                                 // Mutable closure is used after current expr; we cannot consume it.
144                                 snippet = format!("&mut {}", snippet);
145                             }
146                         }
147                         diag.span_suggestion(
148                             expr.span,
149                             "replace the closure with the function itself",
150                             snippet,
151                             Applicability::MachineApplicable,
152                         );
153                     }
154                 });
155             }
156         );
157
158         if_chain!(
159             if let ExprKind::MethodCall(path, _, args, _) = ex.kind;
160
161             // Not the same number of arguments, there is no way the closure is the same as the function return;
162             if args.len() == decl.inputs.len();
163
164             // Are the expression or the arguments type-adjusted? Then we need the closure
165             if !(is_adjusted(cx, ex) || args.iter().skip(1).any(|arg| is_adjusted(cx, arg)));
166
167             let method_def_id = cx.typeck_results().type_dependent_def_id(ex.hir_id).unwrap();
168             if !type_is_unsafe_function(cx, cx.tcx.type_of(method_def_id));
169
170             if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter());
171
172             if let Some(name) = get_ufcs_type_name(cx, method_def_id, &args[0]);
173
174             then {
175                 span_lint_and_sugg(
176                     cx,
177                     REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
178                     expr.span,
179                     "redundant closure",
180                     "replace the closure with the method itself",
181                     format!("{}::{}", name, path.ident.name),
182                     Applicability::MachineApplicable,
183                 );
184             }
185         );
186     }
187 }
188
189 /// Tries to determine the type for universal function call to be used instead of the closure
190 fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_arg: &Expr<'_>) -> Option<String> {
191     let expected_type_of_self = &cx.tcx.fn_sig(method_def_id).inputs_and_output().skip_binder()[0];
192     let actual_type_of_self = &cx.typeck_results().node_type(self_arg.hir_id);
193
194     if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) {
195         if match_borrow_depth(expected_type_of_self, actual_type_of_self)
196             && implements_trait(cx, actual_type_of_self, trait_id, &[])
197         {
198             return Some(cx.tcx.def_path_str(trait_id));
199         }
200     }
201
202     cx.tcx.impl_of_method(method_def_id).and_then(|_| {
203         //a type may implicitly implement other type's methods (e.g. Deref)
204         if match_types(expected_type_of_self, actual_type_of_self) {
205             Some(get_type_name(cx, actual_type_of_self))
206         } else {
207             None
208         }
209     })
210 }
211
212 fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
213     match (&lhs.kind(), &rhs.kind()) {
214         (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(t1, t2),
215         (l, r) => !matches!((l, r), (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _))),
216     }
217 }
218
219 fn match_types(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
220     match (&lhs.kind(), &rhs.kind()) {
221         (ty::Bool, ty::Bool)
222         | (ty::Char, ty::Char)
223         | (ty::Int(_), ty::Int(_))
224         | (ty::Uint(_), ty::Uint(_))
225         | (ty::Str, ty::Str) => true,
226         (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_types(t1, t2),
227         (ty::Array(t1, _), ty::Array(t2, _)) | (ty::Slice(t1), ty::Slice(t2)) => match_types(t1, t2),
228         (ty::Adt(def1, _), ty::Adt(def2, _)) => def1 == def2,
229         (_, _) => false,
230     }
231 }
232
233 fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String {
234     match ty.kind() {
235         ty::Adt(t, _) => cx.tcx.def_path_str(t.did),
236         ty::Ref(_, r, _) => get_type_name(cx, r),
237         _ => ty.to_string(),
238     }
239 }
240
241 fn compare_inputs(
242     closure_inputs: &mut dyn Iterator<Item = &Param<'_>>,
243     call_args: &mut dyn Iterator<Item = &Expr<'_>>,
244 ) -> bool {
245     for (closure_input, function_arg) in closure_inputs.zip(call_args) {
246         if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind {
247             // XXXManishearth Should I be checking the binding mode here?
248             if let ExprKind::Path(QPath::Resolved(None, p)) = function_arg.kind {
249                 if p.segments.len() != 1 {
250                     // If it's a proper path, it can't be a local variable
251                     return false;
252                 }
253                 if p.segments[0].ident.name != ident.name {
254                     // The two idents should be the same
255                     return false;
256                 }
257             } else {
258                 return false;
259             }
260         } else {
261             return false;
262         }
263     }
264     true
265 }