]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs
Box `ExprKind::{Closure,MethodCall}`, and `QSelf` in expressions, types, and patterns.
[rust.git] / src / tools / clippy / clippy_lints / src / unnested_or_patterns.rs
1 #![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
2
3 use clippy_utils::ast_utils::{eq_field_pat, eq_id, eq_maybe_qself, eq_pat, eq_path};
4 use clippy_utils::diagnostics::span_lint_and_then;
5 use clippy_utils::{meets_msrv, msrvs, over};
6 use rustc_ast::mut_visit::*;
7 use rustc_ast::ptr::P;
8 use rustc_ast::{self as ast, Mutability, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
9 use rustc_ast_pretty::pprust;
10 use rustc_errors::Applicability;
11 use rustc_lint::{EarlyContext, EarlyLintPass};
12 use rustc_semver::RustcVersion;
13 use rustc_session::{declare_tool_lint, impl_lint_pass};
14 use rustc_span::DUMMY_SP;
15
16 use std::cell::Cell;
17 use std::mem;
18
19 declare_clippy_lint! {
20     /// ### What it does
21     /// Checks for unnested or-patterns, e.g., `Some(0) | Some(2)` and
22     /// suggests replacing the pattern with a nested one, `Some(0 | 2)`.
23     ///
24     /// Another way to think of this is that it rewrites patterns in
25     /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*.
26     ///
27     /// ### Why is this bad?
28     /// In the example above, `Some` is repeated, which unnecessarily complicates the pattern.
29     ///
30     /// ### Example
31     /// ```rust
32     /// fn main() {
33     ///     if let Some(0) | Some(2) = Some(0) {}
34     /// }
35     /// ```
36     /// Use instead:
37     /// ```rust
38     /// fn main() {
39     ///     if let Some(0 | 2) = Some(0) {}
40     /// }
41     /// ```
42     #[clippy::version = "1.46.0"]
43     pub UNNESTED_OR_PATTERNS,
44     pedantic,
45     "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
46 }
47
48 #[derive(Clone, Copy)]
49 pub struct UnnestedOrPatterns {
50     msrv: Option<RustcVersion>,
51 }
52
53 impl UnnestedOrPatterns {
54     #[must_use]
55     pub fn new(msrv: Option<RustcVersion>) -> Self {
56         Self { msrv }
57     }
58 }
59
60 impl_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]);
61
62 impl EarlyLintPass for UnnestedOrPatterns {
63     fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) {
64         if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
65             lint_unnested_or_patterns(cx, &a.pat);
66         }
67     }
68
69     fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
70         if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
71             if let ast::ExprKind::Let(pat, _, _) = &e.kind {
72                 lint_unnested_or_patterns(cx, pat);
73             }
74         }
75     }
76
77     fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) {
78         if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
79             lint_unnested_or_patterns(cx, &p.pat);
80         }
81     }
82
83     fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) {
84         if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
85             lint_unnested_or_patterns(cx, &l.pat);
86         }
87     }
88
89     extract_msrv_attr!(EarlyContext);
90 }
91
92 fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) {
93     if let Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_) = pat.kind {
94         // This is a leaf pattern, so cloning is unprofitable.
95         return;
96     }
97
98     let mut pat = P(pat.clone());
99
100     // Nix all the paren patterns everywhere so that they aren't in our way.
101     remove_all_parens(&mut pat);
102
103     // Transform all unnested or-patterns into nested ones, and if there were none, quit.
104     if !unnest_or_patterns(&mut pat) {
105         return;
106     }
107
108     span_lint_and_then(cx, UNNESTED_OR_PATTERNS, pat.span, "unnested or-patterns", |db| {
109         insert_necessary_parens(&mut pat);
110         db.span_suggestion_verbose(
111             pat.span,
112             "nest the patterns",
113             pprust::pat_to_string(&pat),
114             Applicability::MachineApplicable,
115         );
116     });
117 }
118
119 /// Remove all `(p)` patterns in `pat`.
120 fn remove_all_parens(pat: &mut P<Pat>) {
121     struct Visitor;
122     impl MutVisitor for Visitor {
123         fn visit_pat(&mut self, pat: &mut P<Pat>) {
124             noop_visit_pat(pat, self);
125             let inner = match &mut pat.kind {
126                 Paren(i) => mem::replace(&mut i.kind, Wild),
127                 _ => return,
128             };
129             pat.kind = inner;
130         }
131     }
132     Visitor.visit_pat(pat);
133 }
134
135 /// Insert parens where necessary according to Rust's precedence rules for patterns.
136 fn insert_necessary_parens(pat: &mut P<Pat>) {
137     struct Visitor;
138     impl MutVisitor for Visitor {
139         fn visit_pat(&mut self, pat: &mut P<Pat>) {
140             use ast::BindingAnnotation;
141             noop_visit_pat(pat, self);
142             let target = match &mut pat.kind {
143                 // `i @ a | b`, `box a | b`, and `& mut? a | b`.
144                 Ident(.., Some(p)) | Box(p) | Ref(p, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p,
145                 Ref(p, Mutability::Not) if matches!(p.kind, Ident(BindingAnnotation::MUT, ..)) => p, // `&(mut x)`
146                 _ => return,
147             };
148             target.kind = Paren(P(take_pat(target)));
149         }
150     }
151     Visitor.visit_pat(pat);
152 }
153
154 /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`.
155 /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`.
156 fn unnest_or_patterns(pat: &mut P<Pat>) -> bool {
157     struct Visitor {
158         changed: bool,
159     }
160     impl MutVisitor for Visitor {
161         fn visit_pat(&mut self, p: &mut P<Pat>) {
162             // This is a bottom up transformation, so recurse first.
163             noop_visit_pat(p, self);
164
165             // Don't have an or-pattern? Just quit early on.
166             let Or(alternatives) = &mut p.kind else {
167                 return
168             };
169
170             // Collapse or-patterns directly nested in or-patterns.
171             let mut idx = 0;
172             let mut this_level_changed = false;
173             while idx < alternatives.len() {
174                 let inner = if let Or(ps) = &mut alternatives[idx].kind {
175                     mem::take(ps)
176                 } else {
177                     idx += 1;
178                     continue;
179                 };
180                 this_level_changed = true;
181                 alternatives.splice(idx..=idx, inner);
182             }
183
184             // Focus on `p_n` and then try to transform all `p_i` where `i > n`.
185             let mut focus_idx = 0;
186             while focus_idx < alternatives.len() {
187                 this_level_changed |= transform_with_focus_on_idx(alternatives, focus_idx);
188                 focus_idx += 1;
189             }
190             self.changed |= this_level_changed;
191
192             // Deal with `Some(Some(0)) | Some(Some(1))`.
193             if this_level_changed {
194                 noop_visit_pat(p, self);
195             }
196         }
197     }
198
199     let mut visitor = Visitor { changed: false };
200     visitor.visit_pat(pat);
201     visitor.changed
202 }
203
204 /// Match `$scrutinee` against `$pat` and extract `$then` from it.
205 /// Panics if there is no match.
206 macro_rules! always_pat {
207     ($scrutinee:expr, $pat:pat => $then:expr) => {
208         match $scrutinee {
209             $pat => $then,
210             _ => unreachable!(),
211         }
212     };
213 }
214
215 /// Focus on `focus_idx` in `alternatives`,
216 /// attempting to extend it with elements of the same constructor `C`
217 /// in `alternatives[focus_idx + 1..]`.
218 fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize) -> bool {
219     // Extract the kind; we'll need to make some changes in it.
220     let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild);
221     // We'll focus on `alternatives[focus_idx]`,
222     // so we're draining from `alternatives[focus_idx + 1..]`.
223     let start = focus_idx + 1;
224
225     // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`.
226     let changed = match &mut focus_kind {
227         // These pattern forms are "leafs" and do not have sub-patterns.
228         // Therefore they are not some form of constructor `C`,
229         // with which a pattern `C(p_0)` may be formed,
230         // which we would want to join with other `C(p_j)`s.
231         Ident(.., None) | Lit(_) | Wild | Path(..) | Range(..) | Rest | MacCall(_)
232         // Skip immutable refs, as grouping them saves few characters,
233         // and almost always requires adding parens (increasing noisiness).
234         // In the case of only two patterns, replacement adds net characters.
235         | Ref(_, Mutability::Not)
236         // Dealt with elsewhere.
237         | Or(_) | Paren(_) => false,
238         // Transform `box x | ... | box y` into `box (x | y)`.
239         //
240         // The cases below until `Slice(...)` deal with *singleton* products.
241         // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`.
242         Box(target) => extend_with_matching(
243             target, start, alternatives,
244             |k| matches!(k, Box(_)),
245             |k| always_pat!(k, Box(p) => p),
246         ),
247         // Transform `&mut x | ... | &mut y` into `&mut (x | y)`.
248         Ref(target, Mutability::Mut) => extend_with_matching(
249             target, start, alternatives,
250             |k| matches!(k, Ref(_, Mutability::Mut)),
251             |k| always_pat!(k, Ref(p, _) => p),
252         ),
253         // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`.
254         Ident(b1, i1, Some(target)) => extend_with_matching(
255             target, start, alternatives,
256             // Binding names must match.
257             |k| matches!(k, Ident(b2, i2, Some(_)) if b1 == b2 && eq_id(*i1, *i2)),
258             |k| always_pat!(k, Ident(_, _, Some(p)) => p),
259         ),
260         // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`.
261         Slice(ps1) => extend_with_matching_product(
262             ps1, start, alternatives,
263             |k, ps1, idx| matches!(k, Slice(ps2) if eq_pre_post(ps1, ps2, idx)),
264             |k| always_pat!(k, Slice(ps) => ps),
265         ),
266         // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post)`.
267         Tuple(ps1) => extend_with_matching_product(
268             ps1, start, alternatives,
269             |k, ps1, idx| matches!(k, Tuple(ps2) if eq_pre_post(ps1, ps2, idx)),
270             |k| always_pat!(k, Tuple(ps) => ps),
271         ),
272         // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post)`.
273         TupleStruct(qself1, path1, ps1) => extend_with_matching_product(
274             ps1, start, alternatives,
275             |k, ps1, idx| matches!(
276                 k,
277                 TupleStruct(qself2, path2, ps2)
278                     if eq_maybe_qself(qself1, qself2) && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx)
279             ),
280             |k| always_pat!(k, TupleStruct(_, _, ps) => ps),
281         ),
282         // Transform a record pattern `S { fp_0, ..., fp_n }`.
283         Struct(qself1, path1, fps1, rest1) => extend_with_struct_pat(qself1, path1, fps1, *rest1, start, alternatives),
284     };
285
286     alternatives[focus_idx].kind = focus_kind;
287     changed
288 }
289
290 /// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`.
291 /// In particular, for a record pattern, the order in which the field patterns is irrelevant.
292 /// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
293 /// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
294 fn extend_with_struct_pat(
295     qself1: &Option<P<ast::QSelf>>,
296     path1: &ast::Path,
297     fps1: &mut [ast::PatField],
298     rest1: bool,
299     start: usize,
300     alternatives: &mut Vec<P<Pat>>,
301 ) -> bool {
302     (0..fps1.len()).any(|idx| {
303         let pos_in_2 = Cell::new(None); // The element `k`.
304         let tail_or = drain_matching(
305             start,
306             alternatives,
307             |k| {
308                 matches!(k, Struct(qself2, path2, fps2, rest2)
309                 if rest1 == *rest2 // If one struct pattern has `..` so must the other.
310                 && eq_maybe_qself(qself1, qself2)
311                 && eq_path(path1, path2)
312                 && fps1.len() == fps2.len()
313                 && fps1.iter().enumerate().all(|(idx_1, fp1)| {
314                     if idx_1 == idx {
315                         // In the case of `k`, we merely require identical field names
316                         // so that we will transform into `ident_k: p1_k | p2_k`.
317                         let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident));
318                         pos_in_2.set(pos);
319                         pos.is_some()
320                     } else {
321                         fps2.iter().any(|fp2| eq_field_pat(fp1, fp2))
322                     }
323                 }))
324             },
325             // Extract `p2_k`.
326             |k| always_pat!(k, Struct(_, _, mut fps, _) => fps.swap_remove(pos_in_2.take().unwrap()).pat),
327         );
328         extend_with_tail_or(&mut fps1[idx].pat, tail_or)
329     })
330 }
331
332 /// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`.
333 /// Here, the idea is that we fixate on some `p_k` in `C`,
334 /// allowing it to vary between two `targets` and `ps2` (returned by `extract`),
335 /// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post),
336 /// where `~` denotes semantic equality.
337 fn extend_with_matching_product(
338     targets: &mut [P<Pat>],
339     start: usize,
340     alternatives: &mut Vec<P<Pat>>,
341     predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool,
342     extract: impl Fn(PatKind) -> Vec<P<Pat>>,
343 ) -> bool {
344     (0..targets.len()).any(|idx| {
345         let tail_or = drain_matching(
346             start,
347             alternatives,
348             |k| predicate(k, targets, idx),
349             |k| extract(k).swap_remove(idx),
350         );
351         extend_with_tail_or(&mut targets[idx], tail_or)
352     })
353 }
354
355 /// Extract the pattern from the given one and replace it with `Wild`.
356 /// This is meant for temporarily swapping out the pattern for manipulation.
357 fn take_pat(from: &mut Pat) -> Pat {
358     let dummy = Pat {
359         id: DUMMY_NODE_ID,
360         kind: Wild,
361         span: DUMMY_SP,
362         tokens: None,
363     };
364     mem::replace(from, dummy)
365 }
366
367 /// Extend `target` as an or-pattern with the alternatives
368 /// in `tail_or` if there are any and return if there were.
369 fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<P<Pat>>) -> bool {
370     fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) {
371         match target {
372             // On an existing or-pattern in the target, append to it.
373             Pat { kind: Or(ps), .. } => ps.append(&mut tail_or),
374             // Otherwise convert the target to an or-pattern.
375             target => {
376                 let mut init_or = vec![P(take_pat(target))];
377                 init_or.append(&mut tail_or);
378                 target.kind = Or(init_or);
379             },
380         }
381     }
382
383     let changed = !tail_or.is_empty();
384     if changed {
385         // Extend the target.
386         extend(target, tail_or);
387     }
388     changed
389 }
390
391 // Extract all inner patterns in `alternatives` matching our `predicate`.
392 // Only elements beginning with `start` are considered for extraction.
393 fn drain_matching(
394     start: usize,
395     alternatives: &mut Vec<P<Pat>>,
396     predicate: impl Fn(&PatKind) -> bool,
397     extract: impl Fn(PatKind) -> P<Pat>,
398 ) -> Vec<P<Pat>> {
399     let mut tail_or = vec![];
400     let mut idx = 0;
401     for pat in alternatives.drain_filter(|p| {
402         // Check if we should extract, but only if `idx >= start`.
403         idx += 1;
404         idx > start && predicate(&p.kind)
405     }) {
406         tail_or.push(extract(pat.into_inner().kind));
407     }
408     tail_or
409 }
410
411 fn extend_with_matching(
412     target: &mut Pat,
413     start: usize,
414     alternatives: &mut Vec<P<Pat>>,
415     predicate: impl Fn(&PatKind) -> bool,
416     extract: impl Fn(PatKind) -> P<Pat>,
417 ) -> bool {
418     extend_with_tail_or(target, drain_matching(start, alternatives, predicate, extract))
419 }
420
421 /// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`?
422 fn eq_pre_post(ps1: &[P<Pat>], ps2: &[P<Pat>], idx: usize) -> bool {
423     ps1.len() == ps2.len()
424         && ps1[idx].is_rest() == ps2[idx].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
425         && over(&ps1[..idx], &ps2[..idx], |l, r| eq_pat(l, r))
426         && over(&ps1[idx + 1..], &ps2[idx + 1..], |l, r| eq_pat(l, r))
427 }