Skip to content

Commit 27704c7

Browse files
committed
Fix union handling in exhaustiveness
1 parent db9b4ea commit 27704c7

File tree

5 files changed

+71
-18
lines changed

5 files changed

+71
-18
lines changed

compiler/rustc_middle/src/thir.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,8 @@ impl<'tcx> fmt::Display for Pat<'tcx> {
11201120
printed += 1;
11211121
}
11221122

1123-
if printed < variant.fields.len() {
1123+
let is_union = self.ty.ty_adt_def().is_some_and(|adt| adt.is_union());
1124+
if printed < variant.fields.len() && (!is_union || printed == 0) {
11241125
write!(f, "{}..", start_or_comma())?;
11251126
}
11261127

compiler/rustc_pattern_analysis/src/constructor.rs

+28
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,34 @@
140140
//! [`ConstructorSet::split`]. The invariants of [`SplitConstructorSet`] are also of interest.
141141
//!
142142
//!
143+
//! ## Unions
144+
//!
145+
//! Unions allow us to match a value via several overlapping representations at the same time. For
146+
//! example, the following is exhaustive because when seeing the value as a boolean we handled all
147+
//! possible cases (other cases such as `n == 3` would trigger UB).
148+
//!
149+
//! ```rust
150+
//! # fn main() {
151+
//! union U8AsBool {
152+
//! n: u8,
153+
//! b: bool,
154+
//! }
155+
//! let x = U8AsBool { n: 1 };
156+
//! unsafe {
157+
//! match x {
158+
//! U8AsBool { n: 2 } => {}
159+
//! U8AsBool { b: true } => {}
160+
//! U8AsBool { b: false } => {}
161+
//! }
162+
//! }
163+
//! # }
164+
//! ```
165+
//!
166+
//! Pattern-matching has no knowledge that e.g. `false as u8 == 0`, so the values we consider in the
167+
//! algorithm look like `U8AsBool { b: true, n: 2 }`. In other words, for the most part a union is
168+
//! treated like a struct with the same fields. The difference lies in how we construct witnesses of
169+
//! non-exhaustiveness.
170+
//!
143171
//!
144172
//! ## Opaque patterns
145173
//!

compiler/rustc_pattern_analysis/src/usefulness.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -1384,12 +1384,35 @@ impl<Cx: PatCx> WitnessStack<Cx> {
13841384
/// pats: [(false, "foo"), _, true]
13851385
/// result: [Enum::Variant { a: (false, "foo"), b: _ }, true]
13861386
/// ```
1387-
fn apply_constructor(&mut self, pcx: &PlaceCtxt<'_, Cx>, ctor: &Constructor<Cx>) {
1387+
fn apply_constructor(
1388+
mut self,
1389+
pcx: &PlaceCtxt<'_, Cx>,
1390+
ctor: &Constructor<Cx>,
1391+
) -> SmallVec<[Self; 1]> {
13881392
let len = self.0.len();
13891393
let arity = pcx.ctor_arity(ctor);
1390-
let fields = self.0.drain((len - arity)..).rev().collect();
1391-
let pat = WitnessPat::new(ctor.clone(), fields, pcx.ty.clone());
1392-
self.0.push(pat);
1394+
let fields: Vec<_> = self.0.drain((len - arity)..).rev().collect();
1395+
if matches!(ctor, Constructor::UnionField)
1396+
&& fields.iter().filter(|p| !matches!(p.ctor(), Constructor::Wildcard)).count() >= 2
1397+
{
1398+
// Convert a `Union { a: p, b: q }` witness into `Union { a: p }` and `Union { b: q }`.
1399+
// First add `Union { .. }` to `self`.
1400+
self.0.push(WitnessPat::wild_from_ctor(pcx.cx, ctor.clone(), pcx.ty.clone()));
1401+
fields
1402+
.into_iter()
1403+
.enumerate()
1404+
.filter(|(_, p)| !matches!(p.ctor(), Constructor::Wildcard))
1405+
.map(|(i, p)| {
1406+
let mut ret = self.clone();
1407+
// Fill the `i`th field of the union with `p`.
1408+
ret.0.last_mut().unwrap().fields[i] = p;
1409+
ret
1410+
})
1411+
.collect()
1412+
} else {
1413+
self.0.push(WitnessPat::new(ctor.clone(), fields, pcx.ty.clone()));
1414+
smallvec![self]
1415+
}
13931416
}
13941417
}
13951418

@@ -1462,8 +1485,8 @@ impl<Cx: PatCx> WitnessMatrix<Cx> {
14621485
*self = ret;
14631486
} else {
14641487
// Any other constructor we unspecialize as expected.
1465-
for witness in self.0.iter_mut() {
1466-
witness.apply_constructor(pcx, ctor)
1488+
for witness in std::mem::take(&mut self.0) {
1489+
self.0.extend(witness.apply_constructor(pcx, ctor));
14671490
}
14681491
}
14691492
}

tests/ui/pattern/usefulness/unions.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ fn main() {
2020
U8AsBool { n: 1.. } => {}
2121
}
2222
match x {
23-
//~^ ERROR non-exhaustive patterns: `U8AsBool { n: 0_u8, b: false }` not covered
23+
//~^ ERROR non-exhaustive patterns: `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
2424
U8AsBool { b: true } => {}
2525
U8AsBool { n: 1.. } => {}
2626
}
27+
// Our approach can report duplicate witnesses sometimes.
2728
match (x, true) {
28-
//~^ ERROR non-exhaustive patterns: `(U8AsBool { n: 0_u8, b: false }, false)` and `(U8AsBool { n: 0_u8, b: true }, false)` not covered
29+
//~^ ERROR non-exhaustive patterns: `(U8AsBool { n: 0_u8 }, false)`, `(U8AsBool { b: false }, false)`, `(U8AsBool { n: 0_u8 }, false)` and 1 more not covered
2930
(U8AsBool { b: true }, true) => {}
3031
(U8AsBool { b: false }, true) => {}
3132
(U8AsBool { n: 1.. }, true) => {}

tests/ui/pattern/usefulness/unions.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
1-
error[E0004]: non-exhaustive patterns: `U8AsBool { n: 0_u8, b: false }` not covered
1+
error[E0004]: non-exhaustive patterns: `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
22
--> $DIR/unions.rs:22:15
33
|
44
LL | match x {
5-
| ^ pattern `U8AsBool { n: 0_u8, b: false }` not covered
5+
| ^ patterns `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
66
|
77
note: `U8AsBool` defined here
88
--> $DIR/unions.rs:3:11
99
|
1010
LL | union U8AsBool {
1111
| ^^^^^^^^
1212
= note: the matched value is of type `U8AsBool`
13-
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
13+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
1414
|
1515
LL ~ U8AsBool { n: 1.. } => {},
16-
LL + U8AsBool { n: 0_u8, b: false } => todo!()
16+
LL + U8AsBool { n: 0_u8 } | U8AsBool { b: false } => todo!()
1717
|
1818

19-
error[E0004]: non-exhaustive patterns: `(U8AsBool { n: 0_u8, b: false }, false)` and `(U8AsBool { n: 0_u8, b: true }, false)` not covered
20-
--> $DIR/unions.rs:27:15
19+
error[E0004]: non-exhaustive patterns: `(U8AsBool { n: 0_u8 }, false)`, `(U8AsBool { b: false }, false)`, `(U8AsBool { n: 0_u8 }, false)` and 1 more not covered
20+
--> $DIR/unions.rs:28:15
2121
|
2222
LL | match (x, true) {
23-
| ^^^^^^^^^ patterns `(U8AsBool { n: 0_u8, b: false }, false)` and `(U8AsBool { n: 0_u8, b: true }, false)` not covered
23+
| ^^^^^^^^^ patterns `(U8AsBool { n: 0_u8 }, false)`, `(U8AsBool { b: false }, false)`, `(U8AsBool { n: 0_u8 }, false)` and 1 more not covered
2424
|
2525
= note: the matched value is of type `(U8AsBool, bool)`
26-
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
26+
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms
2727
|
2828
LL ~ (U8AsBool { n: 1.. }, true) => {},
29-
LL + (U8AsBool { n: 0_u8, b: false }, false) | (U8AsBool { n: 0_u8, b: true }, false) => todo!()
29+
LL + _ => todo!()
3030
|
3131

3232
error: aborting due to 2 previous errors

0 commit comments

Comments
 (0)