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