Merge pull request #21462 from ShoyuVanilla/cast-fix
fix: Sync cast checks to rustc again
This commit is contained in:
commit
332287d3c7
3 changed files with 201 additions and 89 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T>(&mut self, ty: T) -> T
|
||||
where
|
||||
T: TypeFoldable<DbInterner<'db>> + 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)
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue