Skip to content

Commit b025813

Browse files
committed
Handle empty matches cleanly
1 parent 257ed89 commit b025813

File tree

6 files changed

+78
-71
lines changed

6 files changed

+78
-71
lines changed

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

+21-15
Original file line numberDiff line numberDiff line change
@@ -1392,13 +1392,12 @@ impl<'tcx> Usefulness<'tcx> {
13921392
pcx: PatCtxt<'_, 'p, 'tcx>,
13931393
ctor: &Constructor<'tcx>,
13941394
ctor_wild_subpatterns: &Fields<'p, 'tcx>,
1395-
is_top_level: bool,
13961395
) -> Self {
13971396
match self {
13981397
UsefulWithWitness(witnesses) => {
13991398
let new_witnesses = if ctor.is_wildcard() {
14001399
let missing_ctors = MissingConstructors::new(pcx);
1401-
let new_patterns = missing_ctors.report_patterns(pcx, is_top_level);
1400+
let new_patterns = missing_ctors.report_patterns(pcx);
14021401
witnesses
14031402
.into_iter()
14041403
.flat_map(|witness| {
@@ -1454,6 +1453,9 @@ struct PatCtxt<'a, 'p, 'tcx> {
14541453
ty: Ty<'tcx>,
14551454
/// Span of the current pattern under investigation.
14561455
span: Span,
1456+
/// Whether the current pattern is the whole pattern as found in a match arm, or if it's a
1457+
/// subpattern.
1458+
is_top_level: bool,
14571459
}
14581460

14591461
/// A witness of non-exhaustiveness for error reporting, represented
@@ -1585,11 +1587,12 @@ fn all_constructors<'p, 'tcx>(pcx: PatCtxt<'_, 'p, 'tcx>) -> Vec<Constructor<'tc
15851587
let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty);
15861588

15871589
// If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it
1588-
// as though it had an "unknown" constructor to avoid exposing its emptyness. Note that
1589-
// an empty match will still be considered exhaustive because that case is handled
1590-
// separately in `check_match`.
1591-
let is_secretly_empty =
1592-
def.variants.is_empty() && !cx.tcx.features().exhaustive_patterns;
1590+
// as though it had an "unknown" constructor to avoid exposing its emptyness. The
1591+
// exception is if the pattern is at the top level, because we want empty matches to be
1592+
// considered exhaustive.
1593+
let is_secretly_empty = def.variants.is_empty()
1594+
&& !cx.tcx.features().exhaustive_patterns
1595+
&& !pcx.is_top_level;
15931596

15941597
if is_secretly_empty || is_declared_nonexhaustive {
15951598
vec![NonExhaustive]
@@ -1635,6 +1638,13 @@ fn all_constructors<'p, 'tcx>(pcx: PatCtxt<'_, 'p, 'tcx>) -> Vec<Constructor<'tc
16351638
let max = size.truncate(u128::MAX);
16361639
vec![make_range(0, max)]
16371640
}
1641+
// If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot
1642+
// expose its emptyness. The exception is if the pattern is at the top level, because we
1643+
// want empty matches to be considered exhaustive.
1644+
ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => {
1645+
vec![NonExhaustive]
1646+
}
1647+
ty::Never => vec![],
16381648
_ if cx.is_uninhabited(pcx.ty) => vec![],
16391649
ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => vec![Single],
16401650
// This type is one for which we cannot list constructors, like `str` or `f64`.
@@ -2012,11 +2022,7 @@ impl<'tcx> MissingConstructors<'tcx> {
20122022

20132023
/// List the patterns corresponding to the missing constructors. In some cases, instead of
20142024
/// listing all constructors of a given type, we prefer to simply report a wildcard.
2015-
fn report_patterns<'p>(
2016-
&self,
2017-
pcx: PatCtxt<'_, 'p, 'tcx>,
2018-
is_top_level: bool,
2019-
) -> SmallVec<[Pat<'tcx>; 1]> {
2025+
fn report_patterns<'p>(&self, pcx: PatCtxt<'_, 'p, 'tcx>) -> SmallVec<[Pat<'tcx>; 1]> {
20202026
// There are 2 ways we can report a witness here.
20212027
// Commonly, we can report all the "free"
20222028
// constructors as witnesses, e.g., if we have:
@@ -2044,7 +2050,7 @@ impl<'tcx> MissingConstructors<'tcx> {
20442050
// `used_ctors` is empty.
20452051
// The exception is: if we are at the top-level, for example in an empty match, we
20462052
// sometimes prefer reporting the list of constructors instead of just `_`.
2047-
let report_when_all_missing = is_top_level && !IntRange::is_integral(pcx.ty);
2053+
let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty);
20482054
if self.used_ctors.is_empty() && !report_when_all_missing {
20492055
// All constructors are unused. Report only a wildcard
20502056
// rather than each individual constructor.
@@ -2200,7 +2206,7 @@ crate fn is_useful<'p, 'tcx>(
22002206

22012207
// FIXME(Nadrieril): Hack to work around type normalization issues (see #72476).
22022208
let ty = matrix.heads().next().map(|r| r.ty).unwrap_or(v.head().ty);
2203-
let pcx = PatCtxt { cx, matrix, ty, span: v.head().span };
2209+
let pcx = PatCtxt { cx, matrix, ty, span: v.head().span, is_top_level };
22042210

22052211
debug!("is_useful_expand_first_col: ty={:#?}, expanding {:#?}", pcx.ty, v.head());
22062212

@@ -2215,7 +2221,7 @@ crate fn is_useful<'p, 'tcx>(
22152221
let v = v.pop_head_constructor(&ctor_wild_subpatterns);
22162222
let usefulness =
22172223
is_useful(pcx.cx, &matrix, &v, witness_preference, hir_id, is_under_guard, false);
2218-
usefulness.apply_constructor(pcx, &ctor, &ctor_wild_subpatterns, is_top_level)
2224+
usefulness.apply_constructor(pcx, &ctor, &ctor_wild_subpatterns)
22192225
})
22202226
.find(|result| result.is_useful())
22212227
.unwrap_or(NotUseful);

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

-18
Original file line numberDiff line numberDiff line change
@@ -439,24 +439,6 @@ fn check_exhaustive<'p, 'tcx>(
439439
hir_id: HirId,
440440
is_empty_match: bool,
441441
) {
442-
// In the absence of the `exhaustive_patterns` feature, empty matches are not detected by
443-
// `is_useful` to exhaustively match uninhabited types, so we manually check here.
444-
if is_empty_match && !cx.tcx.features().exhaustive_patterns {
445-
let scrutinee_is_visibly_uninhabited = match scrut_ty.kind() {
446-
ty::Never => true,
447-
ty::Adt(def, _) => {
448-
def.is_enum()
449-
&& def.variants.is_empty()
450-
&& !cx.is_foreign_non_exhaustive_enum(scrut_ty)
451-
}
452-
_ => false,
453-
};
454-
if scrutinee_is_visibly_uninhabited {
455-
// If the type *is* uninhabited, an empty match is vacuously exhaustive.
456-
return;
457-
}
458-
}
459-
460442
let witnesses = match check_not_useful(cx, scrut_ty, matrix, hir_id) {
461443
Ok(_) => return,
462444
Err(err) => err,

src/test/ui/pattern/usefulness/match-empty.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,20 @@ macro_rules! match_false {
4444
fn foo(x: Foo) {
4545
match x {} // ok
4646
match x {
47-
_ => {}, // Not detected as unreachable, see #55123.
47+
_ => {}, //~ ERROR unreachable pattern
4848
}
4949
match x {
50-
//~^ ERROR non-exhaustive patterns: `_` not covered
51-
_ if false => {}, // Not detected as unreachable nor exhaustive.
50+
_ if false => {}, //~ ERROR unreachable pattern
5251
}
5352
}
5453

5554
fn never(x: !) {
5655
match x {} // ok
5756
match x {
58-
_ => {}, // Not detected as unreachable.
57+
_ => {}, //~ ERROR unreachable pattern
5958
}
6059
match x {
61-
//~^ ERROR non-exhaustive patterns: `_` not covered
62-
_ if false => {}, // Not detected as unreachable nor exhaustive.
60+
_ if false => {}, //~ ERROR unreachable pattern
6361
}
6462
}
6563

src/test/ui/pattern/usefulness/match-empty.stderr

+39-30
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
error[E0004]: non-exhaustive patterns: `_` not covered
2-
--> $DIR/match-empty.rs:49:11
1+
error: unreachable pattern
2+
--> $DIR/match-empty.rs:47:9
33
|
4-
LL | enum Foo {}
5-
| ----------- `Foo` defined here
6-
...
7-
LL | match x {
8-
| ^ pattern `_` not covered
4+
LL | _ => {},
5+
| ^
96
|
10-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
11-
= note: the matched value is of type `Foo`
7+
note: the lint level is defined here
8+
--> $DIR/match-empty.rs:3:9
9+
|
10+
LL | #![deny(unreachable_patterns)]
11+
| ^^^^^^^^^^^^^^^^^^^^
1212

13-
error[E0004]: non-exhaustive patterns: `_` not covered
14-
--> $DIR/match-empty.rs:60:11
13+
error: unreachable pattern
14+
--> $DIR/match-empty.rs:50:9
1515
|
16-
LL | match x {
17-
| ^ pattern `_` not covered
16+
LL | _ if false => {},
17+
| ^
18+
19+
error: unreachable pattern
20+
--> $DIR/match-empty.rs:57:9
1821
|
19-
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
20-
= note: the matched value is of type `!`
22+
LL | _ => {},
23+
| ^
24+
25+
error: unreachable pattern
26+
--> $DIR/match-empty.rs:60:9
27+
|
28+
LL | _ if false => {},
29+
| ^
2130

2231
error[E0004]: non-exhaustive patterns: type `u8` is non-empty
23-
--> $DIR/match-empty.rs:77:18
32+
--> $DIR/match-empty.rs:75:18
2433
|
2534
LL | match_empty!(0u8);
2635
| ^^^
@@ -29,7 +38,7 @@ LL | match_empty!(0u8);
2938
= note: the matched value is of type `u8`
3039

3140
error[E0004]: non-exhaustive patterns: type `NonEmptyStruct` is non-empty
32-
--> $DIR/match-empty.rs:79:18
41+
--> $DIR/match-empty.rs:77:18
3342
|
3443
LL | struct NonEmptyStruct(bool);
3544
| ---------------------------- `NonEmptyStruct` defined here
@@ -41,7 +50,7 @@ LL | match_empty!(NonEmptyStruct(true));
4150
= note: the matched value is of type `NonEmptyStruct`
4251

4352
error[E0004]: non-exhaustive patterns: type `NonEmptyUnion1` is non-empty
44-
--> $DIR/match-empty.rs:81:18
53+
--> $DIR/match-empty.rs:79:18
4554
|
4655
LL | / union NonEmptyUnion1 {
4756
LL | | foo: (),
@@ -55,7 +64,7 @@ LL | match_empty!((NonEmptyUnion1 { foo: () }));
5564
= note: the matched value is of type `NonEmptyUnion1`
5665

5766
error[E0004]: non-exhaustive patterns: type `NonEmptyUnion2` is non-empty
58-
--> $DIR/match-empty.rs:83:18
67+
--> $DIR/match-empty.rs:81:18
5968
|
6069
LL | / union NonEmptyUnion2 {
6170
LL | | foo: (),
@@ -70,7 +79,7 @@ LL | match_empty!((NonEmptyUnion2 { foo: () }));
7079
= note: the matched value is of type `NonEmptyUnion2`
7180

7281
error[E0004]: non-exhaustive patterns: `Foo(_)` not covered
73-
--> $DIR/match-empty.rs:85:18
82+
--> $DIR/match-empty.rs:83:18
7483
|
7584
LL | / enum NonEmptyEnum1 {
7685
LL | | Foo(bool),
@@ -87,7 +96,7 @@ LL | match_empty!(NonEmptyEnum1::Foo(true));
8796
= note: the matched value is of type `NonEmptyEnum1`
8897

8998
error[E0004]: non-exhaustive patterns: `Foo(_)` and `Bar` not covered
90-
--> $DIR/match-empty.rs:87:18
99+
--> $DIR/match-empty.rs:85:18
91100
|
92101
LL | / enum NonEmptyEnum2 {
93102
LL | | Foo(bool),
@@ -108,7 +117,7 @@ LL | match_empty!(NonEmptyEnum2::Foo(true));
108117
= note: the matched value is of type `NonEmptyEnum2`
109118

110119
error[E0004]: non-exhaustive patterns: `V1`, `V2`, `V3` and 2 more not covered
111-
--> $DIR/match-empty.rs:89:18
120+
--> $DIR/match-empty.rs:87:18
112121
|
113122
LL | / enum NonEmptyEnum5 {
114123
LL | | V1, V2, V3, V4, V5,
@@ -122,7 +131,7 @@ LL | match_empty!(NonEmptyEnum5::V1);
122131
= note: the matched value is of type `NonEmptyEnum5`
123132

124133
error[E0004]: non-exhaustive patterns: `_` not covered
125-
--> $DIR/match-empty.rs:92:18
134+
--> $DIR/match-empty.rs:90:18
126135
|
127136
LL | match_false!(0u8);
128137
| ^^^ pattern `_` not covered
@@ -131,7 +140,7 @@ LL | match_false!(0u8);
131140
= note: the matched value is of type `u8`
132141

133142
error[E0004]: non-exhaustive patterns: `NonEmptyStruct(_)` not covered
134-
--> $DIR/match-empty.rs:94:18
143+
--> $DIR/match-empty.rs:92:18
135144
|
136145
LL | struct NonEmptyStruct(bool);
137146
| ---------------------------- `NonEmptyStruct` defined here
@@ -143,7 +152,7 @@ LL | match_false!(NonEmptyStruct(true));
143152
= note: the matched value is of type `NonEmptyStruct`
144153

145154
error[E0004]: non-exhaustive patterns: `NonEmptyUnion1 { .. }` not covered
146-
--> $DIR/match-empty.rs:96:18
155+
--> $DIR/match-empty.rs:94:18
147156
|
148157
LL | / union NonEmptyUnion1 {
149158
LL | | foo: (),
@@ -157,7 +166,7 @@ LL | match_false!((NonEmptyUnion1 { foo: () }));
157166
= note: the matched value is of type `NonEmptyUnion1`
158167

159168
error[E0004]: non-exhaustive patterns: `NonEmptyUnion2 { .. }` not covered
160-
--> $DIR/match-empty.rs:98:18
169+
--> $DIR/match-empty.rs:96:18
161170
|
162171
LL | / union NonEmptyUnion2 {
163172
LL | | foo: (),
@@ -172,7 +181,7 @@ LL | match_false!((NonEmptyUnion2 { foo: () }));
172181
= note: the matched value is of type `NonEmptyUnion2`
173182

174183
error[E0004]: non-exhaustive patterns: `Foo(_)` not covered
175-
--> $DIR/match-empty.rs:100:18
184+
--> $DIR/match-empty.rs:98:18
176185
|
177186
LL | / enum NonEmptyEnum1 {
178187
LL | | Foo(bool),
@@ -189,7 +198,7 @@ LL | match_false!(NonEmptyEnum1::Foo(true));
189198
= note: the matched value is of type `NonEmptyEnum1`
190199

191200
error[E0004]: non-exhaustive patterns: `Foo(_)` and `Bar` not covered
192-
--> $DIR/match-empty.rs:102:18
201+
--> $DIR/match-empty.rs:100:18
193202
|
194203
LL | / enum NonEmptyEnum2 {
195204
LL | | Foo(bool),
@@ -210,7 +219,7 @@ LL | match_false!(NonEmptyEnum2::Foo(true));
210219
= note: the matched value is of type `NonEmptyEnum2`
211220

212221
error[E0004]: non-exhaustive patterns: `V1`, `V2`, `V3` and 2 more not covered
213-
--> $DIR/match-empty.rs:104:18
222+
--> $DIR/match-empty.rs:102:18
214223
|
215224
LL | / enum NonEmptyEnum5 {
216225
LL | | V1, V2, V3, V4, V5,
@@ -223,6 +232,6 @@ LL | match_false!(NonEmptyEnum5::V1);
223232
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
224233
= note: the matched value is of type `NonEmptyEnum5`
225234

226-
error: aborting due to 16 previous errors
235+
error: aborting due to 18 previous errors
227236

228237
For more information about this error, try `rustc --explain E0004`.

src/test/ui/rfc-2008-non-exhaustive/enum_same_crate_empty_match.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub enum EmptyNonExhaustiveEnum {}
2525
fn empty_non_exhaustive(x: EmptyNonExhaustiveEnum) {
2626
match x {}
2727
match x {
28-
_ => {} // not detected as unreachable
28+
_ => {}, //~ ERROR unreachable pattern
2929
}
3030
}
3131

src/test/ui/rfc-2008-non-exhaustive/enum_same_crate_empty_match.stderr

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
error: unreachable pattern
2+
--> $DIR/enum_same_crate_empty_match.rs:28:9
3+
|
4+
LL | _ => {},
5+
| ^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/enum_same_crate_empty_match.rs:1:9
9+
|
10+
LL | #![deny(unreachable_patterns)]
11+
| ^^^^^^^^^^^^^^^^^^^^
12+
113
error[E0004]: non-exhaustive patterns: `Unit`, `Tuple(_)` and `Struct { .. }` not covered
214
--> $DIR/enum_same_crate_empty_match.rs:33:11
315
|
@@ -42,6 +54,6 @@ LL | match NormalEnum::Unit {}
4254
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
4355
= note: the matched value is of type `NormalEnum`
4456

45-
error: aborting due to 2 previous errors
57+
error: aborting due to 3 previous errors
4658

4759
For more information about this error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)