Skip to content

Commit 96f7d59

Browse files
authored
Unrolled build for rust-lang#123301
Rollup merge of rust-lang#123301 - Nadrieril:unions, r=compiler-errors pattern analysis: fix union handling Little known fact: rust supports union patterns. Exhaustiveness handles them soundly but reports nonsensical missing patterns. This PR fixes the reported patterns and documents what we're doing. r? `@compiler-errors`
2 parents 98efd80 + 27704c7 commit 96f7d59

File tree

6 files changed

+138
-14
lines changed

6 files changed

+138
-14
lines changed

compiler/rustc_middle/src/thir.rs

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

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

compiler/rustc_pattern_analysis/src/constructor.rs

+28-1
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
//!
@@ -974,7 +1002,6 @@ impl<Cx: PatCx> ConstructorSet<Cx> {
9741002
/// any) are missing; 2/ split constructors to handle non-trivial intersections e.g. on ranges
9751003
/// or slices. This can get subtle; see [`SplitConstructorSet`] for details of this operation
9761004
/// and its invariants.
977-
#[instrument(level = "debug", skip(self, ctors), ret)]
9781005
pub fn split<'a>(
9791006
&self,
9801007
ctors: impl Iterator<Item = &'a Constructor<Cx>> + Clone,

compiler/rustc_pattern_analysis/src/rustc.rs

-2
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
186186

187187
/// Returns the types of the fields for a given constructor. The result must have a length of
188188
/// `ctor.arity()`.
189-
#[instrument(level = "trace", skip(self))]
190189
pub(crate) fn ctor_sub_tys<'a>(
191190
&'a self,
192191
ctor: &'a Constructor<'p, 'tcx>,
@@ -283,7 +282,6 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
283282
/// Creates a set that represents all the constructors of `ty`.
284283
///
285284
/// See [`crate::constructor`] for considerations of emptiness.
286-
#[instrument(level = "debug", skip(self), ret)]
287285
pub fn ctors_for_ty(
288286
&self,
289287
ty: RevealedTy<'tcx>,

compiler/rustc_pattern_analysis/src/usefulness.rs

+39-10
Original file line numberDiff line numberDiff line change
@@ -871,12 +871,14 @@ impl<Cx: PatCx> PlaceInfo<Cx> {
871871
where
872872
Cx: 'a,
873873
{
874+
debug!(?self.ty);
874875
if self.private_uninhabited {
875876
// Skip the whole column
876877
return Ok((smallvec![Constructor::PrivateUninhabited], vec![]));
877878
}
878879

879880
let ctors_for_ty = cx.ctors_for_ty(&self.ty)?;
881+
debug!(?ctors_for_ty);
880882

881883
// We treat match scrutinees of type `!` or `EmptyEnum` differently.
882884
let is_toplevel_exception =
@@ -895,6 +897,7 @@ impl<Cx: PatCx> PlaceInfo<Cx> {
895897

896898
// Analyze the constructors present in this column.
897899
let mut split_set = ctors_for_ty.split(ctors);
900+
debug!(?split_set);
898901
let all_missing = split_set.present.is_empty();
899902

900903
// Build the set of constructors we will specialize with. It must cover the whole type, so
@@ -1254,7 +1257,7 @@ impl<'p, Cx: PatCx> Matrix<'p, Cx> {
12541257
/// + true + [Second(true)] +
12551258
/// + false + [_] +
12561259
/// + _ + [_, _, tail @ ..] +
1257-
/// | ✓ | ? | // column validity
1260+
/// | ✓ | ? | // validity
12581261
/// ```
12591262
impl<'p, Cx: PatCx> fmt::Debug for Matrix<'p, Cx> {
12601263
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -1285,7 +1288,7 @@ impl<'p, Cx: PatCx> fmt::Debug for Matrix<'p, Cx> {
12851288
write!(f, " {sep}")?;
12861289
}
12871290
if is_validity_row {
1288-
write!(f, " // column validity")?;
1291+
write!(f, " // validity")?;
12891292
}
12901293
write!(f, "\n")?;
12911294
}
@@ -1381,12 +1384,35 @@ impl<Cx: PatCx> WitnessStack<Cx> {
13811384
/// pats: [(false, "foo"), _, true]
13821385
/// result: [Enum::Variant { a: (false, "foo"), b: _ }, true]
13831386
/// ```
1384-
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]> {
13851392
let len = self.0.len();
13861393
let arity = pcx.ctor_arity(ctor);
1387-
let fields = self.0.drain((len - arity)..).rev().collect();
1388-
let pat = WitnessPat::new(ctor.clone(), fields, pcx.ty.clone());
1389-
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+
}
13901416
}
13911417
}
13921418

@@ -1459,8 +1485,8 @@ impl<Cx: PatCx> WitnessMatrix<Cx> {
14591485
*self = ret;
14601486
} else {
14611487
// Any other constructor we unspecialize as expected.
1462-
for witness in self.0.iter_mut() {
1463-
witness.apply_constructor(pcx, ctor)
1488+
for witness in std::mem::take(&mut self.0) {
1489+
self.0.extend(witness.apply_constructor(pcx, ctor));
14641490
}
14651491
}
14661492
}
@@ -1617,7 +1643,6 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>(
16171643
};
16181644

16191645
// Analyze the constructors present in this column.
1620-
debug!("ty: {:?}", place.ty);
16211646
let ctors = matrix.heads().map(|p| p.ctor());
16221647
let (split_ctors, missing_ctors) = place.split_column_ctors(mcx.tycx, ctors)?;
16231648

@@ -1669,7 +1694,10 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: PatCx>(
16691694
for row in matrix.rows() {
16701695
if row.useful {
16711696
if let PatOrWild::Pat(pat) = row.head() {
1672-
mcx.useful_subpatterns.insert(pat.uid);
1697+
let newly_useful = mcx.useful_subpatterns.insert(pat.uid);
1698+
if newly_useful {
1699+
debug!("newly useful: {pat:?}");
1700+
}
16731701
}
16741702
}
16751703
}
@@ -1768,6 +1796,7 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>(
17681796
.map(|arm| {
17691797
debug!(?arm);
17701798
let usefulness = collect_pattern_usefulness(&cx.useful_subpatterns, arm.pat);
1799+
debug!(?usefulness);
17711800
(arm, usefulness)
17721801
})
17731802
.collect();

tests/ui/pattern/usefulness/unions.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
fn main() {
2+
#[derive(Copy, Clone)]
3+
union U8AsBool {
4+
n: u8,
5+
b: bool,
6+
}
7+
8+
let x = U8AsBool { n: 1 };
9+
unsafe {
10+
match x {
11+
// exhaustive
12+
U8AsBool { n: 2 } => {}
13+
U8AsBool { b: true } => {}
14+
U8AsBool { b: false } => {}
15+
}
16+
match x {
17+
// exhaustive
18+
U8AsBool { b: true } => {}
19+
U8AsBool { n: 0 } => {}
20+
U8AsBool { n: 1.. } => {}
21+
}
22+
match x {
23+
//~^ ERROR non-exhaustive patterns: `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
24+
U8AsBool { b: true } => {}
25+
U8AsBool { n: 1.. } => {}
26+
}
27+
// Our approach can report duplicate witnesses sometimes.
28+
match (x, true) {
29+
//~^ ERROR non-exhaustive patterns: `(U8AsBool { n: 0_u8 }, false)`, `(U8AsBool { b: false }, false)`, `(U8AsBool { n: 0_u8 }, false)` and 1 more not covered
30+
(U8AsBool { b: true }, true) => {}
31+
(U8AsBool { b: false }, true) => {}
32+
(U8AsBool { n: 1.. }, true) => {}
33+
}
34+
}
35+
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error[E0004]: non-exhaustive patterns: `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
2+
--> $DIR/unions.rs:22:15
3+
|
4+
LL | match x {
5+
| ^ patterns `U8AsBool { n: 0_u8 }` and `U8AsBool { b: false }` not covered
6+
|
7+
note: `U8AsBool` defined here
8+
--> $DIR/unions.rs:3:11
9+
|
10+
LL | union U8AsBool {
11+
| ^^^^^^^^
12+
= 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, a match arm with multiple or-patterns as shown, or multiple match arms
14+
|
15+
LL ~ U8AsBool { n: 1.. } => {},
16+
LL + U8AsBool { n: 0_u8 } | U8AsBool { b: false } => todo!()
17+
|
18+
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
21+
|
22+
LL | match (x, true) {
23+
| ^^^^^^^^^ patterns `(U8AsBool { n: 0_u8 }, false)`, `(U8AsBool { b: false }, false)`, `(U8AsBool { n: 0_u8 }, false)` and 1 more not covered
24+
|
25+
= 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 as shown, or multiple match arms
27+
|
28+
LL ~ (U8AsBool { n: 1.. }, true) => {},
29+
LL + _ => todo!()
30+
|
31+
32+
error: aborting due to 2 previous errors
33+
34+
For more information about this error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)