From f17bbfc48b8aac99d0fccd14249d111d113691ca Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 23 May 2025 01:01:21 +0300 Subject: [PATCH] Normalize when checking for uninhabited types for pattern exhaustiveness checking --- crates/hir-ty/src/diagnostics/expr.rs | 10 +++++--- .../diagnostics/match_check/pat_analysis.rs | 24 +++++++++++++----- crates/hir-ty/src/inhabitedness.rs | 23 ++++++++++++++--- crates/hir-ty/src/mir/lower.rs | 24 ++++++++++-------- .../src/handlers/non_exhaustive_let.rs | 25 +++++++++++++++++++ 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs index e4a23cbbac..8665e9d33f 100644 --- a/crates/hir-ty/src/diagnostics/expr.rs +++ b/crates/hir-ty/src/diagnostics/expr.rs @@ -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, infer: Arc, + env: Arc, diagnostics: Vec, 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; diff --git a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs index 785277d70c..dd82a0f45c 100644 --- a/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs +++ b/crates/hir-ty/src/diagnostics/match_check/pat_analysis.rs @@ -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, } 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, + ) -> 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 { diff --git a/crates/hir-ty/src/inhabitedness.rs b/crates/hir-ty/src/inhabitedness.rs index e0c3279d3f..e81a5e3c31 100644 --- a/crates/hir-ty/src/inhabitedness.rs +++ b/crates/hir-ty/src/inhabitedness.rs @@ -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, +) -> 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, ) -> 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, } const CONTINUE_OPAQUELY_INHABITED: ControlFlow = Continue(()); @@ -78,6 +87,12 @@ impl TypeVisitor 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); diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 7fcc89e518..e6caf2d8d9 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -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, + env: Arc, } // 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 { - 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::(); 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 diff --git a/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs index 35cefd2397..f20b6dea12 100644 --- a/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs +++ b/crates/ide-diagnostics/src/handlers/non_exhaustive_let.rs @@ -106,4 +106,29 @@ fn test(x: Result) { "#, ); } + + #[test] + fn empty_patterns_normalize() { + check_diagnostics( + r#" +enum Infallible {} + +trait Foo { + type Assoc; +} +enum Enum { + A, + B(T::Assoc), +} + +impl Foo for () { + type Assoc = Infallible; +} + +fn foo(v: Enum<()>) { + let Enum::A = v; +} + "#, + ); + } }