From 6948a66ad81e149d8a0af5dcc0888b9a44f3d2b4 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Wed, 14 Jan 2026 05:40:27 +0900 Subject: [PATCH] fix: Sync cast checks to rustc again --- .../crates/hir-ty/src/infer/cast.rs | 270 +++++++++++++----- .../crates/hir-ty/src/infer/unify.rs | 10 - .../src/handlers/invalid_cast.rs | 10 +- 3 files changed, 201 insertions(+), 89 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs index d073b06ccc8a..d69b00adb7f7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/cast.rs @@ -2,8 +2,10 @@ use hir_def::{AdtId, hir::ExprId, signatures::TraitFlags}; use rustc_ast_ir::Mutability; +use rustc_hash::FxHashSet; use rustc_type_ir::{ - Flags, InferTy, TypeFlags, UintTy, + InferTy, TypeVisitableExt, UintTy, elaborate, + error::TypeError, inherent::{AdtDef, BoundExistentialPredicates as _, IntoKind, Ty as _}, }; use stdx::never; @@ -12,7 +14,10 @@ use crate::{ InferenceDiagnostic, db::HirDatabase, infer::{AllowTwoPhase, InferenceContext, expr::ExprIsRead}, - next_solver::{BoundExistentialPredicates, DbInterner, ParamTy, Ty, TyKind}, + next_solver::{ + BoundExistentialPredicates, ExistentialPredicate, ParamTy, Region, Ty, TyKind, + infer::traits::ObligationCause, + }, }; #[derive(Debug)] @@ -66,12 +71,13 @@ pub enum CastError { DifferingKinds, SizedUnsizedCast, IllegalCast, - IntToFatCast, + IntToWideCast, NeedDeref, NeedViaPtr, NeedViaThinPtr, NeedViaInt, NonScalar, + PtrPtrAddingAutoTraits, // We don't want to report errors with unknown types currently. // UnknownCastPtrKind, // UnknownExprPtrKind, @@ -137,22 +143,13 @@ impl<'db> CastCheck<'db> { return Ok(()); } - if !self.cast_ty.flags().contains(TypeFlags::HAS_TY_INFER) - && !ctx.table.is_sized(self.cast_ty) - { + if !self.cast_ty.has_infer_types() && !ctx.table.is_sized(self.cast_ty) { return Err(InferenceDiagnostic::CastToUnsized { expr: self.expr, cast_ty: self.cast_ty.store(), }); } - // Chalk doesn't support trait upcasting and fails to solve some obvious goals - // when the trait environment contains some recursive traits (See issue #18047) - // We skip cast checks for such cases for now, until the next-gen solver. - if contains_dyn_trait(self.cast_ty) { - return Ok(()); - } - self.do_check(ctx).map_err(|e| e.into_diagnostic(self.expr, self.expr_ty, self.cast_ty)) } @@ -162,22 +159,23 @@ impl<'db> CastCheck<'db> { (Some(t_from), Some(t_cast)) => (t_from, t_cast), (None, Some(t_cast)) => match self.expr_ty.kind() { TyKind::FnDef(..) => { - let sig = - self.expr_ty.callable_sig(ctx.interner()).expect("FnDef had no sig"); - let sig = ctx.table.normalize_associated_types_in(sig); + // rustc calls `FnCtxt::normalize` on this but it's a no-op in next-solver + let sig = self.expr_ty.fn_sig(ctx.interner()); let fn_ptr = Ty::new_fn_ptr(ctx.interner(), sig); - if ctx - .coerce( - self.source_expr.into(), - self.expr_ty, - fn_ptr, - AllowTwoPhase::No, - ExprIsRead::Yes, - ) - .is_ok() - { - } else { - return Err(CastError::IllegalCast); + match ctx.coerce( + self.source_expr.into(), + self.expr_ty, + fn_ptr, + AllowTwoPhase::No, + ExprIsRead::Yes, + ) { + Ok(_) => {} + Err(TypeError::IntrinsicCast) => { + return Err(CastError::IllegalCast); + } + Err(_) => { + return Err(CastError::NonScalar); + } } (CastTy::FnPtr, t_cast) @@ -213,23 +211,41 @@ impl<'db> CastCheck<'db> { // rustc checks whether the `expr_ty` is foreign adt with `non_exhaustive` sym match (t_from, t_cast) { + // These types have invariants! can't cast into them. (_, CastTy::Int(Int::CEnum) | CastTy::FnPtr) => Err(CastError::NonScalar), + + // * -> Bool (_, CastTy::Int(Int::Bool)) => Err(CastError::CastToBool), - (CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()), + + // * -> Char + (CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()), // u8-char-cast (_, CastTy::Int(Int::Char)) => Err(CastError::CastToChar), + + // prim -> float,ptr (CastTy::Int(Int::Bool | Int::CEnum | Int::Char), CastTy::Float) => { Err(CastError::NeedViaInt) } + (CastTy::Int(Int::Bool | Int::CEnum | Int::Char) | CastTy::Float, CastTy::Ptr(..)) | (CastTy::Ptr(..) | CastTy::FnPtr, CastTy::Float) => Err(CastError::IllegalCast), - (CastTy::Ptr(src, _), CastTy::Ptr(dst, _)) => self.check_ptr_ptr_cast(ctx, src, dst), + + // ptr -> ptr + (CastTy::Ptr(src, _), CastTy::Ptr(dst, _)) => self.check_ptr_ptr_cast(ctx, src, dst), // ptr-ptr-cast + + // // ptr-addr-cast (CastTy::Ptr(src, _), CastTy::Int(_)) => self.check_ptr_addr_cast(ctx, src), + (CastTy::FnPtr, CastTy::Int(_)) => Ok(()), + + // addr-ptr-cast (CastTy::Int(_), CastTy::Ptr(dst, _)) => self.check_addr_ptr_cast(ctx, dst), + + // fn-ptr-cast (CastTy::FnPtr, CastTy::Ptr(dst, _)) => self.check_fptr_ptr_cast(ctx, dst), + + // prim -> prim (CastTy::Int(Int::CEnum), CastTy::Int(_)) => Ok(()), (CastTy::Int(Int::Char | Int::Bool), CastTy::Int(_)) => Ok(()), (CastTy::Int(_) | CastTy::Float, CastTy::Int(_) | CastTy::Float) => Ok(()), - (CastTy::FnPtr, CastTy::Int(_)) => Ok(()), } } @@ -241,10 +257,16 @@ impl<'db> CastCheck<'db> { t_cast: Ty<'db>, m_cast: Mutability, ) -> Result<(), CastError> { - // Mutability order is opposite to rustc. `Mut < Not` - if m_expr <= m_cast + let t_expr = ctx.table.try_structurally_resolve_type(t_expr); + let t_cast = ctx.table.try_structurally_resolve_type(t_cast); + + if m_expr >= m_cast && let TyKind::Array(ety, _) = t_expr.kind() + && ctx.infcx().can_eq(ctx.table.param_env, ety, t_cast) { + // Due to historical reasons we allow directly casting references of + // arrays into raw pointers of their element type. + // Coerce to a raw pointer so that we generate RawPtr in MIR. let array_ptr_type = Ty::new_ptr(ctx.interner(), t_expr, m_expr); if ctx @@ -265,14 +287,9 @@ impl<'db> CastCheck<'db> { ); } - // This is a less strict condition than rustc's `demand_eqtype`, - // but false negative is better than false positive - if ctx - .coerce(self.source_expr.into(), ety, t_cast, AllowTwoPhase::No, ExprIsRead::Yes) - .is_ok() - { - return Ok(()); - } + // this will report a type mismatch if needed + let _ = ctx.demand_eqtype(self.expr.into(), ety, t_cast); + return Ok(()); } Err(CastError::IllegalCast) @@ -289,30 +306,147 @@ impl<'db> CastCheck<'db> { match (src_kind, dst_kind) { (Some(PointerKind::Error), _) | (_, Some(PointerKind::Error)) => Ok(()), + // (_, None) => Err(CastError::UnknownCastPtrKind), // (None, _) => Err(CastError::UnknownExprPtrKind), (_, None) | (None, _) => Ok(()), + + // Cast to thin pointer is OK (_, Some(PointerKind::Thin)) => Ok(()), + + // thin -> fat? report invalid cast (don't complain about vtable kinds) (Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast), + + // trait object -> trait object? need to do additional checks (Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => { match (src_tty.principal_def_id(), dst_tty.principal_def_id()) { + // A + SrcAuto> -> B + DstAuto>. need to make sure + // - `Src` and `Dst` traits are the same + // - traits have the same generic arguments + // - projections are the same + // - `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto` + // + // Note that trait upcasting goes through a different mechanism (`coerce_unsized`) + // and is unaffected by this check. (Some(src_principal), Some(dst_principal)) => { if src_principal == dst_principal { return Ok(()); } - let src_principal = ctx.db.trait_signature(src_principal.0); - let dst_principal = ctx.db.trait_signature(dst_principal.0); - if src_principal.flags.contains(TraitFlags::AUTO) - && dst_principal.flags.contains(TraitFlags::AUTO) + + // We need to reconstruct trait object types. + // `m_src` and `m_dst` won't work for us here because they will potentially + // contain wrappers, which we do not care about. + // + // e.g. we want to allow `dyn T -> (dyn T,)`, etc. + // + // We also need to skip auto traits to emit an FCW and not an error. + let src_obj = Ty::new_dynamic( + ctx.interner(), + BoundExistentialPredicates::new_from_iter( + ctx.interner(), + src_tty.iter().filter(|pred| { + !matches!( + pred.skip_binder(), + ExistentialPredicate::AutoTrait(_) + ) + }), + ), + Region::new_erased(ctx.interner()), + ); + let dst_obj = Ty::new_dynamic( + ctx.interner(), + BoundExistentialPredicates::new_from_iter( + ctx.interner(), + dst_tty.iter().filter(|pred| { + !matches!( + pred.skip_binder(), + ExistentialPredicate::AutoTrait(_) + ) + }), + ), + Region::new_erased(ctx.interner()), + ); + + // `dyn Src = dyn Dst`, this checks for matching traits/generics/projections + // This is `fcx.demand_eqtype`, but inlined to give a better error. + if ctx + .table + .at(&ObligationCause::dummy()) + .eq(src_obj, dst_obj) + .map(|infer_ok| ctx.table.register_infer_ok(infer_ok)) + .is_err() { - Ok(()) - } else { - Err(CastError::DifferingKinds) + return Err(CastError::DifferingKinds); } + + // Check that `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto`. + // Emit an FCW otherwise. + let src_auto: FxHashSet<_> = src_tty + .auto_traits() + .into_iter() + .chain( + elaborate::supertrait_def_ids(ctx.interner(), src_principal) + .filter(|trait_| { + ctx.db + .trait_signature(trait_.0) + .flags + .contains(TraitFlags::AUTO) + }), + ) + .collect(); + + let added = dst_tty + .auto_traits() + .into_iter() + .any(|trait_| !src_auto.contains(&trait_)); + + if added { + return Err(CastError::PtrPtrAddingAutoTraits); + } + + Ok(()) } - _ => Err(CastError::Unknown), + + // dyn Auto -> dyn Auto'? ok. + (None, None) => Ok(()), + + // dyn Trait -> dyn Auto? not ok (for now). + // + // Although dropping the principal is already allowed for unsizing coercions + // (e.g. `*const (dyn Trait + Auto)` to `*const dyn Auto`), dropping it is + // currently **NOT** allowed for (non-coercion) ptr-to-ptr casts (e.g + // `*const Foo` to `*const Bar` where `Foo` has a `dyn Trait + Auto` tail + // and `Bar` has a `dyn Auto` tail), because the underlying MIR operations + // currently work very differently: + // + // * A MIR unsizing coercion on raw pointers to trait objects (`*const dyn Src` + // to `*const dyn Dst`) is currently equivalent to downcasting the source to + // the concrete sized type that it was originally unsized from first (via a + // ptr-to-ptr cast from `*const Src` to `*const T` with `T: Sized`) and then + // unsizing this thin pointer to the target type (unsizing `*const T` to + // `*const Dst`). In particular, this means that the pointer's metadata + // (vtable) will semantically change, e.g. for const eval and miri, even + // though the vtables will always be merged for codegen. + // + // * A MIR ptr-to-ptr cast is currently equivalent to a transmute and does not + // change the pointer metadata (vtable) at all. + // + // In addition to this potentially surprising difference between coercion and + // non-coercion casts, casting away the principal with a MIR ptr-to-ptr cast + // is currently considered undefined behavior: + // + // As a validity invariant of pointers to trait objects, we currently require + // that the principal of the vtable in the pointer metadata exactly matches + // the principal of the pointee type, where "no principal" is also considered + // a kind of principal. + (Some(_), None) => Err(CastError::DifferingKinds), + + // dyn Auto -> dyn Trait? not ok. + (None, Some(_)) => Err(CastError::DifferingKinds), } } + + // fat -> fat? metadata kinds must match (Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(()), (_, _) => Err(CastError::DifferingKinds), } @@ -342,9 +476,9 @@ impl<'db> CastCheck<'db> { None => Ok(()), Some(PointerKind::Error) => Ok(()), Some(PointerKind::Thin) => Ok(()), - Some(PointerKind::VTable(_)) => Err(CastError::IntToFatCast), - Some(PointerKind::Length) => Err(CastError::IntToFatCast), - Some(PointerKind::OfAlias | PointerKind::OfParam(_)) => Err(CastError::IntToFatCast), + Some(PointerKind::VTable(_)) => Err(CastError::IntToWideCast), + Some(PointerKind::Length) => Err(CastError::IntToWideCast), + Some(PointerKind::OfAlias | PointerKind::OfParam(_)) => Err(CastError::IntToWideCast), } } @@ -363,15 +497,20 @@ impl<'db> CastCheck<'db> { } } +/// The kind of pointer and associated metadata (thin, length or vtable) - we +/// only allow casts between wide pointers if their metadata have the same +/// kind. #[derive(Debug, PartialEq, Eq)] enum PointerKind<'db> { - // thin pointer + /// No metadata attached, ie pointer to sized type or foreign type Thin, - // trait object + /// A trait object VTable(BoundExistentialPredicates<'db>), - // slice + /// Slice Length, + /// The unsize info of this projection or opaque type OfAlias, + /// The unsize info of this parameter OfParam(ParamTy), Error, } @@ -439,24 +578,3 @@ fn pointer_kind<'db>( } } } - -fn contains_dyn_trait<'db>(ty: Ty<'db>) -> bool { - use std::ops::ControlFlow; - - use rustc_type_ir::{TypeSuperVisitable, TypeVisitable, TypeVisitor}; - - struct DynTraitVisitor; - - impl<'db> TypeVisitor> for DynTraitVisitor { - type Result = ControlFlow<()>; - - fn visit_ty(&mut self, ty: Ty<'db>) -> ControlFlow<()> { - match ty.kind() { - TyKind::Dynamic(..) => ControlFlow::Break(()), - _ => ty.super_visit_with(self), - } - } - } - - ty.visit_with(&mut DynTraitVisitor).is_break() -} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index d55fc0ab0da6..2057159c46d2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -261,16 +261,6 @@ impl<'db> InferenceTable<'db> { self.infer_ctxt.canonicalize_response(t) } - // FIXME: We should get rid of this method. We cannot deeply normalize during inference, only when finishing. - // Inference should use shallow normalization (`try_structurally_resolve_type()`) only, when needed. - pub(crate) fn normalize_associated_types_in(&mut self, ty: T) -> T - where - T: TypeFoldable> + Clone, - { - let ty = self.resolve_vars_with_obligations(ty); - self.at(&ObligationCause::new()).deeply_normalize(ty.clone()).unwrap_or(ty) - } - pub(crate) fn normalize_alias_ty(&mut self, alias: Ty<'db>) -> Ty<'db> { self.infer_ctxt .at(&ObligationCause::new(), self.param_env) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs index a59077b757b1..7479f8147d2e 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/invalid_cast.rs @@ -51,7 +51,7 @@ pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast<'_ DiagnosticCode::RustcHardError("E0606"), format_ty!(ctx, "casting `{}` as `{}` is invalid", d.expr_ty, d.cast_ty), ), - CastError::IntToFatCast => ( + CastError::IntToWideCast => ( DiagnosticCode::RustcHardError("E0606"), format_ty!(ctx, "cannot cast `{}` to a fat pointer `{}`", d.expr_ty, d.cast_ty), ), @@ -95,6 +95,10 @@ pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast<'_ DiagnosticCode::RustcHardError("E0605"), format_ty!(ctx, "non-primitive cast: `{}` as `{}`", d.expr_ty, d.cast_ty), ), + CastError::PtrPtrAddingAutoTraits => ( + DiagnosticCode::RustcHardError("E0804"), + "cannot add auto trait to dyn bound via pointer cast".to_owned(), + ), // CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => ( // DiagnosticCode::RustcHardError("E0641"), // "cannot cast to a pointer of an unknown kind".to_owned(), @@ -444,8 +448,8 @@ fn main() { q as *const [i32]; //^^^^^^^^^^^^^^^^^ error: cannot cast thin pointer `*const i32` to fat pointer `*const [i32]` - // FIXME: This should emit diagnostics but disabled to prevent many false positives let t: *mut (dyn Trait + 'static) = 0 as *mut _; + //^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*mut (dyn Trait + 'static)` let mut fail: *const str = 0 as *const str; //^^^^^^^^^^^^^^^ error: cannot cast `usize` to a fat pointer `*const str` @@ -543,7 +547,7 @@ fn main() { fn ptr_to_trait_obj_ok() { check_diagnostics( r#" -//- minicore: pointee +//- minicore: pointee, send, sync trait Trait<'a> {} fn remove_auto<'a>(x: *mut (dyn Trait<'a> + Send)) -> *mut dyn Trait<'a> {