]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | #![allow(clippy::wildcard_imports, clippy::enum_glob_use)] |
2 | ||
3 | use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path}; | |
4 | use crate::utils::{over, span_lint_and_then}; | |
5 | use rustc_ast::mut_visit::*; | |
6 | use rustc_ast::ptr::P; | |
7 | use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID}; | |
8 | use rustc_ast_pretty::pprust; | |
9 | use rustc_errors::Applicability; | |
10 | use rustc_lint::{EarlyContext, EarlyLintPass}; | |
11 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
12 | use rustc_span::DUMMY_SP; | |
13 | ||
14 | use std::cell::Cell; | |
15 | use std::mem; | |
16 | ||
17 | declare_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 | ||
52 | declare_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]); | |
53 | ||
54 | impl 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 | ||
74 | fn 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`. | |
107 | fn 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. | |
123 | fn 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`. | |
143 | fn 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. | |
194 | macro_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..]`. | |
206 | fn 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. | |
277 | fn 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. | |
318 | fn 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. | |
338 | fn 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. | |
350 | fn 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. | |
374 | fn 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 | ||
392 | fn 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]`? | |
403 | fn 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 | } |