Skip to content

Commit 773d203

Browse files
committed
Fix mixed enum variant kinds + code cleanup
1 parent 08a7157 commit 773d203

File tree

4 files changed

+123
-63
lines changed

4 files changed

+123
-63
lines changed

clippy_lints/src/matches/match_same_arms.rs

+65-48
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::source::snippet;
33
use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
4+
use core::cmp::Ordering;
45
use core::iter;
6+
use core::slice;
57
use rustc_arena::DroplessArena;
68
use rustc_ast::ast::LitKind;
79
use rustc_errors::Applicability;
@@ -36,7 +38,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
3638
normalized_pats[i + 1..]
3739
.iter()
3840
.enumerate()
39-
.find_map(|(j, other)| pat.can_also_match(other).then(|| i + 1 + j))
41+
.find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
4042
.unwrap_or(normalized_pats.len())
4143
})
4244
.collect();
@@ -52,7 +54,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
5254
.rev()
5355
.zip(forwards_blocking_idxs[..i].iter().copied().rev())
5456
.skip_while(|&(_, forward_block)| forward_block > i)
55-
.find_map(|((j, other), forward_block)| (forward_block == i || pat.can_also_match(other)).then(|| j))
57+
.find_map(|((j, other), forward_block)| {
58+
(forward_block == i || pat.has_overlapping_values(other)).then(|| j)
59+
})
5660
.unwrap_or(0)
5761
})
5862
.collect();
@@ -159,6 +163,10 @@ enum NormalizedPat<'a> {
159163
LitInt(u128),
160164
LitBool(bool),
161165
Range(PatRange),
166+
/// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise
167+
/// the first value contains everything before the `..` wildcard pattern, and the second value
168+
/// contains everything afterwards. Note that either side, or both sides, may contain zero
169+
/// patterns.
162170
Slice(&'a [Self], Option<&'a [Self]>),
163171
}
164172

@@ -178,23 +186,43 @@ impl PatRange {
178186
}
179187

180188
fn overlaps(&self, other: &Self) -> bool {
181-
!(self.is_empty() || other.is_empty())
182-
&& match self.bounds {
183-
RangeEnd::Included => self.end >= other.start,
184-
RangeEnd::Excluded => self.end > other.start,
185-
}
186-
&& match other.bounds {
187-
RangeEnd::Included => self.start <= other.end,
188-
RangeEnd::Excluded => self.start < other.end,
189-
}
189+
// Note: Empty ranges are impossible, so this is correct even though it would return true if an
190+
// empty exclusive range were to reside within an inclusive range.
191+
(match self.bounds {
192+
RangeEnd::Included => self.end >= other.start,
193+
RangeEnd::Excluded => self.end > other.start,
194+
} && match other.bounds {
195+
RangeEnd::Included => self.start <= other.end,
196+
RangeEnd::Excluded => self.start < other.end,
197+
})
190198
}
199+
}
191200

192-
fn is_empty(&self) -> bool {
193-
match self.bounds {
194-
RangeEnd::Included => false,
195-
RangeEnd::Excluded => self.start == self.end,
201+
/// Iterates over the pairs of fields with matching names.
202+
fn iter_matching_struct_fields<'a>(
203+
left: &'a [(Symbol, NormalizedPat<'a>)],
204+
right: &'a [(Symbol, NormalizedPat<'a>)],
205+
) -> impl Iterator<Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>)> + 'a {
206+
struct Iter<'a>(
207+
slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
208+
slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
209+
);
210+
impl<'a> Iterator for Iter<'a> {
211+
type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>);
212+
fn next(&mut self) -> Option<Self::Item> {
213+
// Note: all the fields in each slice are sorted by symbol value.
214+
let mut left = self.0.next()?;
215+
let mut right = self.1.next()?;
216+
loop {
217+
match left.0.cmp(&right.0) {
218+
Ordering::Equal => return Some((&left.1, &right.1)),
219+
Ordering::Less => left = self.0.next()?,
220+
Ordering::Greater => right = self.1.next()?,
221+
}
222+
}
196223
}
197224
}
225+
Iter(left.iter(), right.iter())
198226
}
199227

200228
#[allow(clippy::similar_names)]
@@ -259,6 +287,7 @@ impl<'a> NormalizedPat<'a> {
259287
Self::Tuple(None, pats)
260288
},
261289
PatKind::Lit(e) => match &e.kind {
290+
// TODO: Handle negative integers. They're currently treated as a wild match.
262291
ExprKind::Lit(lit) => match lit.node {
263292
LitKind::Str(sym, _) => Self::LitStr(sym),
264293
LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
@@ -271,6 +300,7 @@ impl<'a> NormalizedPat<'a> {
271300
_ => Self::Wild,
272301
},
273302
PatKind::Range(start, end, bounds) => {
303+
// TODO: Handle negative integers. They're currently treated as a wild match.
274304
let start = match start {
275305
None => 0,
276306
Some(e) => match &e.kind {
@@ -306,42 +336,17 @@ impl<'a> NormalizedPat<'a> {
306336

307337
/// Checks if two patterns overlap in the values they can match assuming they are for the same
308338
/// type.
309-
fn can_also_match(&self, other: &Self) -> bool {
339+
fn has_overlapping_values(&self, other: &Self) -> bool {
310340
match (*self, *other) {
311341
(Self::Wild, _) | (_, Self::Wild) => true,
312342
(Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => {
313-
pats.iter().any(|pat| pat.can_also_match(other))
343+
pats.iter().any(|pat| pat.has_overlapping_values(other))
314344
},
315345
(Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => {
316346
if lpath != rpath {
317347
return false;
318348
}
319-
let mut rfields = rfields.iter();
320-
let mut rfield = match rfields.next() {
321-
Some(x) => x,
322-
None => return true,
323-
};
324-
'outer: for lfield in lfields {
325-
loop {
326-
if lfield.0 < rfield.0 {
327-
continue 'outer;
328-
} else if lfield.0 > rfield.0 {
329-
rfield = match rfields.next() {
330-
Some(x) => x,
331-
None => return true,
332-
};
333-
} else if !lfield.1.can_also_match(&rfield.1) {
334-
return false;
335-
} else {
336-
rfield = match rfields.next() {
337-
Some(x) => x,
338-
None => return true,
339-
};
340-
continue 'outer;
341-
}
342-
}
343-
}
344-
true
349+
iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
345350
},
346351
(Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => {
347352
if lpath != rpath {
@@ -350,7 +355,7 @@ impl<'a> NormalizedPat<'a> {
350355
lpats
351356
.iter()
352357
.zip(rpats.iter())
353-
.all(|(lpat, rpat)| lpat.can_also_match(rpat))
358+
.all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
354359
},
355360
(Self::Path(x), Self::Path(y)) => x == y,
356361
(Self::LitStr(x), Self::LitStr(y)) => x == y,
@@ -360,26 +365,38 @@ impl<'a> NormalizedPat<'a> {
360365
(Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
361366
(Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
362367
(Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
363-
lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.can_also_match(y))
368+
lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
364369
},
365370
(Self::Slice(pats, None), Self::Slice(front, Some(back)))
366371
| (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
372+
// Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
373+
// then the minium length required will be greater than the length of `pats`.
367374
if pats.len() < front.len() + back.len() {
368375
return false;
369376
}
370377
pats[..front.len()]
371378
.iter()
372379
.zip(front.iter())
373380
.chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
374-
.all(|(x, y)| x.can_also_match(y))
381+
.all(|(x, y)| x.has_overlapping_values(y))
375382
},
376383
(Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
377384
.iter()
378385
.zip(rfront.iter())
379386
.chain(lback.iter().rev().zip(rback.iter().rev()))
380-
.all(|(x, y)| x.can_also_match(y)),
387+
.all(|(x, y)| x.has_overlapping_values(y)),
388+
389+
// Enums can mix unit variants with tuple/struct variants. These can never overlap.
390+
(Self::Path(_), Self::Tuple(..) | Self::Struct(..))
391+
| (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false,
392+
393+
// Tuples can be matched like a struct.
394+
(Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => {
395+
// TODO: check fields here.
396+
x == y
397+
},
381398

382-
// Todo: Lit* with Path, Range with Path, LitBytes with Slice, Slice with Slice
399+
// TODO: Lit* with Path, Range with Path, LitBytes with Slice
383400
_ => true,
384401
}
385402
}

clippy_lints/src/write.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,19 @@ impl Write {
581581
};
582582

583583
let replacement: String = match lit.token.kind {
584-
LitKind::Integer | LitKind::Float | LitKind::Err => continue,
585584
LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
586585
lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
587586
},
588587
LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
589588
lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
590589
},
591-
LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
590+
LitKind::StrRaw(_)
591+
| LitKind::Str
592+
| LitKind::ByteStrRaw(_)
593+
| LitKind::ByteStr
594+
| LitKind::Integer
595+
| LitKind::Float
596+
| LitKind::Err => continue,
592597
LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() {
593598
"\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
594599
"\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,

tests/ui/match_same_arms2.rs

+23
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,27 @@ fn main() {
204204
Foo::Z(_) => 1,
205205
_ => 0,
206206
};
207+
208+
// Don't lint.
209+
let _ = match 0 {
210+
-2 => 1,
211+
-5..=50 => 2,
212+
-150..=88 => 1,
213+
_ => 3,
214+
};
215+
216+
struct Bar {
217+
x: u32,
218+
y: u32,
219+
z: u32,
220+
}
221+
222+
// Lint.
223+
let _ = match None {
224+
Some(Bar { x: 0, y: 5, .. }) => 1,
225+
Some(Bar { y: 10, z: 0, .. }) => 2,
226+
None => 50,
227+
Some(Bar { y: 0, x: 5, .. }) => 1,
228+
_ => 200,
229+
};
207230
}

tests/ui/match_same_arms2.stderr

+28-13
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,33 @@ LL | 42 => foo(),
4040
| ^^^^^^^^^^^
4141

4242
error: this match arm has an identical body to another arm
43-
--> $DIR/match_same_arms2.rs:39:9
43+
--> $DIR/match_same_arms2.rs:40:9
4444
|
45-
LL | Some(_) => 24,
46-
| -------^^^^^^
45+
LL | None => 24, //~ ERROR match arms have same body
46+
| ----^^^^^^
4747
| |
48-
| help: try merging the arm patterns: `Some(_) | None`
48+
| help: try merging the arm patterns: `None | Some(_)`
4949
|
5050
= help: or try changing either arm body
5151
note: other arm here
52-
--> $DIR/match_same_arms2.rs:40:9
52+
--> $DIR/match_same_arms2.rs:39:9
5353
|
54-
LL | None => 24, //~ ERROR match arms have same body
55-
| ^^^^^^^^^^
54+
LL | Some(_) => 24,
55+
| ^^^^^^^^^^^^^
5656

5757
error: this match arm has an identical body to another arm
58-
--> $DIR/match_same_arms2.rs:61:9
58+
--> $DIR/match_same_arms2.rs:62:9
5959
|
60-
LL | (Some(a), None) => bar(a),
60+
LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body
6161
| ---------------^^^^^^^^^^
6262
| |
63-
| help: try merging the arm patterns: `(Some(a), None) | (None, Some(a))`
63+
| help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
6464
|
6565
= help: or try changing either arm body
6666
note: other arm here
67-
--> $DIR/match_same_arms2.rs:62:9
67+
--> $DIR/match_same_arms2.rs:61:9
6868
|
69-
LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body
69+
LL | (Some(a), None) => bar(a),
7070
| ^^^^^^^^^^^^^^^^^^^^^^^^^
7171

7272
error: this match arm has an identical body to another arm
@@ -177,5 +177,20 @@ note: other arm here
177177
LL | Foo::X(0) => 1,
178178
| ^^^^^^^^^^^^^^
179179

180-
error: aborting due to 11 previous errors
180+
error: this match arm has an identical body to another arm
181+
--> $DIR/match_same_arms2.rs:227:9
182+
|
183+
LL | Some(Bar { y: 0, x: 5, .. }) => 1,
184+
| ----------------------------^^^^^
185+
| |
186+
| help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })`
187+
|
188+
= help: or try changing either arm body
189+
note: other arm here
190+
--> $DIR/match_same_arms2.rs:224:9
191+
|
192+
LL | Some(Bar { x: 0, y: 5, .. }) => 1,
193+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
194+
195+
error: aborting due to 12 previous errors
181196

0 commit comments

Comments
 (0)