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