]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/map_unit_fn.rs
Fix `doc_markdown` lints
[rust.git] / clippy_lints / src / map_unit_fn.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10
11 use crate::rustc::hir;
12 use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
13 use crate::rustc::{declare_tool_lint, lint_array};
14 use if_chain::if_chain;
15 use crate::rustc::ty;
16 use crate::rustc_errors::Applicability;
17 use crate::syntax::source_map::Span;
18 use crate::utils::{in_macro, iter_input_pats, match_type, method_chain_args, snippet, span_lint_and_then};
19 use crate::utils::paths;
20
21 #[derive(Clone)]
22 pub struct Pass;
23
24 /// **What it does:** Checks for usage of `option.map(f)` where f is a function
25 /// or closure that returns the unit type.
26 ///
27 /// **Why is this bad?** Readability, this can be written more clearly with
28 /// an if let statement
29 ///
30 /// **Known problems:** None.
31 ///
32 /// **Example:**
33 ///
34 /// ```rust
35 /// let x: Option<&str> = do_stuff();
36 /// x.map(log_err_msg);
37 /// x.map(|msg| log_err_msg(format_msg(msg)))
38 /// ```
39 ///
40 /// The correct use would be:
41 ///
42 /// ```rust
43 /// let x: Option<&str> = do_stuff();
44 /// if let Some(msg) = x {
45 ///     log_err_msg(msg)
46 /// }
47 /// if let Some(msg) = x {
48 ///     log_err_msg(format_msg(msg))
49 /// }
50 /// ```
51 declare_clippy_lint! {
52     pub OPTION_MAP_UNIT_FN,
53     complexity,
54     "using `option.map(f)`, where f is a function or closure that returns ()"
55 }
56
57 /// **What it does:** Checks for usage of `result.map(f)` where f is a function
58 /// or closure that returns the unit type.
59 ///
60 /// **Why is this bad?** Readability, this can be written more clearly with
61 /// an if let statement
62 ///
63 /// **Known problems:** None.
64 ///
65 /// **Example:**
66 ///
67 /// ```rust
68 /// let x: Result<&str, &str> = do_stuff();
69 /// x.map(log_err_msg);
70 /// x.map(|msg| log_err_msg(format_msg(msg)))
71 /// ```
72 ///
73 /// The correct use would be:
74 ///
75 /// ```rust
76 /// let x: Result<&str, &str> = do_stuff();
77 /// if let Ok(msg) = x {
78 ///     log_err_msg(msg)
79 /// }
80 /// if let Ok(msg) = x {
81 ///     log_err_msg(format_msg(msg))
82 /// }
83 /// ```
84 declare_clippy_lint! {
85     pub RESULT_MAP_UNIT_FN,
86     complexity,
87     "using `result.map(f)`, where f is a function or closure that returns ()"
88 }
89
90
91 impl LintPass for Pass {
92     fn get_lints(&self) -> LintArray {
93         lint_array!(OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN)
94     }
95 }
96
97 fn is_unit_type(ty: ty::Ty<'_>) -> bool {
98     match ty.sty {
99         ty::Tuple(slice) => slice.is_empty(),
100         ty::Never => true,
101         _ => false,
102     }
103 }
104
105 fn is_unit_function(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
106     let ty = cx.tables.expr_ty(expr);
107
108     if let ty::FnDef(id, _) = ty.sty {
109         if let Some(fn_type) = cx.tcx.fn_sig(id).no_late_bound_regions() {
110             return is_unit_type(fn_type.output());
111         }
112     }
113     false
114 }
115
116 fn is_unit_expression(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> bool {
117     is_unit_type(cx.tables.expr_ty(expr))
118 }
119
120 /// The expression inside a closure may or may not have surrounding braces and
121 /// semicolons, which causes problems when generating a suggestion. Given an
122 /// expression that evaluates to '()' or '!', recursively remove useless braces
123 /// and semi-colons until is suitable for including in the suggestion template
124 fn reduce_unit_expression<'a>(cx: &LateContext<'_, '_>, expr: &'a hir::Expr) -> Option<Span> {
125     if !is_unit_expression(cx, expr) {
126         return None;
127     }
128
129     match expr.node {
130         hir::ExprKind::Call(_, _) |
131         hir::ExprKind::MethodCall(_, _, _) => {
132             // Calls can't be reduced any more
133             Some(expr.span)
134         },
135         hir::ExprKind::Block(ref block, _) => {
136             match (&block.stmts[..], block.expr.as_ref()) {
137                 (&[], Some(inner_expr)) => {
138                     // If block only contains an expression,
139                     // reduce `{ X }` to `X`
140                     reduce_unit_expression(cx, inner_expr)
141                 },
142                 (&[ref inner_stmt], None) => {
143                     // If block only contains statements,
144                     // reduce `{ X; }` to `X` or `X;`
145                     match inner_stmt.node {
146                         hir::StmtKind::Decl(ref d, _) => Some(d.span),
147                         hir::StmtKind::Expr(ref e, _) => Some(e.span),
148                         hir::StmtKind::Semi(_, _) => Some(inner_stmt.span),
149                     }
150                 },
151                 _ => {
152                     // For closures that contain multiple statements
153                     // it's difficult to get a correct suggestion span
154                     // for all cases (multi-line closures specifically)
155                     //
156                     // We do not attempt to build a suggestion for those right now.
157                     None
158                 }
159             }
160         },
161         _ => None,
162     }
163 }
164
165 fn unit_closure<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'a hir::Expr) -> Option<(&'tcx hir::Arg, &'a hir::Expr)> {
166     if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.node {
167         let body = cx.tcx.hir.body(inner_expr_id);
168         let body_expr = &body.value;
169
170         if_chain! {
171             if decl.inputs.len() == 1;
172             if is_unit_expression(cx, body_expr);
173             if let Some(binding) = iter_input_pats(&decl, body).next();
174             then {
175                 return Some((binding, body_expr));
176             }
177         }
178     }
179     None
180 }
181
182 /// Builds a name for the let binding variable (`var_arg`)
183 ///
184 /// `x.field` => `x_field`
185 /// `y` => `_y`
186 ///
187 /// Anything else will return `_`.
188 fn let_binding_name(cx: &LateContext<'_, '_>, var_arg: &hir::Expr) -> String {
189     match &var_arg.node {
190         hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"),
191         hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")),
192         _ => "_".to_string()
193     }
194 }
195
196 fn suggestion_msg(function_type: &str, map_type: &str) -> String {
197     format!(
198         "called `map(f)` on an {0} value where `f` is a unit {1}",
199         map_type,
200         function_type
201     )
202 }
203
204 fn lint_map_unit_fn(cx: &LateContext<'_, '_>, stmt: &hir::Stmt, expr: &hir::Expr, map_args: &[hir::Expr]) {
205     let var_arg = &map_args[0];
206     let fn_arg = &map_args[1];
207
208     let (map_type, variant, lint) =
209         if match_type(cx, cx.tables.expr_ty(var_arg), &paths::OPTION) {
210             ("Option", "Some", OPTION_MAP_UNIT_FN)
211         } else if match_type(cx, cx.tables.expr_ty(var_arg), &paths::RESULT) {
212             ("Result", "Ok", RESULT_MAP_UNIT_FN)
213         } else {
214             return
215         };
216
217     if is_unit_function(cx, fn_arg) {
218         let msg = suggestion_msg("function", map_type);
219         let suggestion = format!("if let {0}({1}) = {2} {{ {3}(...) }}",
220                                  variant,
221                                  let_binding_name(cx, var_arg),
222                                  snippet(cx, var_arg.span, "_"),
223                                  snippet(cx, fn_arg.span, "_"));
224
225         span_lint_and_then(cx, lint, expr.span, &msg, |db| {
226             db.span_suggestion_with_applicability(stmt.span,
227                                                   "try this",
228                                                   suggestion,
229                                                   Applicability::Unspecified);
230         });
231     } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
232         let msg = suggestion_msg("closure", map_type);
233
234         span_lint_and_then(cx, lint, expr.span, &msg, |db| {
235             if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
236                 let suggestion = format!("if let {0}({1}) = {2} {{ {3} }}",
237                                          variant,
238                                          snippet(cx, binding.pat.span, "_"),
239                                          snippet(cx, var_arg.span, "_"),
240                                          snippet(cx, reduced_expr_span, "_"));
241                 db.span_suggestion_with_applicability(
242                     stmt.span,
243                     "try this",
244                     suggestion,
245                     Applicability::MachineApplicable, // snippet
246                 );
247             } else {
248                 let suggestion = format!("if let {0}({1}) = {2} {{ ... }}",
249                                          variant,
250                                          snippet(cx, binding.pat.span, "_"),
251                                          snippet(cx, var_arg.span, "_"));
252                 db.span_suggestion_with_applicability(
253                     stmt.span,
254                     "try this",
255                     suggestion,
256                     Applicability::Unspecified,
257                 );
258             }
259         });
260     }
261 }
262
263 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
264     fn check_stmt(&mut self, cx: &LateContext<'_, '_>, stmt: &hir::Stmt) {
265         if in_macro(stmt.span) {
266             return;
267         }
268
269         if let hir::StmtKind::Semi(ref expr, _) = stmt.node {
270             if let Some(arglists) = method_chain_args(expr, &["map"]) {
271                 lint_map_unit_fn(cx, stmt, expr, arglists[0]);
272             }
273         }
274     }
275 }