Reveal opaque types in exhaustiveness checking

This commit is contained in:
Nadrieril 2023-11-16 04:28:22 +01:00
parent 7e4924b55d
commit 2a87bae48d
7 changed files with 76 additions and 42 deletions

View file

@ -28,6 +28,7 @@ use rustc_span::hygiene::DesugaringKind;
use rustc_span::Span; use rustc_span::Span;
pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> { pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
let typeck_results = tcx.typeck(def_id);
let (thir, expr) = tcx.thir_body(def_id)?; let (thir, expr) = tcx.thir_body(def_id)?;
let thir = thir.borrow(); let thir = thir.borrow();
let pattern_arena = TypedArena::default(); let pattern_arena = TypedArena::default();
@ -35,6 +36,7 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err
let mut visitor = MatchVisitor { let mut visitor = MatchVisitor {
tcx, tcx,
thir: &*thir, thir: &*thir,
typeck_results,
param_env: tcx.param_env(def_id), param_env: tcx.param_env(def_id),
lint_level: tcx.local_def_id_to_hir_id(def_id), lint_level: tcx.local_def_id_to_hir_id(def_id),
let_source: LetSource::None, let_source: LetSource::None,
@ -80,6 +82,7 @@ enum LetSource {
struct MatchVisitor<'thir, 'p, 'tcx> { struct MatchVisitor<'thir, 'p, 'tcx> {
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
thir: &'thir Thir<'tcx>, thir: &'thir Thir<'tcx>,
lint_level: HirId, lint_level: HirId,
let_source: LetSource, let_source: LetSource,
@ -382,6 +385,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> {
scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true); scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true);
MatchCheckCtxt { MatchCheckCtxt {
tcx: self.tcx, tcx: self.tcx,
typeck_results: self.typeck_results,
param_env: self.param_env, param_env: self.param_env,
module: self.tcx.parent_module(self.lint_level).to_def_id(), module: self.tcx.parent_module(self.lint_level).to_def_id(),
pattern_arena: self.pattern_arena, pattern_arena: self.pattern_arena,

View file

@ -62,7 +62,8 @@ pub trait TypeCx: Sized + Clone + fmt::Debug {
/// patterns during analysis. /// patterns during analysis.
type PatData: Clone + Default; type PatData: Clone + Default;
fn is_opaque_ty(ty: Self::Ty) -> bool; /// FIXME(Nadrieril): `Cx` should only give us revealed types.
fn reveal_opaque_ty(&self, ty: Self::Ty) -> Self::Ty;
fn is_exhaustive_patterns_feature_on(&self) -> bool; fn is_exhaustive_patterns_feature_on(&self) -> bool;
/// The number of fields for this constructor. /// The number of fields for this constructor.

View file

@ -48,22 +48,14 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
self.patterns.is_empty() self.patterns.is_empty()
} }
fn head_ty(&self) -> Option<Ty<'tcx>> { fn head_ty(&self, cx: MatchCtxt<'a, 'p, 'tcx>) -> Option<Ty<'tcx>> {
if self.patterns.len() == 0 { if self.patterns.len() == 0 {
return None; return None;
} }
// If the type is opaque and it is revealed anywhere in the column, we take the revealed
// version. Otherwise we could encounter constructors for the revealed type and crash. let ty = self.patterns[0].ty();
let first_ty = self.patterns[0].ty(); // FIXME(Nadrieril): `Cx` should only give us revealed types.
if RustcMatchCheckCtxt::is_opaque_ty(first_ty) { Some(cx.tycx.reveal_opaque_ty(ty))
for pat in &self.patterns {
let ty = pat.ty();
if !RustcMatchCheckCtxt::is_opaque_ty(ty) {
return Some(ty);
}
}
}
Some(first_ty)
} }
/// Do constructor splitting on the constructors of the column. /// Do constructor splitting on the constructors of the column.
@ -125,7 +117,7 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>, cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>, column: &PatternColumn<'a, 'p, 'tcx>,
) -> Vec<WitnessPat<'p, 'tcx>> { ) -> Vec<WitnessPat<'p, 'tcx>> {
let Some(ty) = column.head_ty() else { let Some(ty) = column.head_ty(cx) else {
return Vec::new(); return Vec::new();
}; };
let pcx = &PlaceCtxt::new_dummy(cx, ty); let pcx = &PlaceCtxt::new_dummy(cx, ty);
@ -226,7 +218,7 @@ pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>(
cx: MatchCtxt<'a, 'p, 'tcx>, cx: MatchCtxt<'a, 'p, 'tcx>,
column: &PatternColumn<'a, 'p, 'tcx>, column: &PatternColumn<'a, 'p, 'tcx>,
) { ) {
let Some(ty) = column.head_ty() else { let Some(ty) = column.head_ty(cx) else {
return; return;
}; };
let pcx = &PlaceCtxt::new_dummy(cx, ty); let pcx = &PlaceCtxt::new_dummy(cx, ty);

View file

@ -44,6 +44,7 @@ pub type WitnessPat<'p, 'tcx> = crate::pat::WitnessPat<RustcMatchCheckCtxt<'p, '
#[derive(Clone)] #[derive(Clone)]
pub struct RustcMatchCheckCtxt<'p, 'tcx> { pub struct RustcMatchCheckCtxt<'p, 'tcx> {
pub tcx: TyCtxt<'tcx>, pub tcx: TyCtxt<'tcx>,
pub typeck_results: &'tcx ty::TypeckResults<'tcx>,
/// The module in which the match occurs. This is necessary for /// The module in which the match occurs. This is necessary for
/// checking inhabited-ness of types because whether a type is (visibly) /// checking inhabited-ness of types because whether a type is (visibly)
/// inhabited can depend on whether it was defined in the current module or /// inhabited can depend on whether it was defined in the current module or
@ -101,6 +102,21 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> {
} }
} }
/// Type inference occasionally gives us opaque types in places where corresponding patterns
/// have more specific types. To avoid inconsistencies as well as detect opaque uninhabited
/// types, we use the corresponding concrete type if possible.
fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() {
if let Some(local_def_id) = alias_ty.def_id.as_local() {
let key = ty::OpaqueTypeKey { def_id: local_def_id, args: alias_ty.args };
if let Some(real_ty) = self.typeck_results.concrete_opaque_types.get(&key) {
return real_ty.ty;
}
}
}
ty
}
// In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide // In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide
// uninhabited fields in order not to reveal the uninhabitedness of the whole variant. // uninhabited fields in order not to reveal the uninhabitedness of the whole variant.
// This lists the fields we keep along with their types. // This lists the fields we keep along with their types.
@ -873,8 +889,9 @@ impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> {
fn is_exhaustive_patterns_feature_on(&self) -> bool { fn is_exhaustive_patterns_feature_on(&self) -> bool {
self.tcx.features().exhaustive_patterns self.tcx.features().exhaustive_patterns
} }
fn is_opaque_ty(ty: Self::Ty) -> bool {
matches!(ty.kind(), ty::Alias(ty::Opaque, ..)) fn reveal_opaque_ty(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
self.reveal_opaque_ty(ty)
} }
fn ctor_arity(&self, ctor: &crate::constructor::Constructor<Self>, ty: Self::Ty) -> usize { fn ctor_arity(&self, ctor: &crate::constructor::Constructor<Self>, ty: Self::Ty) -> usize {

View file

@ -865,24 +865,14 @@ impl<'a, 'p, Cx: TypeCx> Matrix<'a, 'p, Cx> {
matrix matrix
} }
fn head_ty(&self) -> Option<Cx::Ty> { fn head_ty(&self, mcx: MatchCtxt<'a, 'p, Cx>) -> Option<Cx::Ty> {
if self.column_count() == 0 { if self.column_count() == 0 {
return None; return None;
} }
let mut ty = self.wildcard_row.head().ty(); let ty = self.wildcard_row.head().ty();
// If the type is opaque and it is revealed anywhere in the column, we take the revealed // FIXME(Nadrieril): `Cx` should only give us revealed types.
// version. Otherwise we could encounter constructors for the revealed type and crash. Some(mcx.tycx.reveal_opaque_ty(ty))
if Cx::is_opaque_ty(ty) {
for pat in self.heads() {
let pat_ty = pat.ty();
if !Cx::is_opaque_ty(pat_ty) {
ty = pat_ty;
break;
}
}
}
Some(ty)
} }
fn column_count(&self) -> usize { fn column_count(&self) -> usize {
self.wildcard_row.len() self.wildcard_row.len()
@ -1181,7 +1171,7 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
) -> WitnessMatrix<Cx> { ) -> WitnessMatrix<Cx> {
debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count())); debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count()));
let Some(ty) = matrix.head_ty() else { let Some(ty) = matrix.head_ty(mcx) else {
// The base case: there are no columns in the matrix. We are morally pattern-matching on (). // The base case: there are no columns in the matrix. We are morally pattern-matching on ().
// A row is useful iff it has no (unguarded) rows above it. // A row is useful iff it has no (unguarded) rows above it.
for row in matrix.rows_mut() { for row in matrix.rows_mut() {

View file

@ -14,7 +14,7 @@ enum Void {}
fn return_never_rpit(x: Void) -> impl Copy { fn return_never_rpit(x: Void) -> impl Copy {
if false { if false {
match return_never_rpit(x) { match return_never_rpit(x) {
_ => {} _ => {} //~ ERROR unreachable
} }
} }
x x
@ -28,7 +28,7 @@ type T = impl Copy;
fn return_never_tait(x: Void) -> T { fn return_never_tait(x: Void) -> T {
if false { if false {
match return_never_tait(x) { match return_never_tait(x) {
_ => {} _ => {} //~ ERROR unreachable
} }
} }
x x
@ -42,7 +42,7 @@ fn option_never(x: Void) -> Option<impl Copy> {
if false { if false {
match option_never(x) { match option_never(x) {
None => {} None => {}
Some(_) => {} Some(_) => {} //~ ERROR unreachable
} }
match option_never(x) { match option_never(x) {
None => {} None => {}
@ -75,7 +75,7 @@ fn inner_never(x: Void) {
type T = impl Copy; type T = impl Copy;
let y: T = x; let y: T = x;
match y { match y {
_ => {} _ => {} //~ ERROR unreachable
} }
} }
@ -93,7 +93,7 @@ type U = impl Copy;
fn unify_never(x: Void, u: U) -> U { fn unify_never(x: Void, u: U) -> U {
if false { if false {
match u { match u {
_ => {} _ => {} //~ ERROR unreachable
} }
} }
x x

View file

@ -1,8 +1,8 @@
error: unreachable pattern error: unreachable pattern
--> $DIR/impl-trait.rs:61:13 --> $DIR/impl-trait.rs:17:13
| |
LL | Some(_) => {} LL | _ => {}
| ^^^^^^^ | ^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/impl-trait.rs:5:9 --> $DIR/impl-trait.rs:5:9
@ -10,12 +10,36 @@ note: the lint level is defined here
LL | #![deny(unreachable_patterns)] LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^
error: unreachable pattern
--> $DIR/impl-trait.rs:31:13
|
LL | _ => {}
| ^
error: unreachable pattern
--> $DIR/impl-trait.rs:45:13
|
LL | Some(_) => {}
| ^^^^^^^
error: unreachable pattern
--> $DIR/impl-trait.rs:61:13
|
LL | Some(_) => {}
| ^^^^^^^
error: unreachable pattern error: unreachable pattern
--> $DIR/impl-trait.rs:65:13 --> $DIR/impl-trait.rs:65:13
| |
LL | _ => {} LL | _ => {}
| ^ | ^
error: unreachable pattern
--> $DIR/impl-trait.rs:78:9
|
LL | _ => {}
| ^
error: unreachable pattern error: unreachable pattern
--> $DIR/impl-trait.rs:88:9 --> $DIR/impl-trait.rs:88:9
| |
@ -24,6 +48,12 @@ LL | _ => {}
LL | Some((a, b)) => {} LL | Some((a, b)) => {}
| ^^^^^^^^^^^^ unreachable pattern | ^^^^^^^^^^^^ unreachable pattern
error: unreachable pattern
--> $DIR/impl-trait.rs:96:13
|
LL | _ => {}
| ^
error: unreachable pattern error: unreachable pattern
--> $DIR/impl-trait.rs:107:9 --> $DIR/impl-trait.rs:107:9
| |
@ -66,6 +96,6 @@ LL + _ => todo!(),
LL + } LL + }
| |
error: aborting due to 7 previous errors error: aborting due to 12 previous errors
For more information about this error, try `rustc --explain E0004`. For more information about this error, try `rustc --explain E0004`.