]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/manual_map.rs
Rollup merge of #81309 - lcnr:lazy-norm-err-msgh, r=nikomatsakis
[rust.git] / src / tools / clippy / clippy_lints / src / manual_map.rs
1 use crate::{
2     map_unit_fn::OPTION_MAP_UNIT_FN,
3     matches::MATCH_AS_REF,
4     utils::{
5         is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs,
6         peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg,
7     },
8 };
9 use rustc_ast::util::parser::PREC_POSTFIX;
10 use rustc_errors::Applicability;
11 use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, QPath};
12 use rustc_lint::{LateContext, LateLintPass, LintContext};
13 use rustc_middle::lint::in_external_macro;
14 use rustc_session::{declare_lint_pass, declare_tool_lint};
15 use rustc_span::symbol::{sym, Ident};
16
17 declare_clippy_lint! {
18     /// **What it does:** Checks for usages of `match` which could be implemented using `map`
19     ///
20     /// **Why is this bad?** Using the `map` method is clearer and more concise.
21     ///
22     /// **Known problems:** None.
23     ///
24     /// **Example:**
25     ///
26     /// ```rust
27     /// match Some(0) {
28     ///     Some(x) => Some(x + 1),
29     ///     None => None,
30     /// };
31     /// ```
32     /// Use instead:
33     /// ```rust
34     /// Some(0).map(|x| x + 1);
35     /// ```
36     pub MANUAL_MAP,
37     style,
38     "reimplementation of `map`"
39 }
40
41 declare_lint_pass!(ManualMap => [MANUAL_MAP]);
42
43 impl LateLintPass<'_> for ManualMap {
44     #[allow(clippy::too_many_lines)]
45     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
46         if in_external_macro(cx.sess(), expr.span) {
47             return;
48         }
49
50         if let ExprKind::Match(scrutinee, [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], _) =
51             expr.kind
52         {
53             let (scrutinee_ty, ty_ref_count, ty_mutability) =
54                 peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
55             if !is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
56                 || !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)
57             {
58                 return;
59             }
60
61             let (some_expr, some_pat, pat_ref_count, is_wild_none) =
62                 match (try_parse_pattern(cx, arm1.pat), try_parse_pattern(cx, arm2.pat)) {
63                     (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count }))
64                         if is_none_expr(cx, arm1.body) =>
65                     {
66                         (arm2.body, pattern, ref_count, true)
67                     },
68                     (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count }))
69                         if is_none_expr(cx, arm1.body) =>
70                     {
71                         (arm2.body, pattern, ref_count, false)
72                     },
73                     (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild))
74                         if is_none_expr(cx, arm2.body) =>
75                     {
76                         (arm1.body, pattern, ref_count, true)
77                     },
78                     (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None))
79                         if is_none_expr(cx, arm2.body) =>
80                     {
81                         (arm1.body, pattern, ref_count, false)
82                     },
83                     _ => return,
84                 };
85
86             // Top level or patterns aren't allowed in closures.
87             if matches!(some_pat.kind, PatKind::Or(_)) {
88                 return;
89             }
90
91             let some_expr = match get_some_expr(cx, some_expr) {
92                 Some(expr) => expr,
93                 None => return,
94             };
95
96             if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
97                 && !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
98             {
99                 return;
100             }
101
102             // Determine which binding mode to use.
103             let explicit_ref = some_pat.contains_explicit_ref_binding();
104             let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
105
106             let as_ref_str = match binding_ref {
107                 Some(Mutability::Mut) => ".as_mut()",
108                 Some(Mutability::Not) => ".as_ref()",
109                 None => "",
110             };
111
112             let mut app = Applicability::MachineApplicable;
113
114             // Remove address-of expressions from the scrutinee. `as_ref` will be called,
115             // the type is copyable, or the option is being passed by value.
116             let scrutinee = peel_hir_expr_refs(scrutinee).0;
117             let scrutinee_str = snippet_with_applicability(cx, scrutinee.span, "_", &mut app);
118             let scrutinee_str = if expr.precedence().order() < PREC_POSTFIX {
119                 // Parens are needed to chain method calls.
120                 format!("({})", scrutinee_str)
121             } else {
122                 scrutinee_str.into()
123             };
124
125             let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind {
126                 if let Some(func) = can_pass_as_func(cx, some_binding, some_expr) {
127                     snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
128                 } else {
129                     if match_var(some_expr, some_binding.name)
130                         && !is_allowed(cx, MATCH_AS_REF, expr.hir_id)
131                         && binding_ref.is_some()
132                     {
133                         return;
134                     }
135
136                     // `ref` and `ref mut` annotations were handled earlier.
137                     let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
138                         "mut "
139                     } else {
140                         ""
141                     };
142                     format!(
143                         "|{}{}| {}",
144                         annotation,
145                         some_binding,
146                         snippet_with_applicability(cx, some_expr.span, "..", &mut app)
147                     )
148                 }
149             } else if !is_wild_none && explicit_ref.is_none() {
150                 // TODO: handle explicit reference annotations.
151                 format!(
152                     "|{}| {}",
153                     snippet_with_applicability(cx, some_pat.span, "..", &mut app),
154                     snippet_with_applicability(cx, some_expr.span, "..", &mut app)
155                 )
156             } else {
157                 // Refutable bindings and mixed reference annotations can't be handled by `map`.
158                 return;
159             };
160
161             span_lint_and_sugg(
162                 cx,
163                 MANUAL_MAP,
164                 expr.span,
165                 "manual implementation of `Option::map`",
166                 "try this",
167                 format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str),
168                 app,
169             );
170         }
171     }
172 }
173
174 // Checks whether the expression could be passed as a function, or whether a closure is needed.
175 // Returns the function to be passed to `map` if it exists.
176 fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
177     match expr.kind {
178         ExprKind::Call(func, [arg])
179             if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
180         {
181             Some(func)
182         },
183         _ => None,
184     }
185 }
186
187 enum OptionPat<'a> {
188     Wild,
189     None,
190     Some {
191         // The pattern contained in the `Some` tuple.
192         pattern: &'a Pat<'a>,
193         // The number of references before the `Some` tuple.
194         // e.g. `&&Some(_)` has a ref count of 2.
195         ref_count: usize,
196     },
197 }
198
199 // Try to parse into a recognized `Option` pattern.
200 // i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
201 fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<OptionPat<'tcx>> {
202     fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize) -> Option<OptionPat<'tcx>> {
203         match pat.kind {
204             PatKind::Wild => Some(OptionPat::Wild),
205             PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1),
206             PatKind::Path(QPath::Resolved(None, path))
207                 if path
208                     .res
209                     .opt_def_id()
210                     .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) =>
211             {
212                 Some(OptionPat::None)
213             },
214             PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _)
215                 if path
216                     .res
217                     .opt_def_id()
218                     .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) =>
219             {
220                 Some(OptionPat::Some { pattern, ref_count })
221             },
222             _ => None,
223         }
224     }
225     f(cx, pat, 0)
226 }
227
228 // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
229 fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
230     // TODO: Allow more complex expressions.
231     match expr.kind {
232         ExprKind::Call(
233             Expr {
234                 kind: ExprKind::Path(QPath::Resolved(None, path)),
235                 ..
236             },
237             [arg],
238         ) => {
239             if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) {
240                 Some(arg)
241             } else {
242                 None
243             }
244         },
245         ExprKind::Block(
246             Block {
247                 stmts: [],
248                 expr: Some(expr),
249                 ..
250             },
251             _,
252         ) => get_some_expr(cx, expr),
253         _ => None,
254     }
255 }
256
257 // Checks for the `None` value.
258 fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
259     match expr.kind {
260         ExprKind::Path(QPath::Resolved(None, path)) => path
261             .res
262             .opt_def_id()
263             .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)),
264         ExprKind::Block(
265             Block {
266                 stmts: [],
267                 expr: Some(expr),
268                 ..
269             },
270             _,
271         ) => is_none_expr(cx, expr),
272         _ => false,
273     }
274 }