//! Type inhabitedness logic. use std::ops::ControlFlow::{self, Break, Continue}; use chalk_ir::{ visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor}, DebruijnIndex, }; use hir_def::{visibility::Visibility, AdtId, EnumVariantId, HasModule, ModuleId, VariantId}; use intern::sym; use rustc_hash::FxHashSet; use crate::{ consteval::try_const_usize, db::HirDatabase, Binders, Interner, Substitution, Ty, TyKind, }; // 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 { 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() }; let inhabitedness = ty.visit_with(&mut uninhabited_from, DebruijnIndex::INNERMOST); 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, variant: EnumVariantId, subst: &Substitution, target_mod: ModuleId, ) -> 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() }; let inhabitedness = uninhabited_from.visit_variant(variant.into(), subst); inhabitedness == BREAK_VISIBLY_UNINHABITED } struct UninhabitedFrom<'a> { target_mod: ModuleId, recursive_ty: FxHashSet, // guard for preventing stack overflow in non trivial non terminating types max_depth: usize, db: &'a dyn HirDatabase, } const CONTINUE_OPAQUELY_INHABITED: ControlFlow = Continue(()); const BREAK_VISIBLY_UNINHABITED: ControlFlow = Break(VisiblyUninhabited); #[derive(PartialEq, Eq)] struct VisiblyUninhabited; impl TypeVisitor for UninhabitedFrom<'_> { type BreakTy = VisiblyUninhabited; fn as_dyn(&mut self) -> &mut dyn TypeVisitor { self } fn visit_ty( &mut self, ty: &Ty, outer_binder: DebruijnIndex, ) -> ControlFlow { 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.max_depth -= 1; let r = match ty.kind(Interner) { TyKind::Adt(adt, subst) => self.visit_adt(adt.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) { Some(0) | None => CONTINUE_OPAQUELY_INHABITED, Some(1..) => item_ty.super_visit_with(self, outer_binder), }, _ => CONTINUE_OPAQUELY_INHABITED, }; 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 { // 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 = self.db.enum_data(e); for &(variant, _) in enum_data.variants.iter() { let variant_inhabitedness = self.visit_variant(variant.into(), subst); match variant_inhabitedness { Break(VisiblyUninhabited) => (), Continue(()) => return CONTINUE_OPAQUELY_INHABITED, } } BREAK_VISIBLY_UNINHABITED } } } fn visit_variant( &mut self, variant: VariantId, subst: &Substitution, ) -> ControlFlow { let is_local = variant.krate(self.db.upcast()) == self.target_mod.krate(); if !is_local && self.db.attrs(variant.into()).by_key(&sym::non_exhaustive).exists() { return CONTINUE_OPAQUELY_INHABITED; } let variant_data = self.db.variant_data(variant); 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)) }; for (fid, _) in fields.iter() { self.visit_field(field_vis.as_ref().map(|it| it[fid]), &field_tys[fid], subst)?; } CONTINUE_OPAQUELY_INHABITED } fn visit_field( &mut self, vis: Option, ty: &Binders, subst: &Substitution, ) -> ControlFlow { if vis.map_or(true, |it| it.is_visible_from(self.db.upcast(), self.target_mod)) { let ty = ty.clone().substitute(Interner, subst); ty.visit_with(self, DebruijnIndex::INNERMOST) } else { CONTINUE_OPAQUELY_INHABITED } } }