Normalize when checking for uninhabited types for pattern exhaustiveness checking

This commit is contained in:
Chayim Refael Friedman 2025-05-23 01:01:21 +03:00
parent 1511c5b7fd
commit f17bbfc48b
5 changed files with 81 additions and 25 deletions

View File

@ -25,7 +25,7 @@ use triomphe::Arc;
use typed_arena::Arena;
use crate::{
Adjust, InferenceResult, Interner, Ty, TyExt, TyKind,
Adjust, InferenceResult, Interner, TraitEnvironment, Ty, TyExt, TyKind,
db::HirDatabase,
diagnostics::match_check::{
self,
@ -74,8 +74,9 @@ impl BodyValidationDiagnostic {
let _p = tracing::info_span!("BodyValidationDiagnostic::collect").entered();
let infer = db.infer(owner);
let body = db.body(owner);
let env = db.trait_environment_for_body(owner);
let mut validator =
ExprValidator { owner, body, infer, diagnostics: Vec::new(), validate_lints };
ExprValidator { owner, body, infer, diagnostics: Vec::new(), validate_lints, env };
validator.validate_body(db);
validator.diagnostics
}
@ -85,6 +86,7 @@ struct ExprValidator {
owner: DefWithBodyId,
body: Arc<Body>,
infer: Arc<InferenceResult>,
env: Arc<TraitEnvironment>,
diagnostics: Vec<BodyValidationDiagnostic>,
validate_lints: bool,
}
@ -190,7 +192,7 @@ impl ExprValidator {
return;
}
let cx = MatchCheckCtx::new(self.owner.module(db), self.owner, db);
let cx = MatchCheckCtx::new(self.owner.module(db), self.owner, db, self.env.clone());
let pattern_arena = Arena::new();
let mut m_arms = Vec::with_capacity(arms.len());
@ -317,7 +319,7 @@ impl ExprValidator {
return;
};
let pattern_arena = Arena::new();
let cx = MatchCheckCtx::new(self.owner.module(db), self.owner, db);
let cx = MatchCheckCtx::new(self.owner.module(db), self.owner, db, self.env.clone());
for stmt in &**statements {
let &Statement::Let { pat, initializer, else_branch: None, .. } = stmt else {
continue;

View File

@ -12,9 +12,10 @@ use rustc_pattern_analysis::{
};
use smallvec::{SmallVec, smallvec};
use stdx::never;
use triomphe::Arc;
use crate::{
AdtId, Interner, Scalar, Ty, TyExt, TyKind,
AdtId, Interner, Scalar, TraitEnvironment, Ty, TyExt, TyKind,
db::HirDatabase,
infer::normalize,
inhabitedness::{is_enum_variant_uninhabited_from, is_ty_uninhabited_from},
@ -69,13 +70,19 @@ pub(crate) struct MatchCheckCtx<'db> {
body: DefWithBodyId,
pub(crate) db: &'db dyn HirDatabase,
exhaustive_patterns: bool,
env: Arc<TraitEnvironment>,
}
impl<'db> MatchCheckCtx<'db> {
pub(crate) fn new(module: ModuleId, body: DefWithBodyId, db: &'db dyn HirDatabase) -> Self {
pub(crate) fn new(
module: ModuleId,
body: DefWithBodyId,
db: &'db dyn HirDatabase,
env: Arc<TraitEnvironment>,
) -> Self {
let def_map = module.crate_def_map(db);
let exhaustive_patterns = def_map.is_unstable_feature_enabled(&sym::exhaustive_patterns);
Self { module, body, db, exhaustive_patterns }
Self { module, body, db, exhaustive_patterns, env }
}
pub(crate) fn compute_match_usefulness(
@ -100,7 +107,7 @@ impl<'db> MatchCheckCtx<'db> {
}
fn is_uninhabited(&self, ty: &Ty) -> bool {
is_ty_uninhabited_from(self.db, ty, self.module)
is_ty_uninhabited_from(self.db, ty, self.module, self.env.clone())
}
/// Returns whether the given ADT is from another crate declared `#[non_exhaustive]`.
@ -459,8 +466,13 @@ impl PatCx for MatchCheckCtx<'_> {
} else {
let mut variants = IndexVec::with_capacity(enum_data.variants.len());
for &(variant, _) in enum_data.variants.iter() {
let is_uninhabited =
is_enum_variant_uninhabited_from(cx.db, variant, subst, cx.module);
let is_uninhabited = is_enum_variant_uninhabited_from(
cx.db,
variant,
subst,
cx.module,
self.env.clone(),
);
let visibility = if is_uninhabited {
VariantVisibility::Empty
} else {

View File

@ -7,17 +7,24 @@ use chalk_ir::{
};
use hir_def::{AdtId, EnumVariantId, ModuleId, VariantId, visibility::Visibility};
use rustc_hash::FxHashSet;
use triomphe::Arc;
use crate::{
Binders, Interner, Substitution, Ty, TyKind, consteval::try_const_usize, db::HirDatabase,
AliasTy, Binders, Interner, Substitution, TraitEnvironment, Ty, TyKind,
consteval::try_const_usize, db::HirDatabase,
};
// FIXME: Turn this into a query, it can be quite slow
/// Checks whether a type is visibly uninhabited from a particular module.
pub(crate) fn is_ty_uninhabited_from(db: &dyn HirDatabase, ty: &Ty, target_mod: ModuleId) -> bool {
pub(crate) fn is_ty_uninhabited_from(
db: &dyn HirDatabase,
ty: &Ty,
target_mod: ModuleId,
env: Arc<TraitEnvironment>,
) -> bool {
let _p = tracing::info_span!("is_ty_uninhabited_from", ?ty).entered();
let mut uninhabited_from =
UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default() };
UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default(), env };
let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
@ -29,11 +36,12 @@ pub(crate) fn is_enum_variant_uninhabited_from(
variant: EnumVariantId,
subst: &Substitution,
target_mod: ModuleId,
env: Arc<TraitEnvironment>,
) -> bool {
let _p = tracing::info_span!("is_enum_variant_uninhabited_from").entered();
let mut uninhabited_from =
UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default() };
UninhabitedFrom { target_mod, db, max_depth: 500, recursive_ty: FxHashSet::default(), env };
let inhabitedness = uninhabited_from.visit_variant(variant.into(), subst);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
@ -44,6 +52,7 @@ struct UninhabitedFrom<'a> {
// guard for preventing stack overflow in non trivial non terminating types
max_depth: usize,
db: &'a dyn HirDatabase,
env: Arc<TraitEnvironment>,
}
const CONTINUE_OPAQUELY_INHABITED: ControlFlow<VisiblyUninhabited> = Continue(());
@ -78,6 +87,12 @@ impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
Some(1..) => item_ty.super_visit_with(self, outer_binder),
},
TyKind::Alias(AliasTy::Projection(projection)) => {
// FIXME: I think this currently isn't used for monomorphized bodies, so there is no need to handle
// `TyKind::AssociatedType`, but perhaps in the future it will.
let normalized = self.db.normalize_projection(projection.clone(), self.env.clone());
self.visit_ty(&normalized, outer_binder)
}
_ => CONTINUE_OPAQUELY_INHABITED,
};
self.recursive_ty.remove(ty);

View File

@ -25,7 +25,7 @@ use syntax::TextRange;
use triomphe::Arc;
use crate::{
Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt,
Adjust, Adjustment, AutoBorrow, CallableDefId, TraitEnvironment, TyBuilder, TyExt,
consteval::ConstEvalError,
db::{HirDatabase, InternedClosure, InternedClosureId},
display::{DisplayTarget, HirDisplay, hir_display_with_store},
@ -79,6 +79,7 @@ struct MirLowerCtx<'db> {
infer: &'db InferenceResult,
resolver: Resolver<'db>,
drop_scopes: Vec<DropScope>,
env: Arc<TraitEnvironment>,
}
// FIXME: Make this smaller, its stored in database queries
@ -288,6 +289,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
closures: vec![],
};
let resolver = owner.resolver(db);
let env = db.trait_environment_for_body(owner);
MirLowerCtx {
result: mir,
@ -300,6 +302,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
labeled_loop_blocks: Default::default(),
discr_temp: None,
drop_scopes: vec![DropScope::default()],
env,
}
}
@ -944,10 +947,7 @@ impl<'ctx> MirLowerCtx<'ctx> {
let cast_kind = if source_ty.as_reference().is_some() {
CastKind::PointerCoercion(PointerCast::ArrayToPointer)
} else {
let mut table = InferenceTable::new(
self.db,
self.db.trait_environment_for_body(self.owner),
);
let mut table = InferenceTable::new(self.db, self.env.clone());
cast_kind(&mut table, &source_ty, &target_ty)?
};
@ -1412,11 +1412,8 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> {
let size = || {
self.db
.layout_of_ty(ty.clone(), self.db.trait_environment_for_body(self.owner))
.map(|it| it.size.bytes_usize())
};
let size =
|| self.db.layout_of_ty(ty.clone(), self.env.clone()).map(|it| it.size.bytes_usize());
const USIZE_SIZE: usize = size_of::<usize>();
let bytes: Box<[_]> = match l {
hir_def::hir::Literal::String(b) => {
@ -1723,7 +1720,12 @@ impl<'ctx> MirLowerCtx<'ctx> {
}
fn is_uninhabited(&self, expr_id: ExprId) -> bool {
is_ty_uninhabited_from(self.db, &self.infer[expr_id], self.owner.module(self.db))
is_ty_uninhabited_from(
self.db,
&self.infer[expr_id],
self.owner.module(self.db),
self.env.clone(),
)
}
/// This function push `StorageLive` statement for the binding, and applies changes to add `StorageDead` and

View File

@ -106,4 +106,29 @@ fn test(x: Result<i32, &'static !>) {
"#,
);
}
#[test]
fn empty_patterns_normalize() {
check_diagnostics(
r#"
enum Infallible {}
trait Foo {
type Assoc;
}
enum Enum<T: Foo> {
A,
B(T::Assoc),
}
impl Foo for () {
type Assoc = Infallible;
}
fn foo(v: Enum<()>) {
let Enum::A = v;
}
"#,
);
}
}