Skip to content

Commit 57e8aeb

Browse files
committed
Lower never patterns to Unreachable in mir
1 parent 92d65a9 commit 57e8aeb

File tree

14 files changed

+203
-114
lines changed

14 files changed

+203
-114
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
581581
self.dcx().emit_err(NeverPatternWithGuard { span: g.span });
582582
}
583583

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

compiler/rustc_middle/src/thir.rs

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

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

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

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

10171017
/// Types that must be asserted.
10181018
ascriptions: Vec<Ascription<'tcx>>,
1019+
1020+
/// Whether this corresponds to a never pattern.
1021+
is_never: bool,
10191022
}
10201023

10211024
impl<'tcx> PatternExtraData<'tcx> {
@@ -1041,12 +1044,14 @@ impl<'tcx, 'pat> FlatPat<'pat, 'tcx> {
10411044
pattern: &'pat Pat<'tcx>,
10421045
cx: &mut Builder<'_, 'tcx>,
10431046
) -> Self {
1047+
let is_never = pattern.is_never_pattern();
10441048
let mut flat_pat = FlatPat {
10451049
match_pairs: vec![MatchPair::new(place, pattern, cx)],
10461050
extra_data: PatternExtraData {
10471051
span: pattern.span,
10481052
bindings: Vec::new(),
10491053
ascriptions: Vec::new(),
1054+
is_never,
10501055
},
10511056
};
10521057
cx.simplify_match_pairs(&mut flat_pat.match_pairs, &mut flat_pat.extra_data);
@@ -1062,6 +1067,8 @@ struct Candidate<'pat, 'tcx> {
10621067
match_pairs: Vec<MatchPair<'pat, 'tcx>>,
10631068

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

10671074
/// ...and the guard must be evaluated if there is one.
@@ -1172,6 +1179,7 @@ enum TestCase<'pat, 'tcx> {
11721179
Range(&'pat PatRange<'tcx>),
11731180
Slice { len: usize, variable_length: bool },
11741181
Deref { temp: Place<'tcx>, mutability: Mutability },
1182+
Never,
11751183
Or { pats: Box<[FlatPat<'pat, 'tcx>]> },
11761184
}
11771185

@@ -1238,6 +1246,9 @@ enum TestKind<'tcx> {
12381246
temp: Place<'tcx>,
12391247
mutability: Mutability,
12401248
},
1249+
1250+
/// Assert unreachability of never patterns.
1251+
Never,
12411252
}
12421253

12431254
/// A test to perform to determine which [`Candidate`] matches a value.
@@ -1662,6 +1673,27 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
16621673
self.cfg.goto(or_block, source_info, any_matches);
16631674
}
16641675
candidate.pre_binding_block = Some(any_matches);
1676+
} else {
1677+
// Never subcandidates may have a set of bindings inconsistent with their siblings,
1678+
// which would break later code. So we filter them out. Note that we can't filter out
1679+
// top-level candidates this way.
1680+
candidate.subcandidates.retain_mut(|candidate| {
1681+
if candidate.extra_data.is_never {
1682+
candidate.visit_leaves(|subcandidate| {
1683+
let block = subcandidate.pre_binding_block.unwrap();
1684+
// That block is already unreachable but needs a terminator to make the MIR well-formed.
1685+
let source_info = self.source_info(subcandidate.extra_data.span);
1686+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
1687+
});
1688+
false
1689+
} else {
1690+
true
1691+
}
1692+
});
1693+
if candidate.subcandidates.is_empty() {
1694+
// If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`.
1695+
candidate.pre_binding_block = Some(self.cfg.start_new_block());
1696+
}
16651697
}
16661698
}
16671699

@@ -2008,6 +2040,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
20082040
block = fresh_block;
20092041
}
20102042

2043+
if candidate.extra_data.is_never {
2044+
// This arm has a dummy body, we don't need to generate code for it. `block` is already
2045+
// unreachable (except via false edge).
2046+
let source_info = self.source_info(candidate.extra_data.span);
2047+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
2048+
return self.cfg.start_new_block();
2049+
}
2050+
20112051
self.ascribe_types(
20122052
block,
20132053
parent_data

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

+21
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
4444

4545
TestCase::Deref { temp, mutability } => TestKind::Deref { temp, mutability },
4646

47+
TestCase::Never => TestKind::Never,
48+
4749
TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
4850

4951
TestCase::Irrefutable { .. } => span_bug!(
@@ -262,6 +264,20 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
262264
let target = target_block(TestBranch::Success);
263265
self.call_deref(block, target, place, mutability, ty, temp, test.span);
264266
}
267+
268+
TestKind::Never => {
269+
// Check that the place is initialized.
270+
// FIXME(never_patterns): Also assert validity of the data at `place`.
271+
self.cfg.push_fake_read(
272+
block,
273+
source_info,
274+
FakeReadCause::ForMatchedPlace(None),
275+
place,
276+
);
277+
// A never pattern is only allowed on an uninhabited type, so validity of the data
278+
// implies unreachability.
279+
self.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
280+
}
265281
}
266282
}
267283

@@ -710,6 +726,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
710726
Some(TestBranch::Success)
711727
}
712728

729+
(TestKind::Never, _) => {
730+
fully_matched = true;
731+
Some(TestBranch::Success)
732+
}
733+
713734
(
714735
TestKind::Switch { .. }
715736
| TestKind::SwitchInt { .. }

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
124124
let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None };
125125
let mut subpairs = Vec::new();
126126
let test_case = match pattern.kind {
127-
PatKind::Never | PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
127+
PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
128+
128129
PatKind::Or { ref pats } => TestCase::Or {
129130
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
130131
},
@@ -260,6 +261,8 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> {
260261
subpairs.push(MatchPair::new(PlaceBuilder::from(temp).deref(), subpattern, cx));
261262
TestCase::Deref { temp, mutability }
262263
}
264+
265+
PatKind::Never => TestCase::Never,
263266
};
264267

265268
MatchPair { place, test_case, subpairs, pattern }

tests/crashes/120421.rs

-12
This file was deleted.

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

+2-15
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ fn opt1(_1: &Result<u32, Void>) -> &u32 {
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: {
@@ -38,17 +38,4 @@ fn opt1(_1: &Result<u32, Void>) -> &u32 {
3838
StorageDead(_3);
3939
return;
4040
}
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-
}
5441
}
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 = /* value */;
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)