Migrate inhabitedness checking to the new solver

This commit is contained in:
Chayim Refael Friedman 2025-10-12 11:13:11 +03:00
parent 4cb250b668
commit 92eef5348a
5 changed files with 139 additions and 88 deletions

View file

@ -19,6 +19,11 @@ use crate::{
db::HirDatabase,
infer::normalize,
inhabitedness::{is_enum_variant_uninhabited_from, is_ty_uninhabited_from},
next_solver::{
DbInterner, TypingMode,
infer::{DbInternerInferExt, InferCtxt},
mapping::ChalkToNextSolver,
},
};
use super::{FieldPat, Pat, PatKind};
@ -28,7 +33,7 @@ use Constructor::*;
// Re-export r-a-specific versions of all these types.
pub(crate) type DeconstructedPat<'db> =
rustc_pattern_analysis::pat::DeconstructedPat<MatchCheckCtx<'db>>;
pub(crate) type MatchArm<'db> = rustc_pattern_analysis::MatchArm<'db, MatchCheckCtx<'db>>;
pub(crate) type MatchArm<'a, 'db> = rustc_pattern_analysis::MatchArm<'a, MatchCheckCtx<'db>>;
pub(crate) type WitnessPat<'db> = rustc_pattern_analysis::pat::WitnessPat<MatchCheckCtx<'db>>;
/// [Constructor] uses this in unimplemented variants.
@ -71,6 +76,7 @@ pub(crate) struct MatchCheckCtx<'db> {
pub(crate) db: &'db dyn HirDatabase,
exhaustive_patterns: bool,
env: Arc<TraitEnvironment<'db>>,
infcx: InferCtxt<'db>,
}
impl<'db> MatchCheckCtx<'db> {
@ -82,15 +88,17 @@ impl<'db> MatchCheckCtx<'db> {
) -> 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, env }
let interner = DbInterner::new_with(db, Some(env.krate), env.block);
let infcx = interner.infer_ctxt().build(TypingMode::typeck_for_body(interner, body.into()));
Self { module, body, db, exhaustive_patterns, env, infcx }
}
pub(crate) fn compute_match_usefulness(
pub(crate) fn compute_match_usefulness<'a>(
&self,
arms: &[MatchArm<'db>],
arms: &[MatchArm<'a, 'db>],
scrut_ty: Ty,
known_valid_scrutinee: Option<bool>,
) -> Result<UsefulnessReport<'db, Self>, ()> {
) -> Result<UsefulnessReport<'a, Self>, ()> {
if scrut_ty.contains_unknown() {
return Err(());
}
@ -107,7 +115,12 @@ impl<'db> MatchCheckCtx<'db> {
}
fn is_uninhabited(&self, ty: &Ty) -> bool {
is_ty_uninhabited_from(self.db, ty, self.module, self.env.clone())
is_ty_uninhabited_from(
&self.infcx,
ty.to_nextsolver(self.infcx.interner),
self.module,
self.env.clone(),
)
}
/// Returns whether the given ADT is from another crate declared `#[non_exhaustive]`.
@ -429,9 +442,9 @@ impl PatCx for MatchCheckCtx<'_> {
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,
&cx.infcx,
variant,
subst,
subst.to_nextsolver(cx.infcx.interner),
cx.module,
self.env.clone(),
);

View file

@ -1,60 +1,62 @@
//! Type inhabitedness logic.
use std::ops::ControlFlow::{self, Break, Continue};
use chalk_ir::{
DebruijnIndex,
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
};
use hir_def::{AdtId, EnumVariantId, ModuleId, VariantId, visibility::Visibility};
use rustc_hash::FxHashSet;
use rustc_type_ir::{
TypeSuperVisitable, TypeVisitable, TypeVisitor,
inherent::{AdtDef, IntoKind},
};
use triomphe::Arc;
use crate::{
AliasTy, Binders, Interner, Substitution, TraitEnvironment, Ty, TyKind,
TraitEnvironment,
consteval::try_const_usize,
db::HirDatabase,
next_solver::{DbInterner, mapping::ChalkToNextSolver},
next_solver::{
DbInterner, EarlyBinder, GenericArgs, Ty, TyKind,
infer::{InferCtxt, traits::ObligationCause},
obligation_ctxt::ObligationCtxt,
},
};
// 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,
pub(crate) fn is_ty_uninhabited_from<'db>(
infcx: &InferCtxt<'db>,
ty: Ty<'db>,
target_mod: ModuleId,
env: Arc<TraitEnvironment<'_>>,
env: Arc<TraitEnvironment<'db>>,
) -> 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(), env };
let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST);
let mut uninhabited_from = UninhabitedFrom::new(infcx, target_mod, env);
let inhabitedness = ty.visit_with(&mut uninhabited_from);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
// FIXME: Turn this into a query, it can be quite slow
/// Checks whether a variant is visibly uninhabited from a particular module.
pub(crate) fn is_enum_variant_uninhabited_from(
db: &dyn HirDatabase,
pub(crate) fn is_enum_variant_uninhabited_from<'db>(
infcx: &InferCtxt<'db>,
variant: EnumVariantId,
subst: &Substitution,
subst: GenericArgs<'db>,
target_mod: ModuleId,
env: Arc<TraitEnvironment<'_>>,
env: Arc<TraitEnvironment<'db>>,
) -> 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(), env };
let mut uninhabited_from = UninhabitedFrom::new(infcx, target_mod, env);
let inhabitedness = uninhabited_from.visit_variant(variant.into(), subst);
inhabitedness == BREAK_VISIBLY_UNINHABITED
}
struct UninhabitedFrom<'a> {
struct UninhabitedFrom<'a, 'db> {
target_mod: ModuleId,
recursive_ty: FxHashSet<Ty>,
recursive_ty: FxHashSet<Ty<'db>>,
// guard for preventing stack overflow in non trivial non terminating types
max_depth: usize,
db: &'a dyn HirDatabase,
env: Arc<TraitEnvironment<'a>>,
infcx: &'a InferCtxt<'db>,
env: Arc<TraitEnvironment<'db>>,
}
const CONTINUE_OPAQUELY_INHABITED: ControlFlow<VisiblyUninhabited> = Continue(());
@ -62,63 +64,73 @@ const BREAK_VISIBLY_UNINHABITED: ControlFlow<VisiblyUninhabited> = Break(Visibly
#[derive(PartialEq, Eq)]
struct VisiblyUninhabited;
impl TypeVisitor<Interner> for UninhabitedFrom<'_> {
type BreakTy = VisiblyUninhabited;
impl<'db> TypeVisitor<DbInterner<'db>> for UninhabitedFrom<'_, 'db> {
type Result = ControlFlow<VisiblyUninhabited>;
fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = VisiblyUninhabited> {
self
}
fn visit_ty(
&mut self,
ty: &Ty,
outer_binder: DebruijnIndex,
) -> ControlFlow<VisiblyUninhabited> {
if self.recursive_ty.contains(ty) || self.max_depth == 0 {
fn visit_ty(&mut self, mut ty: Ty<'db>) -> ControlFlow<VisiblyUninhabited> {
if self.recursive_ty.contains(&ty) || self.max_depth == 0 {
// rustc considers recursive types always inhabited. I think it is valid to consider
// recursive types as always uninhabited, but we should do what rustc is doing.
return CONTINUE_OPAQUELY_INHABITED;
}
self.recursive_ty.insert(ty.clone());
self.recursive_ty.insert(ty);
self.max_depth -= 1;
let interner = DbInterner::new_with(self.db, None, None);
let r = match ty.kind(Interner) {
TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst),
if matches!(ty.kind(), TyKind::Alias(..)) {
let mut ocx = ObligationCtxt::new(self.infcx);
match ocx.structurally_normalize_ty(&ObligationCause::dummy(), self.env.env, ty) {
Ok(it) => ty = it,
Err(_) => return CONTINUE_OPAQUELY_INHABITED,
}
}
let r = match ty.kind() {
TyKind::Adt(adt, subst) => self.visit_adt(adt.def_id().0, subst),
TyKind::Never => BREAK_VISIBLY_UNINHABITED,
TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder),
TyKind::Array(item_ty, len) => {
match try_const_usize(self.db, len.to_nextsolver(interner)) {
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)
}
TyKind::Tuple(..) => ty.super_visit_with(self),
TyKind::Array(item_ty, len) => match try_const_usize(self.infcx.interner.db, len) {
Some(0) | None => CONTINUE_OPAQUELY_INHABITED,
Some(1..) => item_ty.super_visit_with(self),
},
_ => CONTINUE_OPAQUELY_INHABITED,
};
self.recursive_ty.remove(ty);
self.recursive_ty.remove(&ty);
self.max_depth += 1;
r
}
fn interner(&self) -> Interner {
Interner
}
}
impl UninhabitedFrom<'_> {
fn visit_adt(&mut self, adt: AdtId, subst: &Substitution) -> ControlFlow<VisiblyUninhabited> {
impl<'a, 'db> UninhabitedFrom<'a, 'db> {
fn new(
infcx: &'a InferCtxt<'db>,
target_mod: ModuleId,
env: Arc<TraitEnvironment<'db>>,
) -> Self {
Self { target_mod, recursive_ty: FxHashSet::default(), max_depth: 500, infcx, env }
}
#[inline]
fn interner(&self) -> DbInterner<'db> {
self.infcx.interner
}
#[inline]
fn db(&self) -> &'db dyn HirDatabase {
self.interner().db
}
fn visit_adt(
&mut self,
adt: AdtId,
subst: GenericArgs<'db>,
) -> ControlFlow<VisiblyUninhabited> {
// An ADT is uninhabited iff all its variants uninhabited.
match adt {
// rustc: For now, `union`s are never considered uninhabited.
AdtId::UnionId(_) => CONTINUE_OPAQUELY_INHABITED,
AdtId::StructId(s) => self.visit_variant(s.into(), subst),
AdtId::EnumId(e) => {
let enum_data = e.enum_variants(self.db);
let enum_data = e.enum_variants(self.db());
for &(variant, _, _) in enum_data.variants.iter() {
let variant_inhabitedness = self.visit_variant(variant.into(), subst);
@ -135,17 +147,17 @@ impl UninhabitedFrom<'_> {
fn visit_variant(
&mut self,
variant: VariantId,
subst: &Substitution,
subst: GenericArgs<'db>,
) -> ControlFlow<VisiblyUninhabited> {
let variant_data = variant.fields(self.db);
let variant_data = variant.fields(self.db());
let fields = variant_data.fields();
if fields.is_empty() {
return CONTINUE_OPAQUELY_INHABITED;
}
let is_enum = matches!(variant, VariantId::EnumVariantId(..));
let field_tys = self.db.field_types(variant);
let field_vis = if is_enum { None } else { Some(self.db.field_visibilities(variant)) };
let field_tys = self.db().field_types_ns(variant);
let field_vis = if is_enum { None } else { Some(self.db().field_visibilities(variant)) };
for (fid, _) in fields.iter() {
self.visit_field(field_vis.as_ref().map(|it| it[fid]), &field_tys[fid], subst)?;
@ -156,12 +168,12 @@ impl UninhabitedFrom<'_> {
fn visit_field(
&mut self,
vis: Option<Visibility>,
ty: &Binders<Ty>,
subst: &Substitution,
ty: &EarlyBinder<'db, Ty<'db>>,
subst: GenericArgs<'db>,
) -> ControlFlow<VisiblyUninhabited> {
if vis.is_none_or(|it| it.is_visible_from(self.db, self.target_mod)) {
let ty = ty.clone().substitute(Interner, subst);
ty.visit_with(self, DebruijnIndex::INNERMOST)
if vis.is_none_or(|it| it.is_visible_from(self.db(), self.target_mod)) {
let ty = ty.instantiate(self.interner(), subst);
ty.visit_with(self)
} else {
CONTINUE_OPAQUELY_INHABITED
}

View file

@ -43,7 +43,6 @@ use crate::{
next_solver::{
Const, DbInterner, ParamConst, Region, TyKind, TypingMode, UnevaluatedConst,
infer::{DbInternerInferExt, InferCtxt},
mapping::NextSolverToChalk,
},
traits::FnTrait,
};
@ -303,6 +302,7 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> {
let resolver = owner.resolver(db);
let env = db.trait_environment_for_body(owner);
let interner = DbInterner::new_with(db, Some(env.krate), env.block);
// FIXME(next-solver): Is `non_body_analysis()` correct here? Don't we want to reveal opaque types defined by this body?
let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis());
MirLowerCtx {
@ -1766,8 +1766,8 @@ impl<'a, 'db> MirLowerCtx<'a, 'db> {
fn is_uninhabited(&self, expr_id: ExprId) -> bool {
is_ty_uninhabited_from(
self.db,
&self.infer[expr_id].to_chalk(self.interner()),
&self.infcx,
self.infer[expr_id],
self.owner.module(self.db),
self.env.clone(),
)

View file

@ -1,8 +1,8 @@
//! Definition of `SolverDefId`
use hir_def::{
AdtId, CallableDefId, ConstId, EnumId, EnumVariantId, FunctionId, GeneralConstId, GenericDefId,
ImplId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
AdtId, CallableDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId,
GeneralConstId, GenericDefId, ImplId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
};
use rustc_type_ir::inherent;
use stdx::impl_from;
@ -29,6 +29,8 @@ pub enum SolverDefId {
InternedClosureId(InternedClosureId),
InternedCoroutineId(InternedCoroutineId),
InternedOpaqueTyId(InternedOpaqueTyId),
EnumVariantId(EnumVariantId),
// FIXME(next-solver): Do we need the separation of `Ctor`? It duplicates some variants.
Ctor(Ctor),
}
@ -73,6 +75,16 @@ impl std::fmt::Debug for SolverDefId {
SolverDefId::InternedOpaqueTyId(id) => {
f.debug_tuple("InternedOpaqueTyId").field(&id).finish()
}
SolverDefId::EnumVariantId(id) => {
let parent_enum = id.loc(db).parent;
f.debug_tuple("EnumVariantId")
.field(&format_args!(
"\"{}::{}\"",
db.enum_signature(parent_enum).name.as_str(),
parent_enum.enum_variants(db).variant_name_by_id(id).unwrap().as_str()
))
.finish()
}
SolverDefId::Ctor(Ctor::Struct(id)) => {
f.debug_tuple("Ctor").field(&db.struct_signature(id).name.as_str()).finish()
}
@ -101,6 +113,7 @@ impl_from!(
InternedClosureId,
InternedCoroutineId,
InternedOpaqueTyId,
EnumVariantId,
Ctor
for SolverDefId
);
@ -129,6 +142,18 @@ impl From<GeneralConstId> for SolverDefId {
}
}
impl From<DefWithBodyId> for SolverDefId {
#[inline]
fn from(value: DefWithBodyId) -> Self {
match value {
DefWithBodyId::FunctionId(id) => id.into(),
DefWithBodyId::StaticId(id) => id.into(),
DefWithBodyId::ConstId(id) => id.into(),
DefWithBodyId::VariantId(id) => id.into(),
}
}
}
impl TryFrom<SolverDefId> for GenericDefId {
type Error = SolverDefId;
@ -141,10 +166,11 @@ impl TryFrom<SolverDefId> for GenericDefId {
SolverDefId::StaticId(static_id) => GenericDefId::StaticId(static_id),
SolverDefId::TraitId(trait_id) => GenericDefId::TraitId(trait_id),
SolverDefId::TypeAliasId(type_alias_id) => GenericDefId::TypeAliasId(type_alias_id),
SolverDefId::InternedClosureId(_) => return Err(value),
SolverDefId::InternedCoroutineId(_) => return Err(value),
SolverDefId::InternedOpaqueTyId(_) => return Err(value),
SolverDefId::Ctor(_) => return Err(value),
SolverDefId::InternedClosureId(_)
| SolverDefId::InternedCoroutineId(_)
| SolverDefId::InternedOpaqueTyId(_)
| SolverDefId::EnumVariantId(_)
| SolverDefId::Ctor(_) => return Err(value),
})
}
}

View file

@ -1211,6 +1211,7 @@ impl<'db> rustc_type_ir::Interner for DbInterner<'db> {
| SolverDefId::AdtId(_)
| SolverDefId::TraitId(_)
| SolverDefId::ImplId(_)
| SolverDefId::EnumVariantId(..)
| SolverDefId::Ctor(..)
| SolverDefId::InternedOpaqueTyId(..) => panic!(),
};
@ -1969,8 +1970,7 @@ impl<'db> rustc_type_ir::Interner for DbInterner<'db> {
self,
defining_anchor: Self::LocalDefId,
) -> Self::LocalDefIds {
// FIXME(next-solver)
unimplemented!()
Default::default()
}
type Probe = rustc_type_ir::solve::inspect::Probe<DbInterner<'db>>;