Skip to content

Commit 49649bf

Browse files
committed
Auto merge of #128015 - Nadrieril:two-step-or-expansion, r=compiler-errors
match exhaustiveness: Expand or-patterns as a separate step To compute exhaustiveness, we must expand or-patterns. Previously, we expanded them at the same time that we pushed patterns into the matrix. This made it harder to track pattern reachability, because the or-pattern itself would never show up in the matrix so we had to recover missing information. This PR changes that: we no longer expand or-patterns as we push them into the matrix. Instead, if we find an or-pattern in the matrix we expand them in a step very much like the specialization we already do. This simplifies a bunch of things, and should greatly simplify the implementation of #127870. r? `@compiler-errors`
2 parents 8ded134 + 670723e commit 49649bf

File tree

2 files changed

+86
-107
lines changed

2 files changed

+86
-107
lines changed

compiler/rustc_pattern_analysis/src/pat.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,18 @@ impl<'p, Cx: PatCx> PatOrWild<'p, Cx> {
190190
}
191191
}
192192

193-
/// Expand this (possibly-nested) or-pattern into its alternatives.
193+
/// Expand this or-pattern into its alternatives. This only expands one or-pattern; use
194+
/// `flatten_or_pat` to recursively expand nested or-patterns.
195+
pub(crate) fn expand_or_pat(self) -> SmallVec<[Self; 1]> {
196+
match self {
197+
PatOrWild::Pat(pat) if pat.is_or_pat() => {
198+
pat.iter_fields().map(|ipat| PatOrWild::Pat(&ipat.pat)).collect()
199+
}
200+
_ => smallvec![self],
201+
}
202+
}
203+
204+
/// Recursively expand this (possibly-nested) or-pattern into its alternatives.
194205
pub(crate) fn flatten_or_pat(self) -> SmallVec<[Self; 1]> {
195206
match self {
196207
PatOrWild::Pat(pat) if pat.is_or_pat() => pat

compiler/rustc_pattern_analysis/src/usefulness.rs

+74-106
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,9 @@
462462
//! # Or-patterns
463463
//!
464464
//! What we have described so far works well if there are no or-patterns. To handle them, if the
465-
//! first pattern of a row in the matrix is an or-pattern, we expand it by duplicating the rest of
466-
//! the row as necessary. This is handled automatically in [`Matrix`].
465+
//! first pattern of any row in the matrix is an or-pattern, we expand it by duplicating the rest of
466+
//! the row as necessary. For code reuse, this is implemented as "specializing with the `Or`
467+
//! constructor".
467468
//!
468469
//! This makes usefulness tracking subtle, because we also want to compute whether an alternative of
469470
//! an or-pattern is redundant, e.g. in `Some(_) | Some(0)`. We therefore track usefulness of each
@@ -875,6 +876,11 @@ impl<Cx: PatCx> PlaceInfo<Cx> {
875876
return Ok((smallvec![Constructor::PrivateUninhabited], vec![]));
876877
}
877878

879+
if ctors.clone().any(|c| matches!(c, Constructor::Or)) {
880+
// If any constructor is `Or`, we expand or-patterns.
881+
return Ok((smallvec![Constructor::Or], vec![]));
882+
}
883+
878884
let ctors_for_ty = cx.ctors_for_ty(&self.ty)?;
879885
debug!(?ctors_for_ty);
880886

@@ -968,10 +974,6 @@ impl<'p, Cx: PatCx> PatStack<'p, Cx> {
968974
PatStack { pats: smallvec![PatOrWild::Pat(pat)], relevant: true }
969975
}
970976

971-
fn is_empty(&self) -> bool {
972-
self.pats.is_empty()
973-
}
974-
975977
fn len(&self) -> usize {
976978
self.pats.len()
977979
}
@@ -984,10 +986,10 @@ impl<'p, Cx: PatCx> PatStack<'p, Cx> {
984986
self.pats.iter().copied()
985987
}
986988

987-
// Recursively expand the first or-pattern into its subpatterns. Only useful if the pattern is
988-
// an or-pattern. Panics if `self` is empty.
989+
// Expand the first or-pattern into its subpatterns. Only useful if the pattern is an
990+
// or-pattern. Panics if `self` is empty.
989991
fn expand_or_pat(&self) -> impl Iterator<Item = PatStack<'p, Cx>> + Captures<'_> {
990-
self.head().flatten_or_pat().into_iter().map(move |pat| {
992+
self.head().expand_or_pat().into_iter().map(move |pat| {
991993
let mut new = self.clone();
992994
new.pats[0] = pat;
993995
new
@@ -1057,10 +1059,6 @@ struct MatrixRow<'p, Cx: PatCx> {
10571059
}
10581060

10591061
impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
1060-
fn is_empty(&self) -> bool {
1061-
self.pats.is_empty()
1062-
}
1063-
10641062
fn len(&self) -> usize {
10651063
self.pats.len()
10661064
}
@@ -1073,12 +1071,14 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
10731071
self.pats.iter()
10741072
}
10751073

1076-
// Recursively expand the first or-pattern into its subpatterns. Only useful if the pattern is
1077-
// an or-pattern. Panics if `self` is empty.
1078-
fn expand_or_pat(&self) -> impl Iterator<Item = MatrixRow<'p, Cx>> + Captures<'_> {
1079-
self.pats.expand_or_pat().map(|patstack| MatrixRow {
1074+
// Expand the first or-pattern (if any) into its subpatterns. Panics if `self` is empty.
1075+
fn expand_or_pat(
1076+
&self,
1077+
parent_row: usize,
1078+
) -> impl Iterator<Item = MatrixRow<'p, Cx>> + Captures<'_> {
1079+
self.pats.expand_or_pat().map(move |patstack| MatrixRow {
10801080
pats: patstack,
1081-
parent_row: self.parent_row,
1081+
parent_row,
10821082
is_under_guard: self.is_under_guard,
10831083
useful: false,
10841084
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
@@ -1100,7 +1100,7 @@ impl<'p, Cx: PatCx> MatrixRow<'p, Cx> {
11001100
parent_row,
11011101
is_under_guard: self.is_under_guard,
11021102
useful: false,
1103-
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
1103+
intersects: BitSet::new_empty(0), // Initialized in `Matrix::push`.
11041104
})
11051105
}
11061106
}
@@ -1116,7 +1116,7 @@ impl<'p, Cx: PatCx> fmt::Debug for MatrixRow<'p, Cx> {
11161116
/// Invariant: each row must have the same length, and each column must have the same type.
11171117
///
11181118
/// Invariant: the first column must not contain or-patterns. This is handled by
1119-
/// [`Matrix::expand_and_push`].
1119+
/// [`Matrix::push`].
11201120
///
11211121
/// In fact each column corresponds to a place inside the scrutinee of the match. E.g. after
11221122
/// specializing `(,)` and `Some` on a pattern of type `(Option<u32>, bool)`, the first column of
@@ -1136,19 +1136,10 @@ struct Matrix<'p, Cx: PatCx> {
11361136
}
11371137

11381138
impl<'p, Cx: PatCx> Matrix<'p, Cx> {
1139-
/// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively
1140-
/// expands it. Internal method, prefer [`Matrix::new`].
1141-
fn expand_and_push(&mut self, mut row: MatrixRow<'p, Cx>) {
1142-
if !row.is_empty() && row.head().is_or_pat() {
1143-
// Expand nested or-patterns.
1144-
for mut new_row in row.expand_or_pat() {
1145-
new_row.intersects = BitSet::new_empty(self.rows.len());
1146-
self.rows.push(new_row);
1147-
}
1148-
} else {
1149-
row.intersects = BitSet::new_empty(self.rows.len());
1150-
self.rows.push(row);
1151-
}
1139+
/// Pushes a new row to the matrix. Internal method, prefer [`Matrix::new`].
1140+
fn push(&mut self, mut row: MatrixRow<'p, Cx>) {
1141+
row.intersects = BitSet::new_empty(self.rows.len());
1142+
self.rows.push(row);
11521143
}
11531144

11541145
/// Build a new matrix from an iterator of `MatchArm`s.
@@ -1170,9 +1161,9 @@ impl<'p, Cx: PatCx> Matrix<'p, Cx> {
11701161
parent_row: arm_id,
11711162
is_under_guard: arm.has_guard,
11721163
useful: false,
1173-
intersects: BitSet::new_empty(0), // Initialized in `Matrix::expand_and_push`.
1164+
intersects: BitSet::new_empty(0), // Initialized in `Matrix::push`.
11741165
};
1175-
matrix.expand_and_push(v);
1166+
matrix.push(v);
11761167
}
11771168
matrix
11781169
}
@@ -1209,22 +1200,38 @@ impl<'p, Cx: PatCx> Matrix<'p, Cx> {
12091200
ctor: &Constructor<Cx>,
12101201
ctor_is_relevant: bool,
12111202
) -> Result<Matrix<'p, Cx>, Cx::Error> {
1212-
let subfield_place_info = self.place_info[0].specialize(pcx.cx, ctor);
1213-
let arity = subfield_place_info.len();
1214-
let specialized_place_info =
1215-
subfield_place_info.chain(self.place_info[1..].iter().cloned()).collect();
1216-
let mut matrix = Matrix {
1217-
rows: Vec::new(),
1218-
place_info: specialized_place_info,
1219-
wildcard_row_is_relevant: self.wildcard_row_is_relevant && ctor_is_relevant,
1220-
};
1221-
for (i, row) in self.rows().enumerate() {
1222-
if ctor.is_covered_by(pcx.cx, row.head().ctor())? {
1223-
let new_row = row.pop_head_constructor(pcx.cx, ctor, arity, ctor_is_relevant, i)?;
1224-
matrix.expand_and_push(new_row);
1203+
if matches!(ctor, Constructor::Or) {
1204+
// Specializing with `Or` means expanding rows with or-patterns.
1205+
let mut matrix = Matrix {
1206+
rows: Vec::new(),
1207+
place_info: self.place_info.clone(),
1208+
wildcard_row_is_relevant: self.wildcard_row_is_relevant,
1209+
};
1210+
for (i, row) in self.rows().enumerate() {
1211+
for new_row in row.expand_or_pat(i) {
1212+
matrix.push(new_row);
1213+
}
12251214
}
1215+
Ok(matrix)
1216+
} else {
1217+
let subfield_place_info = self.place_info[0].specialize(pcx.cx, ctor);
1218+
let arity = subfield_place_info.len();
1219+
let specialized_place_info =
1220+
subfield_place_info.chain(self.place_info[1..].iter().cloned()).collect();
1221+
let mut matrix = Matrix {
1222+
rows: Vec::new(),
1223+
place_info: specialized_place_info,
1224+
wildcard_row_is_relevant: self.wildcard_row_is_relevant && ctor_is_relevant,
1225+
};
1226+
for (i, row) in self.rows().enumerate() {
1227+
if ctor.is_covered_by(pcx.cx, row.head().ctor())? {
1228+
let new_row =
1229+
row.pop_head_constructor(pcx.cx, ctor, arity, ctor_is_relevant, i)?;
1230+
matrix.push(new_row);
1231+
}
1232+
}
1233+
Ok(matrix)
12261234
}
1227-
Ok(matrix)
12281235
}
12291236

12301237
/// Recover row usefulness and intersection information from a processed specialized matrix.
@@ -1465,7 +1472,9 @@ impl<Cx: PatCx> WitnessMatrix<Cx> {
14651472
missing_ctors: &[Constructor<Cx>],
14661473
ctor: &Constructor<Cx>,
14671474
) {
1468-
if self.is_empty() {
1475+
// The `Or` constructor indicates that we expanded or-patterns. This doesn't affect
1476+
// witnesses.
1477+
if self.is_empty() || matches!(ctor, Constructor::Or) {
14691478
return;
14701479
}
14711480
if matches!(ctor, Constructor::Missing) {
@@ -1715,48 +1724,6 @@ pub enum Usefulness<'p, Cx: PatCx> {
17151724
Redundant,
17161725
}
17171726

1718-
/// Report whether this pattern was found useful, and its subpatterns that were not useful if any.
1719-
fn collect_pattern_usefulness<'p, Cx: PatCx>(
1720-
useful_subpatterns: &FxHashSet<PatId>,
1721-
pat: &'p DeconstructedPat<Cx>,
1722-
) -> Usefulness<'p, Cx> {
1723-
fn pat_is_useful<'p, Cx: PatCx>(
1724-
useful_subpatterns: &FxHashSet<PatId>,
1725-
pat: &'p DeconstructedPat<Cx>,
1726-
) -> bool {
1727-
if useful_subpatterns.contains(&pat.uid) {
1728-
true
1729-
} else if pat.is_or_pat()
1730-
&& pat.iter_fields().any(|f| pat_is_useful(useful_subpatterns, &f.pat))
1731-
{
1732-
// We always expand or patterns in the matrix, so we will never see the actual
1733-
// or-pattern (the one with constructor `Or`) in the column. As such, it will not be
1734-
// marked as useful itself, only its children will. We recover this information here.
1735-
true
1736-
} else {
1737-
false
1738-
}
1739-
}
1740-
1741-
let mut redundant_subpats = Vec::new();
1742-
pat.walk(&mut |p| {
1743-
if pat_is_useful(useful_subpatterns, p) {
1744-
// The pattern is useful, so we recurse to find redundant subpatterns.
1745-
true
1746-
} else {
1747-
// The pattern is redundant.
1748-
redundant_subpats.push(p);
1749-
false // stop recursing
1750-
}
1751-
});
1752-
1753-
if pat_is_useful(useful_subpatterns, pat) {
1754-
Usefulness::Useful(redundant_subpats)
1755-
} else {
1756-
Usefulness::Redundant
1757-
}
1758-
}
1759-
17601727
/// The output of checking a match for exhaustiveness and arm usefulness.
17611728
pub struct UsefulnessReport<'p, Cx: PatCx> {
17621729
/// For each arm of the input, whether that arm is useful after the arms above it.
@@ -1793,25 +1760,26 @@ pub fn compute_match_usefulness<'p, Cx: PatCx>(
17931760
.copied()
17941761
.map(|arm| {
17951762
debug!(?arm);
1796-
let usefulness = collect_pattern_usefulness(&cx.useful_subpatterns, arm.pat);
1763+
let usefulness = if cx.useful_subpatterns.contains(&arm.pat.uid) {
1764+
let mut redundant_subpats = Vec::new();
1765+
arm.pat.walk(&mut |subpat| {
1766+
if cx.useful_subpatterns.contains(&subpat.uid) {
1767+
true // keep recursing
1768+
} else {
1769+
redundant_subpats.push(subpat);
1770+
false // stop recursing
1771+
}
1772+
});
1773+
Usefulness::Useful(redundant_subpats)
1774+
} else {
1775+
Usefulness::Redundant
1776+
};
17971777
debug!(?usefulness);
17981778
(arm, usefulness)
17991779
})
18001780
.collect();
18011781

1802-
let mut arm_intersections: Vec<_> =
1803-
arms.iter().enumerate().map(|(i, _)| BitSet::new_empty(i)).collect();
1804-
for row in matrix.rows() {
1805-
let arm_id = row.parent_row;
1806-
for intersection in row.intersects.iter() {
1807-
// Convert the matrix row ids into arm ids (they can differ because we expand or-patterns).
1808-
let arm_intersection = matrix.rows[intersection].parent_row;
1809-
// Note: self-intersection can happen with or-patterns.
1810-
if arm_intersection != arm_id {
1811-
arm_intersections[arm_id].insert(arm_intersection);
1812-
}
1813-
}
1814-
}
1782+
let arm_intersections: Vec<_> = matrix.rows().map(|row| row.intersects.clone()).collect();
18151783

18161784
Ok(UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses, arm_intersections })
18171785
}

0 commit comments

Comments
 (0)