Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/infer/cast.rs')
-rw-r--r--crates/hir-ty/src/infer/cast.rs270
1 files changed, 194 insertions, 76 deletions
diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs
index d073b06ccc..d69b00adb7 100644
--- a/crates/hir-ty/src/infer/cast.rs
+++ b/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<dyn Src<...> + SrcAuto> -> B<dyn Dst<...> + 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<DbInterner<'db>> 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()
-}