Skip to content

Commit c1fc1d1

Browse files
committed
Auto merge of #116821 - Nadrieril:fix-opaque-ice, r=compiler-errors
Exhaustiveness: reveal opaque types properly Previously, exhaustiveness had no clear policy around opaque types. In this PR I propose the following policy: within the body of an item that defines the hidden type of some opaque type, exhaustiveness checking on a value of that opaque type is performed using the concrete hidden type inferred in this body. I'm not sure how consistent this is with other operations allowed on opaque types; I believe this will require FCP. From what I can tell, this doesn't change anything for non-empty types. The observable changes are: - when the real type is uninhabited, matches within the defining scopes can now rely on that for exhaustiveness, e.g.: ```rust #[derive(Copy, Clone)] enum Void {} fn return_never_rpit(x: Void) -> impl Copy { if false { match return_never_rpit(x) {} } x } ``` - this properly fixes ICEs like #117100 that occurred because a same match could have some patterns where the type is revealed and some where it is not. Bonus subtle point: if `x` is opaque, a match like `match x { ("", "") => {} ... }` will constrain its type ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=901d715330eac40339b4016ac566d6c3)). This is not the case for `match x {}`: this will not constain the type, and will only compile if something else constrains the type to be empty. Fixes #117100 r? `@oli-obk` Edited for precision of the wording [Included](#116821 (comment)) in the FCP on this PR is this rule: > Within the body of an item that defines the hidden type of some opaque type, exhaustiveness checking on a value of that opaque type is performed using the concrete hidden type inferred in this body.
2 parents ef1b78e + 2a87bae commit c1fc1d1

File tree

7 files changed

+284
-33
lines changed

7 files changed

+284
-33
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ use rustc_span::hygiene::DesugaringKind;
2828
use rustc_span::Span;
2929

3030
pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
31+
let typeck_results = tcx.typeck(def_id);
3132
let (thir, expr) = tcx.thir_body(def_id)?;
3233
let thir = thir.borrow();
3334
let pattern_arena = TypedArena::default();
3435
let dropless_arena = DroplessArena::default();
3536
let mut visitor = MatchVisitor {
3637
tcx,
3738
thir: &*thir,
39+
typeck_results,
3840
param_env: tcx.param_env(def_id),
3941
lint_level: tcx.local_def_id_to_hir_id(def_id),
4042
let_source: LetSource::None,
@@ -80,6 +82,7 @@ enum LetSource {
8082
struct MatchVisitor<'thir, 'p, 'tcx> {
8183
tcx: TyCtxt<'tcx>,
8284
param_env: ty::ParamEnv<'tcx>,
85+
typeck_results: &'tcx ty::TypeckResults<'tcx>,
8386
thir: &'thir Thir<'tcx>,
8487
lint_level: HirId,
8588
let_source: LetSource,
@@ -382,6 +385,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
382385
scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true);
383386
MatchCheckCtxt {
384387
tcx: self.tcx,
388+
typeck_results: self.typeck_results,
385389
param_env: self.param_env,
386390
module: self.tcx.parent_module(self.lint_level).to_def_id(),
387391
pattern_arena: self.pattern_arena,

compiler/rustc_pattern_analysis/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ pub trait TypeCx: Sized + Clone + fmt::Debug {
6262
/// patterns during analysis.
6363
type PatData: Clone + Default;
6464

65-
fn is_opaque_ty(ty: Self::Ty) -> bool;
65+
/// FIXME(Nadrieril): `Cx` should only give us revealed types.
66+
fn reveal_opaque_ty(&self, ty: Self::Ty) -> Self::Ty;
6667
fn is_exhaustive_patterns_feature_on(&self) -> bool;
6768

6869
/// The number of fields for this constructor.

compiler/rustc_pattern_analysis/src/lints.rs

+7-15
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,14 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> {
4848
fn is_empty(&self) -> bool {
4949
self.patterns.is_empty()
5050
}
51-
fn head_ty(&self) -> Option<Ty<'tcx>> {
51+
fn head_ty(&self, cx: MatchCtxt<'a, 'p, 'tcx>) -> Option<Ty<'tcx>> {
5252
if self.patterns.len() == 0 {
5353
return None;
5454
}
55-
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
56-
// version. Otherwise we could encounter constructors for the revealed type and crash.
57-
let first_ty = self.patterns[0].ty();
58-
if RustcMatchCheckCtxt::is_opaque_ty(first_ty) {
59-
for pat in &self.patterns {
60-
let ty = pat.ty();
61-
if !RustcMatchCheckCtxt::is_opaque_ty(ty) {
62-
return Some(ty);
63-
}
64-
}
65-
}
66-
Some(first_ty)
55+
56+
let ty = self.patterns[0].ty();
57+
// FIXME(Nadrieril): `Cx` should only give us revealed types.
58+
Some(cx.tycx.reveal_opaque_ty(ty))
6759
}
6860

6961
/// Do constructor splitting on the constructors of the column.
@@ -125,7 +117,7 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
125117
cx: MatchCtxt<'a, 'p, 'tcx>,
126118
column: &PatternColumn<'a, 'p, 'tcx>,
127119
) -> Vec<WitnessPat<'p, 'tcx>> {
128-
let Some(ty) = column.head_ty() else {
120+
let Some(ty) = column.head_ty(cx) else {
129121
return Vec::new();
130122
};
131123
let pcx = &PlaceCtxt::new_dummy(cx, ty);
@@ -226,7 +218,7 @@ pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
226218
cx: MatchCtxt<'a, 'p, 'tcx>,
227219
column: &PatternColumn<'a, 'p, 'tcx>,
228220
) {
229-
let Some(ty) = column.head_ty() else {
221+
let Some(ty) = column.head_ty(cx) else {
230222
return;
231223
};
232224
let pcx = &PlaceCtxt::new_dummy(cx, ty);

compiler/rustc_pattern_analysis/src/rustc.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub type WitnessPat<'p, 'tcx> = crate::pat::WitnessPat<RustcMatchCheckCtxt<'p, '
4444
#[derive(Clone)]
4545
pub struct RustcMatchCheckCtxt<'p, 'tcx> {
4646
pub tcx: TyCtxt<'tcx>,
47+
pub typeck_results: &'tcx ty::TypeckResults<'tcx>,
4748
/// The module in which the match occurs. This is necessary for
4849
/// checking inhabited-ness of types because whether a type is (visibly)
4950
/// inhabited can depend on whether it was defined in the current module or
@@ -101,6 +102,21 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
101102
}
102103
}
103104

105+
/// Type inference occasionally gives us opaque types in places where corresponding patterns
106+
/// have more specific types. To avoid inconsistencies as well as detect opaque uninhabited
107+
/// types, we use the corresponding concrete type if possible.
108+
fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
109+
if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() {
110+
if let Some(local_def_id) = alias_ty.def_id.as_local() {
111+
let key = ty::OpaqueTypeKey { def_id: local_def_id, args: alias_ty.args };
112+
if let Some(real_ty) = self.typeck_results.concrete_opaque_types.get(&key) {
113+
return real_ty.ty;
114+
}
115+
}
116+
}
117+
ty
118+
}
119+
104120
// In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide
105121
// uninhabited fields in order not to reveal the uninhabitedness of the whole variant.
106122
// This lists the fields we keep along with their types.
@@ -873,8 +889,9 @@ impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
873889
fn is_exhaustive_patterns_feature_on(&self) -> bool {
874890
self.tcx.features().exhaustive_patterns
875891
}
876-
fn is_opaque_ty(ty: Self::Ty) -> bool {
877-
matches!(ty.kind(), ty::Alias(ty::Opaque, ..))
892+
893+
fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
894+
self.reveal_opaque_ty(ty)
878895
}
879896

880897
fn ctor_arity(&self, ctor: &crate::constructor::Constructor<Self>, ty: Self::Ty) -> usize {

compiler/rustc_pattern_analysis/src/usefulness.rs

+5-15
Original file line numberDiff line numberDiff line change
@@ -865,24 +865,14 @@ impl<'a, 'p, Cx: TypeCx> Matrix<'a, 'p, Cx> {
865865
matrix
866866
}
867867

868-
fn head_ty(&self) -> Option<Cx::Ty> {
868+
fn head_ty(&self, mcx: MatchCtxt<'a, 'p, Cx>) -> Option<Cx::Ty> {
869869
if self.column_count() == 0 {
870870
return None;
871871
}
872872

873-
let mut ty = self.wildcard_row.head().ty();
874-
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
875-
// version. Otherwise we could encounter constructors for the revealed type and crash.
876-
if Cx::is_opaque_ty(ty) {
877-
for pat in self.heads() {
878-
let pat_ty = pat.ty();
879-
if !Cx::is_opaque_ty(pat_ty) {
880-
ty = pat_ty;
881-
break;
882-
}
883-
}
884-
}
885-
Some(ty)
873+
let ty = self.wildcard_row.head().ty();
874+
// FIXME(Nadrieril): `Cx` should only give us revealed types.
875+
Some(mcx.tycx.reveal_opaque_ty(ty))
886876
}
887877
fn column_count(&self) -> usize {
888878
self.wildcard_row.len()
@@ -1181,7 +1171,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
11811171
) -> WitnessMatrix<Cx> {
11821172
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));
11831173

1184-
let Some(ty) = matrix.head_ty() else {
1174+
let Some(ty) = matrix.head_ty(mcx) else {
11851175
// The base case: there are no columns in the matrix. We are morally pattern-matching on ().
11861176
// A row is useful iff it has no (unguarded) rows above it.
11871177
for row in matrix.rows_mut() {
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#![feature(never_type)]
2+
#![feature(exhaustive_patterns)]
3+
#![feature(type_alias_impl_trait)]
4+
#![feature(non_exhaustive_omitted_patterns_lint)]
5+
#![deny(unreachable_patterns)]
6+
// Test that the lint traversal handles opaques correctly
7+
#![deny(non_exhaustive_omitted_patterns)]
8+
9+
fn main() {}
10+
11+
#[derive(Copy, Clone)]
12+
enum Void {}
13+
14+
fn return_never_rpit(x: Void) -> impl Copy {
15+
if false {
16+
match return_never_rpit(x) {
17+
_ => {} //~ ERROR unreachable
18+
}
19+
}
20+
x
21+
}
22+
fn friend_of_return_never_rpit(x: Void) {
23+
match return_never_rpit(x) {}
24+
//~^ ERROR non-empty
25+
}
26+
27+
type T = impl Copy;
28+
fn return_never_tait(x: Void) -> T {
29+
if false {
30+
match return_never_tait(x) {
31+
_ => {} //~ ERROR unreachable
32+
}
33+
}
34+
x
35+
}
36+
fn friend_of_return_never_tait(x: Void) {
37+
match return_never_tait(x) {}
38+
//~^ ERROR non-empty
39+
}
40+
41+
fn option_never(x: Void) -> Option<impl Copy> {
42+
if false {
43+
match option_never(x) {
44+
None => {}
45+
Some(_) => {} //~ ERROR unreachable
46+
}
47+
match option_never(x) {
48+
None => {}
49+
// FIXME: Unreachable not detected because `is_uninhabited` does not look into opaque
50+
// types.
51+
_ => {}
52+
}
53+
}
54+
Some(x)
55+
}
56+
57+
fn option_never2(x: Void) -> impl Copy {
58+
if false {
59+
match option_never2(x) {
60+
None => {}
61+
Some(_) => {} //~ ERROR unreachable
62+
}
63+
match option_never2(x) {
64+
None => {}
65+
_ => {} //~ ERROR unreachable
66+
}
67+
match option_never2(x) {
68+
None => {}
69+
}
70+
}
71+
Some(x)
72+
}
73+
74+
fn inner_never(x: Void) {
75+
type T = impl Copy;
76+
let y: T = x;
77+
match y {
78+
_ => {} //~ ERROR unreachable
79+
}
80+
}
81+
82+
// This one caused ICE https://github.com/rust-lang/rust/issues/117100.
83+
fn inner_tuple() {
84+
type T = impl Copy;
85+
let foo: T = Some((1u32, 2u32));
86+
match foo {
87+
_ => {}
88+
Some((a, b)) => {} //~ ERROR unreachable
89+
}
90+
}
91+
92+
type U = impl Copy;
93+
fn unify_never(x: Void, u: U) -> U {
94+
if false {
95+
match u {
96+
_ => {} //~ ERROR unreachable
97+
}
98+
}
99+
x
100+
}
101+
102+
type V = impl Copy;
103+
fn infer_in_match(x: Option<V>) {
104+
match x {
105+
None => {}
106+
Some((a, b)) => {}
107+
Some((mut x, mut y)) => {
108+
//~^ ERROR unreachable
109+
x = 42;
110+
y = "foo";
111+
}
112+
}
113+
}
114+
115+
type W = impl Copy;
116+
#[derive(Copy, Clone)]
117+
struct Rec<'a> {
118+
n: u32,
119+
w: Option<&'a W>,
120+
}
121+
fn recursive_opaque() -> W {
122+
if false {
123+
match recursive_opaque() {
124+
// Check for the ol' ICE when the type is recursively opaque.
125+
_ => {}
126+
Rec { n: 0, w: Some(Rec { n: 0, w: _ }) } => {} //~ ERROR unreachable
127+
}
128+
}
129+
let w: Option<&'static W> = None;
130+
Rec { n: 0, w }
131+
}
132+
133+
type X = impl Copy;
134+
struct SecretelyVoid(X);
135+
fn nested_empty_opaque(x: Void) -> X {
136+
if false {
137+
let opaque_void = nested_empty_opaque(x);
138+
let secretely_void = SecretelyVoid(opaque_void);
139+
match secretely_void {
140+
// FIXME: Unreachable not detected because `is_uninhabited` does not look into opaque
141+
// types.
142+
_ => {}
143+
}
144+
}
145+
x
146+
}

0 commit comments

Comments
 (0)