Skip to content

Commit 25e272e

Browse files
committed
Fix unreachable sub-branch detection
This fixes #76836
1 parent 03f0ca0 commit 25e272e

File tree

4 files changed

+105
-41
lines changed

4 files changed

+105
-41
lines changed

compiler/rustc_mir_build/src/thir/pattern/_match.rs

+88-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
//! Note: most of the tests relevant to this file can be found (at the time of writing) in
2-
//! src/tests/ui/pattern/usefulness.
1+
//! Note: tests specific to this file can be found in:
2+
//! - ui/pattern/usefulness
3+
//! - ui/or-patterns
4+
//! - ui/consts/const_in_pattern
5+
//! - ui/rfc-2008-non-exhaustive
6+
//! - probably many others
7+
//! I (Nadrieril) prefer to put new tests in `ui/pattern/usefulness` unless there's a specific
8+
//! reason not to, for example if they depend on a particular feature like or_patterns.
39
//!
410
//! This file includes the logic for exhaustiveness and usefulness checking for
511
//! pattern-matching. Specifically, given a list of patterns for a type, we can
@@ -1361,8 +1367,9 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
13611367

13621368
#[derive(Clone, Debug)]
13631369
crate enum Usefulness<'tcx> {
1364-
/// Carries a list of unreachable subpatterns. Used only in the presence of or-patterns.
1365-
Useful(Vec<Span>),
1370+
/// Carries, for each column in the matrix, a set of sub-branches that have been found to be
1371+
/// unreachable. Used only in the presence of or-patterns, otherwise it stays empty.
1372+
Useful(Vec<FxHashSet<Span>>),
13661373
/// Carries a list of witnesses of non-exhaustiveness.
13671374
UsefulWithWitness(Vec<Witness<'tcx>>),
13681375
NotUseful,
@@ -1410,6 +1417,23 @@ impl<'tcx> Usefulness<'tcx> {
14101417
};
14111418
UsefulWithWitness(new_witnesses)
14121419
}
1420+
Useful(mut unreachables) => {
1421+
if !unreachables.is_empty() {
1422+
// When we apply a constructor, there are `arity` columns of the matrix that
1423+
// corresponded to its arguments. All the unreachables found in these columns
1424+
// will, after `apply`, come from the first column. So we take the union of all
1425+
// the corresponding sets and put them in the first column.
1426+
// Note that `arity` may be 0, in which case we just push a new empty set.
1427+
let len = unreachables.len();
1428+
let arity = ctor_wild_subpatterns.len();
1429+
let mut unioned = FxHashSet::default();
1430+
for set in unreachables.drain((len - arity)..) {
1431+
unioned.extend(set)
1432+
}
1433+
unreachables.push(unioned);
1434+
}
1435+
Useful(unreachables)
1436+
}
14131437
x => x,
14141438
}
14151439
}
@@ -2091,55 +2115,87 @@ crate fn is_useful<'p, 'tcx>(
20912115

20922116
// If the first pattern is an or-pattern, expand it.
20932117
if let Some(vs) = v.expand_or_pat() {
2094-
// We need to push the already-seen patterns into the matrix in order to detect redundant
2095-
// branches like `Some(_) | Some(0)`. We also keep track of the unreachable subpatterns.
2096-
let mut matrix = matrix.clone();
2097-
// `Vec` of all the unreachable branches of the current or-pattern.
2098-
let mut unreachable_branches = Vec::new();
2099-
// Subpatterns that are unreachable from all branches. E.g. in the following case, the last
2100-
// `true` is unreachable only from one branch, so it is overall reachable.
2118+
// We expand the or pattern, trying each of its branches in turn and keeping careful track
2119+
// of possible unreachable sub-branches.
2120+
//
2121+
// If two branches have detected some unreachable sub-branches, we need to be careful. If
2122+
// they were detected in columns that are not the current one, we want to keep only the
2123+
// sub-branches that were unreachable in _all_ branches. Eg. in the following, the last
2124+
// `true` is unreachable in the second branch of the first or-pattern, but not otherwise.
2125+
// Therefore we don't want to lint that it is unreachable.
21012126
//
21022127
// ```
21032128
// match (true, true) {
21042129
// (true, true) => {}
21052130
// (false | true, false | true) => {}
21062131
// }
21072132
// ```
2108-
let mut unreachable_subpats = FxHashSet::default();
2109-
// Whether any branch at all is useful.
2133+
// If however the sub-branches come from the current column, they come from the inside of
2134+
// the current or-pattern, and we want to keep them all. Eg. in the following, we _do_ want
2135+
// to lint that the last `false` is unreachable.
2136+
// ```
2137+
// match None {
2138+
// Some(false) => {}
2139+
// None | Some(true | false) => {}
2140+
// }
2141+
// ```
2142+
2143+
let mut matrix = matrix.clone();
2144+
// We keep track of sub-branches separately depending on whether they come from this column
2145+
// or from others.
2146+
let mut unreachables_this_column: FxHashSet<Span> = FxHashSet::default();
2147+
let mut unreachables_other_columns: Vec<FxHashSet<Span>> = Vec::default();
2148+
// Whether at least one branch is reachable.
21102149
let mut any_is_useful = false;
21112150

21122151
for v in vs {
21132152
let res = is_useful(cx, &matrix, &v, witness_preference, hir_id, is_under_guard, false);
21142153
match res {
2115-
Useful(pats) => {
2116-
if !any_is_useful {
2117-
any_is_useful = true;
2118-
// Initialize with the first set of unreachable subpatterns encountered.
2119-
unreachable_subpats = pats.into_iter().collect();
2120-
} else {
2121-
// Keep the patterns unreachable from both this and previous branches.
2122-
unreachable_subpats =
2123-
pats.into_iter().filter(|p| unreachable_subpats.contains(p)).collect();
2154+
Useful(unreachables) => {
2155+
if let Some((this_column, other_columns)) = unreachables.split_last() {
2156+
// We keep the union of unreachables found in the first column.
2157+
unreachables_this_column.extend(this_column);
2158+
// We keep the intersection of unreachables found in other columns.
2159+
if unreachables_other_columns.is_empty() {
2160+
unreachables_other_columns = other_columns.to_vec();
2161+
} else {
2162+
unreachables_other_columns = unreachables_other_columns
2163+
.into_iter()
2164+
.zip(other_columns)
2165+
.map(|(x, y)| x.intersection(&y).copied().collect())
2166+
.collect();
2167+
}
21242168
}
2169+
any_is_useful = true;
21252170
}
2126-
NotUseful => unreachable_branches.push(v.head().span),
2127-
UsefulWithWitness(_) => {
2128-
bug!("Encountered or-pat in `v` during exhaustiveness checking")
2171+
NotUseful => {
2172+
unreachables_this_column.insert(v.head().span);
21292173
}
2174+
UsefulWithWitness(_) => bug!(
2175+
"encountered or-pat in the expansion of `_` during exhaustiveness checking"
2176+
),
21302177
}
2131-
// If pattern has a guard don't add it to the matrix
2178+
2179+
// If pattern has a guard don't add it to the matrix.
21322180
if !is_under_guard {
2181+
// We push the already-seen patterns into the matrix in order to detect redundant
2182+
// branches like `Some(_) | Some(0)`.
21332183
matrix.push(v);
21342184
}
21352185
}
2136-
if any_is_useful {
2137-
// Collect all the unreachable patterns.
2138-
unreachable_branches.extend(unreachable_subpats);
2139-
return Useful(unreachable_branches);
2186+
2187+
return if any_is_useful {
2188+
let mut unreachables = if unreachables_other_columns.is_empty() {
2189+
let n_columns = v.len();
2190+
(0..n_columns - 1).map(|_| FxHashSet::default()).collect()
2191+
} else {
2192+
unreachables_other_columns
2193+
};
2194+
unreachables.push(unreachables_this_column);
2195+
Useful(unreachables)
21402196
} else {
2141-
return NotUseful;
2142-
}
2197+
NotUseful
2198+
};
21432199
}
21442200

21452201
// FIXME(Nadrieril): Hack to work around type normalization issues (see #72476).

compiler/rustc_mir_build/src/thir/pattern/check_match.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,11 @@ fn check_arms<'p, 'tcx>(
389389
hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {}
390390
}
391391
}
392-
Useful(unreachable_subpatterns) => {
393-
for span in unreachable_subpatterns {
394-
unreachable_pattern(cx.tcx, span, id, None);
392+
Useful(unreachables) => {
393+
for set in unreachables {
394+
for span in set {
395+
unreachable_pattern(cx.tcx, span, id, None);
396+
}
395397
}
396398
}
397399
UsefulWithWitness(_) => bug!(),

src/test/ui/or-patterns/exhaustiveness-unreachable-pattern.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ fn main() {
8585
match None {
8686
Some(false) => {}
8787
None | Some(true
88-
| false) => {} // expected unreachable warning here
88+
| false) => {} //~ ERROR unreachable
8989
}
9090

9191
// A subpattern that is unreachable in all branches is overall unreachable.

src/test/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr

+11-5
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ LL | (1 | 1,) => {}
7777
| ^
7878

7979
error: unreachable pattern
80-
--> $DIR/exhaustiveness-unreachable-pattern.rs:53:15
80+
--> $DIR/exhaustiveness-unreachable-pattern.rs:55:15
8181
|
82-
LL | | 0
82+
LL | | 0] => {}
8383
| ^
8484

8585
error: unreachable pattern
86-
--> $DIR/exhaustiveness-unreachable-pattern.rs:55:15
86+
--> $DIR/exhaustiveness-unreachable-pattern.rs:53:15
8787
|
88-
LL | | 0] => {}
88+
LL | | 0
8989
| ^
9090

9191
error: unreachable pattern
@@ -100,6 +100,12 @@ error: unreachable pattern
100100
LL | Some(0
101101
| ^
102102

103+
error: unreachable pattern
104+
--> $DIR/exhaustiveness-unreachable-pattern.rs:88:19
105+
|
106+
LL | | false) => {}
107+
| ^^^^^
108+
103109
error: unreachable pattern
104110
--> $DIR/exhaustiveness-unreachable-pattern.rs:96:15
105111
|
@@ -112,5 +118,5 @@ error: unreachable pattern
112118
LL | | true,
113119
| ^^^^
114120

115-
error: aborting due to 18 previous errors
121+
error: aborting due to 19 previous errors
116122

0 commit comments

Comments
 (0)