Skip to content

Commit 0f40f14

Browse files
committed
Auto merge of rust-lang#123332 - Nadrieril:testkind-never, r=matthewjasper
never patterns: lower never patterns to `Unreachable` in MIR This lowers a `!` pattern to "goto Unreachable". Ideally I'd like to read from the place to make it clear that the UB is coming from an invalid value, but that's tricky so I'm leaving it for later. r? `@compiler-errors` how do you feel about a lil bit of MIR lowering
2 parents cc8d9b6 + 57e8aeb commit 0f40f14

16 files changed

+315
-21
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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// MIR for `opt1` after SimplifyCfg-initial
2+
3+
fn opt1(_1: &Result<u32, Void>) -> &u32 {
4+
debug res => _1;
5+
let mut _0: &u32;
6+
let mut _2: isize;
7+
let _3: &u32;
8+
let mut _4: !;
9+
let mut _5: ();
10+
scope 1 {
11+
debug x => _3;
12+
}
13+
14+
bb0: {
15+
PlaceMention(_1);
16+
_2 = discriminant((*_1));
17+
switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1];
18+
}
19+
20+
bb1: {
21+
FakeRead(ForMatchedPlace(None), _1);
22+
unreachable;
23+
}
24+
25+
bb2: {
26+
falseEdge -> [real: bb4, imaginary: bb3];
27+
}
28+
29+
bb3: {
30+
FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void));
31+
unreachable;
32+
}
33+
34+
bb4: {
35+
StorageLive(_3);
36+
_3 = &(((*_1) as Ok).0: u32);
37+
_0 = &(*_3);
38+
StorageDead(_3);
39+
return;
40+
}
41+
}
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
@@ -0,0 +1,44 @@
1+
#![feature(never_patterns)]
2+
#![allow(incomplete_features)]
3+
4+
enum Void {}
5+
6+
// EMIT_MIR never_patterns.opt1.SimplifyCfg-initial.after.mir
7+
fn opt1(res: &Result<u32, Void>) -> &u32 {
8+
// CHECK-LABEL: fn opt1(
9+
// CHECK: bb0: {
10+
// CHECK-NOT: {{bb.*}}: {
11+
// CHECK: return;
12+
match res {
13+
Ok(x) => x,
14+
Err(!),
15+
}
16+
}
17+
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+
40+
fn main() {
41+
assert_eq!(opt1(&Ok(0)), &0);
42+
assert_eq!(opt2(&Ok(0)), &0);
43+
assert_eq!(opt3(&Ok(0)), &0);
44+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Check that never patterns can't have bodies or guards.
12
#![feature(never_patterns)]
23
#![allow(incomplete_features)]
34

0 commit comments

Comments
 (0)