Migrate inhabitedness checking to the new solver
This commit is contained in:
parent
4cb250b668
commit
92eef5348a
5 changed files with 139 additions and 88 deletions
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue