Skip to content

Commit 0e45a79

Browse files
committed
Lower never patterns to Unreachable in mir
1 parent 8f7d58d commit 0e45a79

File tree

13 files changed

+208
-103
lines changed

13 files changed

+208
-103
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -578,8 +578,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
578578
self.dcx().emit_err(NeverPatternWithGuard { span: g.span });
579579
}
580580

581-
// We add a fake `loop {}` arm body so that it typecks to `!`.
582-
// FIXME(never_patterns): Desugar into a call to `unreachable_unchecked`.
581+
// We add a fake `loop {}` arm body so that it typecks to `!`. The mir lowering of never
582+
// patterns ensures this loop is not reachable.
583583
let block = self.arena.alloc(hir::Block {
584584
stmts: &[],
585585
expr: None,

compiler/rustc_middle/src/thir.rs

+17
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,23 @@ impl<'tcx> Pat<'tcx> {
683683
true
684684
})
685685
}
686+
687+
/// Whether this a never pattern.
688+
pub fn is_never_pattern(&self) -> bool {
689+
let mut is_never_pattern = false;
690+
self.walk(|pat| match &pat.kind {
691+
PatKind::Never => {
692+
is_never_pattern = true;
693+
false
694+
}
695+
PatKind::Or { pats } => {
696+
is_never_pattern = pats.iter().all(|p| p.is_never_pattern());
697+
false
698+
}
699+
_ => true,
700+
});
701+
is_never_pattern
702+
}
686703
}
687704

688705
impl<'tcx> IntoDiagArg for Pat<'tcx> {

compiler/rustc_mir_build/src/build/matches/mod.rs

+40
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,9 @@ struct PatternExtraData<'tcx> {
947947

948948
/// Types that must be asserted.
949949
ascriptions: Vec<Ascription<'tcx>>,
950+
951+
/// Whether this corresponds to a never pattern.
952+
is_never: bool,
950953
}
951954

952955
impl<'tcx> PatternExtraData<'tcx> {
@@ -972,12 +975,14 @@ impl<'tcx, 'pat> FlatPat<'pat, 'tcx> {
972975
pattern: &'pat Pat<'tcx>,
973976
cx: &mut Builder<'_, 'tcx>,
974977
) -> Self {
978+
let is_never = pattern.is_never_pattern();
975979
let mut flat_pat = FlatPat {
976980
match_pairs: vec![MatchPair::new(place, pattern, cx)],
977981
extra_data: PatternExtraData {
978982
span: pattern.span,
979983
bindings: Vec::new(),
980984
ascriptions: Vec::new(),
985+
is_never,
981986
},
982987
};
983988
cx.simplify_match_pairs(&mut flat_pat.match_pairs, &mut flat_pat.extra_data);
@@ -993,6 +998,8 @@ struct Candidate<'pat, 'tcx> {
993998
match_pairs: Vec<MatchPair<'pat, 'tcx>>,
994999

9951000
/// ...and if this is non-empty, one of these subcandidates also has to match...
1001+
// Invariant: at the end of the algorithm, this must never contain a `is_never` candidate
1002+
// because that would break binding consistency.
9961003
subcandidates: Vec<Candidate<'pat, 'tcx>>,
9971004

9981005
/// ...and the guard must be evaluated if there is one.
@@ -1097,6 +1104,7 @@ enum TestCase<'pat, 'tcx> {
10971104
Constant { value: mir::Const<'tcx> },
10981105
Range(&'pat PatRange<'tcx>),
10991106
Slice { len: usize, variable_length: bool },
1107+
Never,
11001108
Or { pats: Box<[FlatPat<'pat, 'tcx>]> },
11011109
}
11021110

@@ -1156,6 +1164,9 @@ enum TestKind<'tcx> {
11561164

11571165
/// Test that the length of the slice is equal to `len`.
11581166
Len { len: u64, op: BinOp },
1167+
1168+
/// Assert unreachability of never patterns.
1169+
Never,
11591170
}
11601171

11611172
/// A test to perform to determine which [`Candidate`] matches a value.
@@ -1569,6 +1580,27 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
15691580
self.cfg.goto(or_block, source_info, any_matches);
15701581
}
15711582
candidate.pre_binding_block = Some(any_matches);
1583+
} else {
1584+
// Never subcandidates may have a set of bindings inconsistent with their siblings,
1585+
// which would break later code. So we filter them out. Note that we can't filter out
1586+
// top-level candidates this way.
1587+
candidate.subcandidates.retain_mut(|candidate| {
1588+
if candidate.extra_data.is_never {
1589+
candidate.visit_leaves(|subcandidate| {
1590+
let block = subcandidate.pre_binding_block.unwrap();
1591+
// That block is already unreachable but needs a terminator to make the MIR well-formed.
1592+
let source_info = self.source_info(subcandidate.extra_data.span);
1593+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
1594+
});
1595+
false
1596+
} else {
1597+
true
1598+
}
1599+
});
1600+
if candidate.subcandidates.is_empty() {
1601+
// If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1602+
candidate.pre_binding_block = Some(self.cfg.start_new_block());
1603+
}
15721604
}
15731605
}
15741606

@@ -1990,6 +2022,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
19902022
block = fresh_block;
19912023
}
19922024

2025+
if candidate.extra_data.is_never {
2026+
// This arm has a dummy body, we don't need to generate code for it. `block` is already
2027+
// unreachable (except via false edge).
2028+
let source_info = self.source_info(candidate.extra_data.span);
2029+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
2030+
return self.cfg.start_new_block();
2031+
}
2032+
19932033
self.ascribe_types(
19942034
block,
19952035
parent_data

compiler/rustc_mir_build/src/build/matches/test.rs

+21
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
4242
TestKind::Len { len: len as u64, op }
4343
}
4444

45+
TestCase::Never => TestKind::Never,
46+
4547
TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
4648

4749
TestCase::Irrefutable { .. } => span_bug!(
@@ -270,6 +272,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
270272
Operand::Move(expected),
271273
);
272274
}
275+
276+
TestKind::Never => {
277+
// Check that the place is initialized.
278+
// FIXME(never_patterns): Also assert validity of the data at `place`.
279+
self.cfg.push_fake_read(
280+
block,
281+
source_info,
282+
FakeReadCause::ForMatchedPlace(None),
283+
place,
284+
);
285+
// A never pattern is only allowed on an uninhabited type, so validity of the data
286+
// implies unreachability.
287+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
288+
}
273289
}
274290
}
275291

@@ -660,6 +676,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
660676
}
661677
}
662678

679+
(TestKind::Never, _) => {
680+
fully_matched = true;
681+
Some(TestBranch::Success)
682+
}
683+
663684
(
664685
TestKind::Switch { .. }
665686
| TestKind::SwitchInt { .. }

compiler/rustc_mir_build/src/build/matches/util.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
123123
let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None };
124124
let mut subpairs = Vec::new();
125125
let test_case = match pattern.kind {
126-
PatKind::Never | PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
126+
PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
127+
127128
PatKind::Or { ref pats } => TestCase::Or {
128129
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
129130
},
@@ -255,6 +256,8 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
255256
// Treat it like a wildcard for now.
256257
default_irrefutable()
257258
}
259+
260+
PatKind::Never => TestCase::Never,
258261
};
259262

260263
MatchPair { place, test_case, subpairs, pattern }

tests/mir-opt/building/match/never_patterns.opt1.SimplifyCfg-initial.after.mir

+7-16
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,23 @@ fn opt1(_1: &Result<u32, Void>) -> &u32 {
2323
}
2424

2525
bb2: {
26-
falseEdge -> [real: bb4, imaginary: bb3];
26+
falseEdge -> [real: bb5, imaginary: bb4];
2727
}
2828

2929
bb3: {
30-
StorageLive(_4);
31-
goto -> bb5;
30+
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
31+
unreachable;
3232
}
3333

3434
bb4: {
35+
unreachable;
36+
}
37+
38+
bb5: {
3539
StorageLive(_3);
3640
_3 = &(((*_1) as Ok).0: u32);
3741
_0 = &(*_3);
3842
StorageDead(_3);
3943
return;
4044
}
41-
42-
bb5: {
43-
falseUnwind -> [real: bb6, unwind: bb7];
44-
}
45-
46-
bb6: {
47-
_5 = const ();
48-
goto -> bb5;
49-
}
50-
51-
bb7 (cleanup): {
52-
resume;
53-
}
5445
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// MIR for `opt2` after SimplifyCfg-initial
2+
3+
fn opt2(_1: &Result<u32, Void>) -> &u32 {
4+
debug res => _1;
5+
let mut _0: &u32;
6+
let mut _2: isize;
7+
let _3: &u32;
8+
scope 1 {
9+
debug x => _3;
10+
}
11+
12+
bb0: {
13+
PlaceMention(_1);
14+
_2 = discriminant((*_1));
15+
switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1];
16+
}
17+
18+
bb1: {
19+
FakeRead(ForMatchedPlace(None), _1);
20+
unreachable;
21+
}
22+
23+
bb2: {
24+
StorageLive(_3);
25+
_3 = &(((*_1) as Ok).0: u32);
26+
_0 = &(*_3);
27+
StorageDead(_3);
28+
return;
29+
}
30+
31+
bb3: {
32+
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
33+
unreachable;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// MIR for `opt3` after SimplifyCfg-initial
2+
3+
fn opt3(_1: &Result<u32, Void>) -> &u32 {
4+
debug res => _1;
5+
let mut _0: &u32;
6+
let mut _2: isize;
7+
let _3: &u32;
8+
scope 1 {
9+
debug x => _3;
10+
}
11+
12+
bb0: {
13+
PlaceMention(_1);
14+
_2 = discriminant((*_1));
15+
switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
16+
}
17+
18+
bb1: {
19+
FakeRead(ForMatchedPlace(None), _1);
20+
unreachable;
21+
}
22+
23+
bb2: {
24+
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
25+
unreachable;
26+
}
27+
28+
bb3: {
29+
StorageLive(_3);
30+
_3 = &(((*_1) as Ok).0: u32);
31+
_0 = &(*_3);
32+
StorageDead(_3);
33+
return;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,44 @@
1-
// skip-filecheck
21
#![feature(never_patterns)]
32
#![allow(incomplete_features)]
43

54
enum Void {}
65

76
// EMIT_MIR never_patterns.opt1.SimplifyCfg-initial.after.mir
87
fn opt1(res: &Result<u32, Void>) -> &u32 {
8+
// CHECK-LABEL: fn opt1(
9+
// CHECK: bb0: {
10+
// CHECK-NOT: {{bb.*}}: {
11+
// CHECK: return;
912
match res {
1013
Ok(x) => x,
1114
Err(!),
1215
}
1316
}
1417

18+
// EMIT_MIR never_patterns.opt2.SimplifyCfg-initial.after.mir
19+
fn opt2(res: &Result<u32, Void>) -> &u32 {
20+
// CHECK-LABEL: fn opt2(
21+
// CHECK: bb0: {
22+
// CHECK-NOT: {{bb.*}}: {
23+
// CHECK: return;
24+
match res {
25+
Ok(x) | Err(!) => x,
26+
}
27+
}
28+
29+
// EMIT_MIR never_patterns.opt3.SimplifyCfg-initial.after.mir
30+
fn opt3(res: &Result<u32, Void>) -> &u32 {
31+
// CHECK-LABEL: fn opt3(
32+
// CHECK: bb0: {
33+
// CHECK-NOT: {{bb.*}}: {
34+
// CHECK: return;
35+
match res {
36+
Err(!) | Ok(x) => x,
37+
}
38+
}
39+
1540
fn main() {
16-
opt1(&Ok(0));
41+
assert_eq!(opt1(&Ok(0)), &0);
42+
assert_eq!(opt2(&Ok(0)), &0);
43+
assert_eq!(opt3(&Ok(0)), &0);
1744
}

tests/ui/rfcs/rfc-0000-never_patterns/check_place_is_initialized.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
//@ check-pass
21
#![feature(never_patterns)]
32
#![allow(incomplete_features)]
43

@@ -9,4 +8,5 @@ fn main() {}
98
fn anything<T>() -> T {
109
let x: Void;
1110
match x { ! }
11+
//~^ ERROR used binding `x` isn't initialized
1212
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error[E0381]: used binding `x` isn't initialized
2+
--> $DIR/check_place_is_initialized.rs:10:15
3+
|
4+
LL | let x: Void;
5+
| - binding declared here but left uninitialized
6+
LL | match x { ! }
7+
| ^ `x` used here but it isn't initialized
8+
|
9+
help: consider assigning a value
10+
|
11+
LL | let x: Void = todo!();
12+
| +++++++++
13+
14+
error: aborting due to 1 previous error
15+
16+
For more information about this error, try `rustc --explain E0381`.

0 commit comments

Comments
 (0)