Fix union handling in exhaustiveness

This commit is contained in:
Nadrieril 2024-03-31 23:57:53 +02:00
parent db9b4eac48
commit 27704c7f9e
5 changed files with 78 additions and 25 deletions

View file

@ -140,6 +140,34 @@
//! [`ConstructorSet::split`]. The invariants of [`SplitConstructorSet`] are also of interest.
//!
//!
//! ## Unions
//!
//! Unions allow us to match a value via several overlapping representations at the same time. For
//! example, the following is exhaustive because when seeing the value as a boolean we handled all
//! possible cases (other cases such as `n == 3` would trigger UB).
//!
//! ```rust
//! # fn main() {
//! union U8AsBool {
//! n: u8,
//! b: bool,
//! }
//! let x = U8AsBool { n: 1 };
//! unsafe {
//! match x {
//! U8AsBool { n: 2 } => {}
//! U8AsBool { b: true } => {}
//! U8AsBool { b: false } => {}
//! }
//! }
//! # }
//! ```
//!
//! Pattern-matching has no knowledge that e.g. `false as u8 == 0`, so the values we consider in the
//! algorithm look like `U8AsBool { b: true, n: 2 }`. In other words, for the most part a union is
//! treated like a struct with the same fields. The difference lies in how we construct witnesses of
//! non-exhaustiveness.
//!
//!
//! ## Opaque patterns
//!

View file

@ -1384,12 +1384,35 @@ impl<Cx: PatCx> WitnessStack<Cx> {
/// pats: [(false, "foo"), _, true]
/// result: [Enum::Variant { a: (false, "foo"), b: _ }, true]
/// ```
fn apply_constructor(&mut self, pcx: &PlaceCtxt<'_, Cx>, ctor: &Constructor<Cx>) {
fn apply_constructor(
mut self,
pcx: &PlaceCtxt<'_, Cx>,
ctor: &Constructor<Cx>,
) -> SmallVec<[Self; 1]> {
let len = self.0.len();
let arity = pcx.ctor_arity(ctor);
let fields = self.0.drain((len - arity)..).rev().collect();
let pat = WitnessPat::new(ctor.clone(), fields, pcx.ty.clone());
self.0.push(pat);
let fields: Vec<_> = self.0.drain((len - arity)..).rev().collect();
if matches!(ctor, Constructor::UnionField)
&& fields.iter().filter(|p| !matches!(p.ctor(), Constructor::Wildcard)).count() >= 2
{
// Convert a `Union { a: p, b: q }` witness into `Union { a: p }` and `Union { b: q }`.
// First add `Union { .. }` to `self`.
self.0.push(WitnessPat::wild_from_ctor(pcx.cx, ctor.clone(), pcx.ty.clone()));
fields
.into_iter()
.enumerate()
.filter(|(_, p)| !matches!(p.ctor(), Constructor::Wildcard))
.map(|(i, p)| {
let mut ret = self.clone();
// Fill the `i`th field of the union with `p`.
ret.0.last_mut().unwrap().fields[i] = p;
ret
})
.collect()
} else {
self.0.push(WitnessPat::new(ctor.clone(), fields, pcx.ty.clone()));
smallvec![self]
}
}
}
@ -1462,8 +1485,8 @@ impl<Cx: PatCx> WitnessMatrix<Cx> {
*self = ret;
} else {
// Any other constructor we unspecialize as expected.
for witness in self.0.iter_mut() {
witness.apply_constructor(pcx, ctor)
for witness in std::mem::take(&mut self.0) {
self.0.extend(witness.apply_constructor(pcx, ctor));
}
}
}