Auto merge of #17984 - ShoyuVanilla:cast, r=Veykril

feat: Implement cast typecheck and diagnostics

Fixes  #17897 and fixes #16564
Mainly adopted from https://github.com/rust-lang/rust/blob/100fde5246bf56f22fb5cc85374dd841296fce0e/compiler/rustc_hir_typeck/src/cast.rs
This commit is contained in:
bors
2024-09-03 06:00:10 +00:00
25 changed files with 1614 additions and 93 deletions
@@ -14,6 +14,7 @@
use crate::{
builtin_type::{BuiltinInt, BuiltinUint},
db::DefDatabase,
hir::Expr,
item_tree::{
AttrOwner, Field, FieldParent, FieldsShape, ItemTree, ModItem, RawVisibilityId, TreeId,
},
@@ -317,6 +318,27 @@ pub fn variant_body_type(&self) -> IntegerType {
_ => IntegerType::Pointer(true),
}
}
// [Adopted from rustc](https://github.com/rust-lang/rust/blob/bd53aa3bf7a24a70d763182303bd75e5fc51a9af/compiler/rustc_middle/src/ty/adt.rs#L446-L448)
pub fn is_payload_free(&self, db: &dyn DefDatabase) -> bool {
self.variants.iter().all(|(v, _)| {
// The condition check order is slightly modified from rustc
// to improve performance by early returning with relatively fast checks
let variant = &db.enum_variant_data(*v).variant_data;
if !variant.fields().is_empty() {
return false;
}
// The outer if condition is whether this variant has const ctor or not
if !matches!(variant.kind(), StructKind::Unit) {
let body = db.body((*v).into());
// A variant with explicit discriminant
if body.exprs[body.body_expr] != Expr::Missing {
return false;
}
}
true
})
}
}
impl EnumVariantData {
@@ -186,7 +186,13 @@ fn floating_point() {
#[test]
fn casts() {
check_number(r#"const GOAL: usize = 12 as *const i32 as usize"#, 12);
check_number(
r#"
//- minicore: sized
const GOAL: usize = 12 as *const i32 as usize
"#,
12,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
@@ -204,7 +210,7 @@ fn casts() {
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: i16 = {
let a = &mut 5;
let a = &mut 5_i16;
let z = a as *mut _;
unsafe { *z }
};
@@ -244,7 +250,13 @@ struct X {
"#,
4,
);
check_number(r#"const GOAL: i32 = -12i8 as i32"#, -12);
check_number(
r#"
//- minicore: sized
const GOAL: i32 = -12i8 as i32
"#,
-12,
);
}
#[test]
@@ -1911,6 +1923,7 @@ fn add2(x: u8) -> u8 {
);
check_number(
r#"
//- minicore: sized
fn add2(x: u8) -> u8 {
x + 2
}
@@ -2422,6 +2435,7 @@ fn f() -> i32 {
fn extern_weak_statics() {
check_number(
r#"
//- minicore: sized
extern "C" {
#[linkage = "extern_weak"]
static __dso_handle: *mut u8;
@@ -2716,6 +2730,7 @@ impl Tr for i32 {
);
check_number(
r#"
//- minicore: sized
struct S<T>(*mut T);
trait MySized: Sized {
@@ -311,6 +311,7 @@ fn saturating() {
fn allocator() {
check_number(
r#"
//- minicore: sized
extern "Rust" {
#[rustc_allocator]
fn __rust_alloc(size: usize, align: usize) -> *mut u8;
@@ -13,7 +13,7 @@
//! to certain types. To record this, we use the union-find implementation from
//! the `ena` crate, which is extracted from rustc.
mod cast;
pub(crate) mod cast;
pub(crate) mod closure;
mod coerce;
mod expr;
@@ -76,7 +76,7 @@
#[allow(unreachable_pub)]
pub use unify::{could_unify, could_unify_deeply};
use cast::CastCheck;
use cast::{CastCheck, CastError};
pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy};
/// The entry point of type inference.
@@ -254,6 +254,16 @@ pub enum InferenceDiagnostic {
expr: ExprId,
expected: Ty,
},
CastToUnsized {
expr: ExprId,
cast_ty: Ty,
},
InvalidCast {
expr: ExprId,
error: CastError,
expr_ty: Ty,
cast_ty: Ty,
},
}
/// A mismatch between an expected and an inferred type.
@@ -456,6 +466,7 @@ pub struct InferenceResult {
pub(crate) closure_info: FxHashMap<ClosureId, (Vec<CapturedItem>, FnTrait)>,
// FIXME: remove this field
pub mutated_bindings_in_closure: FxHashSet<BindingId>,
pub coercion_casts: FxHashSet<ExprId>,
}
impl InferenceResult {
@@ -666,7 +677,7 @@ pub(crate) fn resolve_all(self) -> InferenceResult {
let InferenceContext {
mut table,
mut result,
deferred_cast_checks,
mut deferred_cast_checks,
tuple_field_accesses_rev,
..
} = self;
@@ -695,6 +706,7 @@ pub(crate) fn resolve_all(self) -> InferenceResult {
closure_info: _,
mutated_bindings_in_closure: _,
tuple_field_access_types: _,
coercion_casts,
} = &mut result;
table.fallback_if_possible();
@@ -702,8 +714,18 @@ pub(crate) fn resolve_all(self) -> InferenceResult {
// Comment from rustc:
// Even though coercion casts provide type hints, we check casts after fallback for
// backwards compatibility. This makes fallback a stronger type hint than a cast coercion.
for cast in deferred_cast_checks {
cast.check(&mut table);
let mut apply_adjustments = |expr, adj| {
expr_adjustments.insert(expr, adj);
};
let mut set_coercion_cast = |expr| {
coercion_casts.insert(expr);
};
for cast in deferred_cast_checks.iter_mut() {
if let Err(diag) =
cast.check(&mut table, &mut apply_adjustments, &mut set_coercion_cast)
{
diagnostics.push(diag);
}
}
// FIXME resolve obligations as well (use Guidance if necessary)
@@ -1,47 +1,412 @@
//! Type cast logic. Basically coercion + additional casts.
use crate::{infer::unify::InferenceTable, Interner, Ty, TyExt, TyKind};
use chalk_ir::{Mutability, Scalar, TyVariableKind, UintTy};
use hir_def::{hir::ExprId, AdtId};
use stdx::never;
use crate::{
infer::unify::InferenceTable, Adjustment, Binders, DynTy, InferenceDiagnostic, Interner,
PlaceholderIndex, QuantifiedWhereClauses, Ty, TyExt, TyKind, TypeFlags, WhereClause,
};
#[derive(Debug)]
pub(crate) enum Int {
I,
U(UintTy),
Bool,
Char,
CEnum,
InferenceVar,
}
#[derive(Debug)]
pub(crate) enum CastTy {
Int(Int),
Float,
FnPtr,
Ptr(Ty, Mutability),
// `DynStar` is Not supported yet in r-a
}
impl CastTy {
pub(crate) fn from_ty(table: &mut InferenceTable<'_>, t: &Ty) -> Option<Self> {
match t.kind(Interner) {
TyKind::Scalar(Scalar::Bool) => Some(Self::Int(Int::Bool)),
TyKind::Scalar(Scalar::Char) => Some(Self::Int(Int::Char)),
TyKind::Scalar(Scalar::Int(_)) => Some(Self::Int(Int::I)),
TyKind::Scalar(Scalar::Uint(it)) => Some(Self::Int(Int::U(*it))),
TyKind::InferenceVar(_, TyVariableKind::Integer) => Some(Self::Int(Int::InferenceVar)),
TyKind::InferenceVar(_, TyVariableKind::Float) => Some(Self::Float),
TyKind::Scalar(Scalar::Float(_)) => Some(Self::Float),
TyKind::Adt(..) => {
let (AdtId::EnumId(id), _) = t.as_adt()? else {
return None;
};
let enum_data = table.db.enum_data(id);
if enum_data.is_payload_free(table.db.upcast()) {
Some(Self::Int(Int::CEnum))
} else {
None
}
}
TyKind::Raw(m, ty) => Some(Self::Ptr(table.resolve_ty_shallow(ty), *m)),
TyKind::Function(_) => Some(Self::FnPtr),
_ => None,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CastError {
Unknown,
CastToBool,
CastToChar,
DifferingKinds,
SizedUnsizedCast,
IllegalCast,
IntToFatCast,
NeedDeref,
NeedViaPtr,
NeedViaThinPtr,
NeedViaInt,
NonScalar,
UnknownCastPtrKind,
UnknownExprPtrKind,
}
impl CastError {
fn into_diagnostic(self, expr: ExprId, expr_ty: Ty, cast_ty: Ty) -> InferenceDiagnostic {
InferenceDiagnostic::InvalidCast { expr, error: self, expr_ty, cast_ty }
}
}
#[derive(Clone, Debug)]
pub(super) struct CastCheck {
expr: ExprId,
source_expr: ExprId,
expr_ty: Ty,
cast_ty: Ty,
}
impl CastCheck {
pub(super) fn new(expr_ty: Ty, cast_ty: Ty) -> Self {
Self { expr_ty, cast_ty }
pub(super) fn new(expr: ExprId, source_expr: ExprId, expr_ty: Ty, cast_ty: Ty) -> Self {
Self { expr, source_expr, expr_ty, cast_ty }
}
pub(super) fn check(self, table: &mut InferenceTable<'_>) {
// FIXME: This function currently only implements the bits that influence the type
// inference. We should return the adjustments on success and report diagnostics on error.
let expr_ty = table.resolve_ty_shallow(&self.expr_ty);
let cast_ty = table.resolve_ty_shallow(&self.cast_ty);
pub(super) fn check<F, G>(
&mut self,
table: &mut InferenceTable<'_>,
apply_adjustments: &mut F,
set_coercion_cast: &mut G,
) -> Result<(), InferenceDiagnostic>
where
F: FnMut(ExprId, Vec<Adjustment>),
G: FnMut(ExprId),
{
table.resolve_obligations_as_possible();
self.expr_ty = table.resolve_ty_shallow(&self.expr_ty);
self.cast_ty = table.resolve_ty_shallow(&self.cast_ty);
if table.coerce(&expr_ty, &cast_ty).is_ok() {
return;
if self.expr_ty.contains_unknown() || self.cast_ty.contains_unknown() {
return Ok(());
}
if check_ref_to_ptr_cast(expr_ty, cast_ty, table) {
// Note that this type of cast is actually split into a coercion to a
// pointer type and a cast:
// &[T; N] -> *[T; N] -> *T
if !self.cast_ty.data(Interner).flags.contains(TypeFlags::HAS_TY_INFER)
&& !table.is_sized(&self.cast_ty)
{
return Err(InferenceDiagnostic::CastToUnsized {
expr: self.expr,
cast_ty: self.cast_ty.clone(),
});
}
// FIXME: Check other kinds of non-coercion casts and report error if any?
if let Ok((adj, _)) = table.coerce(&self.expr_ty, &self.cast_ty) {
apply_adjustments(self.source_expr, adj);
set_coercion_cast(self.source_expr);
return Ok(());
}
self.do_check(table, apply_adjustments)
.map_err(|e| e.into_diagnostic(self.expr, self.expr_ty.clone(), self.cast_ty.clone()))
}
fn do_check<F>(
&self,
table: &mut InferenceTable<'_>,
apply_adjustments: &mut F,
) -> Result<(), CastError>
where
F: FnMut(ExprId, Vec<Adjustment>),
{
let (t_from, t_cast) =
match (CastTy::from_ty(table, &self.expr_ty), CastTy::from_ty(table, &self.cast_ty)) {
(Some(t_from), Some(t_cast)) => (t_from, t_cast),
(None, Some(t_cast)) => match self.expr_ty.kind(Interner) {
TyKind::FnDef(..) => {
let sig = self.expr_ty.callable_sig(table.db).expect("FnDef had no sig");
let sig = table.normalize_associated_types_in(sig);
let fn_ptr = TyKind::Function(sig.to_fn_ptr()).intern(Interner);
if let Ok((adj, _)) = table.coerce(&self.expr_ty, &fn_ptr) {
apply_adjustments(self.source_expr, adj);
} else {
return Err(CastError::IllegalCast);
}
(CastTy::FnPtr, t_cast)
}
TyKind::Ref(mutbl, _, inner_ty) => {
let inner_ty = table.resolve_ty_shallow(inner_ty);
return match t_cast {
CastTy::Int(_) | CastTy::Float => match inner_ty.kind(Interner) {
TyKind::Scalar(
Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_),
)
| TyKind::InferenceVar(
_,
TyVariableKind::Integer | TyVariableKind::Float,
) => Err(CastError::NeedDeref),
_ => Err(CastError::NeedViaPtr),
},
// array-ptr-cast
CastTy::Ptr(t, m) => {
let t = table.resolve_ty_shallow(&t);
if !table.is_sized(&t) {
return Err(CastError::IllegalCast);
}
self.check_ref_cast(
table,
&inner_ty,
*mutbl,
&t,
m,
apply_adjustments,
)
}
_ => Err(CastError::NonScalar),
};
}
_ => return Err(CastError::NonScalar),
},
_ => return Err(CastError::NonScalar),
};
// rustc checks whether the `expr_ty` is foreign adt with `non_exhaustive` sym
match (t_from, t_cast) {
(_, CastTy::Int(Int::CEnum) | CastTy::FnPtr) => Err(CastError::NonScalar),
(_, CastTy::Int(Int::Bool)) => Err(CastError::CastToBool),
(CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()),
(_, CastTy::Int(Int::Char)) => Err(CastError::CastToChar),
(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(table, &src, &dst)
}
(CastTy::Ptr(src, _), CastTy::Int(_)) => self.check_ptr_addr_cast(table, &src),
(CastTy::Int(_), CastTy::Ptr(dst, _)) => self.check_addr_ptr_cast(table, &dst),
(CastTy::FnPtr, CastTy::Ptr(dst, _)) => self.check_fptr_ptr_cast(table, &dst),
(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(()),
}
}
fn check_ref_cast<F>(
&self,
table: &mut InferenceTable<'_>,
t_expr: &Ty,
m_expr: Mutability,
t_cast: &Ty,
m_cast: Mutability,
apply_adjustments: &mut F,
) -> Result<(), CastError>
where
F: FnMut(ExprId, Vec<Adjustment>),
{
// Mutability order is opposite to rustc. `Mut < Not`
if m_expr <= m_cast {
if let TyKind::Array(ety, _) = t_expr.kind(Interner) {
// Coerce to a raw pointer so that we generate RawPtr in MIR.
let array_ptr_type = TyKind::Raw(m_expr, t_expr.clone()).intern(Interner);
if let Ok((adj, _)) = table.coerce(&self.expr_ty, &array_ptr_type) {
apply_adjustments(self.source_expr, adj);
} else {
never!(
"could not cast from reference to array to pointer to array ({:?} to {:?})",
self.expr_ty,
array_ptr_type
);
}
// This is a less strict condition than rustc's `demand_eqtype`,
// but false negative is better than false positive
if table.coerce(ety, t_cast).is_ok() {
return Ok(());
}
}
}
Err(CastError::IllegalCast)
}
fn check_ptr_ptr_cast(
&self,
table: &mut InferenceTable<'_>,
src: &Ty,
dst: &Ty,
) -> Result<(), CastError> {
let src_kind = pointer_kind(src, table).map_err(|_| CastError::Unknown)?;
let dst_kind = pointer_kind(dst, table).map_err(|_| CastError::Unknown)?;
match (src_kind, dst_kind) {
(Some(PointerKind::Error), _) | (_, Some(PointerKind::Error)) => Ok(()),
(_, None) => Err(CastError::UnknownCastPtrKind),
(_, Some(PointerKind::Thin)) => Ok(()),
(None, _) => Err(CastError::UnknownExprPtrKind),
(Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast),
(Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => {
let principal = |tty: &Binders<QuantifiedWhereClauses>| {
tty.skip_binders().as_slice(Interner).first().and_then(|pred| {
if let WhereClause::Implemented(tr) = pred.skip_binders() {
Some(tr.trait_id)
} else {
None
}
})
};
match (principal(&src_tty), principal(&dst_tty)) {
(Some(src_principal), Some(dst_principal)) => {
if src_principal == dst_principal {
return Ok(());
}
let src_principal =
table.db.trait_datum(table.trait_env.krate, src_principal);
let dst_principal =
table.db.trait_datum(table.trait_env.krate, dst_principal);
if src_principal.is_auto_trait() && dst_principal.is_auto_trait() {
Ok(())
} else {
Err(CastError::DifferingKinds)
}
}
_ => Err(CastError::Unknown),
}
}
(Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(()),
(_, _) => Err(CastError::DifferingKinds),
}
}
fn check_ptr_addr_cast(
&self,
table: &mut InferenceTable<'_>,
expr_ty: &Ty,
) -> Result<(), CastError> {
match pointer_kind(expr_ty, table).map_err(|_| CastError::Unknown)? {
None => Err(CastError::UnknownExprPtrKind),
Some(PointerKind::Error) => Ok(()),
Some(PointerKind::Thin) => Ok(()),
_ => Err(CastError::NeedViaThinPtr),
}
}
fn check_addr_ptr_cast(
&self,
table: &mut InferenceTable<'_>,
cast_ty: &Ty,
) -> Result<(), CastError> {
match pointer_kind(cast_ty, table).map_err(|_| CastError::Unknown)? {
None => Err(CastError::UnknownCastPtrKind),
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),
}
}
fn check_fptr_ptr_cast(
&self,
table: &mut InferenceTable<'_>,
cast_ty: &Ty,
) -> Result<(), CastError> {
match pointer_kind(cast_ty, table).map_err(|_| CastError::Unknown)? {
None => Err(CastError::UnknownCastPtrKind),
Some(PointerKind::Error) => Ok(()),
Some(PointerKind::Thin) => Ok(()),
_ => Err(CastError::IllegalCast),
}
}
}
fn check_ref_to_ptr_cast(expr_ty: Ty, cast_ty: Ty, table: &mut InferenceTable<'_>) -> bool {
let Some((expr_inner_ty, _, _)) = expr_ty.as_reference() else {
return false;
};
let Some((cast_inner_ty, _)) = cast_ty.as_raw_ptr() else {
return false;
};
let TyKind::Array(expr_elt_ty, _) = expr_inner_ty.kind(Interner) else {
return false;
};
table.coerce(expr_elt_ty, cast_inner_ty).is_ok()
#[derive(PartialEq, Eq)]
enum PointerKind {
// thin pointer
Thin,
// trait object
VTable(Binders<QuantifiedWhereClauses>),
// slice
Length,
OfAlias,
OfParam(PlaceholderIndex),
Error,
}
fn pointer_kind(ty: &Ty, table: &mut InferenceTable<'_>) -> Result<Option<PointerKind>, ()> {
let ty = table.resolve_ty_shallow(ty);
if table.is_sized(&ty) {
return Ok(Some(PointerKind::Thin));
}
match ty.kind(Interner) {
TyKind::Slice(_) | TyKind::Str => Ok(Some(PointerKind::Length)),
TyKind::Dyn(DynTy { bounds, .. }) => Ok(Some(PointerKind::VTable(bounds.clone()))),
TyKind::Adt(chalk_ir::AdtId(id), subst) => {
let AdtId::StructId(id) = *id else {
never!("`{:?}` should be sized but is not?", ty);
return Err(());
};
let struct_data = table.db.struct_data(id);
if let Some((last_field, _)) = struct_data.variant_data.fields().iter().last() {
let last_field_ty =
table.db.field_types(id.into())[last_field].clone().substitute(Interner, subst);
pointer_kind(&last_field_ty, table)
} else {
Ok(Some(PointerKind::Thin))
}
}
TyKind::Tuple(_, subst) => {
match subst.iter(Interner).last().and_then(|arg| arg.ty(Interner)) {
None => Ok(Some(PointerKind::Thin)),
Some(ty) => pointer_kind(ty, table),
}
}
TyKind::Foreign(_) => Ok(Some(PointerKind::Thin)),
TyKind::Alias(_) | TyKind::AssociatedType(..) | TyKind::OpaqueType(..) => {
Ok(Some(PointerKind::OfAlias))
}
TyKind::Error => Ok(Some(PointerKind::Error)),
TyKind::Placeholder(idx) => Ok(Some(PointerKind::OfParam(*idx))),
TyKind::BoundVar(_) | TyKind::InferenceVar(..) => Ok(None),
TyKind::Scalar(_)
| TyKind::Array(..)
| TyKind::CoroutineWitness(..)
| TyKind::Raw(..)
| TyKind::Ref(..)
| TyKind::FnDef(..)
| TyKind::Function(_)
| TyKind::Closure(..)
| TyKind::Coroutine(..)
| TyKind::Never => {
never!("`{:?}` should be sized but is not?", ty);
Err(())
}
}
}
@@ -610,7 +610,12 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
Expr::Cast { expr, type_ref } => {
let cast_ty = self.make_ty(type_ref);
let expr_ty = self.infer_expr(*expr, &Expectation::Castable(cast_ty.clone()));
self.deferred_cast_checks.push(CastCheck::new(expr_ty, cast_ty.clone()));
self.deferred_cast_checks.push(CastCheck::new(
tgt_expr,
*expr,
expr_ty,
cast_ty.clone(),
));
cast_ty
}
Expr::Ref { expr, rawness, mutability } => {
@@ -9,6 +9,7 @@
use chalk_solve::infer::ParameterEnaVariableExt;
use either::Either;
use ena::unify::UnifyKey;
use hir_def::{lang_item::LangItem, AdtId};
use hir_expand::name::Name;
use intern::sym;
use rustc_hash::FxHashMap;
@@ -21,7 +22,7 @@
to_chalk_trait_id, traits::FnTrait, AliasEq, AliasTy, BoundVar, Canonical, Const, ConstValue,
DebruijnIndex, DomainGoal, GenericArg, GenericArgData, Goal, GoalData, Guidance, InEnvironment,
InferenceVar, Interner, Lifetime, OpaqueTyId, ParamKind, ProjectionTy, ProjectionTyExt, Scalar,
Solution, Substitution, TraitEnvironment, Ty, TyBuilder, TyExt, TyKind, VariableKind,
Solution, Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind, VariableKind,
WhereClause,
};
@@ -898,6 +899,33 @@ pub(super) fn insert_const_vars_shallow(&mut self, c: Const) -> Const {
_ => c,
}
}
/// Check if given type is `Sized` or not
pub(crate) fn is_sized(&mut self, ty: &Ty) -> bool {
if let Some((AdtId::StructId(id), subst)) = ty.as_adt() {
let struct_data = self.db.struct_data(id);
if let Some((last_field, _)) = struct_data.variant_data.fields().iter().last() {
let last_field_ty =
self.db.field_types(id.into())[last_field].clone().substitute(Interner, subst);
// Structs can have DST as its last field and such cases are not handled
// as unsized by the chalk, so we do this manually
return self.is_sized(&last_field_ty);
}
}
let Some(sized) = self
.db
.lang_item(self.trait_env.krate, LangItem::Sized)
.and_then(|sized| sized.as_trait())
else {
return false;
};
let sized_pred = WhereClause::Implemented(TraitRef {
trait_id: to_chalk_trait_id(sized),
substitution: Substitution::from1(Interner, ty.clone()),
});
let goal = GoalData::DomainGoal(chalk_ir::DomainGoal::Holds(sized_pred)).intern(Interner);
matches!(self.try_obligation(goal), Some(Solution::Unique(_)))
}
}
impl fmt::Debug for InferenceTable<'_> {
@@ -83,6 +83,7 @@
pub use builder::{ParamKind, TyBuilder};
pub use chalk_ext::*;
pub use infer::{
cast::CastError,
closure::{CaptureKind, CapturedItem},
could_coerce, could_unify, could_unify_deeply, Adjust, Adjustment, AutoBorrow, BindingMode,
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
@@ -837,7 +837,9 @@ pub enum CastKind {
PointerFromExposedAddress,
/// All sorts of pointer-to-pointer casts. Note that reference-to-raw-ptr casts are
/// translated into `&raw mut/const *r`, i.e., they are not actually casts.
Pointer(PointerCast),
PtrToPtr,
/// Pointer related casts that are done by coercions.
PointerCoercion(PointerCast),
/// Cast into a dyn* object.
DynStar,
IntToInt,
@@ -1475,7 +1475,7 @@ fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals) -> Result<IntervalOrO
}
}
Rvalue::Cast(kind, operand, target_ty) => match kind {
CastKind::Pointer(cast) => match cast {
CastKind::PointerCoercion(cast) => match cast {
PointerCast::ReifyFnPointer | PointerCast::ClosureFnPointer(_) => {
let current_ty = self.operand_ty(operand, locals)?;
if let TyKind::FnDef(_, _) | TyKind::Closure(_, _) =
@@ -1506,6 +1506,7 @@ fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals) -> Result<IntervalOrO
},
CastKind::DynStar => not_supported!("dyn star cast"),
CastKind::IntToInt
| CastKind::PtrToPtr
| CastKind::PointerExposeAddress
| CastKind::PointerFromExposedAddress => {
let current_ty = self.operand_ty(operand, locals)?;
@@ -399,7 +399,7 @@ fn should_not_reach() -> bool {
fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32;
}
fn my_cmp(x: &[u8], y: &[u8]) -> i32 {
fn my_cmp(x: &[u8; 3], y: &[u8; 3]) -> i32 {
memcmp(x as *const u8, y as *const u8, x.len())
}
@@ -779,6 +779,7 @@ fn main() {
fn posix_getenv() {
check_pass(
r#"
//- minicore: sized
//- /main.rs env:foo=bar
type c_char = u8;
@@ -31,7 +31,7 @@
display::HirDisplay,
error_lifetime,
generics::generics,
infer::{CaptureKind, CapturedItem, TypeMismatch},
infer::{cast::CastTy, unify::InferenceTable, CaptureKind, CapturedItem, TypeMismatch},
inhabitedness::is_ty_uninhabited_from,
layout::LayoutError,
mapping::ToChalk,
@@ -362,7 +362,7 @@ fn lower_expr_to_place_with_adjust(
current,
place,
Rvalue::Cast(
CastKind::Pointer(*cast),
CastKind::PointerCoercion(*cast),
Operand::Copy(p),
last.target.clone(),
),
@@ -898,14 +898,26 @@ fn lower_expr_to_place_without_adjust(
let Some((it, current)) = self.lower_expr_to_some_operand(*expr, current)? else {
return Ok(None);
};
let source_ty = self.infer[*expr].clone();
let target_ty = self.infer[expr_id].clone();
self.push_assignment(
current,
place,
Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, it, target_ty),
expr_id.into(),
);
// Since we don't have THIR, this is the "zipped" version of [rustc's HIR lowering](https://github.com/rust-lang/rust/blob/e71f9529121ca8f687e4b725e3c9adc3f1ebab4d/compiler/rustc_mir_build/src/thir/cx/expr.rs#L165-L178)
// and [THIR lowering as RValue](https://github.com/rust-lang/rust/blob/a4601859ae3875732797873612d424976d9e3dd0/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs#L193-L313)
let rvalue = if self.infer.coercion_casts.contains(expr) {
Rvalue::Use(it)
} else {
let source_ty = self.infer[*expr].clone();
let target_ty = self.infer[expr_id].clone();
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),
);
cast_kind(&mut table, &source_ty, &target_ty)?
};
Rvalue::Cast(cast_kind, it, target_ty)
};
self.push_assignment(current, place, rvalue, expr_id.into());
Ok(Some(current))
}
Expr::Ref { expr, rawness: _, mutability } => {
@@ -2005,40 +2017,21 @@ fn emit_drop_and_storage_dead_for_scope(
}
}
fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> {
Ok(match (source_ty.kind(Interner), target_ty.kind(Interner)) {
(TyKind::FnDef(..), TyKind::Function(_)) => CastKind::Pointer(PointerCast::ReifyFnPointer),
(TyKind::Scalar(s), TyKind::Scalar(t)) => match (s, t) {
(chalk_ir::Scalar::Float(_), chalk_ir::Scalar::Float(_)) => CastKind::FloatToFloat,
(chalk_ir::Scalar::Float(_), _) => CastKind::FloatToInt,
(_, chalk_ir::Scalar::Float(_)) => CastKind::IntToFloat,
(_, _) => CastKind::IntToInt,
},
(TyKind::Scalar(_), TyKind::Raw(..)) => CastKind::PointerFromExposedAddress,
(TyKind::Raw(..), TyKind::Scalar(_)) => CastKind::PointerExposeAddress,
(TyKind::Raw(_, a) | TyKind::Ref(_, _, a), TyKind::Raw(_, b) | TyKind::Ref(_, _, b)) => {
CastKind::Pointer(if a == b {
PointerCast::MutToConstPointer
} else if matches!(b.kind(Interner), TyKind::Slice(_))
&& matches!(a.kind(Interner), TyKind::Array(_, _))
|| matches!(b.kind(Interner), TyKind::Dyn(_))
{
PointerCast::Unsize
} else if matches!(a.kind(Interner), TyKind::Slice(s) if s == b) {
PointerCast::ArrayToPointer
} else {
// cast between two sized pointer, like *const i32 to *const i8, or two unsized pointer, like
// slice to slice, slice to str, ... . These are no-ops (even in the unsized case, no metadata
// will be touched) but there is no specific variant
// for it in `PointerCast` so we use `MutToConstPointer`
PointerCast::MutToConstPointer
})
fn cast_kind(table: &mut InferenceTable<'_>, source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> {
let from = CastTy::from_ty(table, source_ty);
let cast = CastTy::from_ty(table, target_ty);
Ok(match (from, cast) {
(Some(CastTy::Ptr(..) | CastTy::FnPtr), Some(CastTy::Int(_))) => {
CastKind::PointerExposeAddress
}
// Enum to int casts
(TyKind::Scalar(_), TyKind::Adt(..)) | (TyKind::Adt(..), TyKind::Scalar(_)) => {
CastKind::IntToInt
}
(a, b) => not_supported!("Unknown cast between {a:?} and {b:?}"),
(Some(CastTy::Int(_)), Some(CastTy::Ptr(..))) => CastKind::PointerFromExposedAddress,
(Some(CastTy::Int(_)), Some(CastTy::Int(_))) => CastKind::IntToInt,
(Some(CastTy::FnPtr), Some(CastTy::Ptr(..))) => CastKind::FnPtrToPtr,
(Some(CastTy::Float), Some(CastTy::Int(_))) => CastKind::FloatToInt,
(Some(CastTy::Int(_)), Some(CastTy::Float)) => CastKind::IntToFloat,
(Some(CastTy::Float), Some(CastTy::Float)) => CastKind::FloatToFloat,
(Some(CastTy::Ptr(..)), Some(CastTy::Ptr(..))) => CastKind::PtrToPtr,
_ => not_supported!("Unknown cast between {source_ty:?} and {target_ty:?}"),
})
}
@@ -1286,6 +1286,7 @@ fn main() {
fn method_on_dyn_impl() {
check_types(
r#"
//- minicore: coerce_unsized
trait Foo {}
impl Foo for u32 {}
@@ -1907,6 +1907,7 @@ fn dont_unify_on_casts() {
// #15246
check_types(
r#"
//- minicore: sized
fn unify(_: [bool; 1]) {}
fn casted(_: *const bool) {}
fn default<T>() -> T { loop {} }
@@ -1926,6 +1927,7 @@ fn test() {
fn rustc_test_issue_52437() {
check_types(
r#"
//- minicore: sized
fn main() {
let x = [(); &(&'static: loop { |x| {}; }) as *const _ as usize]
//^ [(); _]
@@ -3572,6 +3572,7 @@ fn f<T>(t: Ark<T>) {
fn ref_to_array_to_ptr_cast() {
check_types(
r#"
//- minicore: sized
fn default<T>() -> T { loop {} }
fn foo() {
let arr = [default()];
@@ -4,7 +4,9 @@
//! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves.
pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
use hir_ty::{db::HirDatabase, diagnostics::BodyValidationDiagnostic, InferenceDiagnostic};
use hir_ty::{
db::HirDatabase, diagnostics::BodyValidationDiagnostic, CastError, InferenceDiagnostic,
};
use cfg::{CfgExpr, CfgOptions};
use either::Either;
@@ -50,10 +52,12 @@ fn from(d: $diag) -> AnyDiagnostic {
diagnostics![
AwaitOutsideOfAsync,
BreakOutsideOfLoop,
CastToUnsized,
ExpectedFunction,
InactiveCode,
IncoherentImpl,
IncorrectCase,
InvalidCast,
InvalidDeriveTarget,
MacroDefError,
MacroError,
@@ -364,6 +368,20 @@ pub struct RemoveUnnecessaryElse {
pub if_expr: InFile<AstPtr<ast::IfExpr>>,
}
#[derive(Debug)]
pub struct CastToUnsized {
pub expr: InFile<AstPtr<ast::Expr>>,
pub cast_ty: Type,
}
#[derive(Debug)]
pub struct InvalidCast {
pub expr: InFile<AstPtr<ast::Expr>>,
pub error: CastError,
pub expr_ty: Type,
pub cast_ty: Type,
}
impl AnyDiagnostic {
pub(crate) fn body_validation_diagnostic(
db: &dyn HirDatabase,
@@ -620,6 +638,16 @@ pub(crate) fn inference_diagnostic(
};
MismatchedTupleStructPatArgCount { expr_or_pat, expected, found }.into()
}
InferenceDiagnostic::CastToUnsized { expr, cast_ty } => {
let expr = expr_syntax(*expr)?;
CastToUnsized { expr, cast_ty: Type::new(db, def, cast_ty.clone()) }.into()
}
InferenceDiagnostic::InvalidCast { expr, error, expr_ty, cast_ty } => {
let expr = expr_syntax(*expr)?;
let expr_ty = Type::new(db, def, expr_ty.clone());
let cast_ty = Type::new(db, def, cast_ty.clone());
InvalidCast { expr, error: *error, expr_ty, cast_ty }.into()
}
})
}
}
@@ -147,7 +147,7 @@
layout::LayoutError,
mir::{MirEvalError, MirLowerError},
object_safety::{MethodViolationCode, ObjectSafetyViolation},
FnAbi, PointerCast, Safety,
CastError, FnAbi, PointerCast, Safety,
},
// FIXME: Properly encapsulate mir
hir_ty::{mir, Interner as ChalkTyInterner},
@@ -1020,6 +1020,7 @@ fn inline_emits_type_for_coercion() {
check_assist(
inline_call,
r#"
//- minicore: sized
fn foo(x: *const u32) -> u32 {
x as u32
}
@@ -333,7 +333,8 @@ fn test_inline_let_bind_cast_expr() {
check_assist(
inline_local_variable,
r"
fn bar(a: usize): usize { a }
//- minicore: sized
fn bar(a: usize) -> usize { a }
fn foo() {
let a$0 = bar(1) as u64;
a + 1;
@@ -347,7 +348,7 @@ fn foo() {
bar(a);
}",
r"
fn bar(a: usize): usize { a }
fn bar(a: usize) -> usize { a }
fn foo() {
(bar(1) as u64) + 1;
if (bar(1) as u64) > 10 {
@@ -0,0 +1,1007 @@
use hir::{CastError, ClosureStyle, HirDisplay};
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
macro_rules! format_ty {
($ctx:expr, $fmt:literal, $($arg:expr),* $(,)?) => {{
std::format!(
$fmt,
$(
$arg
.display($ctx.sema.db, $ctx.edition)
.with_closure_style(ClosureStyle::ClosureWithId)
),*
)
}}
}
// Diagnostic: invalid-cast
//
// This diagnostic is triggered if the code contains an illegal cast
pub(crate) fn invalid_cast(ctx: &DiagnosticsContext<'_>, d: &hir::InvalidCast) -> Diagnostic {
let display_range = ctx.sema.diagnostics_display_range(d.expr.map(|it| it.into()));
let (code, message) = match d.error {
CastError::CastToBool => (
DiagnosticCode::RustcHardError("E0054"),
format_ty!(ctx, "cannot cast `{}` as `bool`", d.expr_ty),
),
CastError::CastToChar => (
DiagnosticCode::RustcHardError("E0604"),
format_ty!(ctx, "only `u8` can be cast as `char`, not {}", d.expr_ty),
),
CastError::DifferingKinds => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(
ctx,
"casting `{}` as `{}` is invalid: vtable kinds may not match",
d.expr_ty,
d.cast_ty
),
),
CastError::SizedUnsizedCast => (
DiagnosticCode::RustcHardError("E0607"),
format_ty!(
ctx,
"cannot cast thin pointer `{}` to fat pointer `{}`",
d.expr_ty,
d.cast_ty
),
),
CastError::Unknown | CastError::IllegalCast => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(ctx, "casting `{}` as `{}` is invalid", d.expr_ty, d.cast_ty),
),
CastError::IntToFatCast => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(ctx, "cannot cast `{}` to a fat pointer `{}`", d.expr_ty, d.cast_ty),
),
CastError::NeedDeref => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(
ctx,
"casting `{}` as `{}` is invalid: needs defererence or removal of unneeded borrow",
d.expr_ty,
d.cast_ty
),
),
CastError::NeedViaPtr => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(
ctx,
"casting `{}` as `{}` is invalid: needs casting through a raw pointer first",
d.expr_ty,
d.cast_ty
),
),
CastError::NeedViaThinPtr => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(
ctx,
"casting `{}` as `{}` is invalid: needs casting through a thin pointer first",
d.expr_ty,
d.cast_ty
),
),
CastError::NeedViaInt => (
DiagnosticCode::RustcHardError("E0606"),
format_ty!(
ctx,
"casting `{}` as `{}` is invalid: needs casting through an integer first",
d.expr_ty,
d.cast_ty
),
),
CastError::NonScalar => (
DiagnosticCode::RustcHardError("E0605"),
format_ty!(ctx, "non-primitive cast: `{}` as `{}`", d.expr_ty, d.cast_ty),
),
CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => (
DiagnosticCode::RustcHardError("E0641"),
"cannot cast to a pointer of an unknown kind".to_owned(),
),
};
Diagnostic::new(code, message, display_range)
}
// Diagnostic: cast-to-unsized
//
// This diagnostic is triggered when casting to an unsized type
pub(crate) fn cast_to_unsized(ctx: &DiagnosticsContext<'_>, d: &hir::CastToUnsized) -> Diagnostic {
let display_range = ctx.sema.diagnostics_display_range(d.expr.map(|it| it.into()));
Diagnostic::new(
DiagnosticCode::RustcHardError("E0620"),
format_ty!(ctx, "cast to unsized type: `{}`", d.cast_ty),
display_range,
)
}
#[cfg(test)]
mod tests {
use crate::tests::{check_diagnostics, check_diagnostics_with_disabled};
#[test]
fn cast_as_bool() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let u = 5 as bool;
//^^^^^^^^^ error: cannot cast `i32` as `bool`
let t = (1 + 2) as bool;
//^^^^^^^^^^^^^^^ error: cannot cast `i32` as `bool`
let _ = 5_u32 as bool;
//^^^^^^^^^^^^^ error: cannot cast `u32` as `bool`
let _ = 64.0_f64 as bool;
//^^^^^^^^^^^^^^^^ error: cannot cast `f64` as `bool`
enum IntEnum {
Zero,
One,
Two
}
let _ = IntEnum::One as bool;
//^^^^^^^^^^^^^^^^^^^^ error: cannot cast `IntEnum` as `bool`
fn uwu(_: u8) -> i32 {
5
}
unsafe fn owo() {}
let _ = uwu as bool;
//^^^^^^^^^^^ error: cannot cast `fn uwu(u8) -> i32` as `bool`
let _ = owo as bool;
//^^^^^^^^^^^ error: cannot cast `unsafe fn owo()` as `bool`
let _ = uwu as fn(u8) -> i32 as bool;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: cannot cast `fn(u8) -> i32` as `bool`
let _ = 'x' as bool;
//^^^^^^^^^^^ error: cannot cast `char` as `bool`
let ptr = 1 as *const ();
let _ = ptr as bool;
//^^^^^^^^^^^ error: cannot cast `*const ()` as `bool`
let v = "hello" as bool;
//^^^^^^^^^^^^^^^ error: casting `&str` as `bool` is invalid: needs casting through a raw pointer first
}
"#,
);
}
#[test]
fn cast_pointee_projection() {
check_diagnostics(
r#"
//- minicore: sized
trait Tag<'a> {
type Type: ?Sized;
}
trait IntoRaw: for<'a> Tag<'a> {
fn into_raw(this: *const <Self as Tag<'_>>::Type) -> *mut <Self as Tag<'_>>::Type;
}
impl<T: for<'a> Tag<'a>> IntoRaw for T {
fn into_raw(this: *const <Self as Tag<'_>>::Type) -> *mut <Self as Tag<'_>>::Type {
this as *mut T::Type
}
}
fn main() {}
"#,
);
}
#[test]
fn cast_region_to_int() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let x: isize = 3;
let _ = &x as *const isize as usize;
}
"#,
);
}
#[test]
fn cast_to_bare_fn() {
check_diagnostics(
r#"
//- minicore: sized
fn foo(_x: isize) { }
fn main() {
let v: u64 = 5;
let x = foo as extern "C" fn() -> isize;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `fn foo(isize)` as `fn() -> isize`
let y = v as extern "Rust" fn(isize) -> (isize, isize);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `u64` as `fn(isize) -> (isize, isize)`
y(x());
}
"#,
);
}
#[test]
fn cast_to_unit() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let _ = 0u32 as ();
//^^^^^^^^^^ error: non-primitive cast: `u32` as `()`
}
"#,
);
}
#[test]
fn cast_to_slice() {
check_diagnostics_with_disabled(
r#"
//- minicore: sized
fn as_bytes(_: &str) -> &[u8] {
loop {}
}
fn main() {
as_bytes("example") as [char];
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: cast to unsized type: `[char]`
let arr: &[u8] = &[0, 2, 3];
arr as [char];
//^^^^^^^^^^^^^ error: cast to unsized type: `[char]`
}
"#,
&["E0308"],
);
}
#[test]
fn cast() {
check_diagnostics(
r#"
//- minicore: sized
fn null_mut<T: ?Sized>() -> *mut T {
loop {}
}
pub fn main() {
let i: isize = 'Q' as isize;
let _u: u32 = i as u32;
// Test that `_` is correctly inferred.
let x = &"hello";
let mut y = x as *const _;
y = null_mut();
}
"#,
);
}
#[test]
fn dyn_tail_need_normalization() {
check_diagnostics(
r#"
//- minicore: dispatch_from_dyn
trait Trait {
type Associated;
}
impl Trait for i32 {
type Associated = i64;
}
trait Generic<T> {}
type TraitObject = dyn Generic<<i32 as Trait>::Associated>;
struct Wrap(TraitObject);
fn cast(x: *mut TraitObject) {
x as *mut Wrap;
}
"#,
);
}
#[test]
fn enum_to_numeric_cast() {
check_diagnostics(
r#"
//- minicore: sized
pub enum UnitOnly {
Foo,
Bar,
Baz,
}
pub enum Fieldless {
Tuple(),
Struct{},
Unit,
}
pub enum NotUnitOnlyOrFieldless {
Foo,
Bar(u8),
Baz
}
fn main() {
let unit_only = UnitOnly::Foo;
let _ = unit_only as isize;
let _ = unit_only as i32;
let _ = unit_only as usize;
let _ = unit_only as u32;
let fieldless = Fieldless::Struct{};
let _ = fieldless as isize;
let _ = fieldless as i32;
let _ = fieldless as usize;
let _ = fieldless as u32;
let not_unit_only_or_fieldless = NotUnitOnlyOrFieldless::Foo;
let _ = not_unit_only_or_fieldless as isize;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `NotUnitOnlyOrFieldless` as `isize`
let _ = not_unit_only_or_fieldless as i32;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `NotUnitOnlyOrFieldless` as `i32`
let _ = not_unit_only_or_fieldless as usize;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `NotUnitOnlyOrFieldless` as `usize`
let _ = not_unit_only_or_fieldless as u32;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `NotUnitOnlyOrFieldless` as `u32`
}
"#,
);
}
#[test]
fn fat_ptr_cast() {
check_diagnostics_with_disabled(
r#"
//- minicore: sized
trait Foo {
fn foo(&self) {} //~ WARN method `foo` is never used
}
struct Bar;
impl Foo for Bar {}
fn to_raw<T>(_: *mut T) -> *mut () {
loop {}
}
fn main() {
// Test we can turn a fat pointer to array back into a thin pointer.
let a: *const [i32] = &[1, 2, 3];
let b = a as *const [i32; 2];
// Test conversion to an address (usize).
let a: *const [i32; 3] = &[1, 2, 3];
let b: *const [i32] = a;
// And conversion to a void pointer/address for trait objects too.
let a: *mut dyn Foo = &mut Bar;
let b = a as *mut () as usize;
let c = a as *const () as usize;
let d = to_raw(a) as usize;
}
"#,
&["E0308"],
);
check_diagnostics_with_disabled(
r#"
//- minicore: sized
trait Trait {}
struct Box<T: ?Sized>;
impl<T: ?Sized> Box<T> {
fn new(_: T) -> Self {
loop {}
}
}
fn as_ptr(_: &[i32]) -> *const i32 {
loop {}
}
fn main() {
let a: &[i32] = &[1, 2, 3];
let b: Box<[i32]> = Box::new([1, 2, 3]);
let p = a as *const [i32];
let q = as_ptr(a);
a as usize;
//^^^^^^^^^^ error: casting `&[i32]` as `usize` is invalid: needs casting through a raw pointer first
a as isize;
//^^^^^^^^^^ error: casting `&[i32]` as `isize` is invalid: needs casting through a raw pointer first
a as i16;
//^^^^^^^^ error: casting `&[i32]` as `i16` is invalid: needs casting through a raw pointer first
a as u32;
//^^^^^^^^ error: casting `&[i32]` as `u32` is invalid: needs casting through a raw pointer first
b as usize;
//^^^^^^^^^^ error: non-primitive cast: `Box<[i32]>` as `usize`
p as usize;
//^^^^^^^^^^ error: casting `*const [i32]` as `usize` is invalid: needs casting through a thin pointer first
q as *const [i32];
//^^^^^^^^^^^^^^^^^ error: cannot cast thin pointer `*const i32` to fat pointer `*const [i32]`
let t: *mut (dyn Trait + 'static) = 0 as *mut _;
//^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*mut _`
let mut fail: *const str = 0 as *const str;
//^^^^^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*const str`
let mut fail2: *const str = 0isize as *const str;
//^^^^^^^^^^^^^^^^^^^^ error: cannot cast `isize` to a fat pointer `*const str`
}
fn foo<T: ?Sized>() {
let s = 0 as *const T;
//^^^^^^^^^^^^^ error: cannot cast `i32` to a fat pointer `*const T`
}
"#,
&["E0308", "unused_variables"],
);
}
#[test]
fn order_dependent_cast_inference() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let x = &"hello";
let mut y = 0 as *const _;
//^^^^^^^^^^^^^ error: cannot cast to a pointer of an unknown kind
y = x as *const _;
}
"#,
);
}
#[test]
fn ptr_to_ptr_different_regions() {
check_diagnostics(
r#"
//- minicore: sized
struct Foo<'a> { a: &'a () }
fn extend_lifetime_very_very_safely<'a>(v: *const Foo<'a>) -> *const Foo<'static> {
// This should pass because raw pointer casts can do anything they want.
v as *const Foo<'static>
}
trait Trait {}
fn assert_static<'a>(ptr: *mut (dyn Trait + 'a)) -> *mut (dyn Trait + 'static) {
ptr as _
}
fn main() {
let unit = ();
let foo = Foo { a: &unit };
let _long: *const Foo<'static> = extend_lifetime_very_very_safely(&foo);
}
"#,
);
}
#[test]
fn ptr_to_trait_obj_add_auto() {
check_diagnostics(
r#"
//- minicore: pointee
trait Trait<'a> {}
fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) {
x as _
}
// (to test diagnostic list formatting)
fn add_multiple_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send + Sync + Unpin) {
x as _
}
"#,
);
}
#[test]
fn ptr_to_trait_obj_add_super_auto() {
check_diagnostics(
r#"
//- minicore: pointee
trait Trait: Send {}
impl Trait for () {}
fn main() {
// This is OK: `Trait` has `Send` super trait.
&() as *const dyn Trait as *const (dyn Trait + Send);
}
"#,
);
}
#[test]
fn ptr_to_trait_obj_ok() {
check_diagnostics(
r#"
//- minicore: pointee
trait Trait<'a> {}
fn remove_auto<'a>(x: *mut (dyn Trait<'a> + Send)) -> *mut dyn Trait<'a> {
x as _
}
fn cast_inherent_lt<'a, 'b>(x: *mut (dyn Trait<'static> + 'a)) -> *mut (dyn Trait<'static> + 'b) {
x as _
}
fn unprincipled<'a, 'b>(x: *mut (dyn Send + 'a)) -> *mut (dyn Sync + 'b) {
x as _
}
"#,
);
}
#[test]
fn ptr_to_trait_obj_wrap_upcast() {
check_diagnostics(
r#"
//- minicore: sized
trait Super {}
trait Sub: Super {}
struct Wrapper<T: ?Sized>(T);
// This cast should not compile.
// Upcasting can't work here, because we are also changing the type (`Wrapper`),
// and reinterpreting would be confusing/surprising.
// See <https://github.com/rust-lang/rust/pull/120248#discussion_r1487739518>
fn cast(ptr: *const dyn Sub) -> *const Wrapper<dyn Super> {
ptr as _
//^^^^^^^^ error: casting `*const dyn Sub` as `*const Wrapper<dyn Super>` is invalid: vtable kinds may not match
}
"#,
);
}
#[test]
fn supported_cast() {
check_diagnostics(
r#"
//- minicore: sized
pub fn main() {
struct String;
let f = 1_usize as *const String;
let _ = f as isize;
let _ = f as usize;
let _ = f as i8;
let _ = f as i16;
let _ = f as i32;
let _ = f as i64;
let _ = f as u8;
let _ = f as u16;
let _ = f as u32;
let _ = f as u64;
let _ = 1 as isize;
let _ = 1 as usize;
let _ = 1 as *const String;
let _ = 1 as i8;
let _ = 1 as i16;
let _ = 1 as i32;
let _ = 1 as i64;
let _ = 1 as u8;
let _ = 1 as u16;
let _ = 1 as u32;
let _ = 1 as u64;
let _ = 1 as f32;
let _ = 1 as f64;
let _ = 1_usize as isize;
let _ = 1_usize as usize;
let _ = 1_usize as *const String;
let _ = 1_usize as i8;
let _ = 1_usize as i16;
let _ = 1_usize as i32;
let _ = 1_usize as i64;
let _ = 1_usize as u8;
let _ = 1_usize as u16;
let _ = 1_usize as u32;
let _ = 1_usize as u64;
let _ = 1_usize as f32;
let _ = 1_usize as f64;
let _ = 1i8 as isize;
let _ = 1i8 as usize;
let _ = 1i8 as *const String;
let _ = 1i8 as i8;
let _ = 1i8 as i16;
let _ = 1i8 as i32;
let _ = 1i8 as i64;
let _ = 1i8 as u8;
let _ = 1i8 as u16;
let _ = 1i8 as u32;
let _ = 1i8 as u64;
let _ = 1i8 as f32;
let _ = 1i8 as f64;
let _ = 1u8 as isize;
let _ = 1u8 as usize;
let _ = 1u8 as *const String;
let _ = 1u8 as i8;
let _ = 1u8 as i16;
let _ = 1u8 as i32;
let _ = 1u8 as i64;
let _ = 1u8 as u8;
let _ = 1u8 as u16;
let _ = 1u8 as u32;
let _ = 1u8 as u64;
let _ = 1u8 as f32;
let _ = 1u8 as f64;
let _ = 1i16 as isize;
let _ = 1i16 as usize;
let _ = 1i16 as *const String;
let _ = 1i16 as i8;
let _ = 1i16 as i16;
let _ = 1i16 as i32;
let _ = 1i16 as i64;
let _ = 1i16 as u8;
let _ = 1i16 as u16;
let _ = 1i16 as u32;
let _ = 1i16 as u64;
let _ = 1i16 as f32;
let _ = 1i16 as f64;
let _ = 1u16 as isize;
let _ = 1u16 as usize;
let _ = 1u16 as *const String;
let _ = 1u16 as i8;
let _ = 1u16 as i16;
let _ = 1u16 as i32;
let _ = 1u16 as i64;
let _ = 1u16 as u8;
let _ = 1u16 as u16;
let _ = 1u16 as u32;
let _ = 1u16 as u64;
let _ = 1u16 as f32;
let _ = 1u16 as f64;
let _ = 1i32 as isize;
let _ = 1i32 as usize;
let _ = 1i32 as *const String;
let _ = 1i32 as i8;
let _ = 1i32 as i16;
let _ = 1i32 as i32;
let _ = 1i32 as i64;
let _ = 1i32 as u8;
let _ = 1i32 as u16;
let _ = 1i32 as u32;
let _ = 1i32 as u64;
let _ = 1i32 as f32;
let _ = 1i32 as f64;
let _ = 1u32 as isize;
let _ = 1u32 as usize;
let _ = 1u32 as *const String;
let _ = 1u32 as i8;
let _ = 1u32 as i16;
let _ = 1u32 as i32;
let _ = 1u32 as i64;
let _ = 1u32 as u8;
let _ = 1u32 as u16;
let _ = 1u32 as u32;
let _ = 1u32 as u64;
let _ = 1u32 as f32;
let _ = 1u32 as f64;
let _ = 1i64 as isize;
let _ = 1i64 as usize;
let _ = 1i64 as *const String;
let _ = 1i64 as i8;
let _ = 1i64 as i16;
let _ = 1i64 as i32;
let _ = 1i64 as i64;
let _ = 1i64 as u8;
let _ = 1i64 as u16;
let _ = 1i64 as u32;
let _ = 1i64 as u64;
let _ = 1i64 as f32;
let _ = 1i64 as f64;
let _ = 1u64 as isize;
let _ = 1u64 as usize;
let _ = 1u64 as *const String;
let _ = 1u64 as i8;
let _ = 1u64 as i16;
let _ = 1u64 as i32;
let _ = 1u64 as i64;
let _ = 1u64 as u8;
let _ = 1u64 as u16;
let _ = 1u64 as u32;
let _ = 1u64 as u64;
let _ = 1u64 as f32;
let _ = 1u64 as f64;
let _ = 1u64 as isize;
let _ = 1u64 as usize;
let _ = 1u64 as *const String;
let _ = 1u64 as i8;
let _ = 1u64 as i16;
let _ = 1u64 as i32;
let _ = 1u64 as i64;
let _ = 1u64 as u8;
let _ = 1u64 as u16;
let _ = 1u64 as u32;
let _ = 1u64 as u64;
let _ = 1u64 as f32;
let _ = 1u64 as f64;
let _ = true as isize;
let _ = true as usize;
let _ = true as i8;
let _ = true as i16;
let _ = true as i32;
let _ = true as i64;
let _ = true as u8;
let _ = true as u16;
let _ = true as u32;
let _ = true as u64;
let _ = 1f32 as isize;
let _ = 1f32 as usize;
let _ = 1f32 as i8;
let _ = 1f32 as i16;
let _ = 1f32 as i32;
let _ = 1f32 as i64;
let _ = 1f32 as u8;
let _ = 1f32 as u16;
let _ = 1f32 as u32;
let _ = 1f32 as u64;
let _ = 1f32 as f32;
let _ = 1f32 as f64;
let _ = 1f64 as isize;
let _ = 1f64 as usize;
let _ = 1f64 as i8;
let _ = 1f64 as i16;
let _ = 1f64 as i32;
let _ = 1f64 as i64;
let _ = 1f64 as u8;
let _ = 1f64 as u16;
let _ = 1f64 as u32;
let _ = 1f64 as u64;
let _ = 1f64 as f32;
let _ = 1f64 as f64;
}
"#,
);
}
#[test]
fn unsized_struct_cast() {
check_diagnostics(
r#"
//- minicore: sized
pub struct Data([u8]);
fn foo(x: &[u8]) {
let _: *const Data = x as *const Data;
//^^^^^^^^^^^^^^^^ error: casting `&[u8]` as `*const Data` is invalid
}
"#,
);
}
#[test]
fn unsupported_cast() {
check_diagnostics(
r#"
//- minicore: sized
struct A;
fn main() {
let _ = 1.0 as *const A;
//^^^^^^^^^^^^^^^ error: casting `f64` as `*const A` is invalid
}
"#,
);
}
#[test]
fn issue_17897() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
_ = ((), ()) as ();
//^^^^^^^^^^^^^^ error: non-primitive cast: `(_, _)` as `()`
}
"#,
);
}
#[test]
fn rustc_issue_10991() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let nil = ();
let _t = nil as usize;
//^^^^^^^^^^^^ error: non-primitive cast: `()` as `usize`
}
"#,
);
}
#[test]
fn rustc_issue_17444() {
check_diagnostics(
r#"
//- minicore: sized
enum Test {
Foo = 0
}
fn main() {
let _x = Test::Foo as *const isize;
//^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `Test` as `*const isize` is invalid
}
"#,
);
}
#[test]
fn rustc_issue_43825() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let error = error;
//^^^^^ error: no such value in this scope
0 as f32;
0.0 as u32;
}
"#,
);
}
#[test]
fn rustc_issue_84213() {
check_diagnostics(
r#"
//- minicore: sized
struct Something {
pub field: u32,
}
fn main() {
let mut something = Something { field: 1337 };
let _ = something.field;
let _pointer_to_something = something as *const Something;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `Something` as `*const Something`
let _mut_pointer_to_something = something as *mut Something;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `Something` as `*mut Something`
}
"#,
);
// Fixed
check_diagnostics(
r#"
//- minicore: sized
struct Something {
pub field: u32,
}
fn main() {
let mut something = Something { field: 1337 };
let _ = something.field;
let _pointer_to_something = &something as *const Something;
let _mut_pointer_to_something = &mut something as *mut Something;
}
"#,
);
}
#[test]
fn rustc_issue_88621() {
check_diagnostics(
r#"
//- minicore: sized
#[repr(u8)]
enum Kind2 {
Foo() = 1,
Bar{} = 2,
Baz = 3,
}
fn main() {
let _ = Kind2::Foo() as u8;
//^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `Kind2` as `u8`
}
"#,
);
}
#[test]
fn rustc_issue_89497() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let pointer: usize = &1_i32 as *const i32 as usize;
let _reference: &'static i32 = unsafe { pointer as *const i32 as &'static i32 };
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: non-primitive cast: `*const i32` as `&i32`
}
"#,
);
// Fixed
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let pointer: usize = &1_i32 as *const i32 as usize;
let _reference: &'static i32 = unsafe { &*(pointer as *const i32) };
}
"#,
);
}
#[test]
fn rustc_issue_106883() {
check_diagnostics_with_disabled(
r#"
//- minicore: sized, deref
use core::ops::Deref;
struct Foo;
impl Deref for Foo {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&[]
}
}
fn main() {
let _ = "foo" as bool;
//^^^^^^^^^^^^^ error: casting `&str` as `bool` is invalid: needs casting through a raw pointer first
let _ = Foo as bool;
//^^^^^^^^^^^ error: non-primitive cast: `Foo` as `bool`
}
fn _slice(bar: &[i32]) -> bool {
bar as bool
//^^^^^^^^^^^ error: casting `&[i32]` as `bool` is invalid: needs casting through a raw pointer first
}
"#,
&["E0308"],
);
}
}
@@ -99,8 +99,9 @@ mod tests {
fn missing_unsafe_diagnostic_with_raw_ptr() {
check_diagnostics(
r#"
//- minicore: sized
fn main() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
unsafe { let _y = *x; }
let _z = *x;
} //^^💡 error: this operation is unsafe and requires an unsafe function or block
@@ -112,17 +113,18 @@ fn main() {
fn missing_unsafe_diagnostic_with_unsafe_call() {
check_diagnostics(
r#"
//- minicore: sized
struct HasUnsafe;
impl HasUnsafe {
unsafe fn unsafe_fn(&self) {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let _y = *x;
}
}
unsafe fn unsafe_fn() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let _y = *x;
}
@@ -250,14 +252,15 @@ fn main() {
fn add_unsafe_block_when_dereferencing_a_raw_pointer() {
check_fix(
r#"
//- minicore: sized
fn main() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let _z = *x$0;
}
"#,
r#"
fn main() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let _z = unsafe { *x };
}
"#,
@@ -268,8 +271,9 @@ fn main() {
fn add_unsafe_block_when_calling_unsafe_function() {
check_fix(
r#"
//- minicore: sized
unsafe fn func() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let z = *x;
}
fn main() {
@@ -278,7 +282,7 @@ fn main() {
"#,
r#"
unsafe fn func() {
let x = &5 as *const usize;
let x = &5_usize as *const usize;
let z = *x;
}
fn main() {
@@ -292,6 +296,7 @@ fn main() {
fn add_unsafe_block_when_calling_unsafe_method() {
check_fix(
r#"
//- minicore: sized
struct S(usize);
impl S {
unsafe fn func(&self) {
@@ -190,4 +190,16 @@ fn foo(mut slice: &[u32]) -> usize {
"#,
);
}
#[test]
fn regression_16564() {
check_diagnostics(
r#"
//- minicore: copy
fn test() {
let _x = (&(&mut (),)).0 as *const ();
}
"#,
)
}
}
@@ -30,6 +30,7 @@ mod handlers {
pub(crate) mod inactive_code;
pub(crate) mod incoherent_impl;
pub(crate) mod incorrect_case;
pub(crate) mod invalid_cast;
pub(crate) mod invalid_derive_target;
pub(crate) mod macro_error;
pub(crate) mod malformed_derive;
@@ -390,6 +391,7 @@ pub fn semantic_diagnostics(
for diag in diags {
let d = match diag {
AnyDiagnostic::AwaitOutsideOfAsync(d) => handlers::await_outside_of_async::await_outside_of_async(&ctx, &d),
AnyDiagnostic::CastToUnsized(d) => handlers::invalid_cast::cast_to_unsized(&ctx, &d),
AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
Some(it) => it,
@@ -397,6 +399,7 @@ pub fn semantic_diagnostics(
}
AnyDiagnostic::IncoherentImpl(d) => handlers::incoherent_impl::incoherent_impl(&ctx, &d),
AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d),
AnyDiagnostic::InvalidCast(d) => handlers::invalid_cast::invalid_cast(&ctx, &d),
AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
AnyDiagnostic::MacroDefError(d) => handlers::macro_error::macro_def_error(&ctx, &d),
AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d),
@@ -313,6 +313,7 @@ fn main() {
//^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
//^^^^^^^^^^^^(
//^^^^^^^^^^^^)
//^^^^<fn-item-to-fn-pointer>
let _: fn() = || {};
//^^^^^<closure-to-fn-pointer>
let _: unsafe fn() = || {};
@@ -321,6 +322,8 @@ fn main() {
//^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
//^^^^^^^^^^^^^^^^^^^^^(
//^^^^^^^^^^^^^^^^^^^^^)
//^^^^^^^^^&raw mut $
//^^^^^^^^^*
let _: &mut [_] = &mut [0; 0];
//^^^^^^^^^^^<unsize>
//^^^^^^^^^^^&mut $
@@ -554,6 +554,7 @@ fn main() {
fn test_unsafe_highlighting() {
check_highlighting(
r#"
//- minicore: sized
macro_rules! id {
($($tt:tt)*) => {
$($tt)*