1 #![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
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
::msrvs
::{self, Msrv}
;
6 use clippy_utils
::over
;
7 use rustc_ast
::mut_visit
::*;
9 use rustc_ast
::{self as ast, Mutability, Pat, PatKind, PatKind::*, DUMMY_NODE_ID}
;
10 use rustc_ast_pretty
::pprust
;
11 use rustc_errors
::Applicability
;
12 use rustc_lint
::{EarlyContext, EarlyLintPass}
;
13 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
14 use rustc_span
::DUMMY_SP
;
17 use thin_vec
::{thin_vec, ThinVec}
;
19 declare_clippy_lint
! {
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)`.
24 /// Another way to think of this is that it rewrites patterns in
25 /// *disjunctive normal form (DNF)* into *conjunctive normal form (CNF)*.
27 /// ### Why is this bad?
28 /// In the example above, `Some` is repeated, which unnecessarily complicates the pattern.
33 /// if let Some(0) | Some(2) = Some(0) {}
39 /// if let Some(0 | 2) = Some(0) {}
42 #[clippy::version = "1.46.0"]
43 pub UNNESTED_OR_PATTERNS
,
45 "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
48 pub struct UnnestedOrPatterns
{
52 impl UnnestedOrPatterns
{
54 pub fn new(msrv
: Msrv
) -> Self {
59 impl_lint_pass
!(UnnestedOrPatterns
=> [UNNESTED_OR_PATTERNS
]);
61 impl EarlyLintPass
for UnnestedOrPatterns
{
62 fn check_arm(&mut self, cx
: &EarlyContext
<'_
>, a
: &ast
::Arm
) {
63 if self.msrv
.meets(msrvs
::OR_PATTERNS
) {
64 lint_unnested_or_patterns(cx
, &a
.pat
);
68 fn check_expr(&mut self, cx
: &EarlyContext
<'_
>, e
: &ast
::Expr
) {
69 if self.msrv
.meets(msrvs
::OR_PATTERNS
) {
70 if let ast
::ExprKind
::Let(pat
, _
, _
) = &e
.kind
{
71 lint_unnested_or_patterns(cx
, pat
);
76 fn check_param(&mut self, cx
: &EarlyContext
<'_
>, p
: &ast
::Param
) {
77 if self.msrv
.meets(msrvs
::OR_PATTERNS
) {
78 lint_unnested_or_patterns(cx
, &p
.pat
);
82 fn check_local(&mut self, cx
: &EarlyContext
<'_
>, l
: &ast
::Local
) {
83 if self.msrv
.meets(msrvs
::OR_PATTERNS
) {
84 lint_unnested_or_patterns(cx
, &l
.pat
);
88 extract_msrv_attr
!(EarlyContext
);
91 fn lint_unnested_or_patterns(cx
: &EarlyContext
<'_
>, pat
: &Pat
) {
92 if let Ident(.., None
) | Lit(_
) | Wild
| Path(..) | Range(..) | Rest
| MacCall(_
) = pat
.kind
{
93 // This is a leaf pattern, so cloning is unprofitable.
97 let mut pat
= P(pat
.clone());
99 // Nix all the paren patterns everywhere so that they aren't in our way.
100 remove_all_parens(&mut pat
);
102 // Transform all unnested or-patterns into nested ones, and if there were none, quit.
103 if !unnest_or_patterns(&mut pat
) {
107 span_lint_and_then(cx
, UNNESTED_OR_PATTERNS
, pat
.span
, "unnested or-patterns", |db
| {
108 insert_necessary_parens(&mut pat
);
109 db
.span_suggestion_verbose(
112 pprust
::pat_to_string(&pat
),
113 Applicability
::MachineApplicable
,
118 /// Remove all `(p)` patterns in `pat`.
119 fn remove_all_parens(pat
: &mut P
<Pat
>) {
121 impl MutVisitor
for Visitor
{
122 fn visit_pat(&mut self, pat
: &mut P
<Pat
>) {
123 noop_visit_pat(pat
, self);
124 let inner
= match &mut pat
.kind
{
125 Paren(i
) => mem
::replace(&mut i
.kind
, Wild
),
131 Visitor
.visit_pat(pat
);
134 /// Insert parens where necessary according to Rust's precedence rules for patterns.
135 fn insert_necessary_parens(pat
: &mut P
<Pat
>) {
137 impl MutVisitor
for Visitor
{
138 fn visit_pat(&mut self, pat
: &mut P
<Pat
>) {
139 use ast
::BindingAnnotation
;
140 noop_visit_pat(pat
, self);
141 let target
= match &mut pat
.kind
{
142 // `i @ a | b`, `box a | b`, and `& mut? a | b`.
143 Ident(.., Some(p
)) | Box(p
) | Ref(p
, _
) if matches
!(&p
.kind
, Or(ps
) if ps
.len() > 1) => p
,
144 Ref(p
, Mutability
::Not
) if matches
!(p
.kind
, Ident(BindingAnnotation
::MUT
, ..)) => p
, // `&(mut x)`
147 target
.kind
= Paren(P(take_pat(target
)));
150 Visitor
.visit_pat(pat
);
153 /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`.
154 /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`.
155 fn unnest_or_patterns(pat
: &mut P
<Pat
>) -> bool
{
159 impl MutVisitor
for Visitor
{
160 fn visit_pat(&mut self, p
: &mut P
<Pat
>) {
161 // This is a bottom up transformation, so recurse first.
162 noop_visit_pat(p
, self);
164 // Don't have an or-pattern? Just quit early on.
165 let Or(alternatives
) = &mut p
.kind
else {
169 // Collapse or-patterns directly nested in or-patterns.
171 let mut this_level_changed
= false;
172 while idx
< alternatives
.len() {
173 let inner
= if let Or(ps
) = &mut alternatives
[idx
].kind
{
179 this_level_changed
= true;
180 alternatives
.splice(idx
..=idx
, inner
);
183 // Focus on `p_n` and then try to transform all `p_i` where `i > n`.
184 let mut focus_idx
= 0;
185 while focus_idx
< alternatives
.len() {
186 this_level_changed
|= transform_with_focus_on_idx(alternatives
, focus_idx
);
189 self.changed
|= this_level_changed
;
191 // Deal with `Some(Some(0)) | Some(Some(1))`.
192 if this_level_changed
{
193 noop_visit_pat(p
, self);
198 let mut visitor
= Visitor { changed: false }
;
199 visitor
.visit_pat(pat
);
203 /// Match `$scrutinee` against `$pat` and extract `$then` from it.
204 /// Panics if there is no match.
205 macro_rules
! always_pat
{
206 ($scrutinee
:expr
, $pat
:pat
=> $then
:expr
) => {
214 /// Focus on `focus_idx` in `alternatives`,
215 /// attempting to extend it with elements of the same constructor `C`
216 /// in `alternatives[focus_idx + 1..]`.
217 fn transform_with_focus_on_idx(alternatives
: &mut ThinVec
<P
<Pat
>>, focus_idx
: usize) -> bool
{
218 // Extract the kind; we'll need to make some changes in it.
219 let mut focus_kind
= mem
::replace(&mut alternatives
[focus_idx
].kind
, PatKind
::Wild
);
220 // We'll focus on `alternatives[focus_idx]`,
221 // so we're draining from `alternatives[focus_idx + 1..]`.
222 let start
= focus_idx
+ 1;
224 // We're trying to find whatever kind (~"constructor") we found in `alternatives[start..]`.
225 let changed
= match &mut focus_kind
{
226 // These pattern forms are "leafs" and do not have sub-patterns.
227 // Therefore they are not some form of constructor `C`,
228 // with which a pattern `C(p_0)` may be formed,
229 // which we would want to join with other `C(p_j)`s.
230 Ident(.., None
) | Lit(_
) | Wild
| Path(..) | Range(..) | Rest
| MacCall(_
)
231 // Skip immutable refs, as grouping them saves few characters,
232 // and almost always requires adding parens (increasing noisiness).
233 // In the case of only two patterns, replacement adds net characters.
234 | Ref(_
, Mutability
::Not
)
235 // Dealt with elsewhere.
236 | Or(_
) | Paren(_
) => false,
237 // Transform `box x | ... | box y` into `box (x | y)`.
239 // The cases below until `Slice(...)` deal with *singleton* products.
240 // These patterns have the shape `C(p)`, and not e.g., `C(p0, ..., pn)`.
241 Box(target
) => extend_with_matching(
242 target
, start
, alternatives
,
243 |k
| matches
!(k
, Box(_
)),
244 |k
| always_pat
!(k
, Box(p
) => p
),
246 // Transform `&mut x | ... | &mut y` into `&mut (x | y)`.
247 Ref(target
, Mutability
::Mut
) => extend_with_matching(
248 target
, start
, alternatives
,
249 |k
| matches
!(k
, Ref(_
, Mutability
::Mut
)),
250 |k
| always_pat
!(k
, Ref(p
, _
) => p
),
252 // Transform `b @ p0 | ... b @ p1` into `b @ (p0 | p1)`.
253 Ident(b1
, i1
, Some(target
)) => extend_with_matching(
254 target
, start
, alternatives
,
255 // Binding names must match.
256 |k
| matches
!(k
, Ident(b2
, i2
, Some(_
)) if b1
== b2
&& eq_id(*i1
, *i2
)),
257 |k
| always_pat
!(k
, Ident(_
, _
, Some(p
)) => p
),
259 // Transform `[pre, x, post] | ... | [pre, y, post]` into `[pre, x | y, post]`.
260 Slice(ps1
) => extend_with_matching_product(
261 ps1
, start
, alternatives
,
262 |k
, ps1
, idx
| matches
!(k
, Slice(ps2
) if eq_pre_post(ps1
, ps2
, idx
)),
263 |k
| always_pat
!(k
, Slice(ps
) => ps
),
265 // Transform `(pre, x, post) | ... | (pre, y, post)` into `(pre, x | y, post)`.
266 Tuple(ps1
) => extend_with_matching_product(
267 ps1
, start
, alternatives
,
268 |k
, ps1
, idx
| matches
!(k
, Tuple(ps2
) if eq_pre_post(ps1
, ps2
, idx
)),
269 |k
| always_pat
!(k
, Tuple(ps
) => ps
),
271 // Transform `S(pre, x, post) | ... | S(pre, y, post)` into `S(pre, x | y, post)`.
272 TupleStruct(qself1
, path1
, ps1
) => extend_with_matching_product(
273 ps1
, start
, alternatives
,
274 |k
, ps1
, idx
| matches
!(
276 TupleStruct(qself2
, path2
, ps2
)
277 if eq_maybe_qself(qself1
, qself2
) && eq_path(path1
, path2
) && eq_pre_post(ps1
, ps2
, idx
)
279 |k
| always_pat
!(k
, TupleStruct(_
, _
, ps
) => ps
),
281 // Transform a record pattern `S { fp_0, ..., fp_n }`.
282 Struct(qself1
, path1
, fps1
, rest1
) => extend_with_struct_pat(qself1
, path1
, fps1
, *rest1
, start
, alternatives
),
285 alternatives
[focus_idx
].kind
= focus_kind
;
289 /// Here we focusing on a record pattern `S { fp_0, ..., fp_n }`.
290 /// In particular, for a record pattern, the order in which the field patterns is irrelevant.
291 /// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern
292 /// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal.
293 fn extend_with_struct_pat(
294 qself1
: &Option
<P
<ast
::QSelf
>>,
296 fps1
: &mut [ast
::PatField
],
299 alternatives
: &mut ThinVec
<P
<Pat
>>,
301 (0..fps1
.len()).any(|idx
| {
302 let pos_in_2
= Cell
::new(None
); // The element `k`.
303 let tail_or
= drain_matching(
307 matches
!(k
, Struct(qself2
, path2
, fps2
, rest2
)
308 if rest1
== *rest2
// If one struct pattern has `..` so must the other.
309 && eq_maybe_qself(qself1
, qself2
)
310 && eq_path(path1
, path2
)
311 && fps1
.len() == fps2
.len()
312 && fps1
.iter().enumerate().all(|(idx_1
, fp1
)| {
314 // In the case of `k`, we merely require identical field names
315 // so that we will transform into `ident_k: p1_k | p2_k`.
316 let pos
= fps2
.iter().position(|fp2
| eq_id(fp1
.ident
, fp2
.ident
));
320 fps2
.iter().any(|fp2
| eq_field_pat(fp1
, fp2
))
325 |k
| always_pat
!(k
, Struct(_
, _
, mut fps
, _
) => fps
.swap_remove(pos_in_2
.take().unwrap()).pat
),
327 extend_with_tail_or(&mut fps1
[idx
].pat
, tail_or
)
331 /// Like `extend_with_matching` but for products with > 1 factor, e.g., `C(p_0, ..., p_n)`.
332 /// Here, the idea is that we fixate on some `p_k` in `C`,
333 /// allowing it to vary between two `targets` and `ps2` (returned by `extract`),
334 /// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post),
335 /// where `~` denotes semantic equality.
336 fn extend_with_matching_product(
337 targets
: &mut [P
<Pat
>],
339 alternatives
: &mut ThinVec
<P
<Pat
>>,
340 predicate
: impl Fn(&PatKind
, &[P
<Pat
>], usize) -> bool
,
341 extract
: impl Fn(PatKind
) -> ThinVec
<P
<Pat
>>,
343 (0..targets
.len()).any(|idx
| {
344 let tail_or
= drain_matching(
347 |k
| predicate(k
, targets
, idx
),
348 |k
| extract(k
).swap_remove(idx
),
350 extend_with_tail_or(&mut targets
[idx
], tail_or
)
354 /// Extract the pattern from the given one and replace it with `Wild`.
355 /// This is meant for temporarily swapping out the pattern for manipulation.
356 fn take_pat(from
: &mut Pat
) -> Pat
{
363 mem
::replace(from
, dummy
)
366 /// Extend `target` as an or-pattern with the alternatives
367 /// in `tail_or` if there are any and return if there were.
368 fn extend_with_tail_or(target
: &mut Pat
, tail_or
: ThinVec
<P
<Pat
>>) -> bool
{
369 fn extend(target
: &mut Pat
, mut tail_or
: ThinVec
<P
<Pat
>>) {
371 // On an existing or-pattern in the target, append to it.
372 Pat { kind: Or(ps), .. }
=> ps
.append(&mut tail_or
),
373 // Otherwise convert the target to an or-pattern.
375 let mut init_or
= thin_vec
![P(take_pat(target
))];
376 init_or
.append(&mut tail_or
);
377 target
.kind
= Or(init_or
);
382 let changed
= !tail_or
.is_empty();
384 // Extend the target.
385 extend(target
, tail_or
);
390 // Extract all inner patterns in `alternatives` matching our `predicate`.
391 // Only elements beginning with `start` are considered for extraction.
394 alternatives
: &mut ThinVec
<P
<Pat
>>,
395 predicate
: impl Fn(&PatKind
) -> bool
,
396 extract
: impl Fn(PatKind
) -> P
<Pat
>,
397 ) -> ThinVec
<P
<Pat
>> {
398 let mut tail_or
= ThinVec
::new();
401 // If `ThinVec` had the `drain_filter` method, this loop could be rewritten
404 // for pat in alternatives.drain_filter(|p| {
405 // // Check if we should extract, but only if `idx >= start`.
407 // idx > start && predicate(&p.kind)
409 // tail_or.push(extract(pat.into_inner().kind));
412 while i
< alternatives
.len() {
414 // Check if we should extract, but only if `idx >= start`.
415 if idx
> start
&& predicate(&alternatives
[i
].kind
) {
416 let pat
= alternatives
.remove(i
);
417 tail_or
.push(extract(pat
.into_inner().kind
));
426 fn extend_with_matching(
429 alternatives
: &mut ThinVec
<P
<Pat
>>,
430 predicate
: impl Fn(&PatKind
) -> bool
,
431 extract
: impl Fn(PatKind
) -> P
<Pat
>,
433 extend_with_tail_or(target
, drain_matching(start
, alternatives
, predicate
, extract
))
436 /// Are the patterns in `ps1` and `ps2` equal save for `ps1[idx]` compared to `ps2[idx]`?
437 fn eq_pre_post(ps1
: &[P
<Pat
>], ps2
: &[P
<Pat
>], idx
: usize) -> bool
{
438 ps1
.len() == ps2
.len()
439 && ps1
[idx
].is_rest() == ps2
[idx
].is_rest() // Avoid `[x, ..] | [x, 0]` => `[x, .. | 0]`.
440 && over(&ps1
[..idx
], &ps2
[..idx
], |l
, r
| eq_pat(l
, r
))
441 && over(&ps1
[idx
+ 1..], &ps2
[idx
+ 1..], |l
, r
| eq_pat(l
, r
))