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