diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index c4cbaaa30175..b7603ccafc2e 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -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 @@ fn casts() { "#, 4, ); - check_number(r#"const GOAL: i32 = -12i8 as i32"#, -12); + check_number( + r#" + //- minicore: sized + const GOAL: i32 = -12i8 as i32 + "#, + -12, + ); } #[test] @@ -2422,6 +2434,7 @@ fn statics() { fn extern_weak_statics() { check_number( r#" + //- minicore: sized extern "C" { #[linkage = "extern_weak"] static __dso_handle: *mut u8; @@ -2716,6 +2729,7 @@ fn const_trait_assoc() { ); check_number( r#" + //- minicore: sized struct S(*mut T); trait MySized: Sized { diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs index 6f294494c01e..c5706172b20c 100644 --- a/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -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; diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 062ea278151b..dae78bbaedc8 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -254,6 +254,10 @@ pub enum InferenceDiagnostic { expr: ExprId, expected: Ty, }, + // CastToUnsized { + // expr: ExprId, + // cast_ty: Ty, + // }, } /// A mismatch between an expected and an inferred type. @@ -703,7 +707,15 @@ impl<'a> InferenceContext<'a> { // 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 expr = cast.expr(); + match cast.check(&mut table) { + Ok(Some(adjustments)) => { + // Using this causes some mir lowerings failures + // expr_adjustments.insert(expr, adjustments); + } + Ok(None) => (), + Err(diag) => diagnostics.push(diag), + } } // FIXME resolve obligations as well (use Guidance if necessary) diff --git a/crates/hir-ty/src/infer/cast.rs b/crates/hir-ty/src/infer/cast.rs index 060b5f36f292..525ff73a1162 100644 --- a/crates/hir-ty/src/infer/cast.rs +++ b/crates/hir-ty/src/infer/cast.rs @@ -1,47 +1,392 @@ //! Type cast logic. Basically coercion + additional casts. -use crate::{infer::unify::InferenceTable, Interner, Ty, TyExt, TyKind}; +use chalk_ir::{Mutability, Scalar, TyVariableKind, UintTy}; +use chalk_solve::rust_ir::AdtKind; +use hir_def::{hir::ExprId, lang_item::LangItem, AdtId}; +use stdx::never; + +use crate::{ + infer::unify::InferenceTable, to_chalk_trait_id, Adjustment, Binders, DynTy, GoalData, + InferenceDiagnostic, Interner, PlaceholderIndex, QuantifiedWhereClauses, Solution, + Substitution, TraitRef, Ty, TyExt, TyKind, TypeFlags, WhereClause, +}; #[derive(Clone, Debug)] pub(super) struct CastCheck { + expr: ExprId, expr_ty: Ty, cast_ty: Ty, } +#[derive(Debug)] +enum Int { + I, + U(UintTy), + Bool, + Char, + CEnum, + InfereceVar, +} + +#[derive(Debug)] +enum CastTy { + Int(Int), + Float, + FnPtr, + Ptr(Ty, Mutability), + // `DynStar` is Not supported yet in r-a +} + +impl CastTy { + fn from_ty(table: &mut InferenceTable<'_>, t: &Ty) -> Option { + 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::InfereceVar)), + TyKind::InferenceVar(_, TyVariableKind::Float) => Some(Self::Float), + TyKind::Scalar(Scalar::Float(_)) => Some(Self::Float), + TyKind::Adt(d, _) => { + let adt_datum = table.db.adt_datum(table.trait_env.krate, *d); + if adt_datum.kind != AdtKind::Enum { + return None; + } + if adt_datum.binders.skip_binders().variants.iter().all(|v| v.fields.is_empty()) { + 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)] +enum CastError { + Unknown, + CastToBool, + CastToChar, + DifferingKinds, + SizedUnsizedCast, + IllegalCast, + IntToFatCast, + NeedDeref, + NeedViaPtr, + NeedViaThinPtr, + NeedViaInt, + NonScalar, + UnknownCastPtrKind, + UnknownExprPtrKind, +} + +impl From for InferenceDiagnostic { + fn from(val: CastError) -> Self { + // TODO: Implement diagnostics + todo!() + } +} + impl CastCheck { - pub(super) fn new(expr_ty: Ty, cast_ty: Ty) -> Self { - Self { expr_ty, cast_ty } + pub(super) fn new(expr: ExprId, expr_ty: Ty, cast_ty: Ty) -> Self { + Self { 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. + pub(super) fn expr(&self) -> ExprId { + self.expr + } + + pub(super) fn check( + self, + table: &mut InferenceTable<'_>, + ) -> Result>, InferenceDiagnostic> { + table.resolve_obligations_as_possible(); let expr_ty = table.resolve_ty_shallow(&self.expr_ty); let cast_ty = table.resolve_ty_shallow(&self.cast_ty); - if table.coerce(&expr_ty, &cast_ty).is_ok() { - return; + if expr_ty.contains_unknown() || cast_ty.contains_unknown() { + return Ok(None); + } + + if !cast_ty.data(Interner).flags.contains(TypeFlags::HAS_TY_INFER) + && !is_sized(&cast_ty, table) + { + // TODO: return E0620 + eprintln!("E0620"); } - 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 let Ok((adjusts, _)) = table.coerce(&expr_ty, &cast_ty) { + return Ok(Some(adjusts)); } - // FIXME: Check other kinds of non-coercion casts and report error if any? + do_check(expr_ty, cast_ty, table).map_err(Into::into) } } -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 { +fn do_check( + expr_ty: Ty, + cast_ty: Ty, + table: &mut InferenceTable<'_>, +) -> Result>, CastError> { + let mut adjustments = None; + let (t_from, t_cast) = + match (CastTy::from_ty(table, &expr_ty), CastTy::from_ty(table, &cast_ty)) { + (Some(t_from), Some(t_cast)) => (t_from, t_cast), + (None, Some(t_cast)) => match expr_ty.kind(Interner) { + TyKind::FnDef(..) => { + let sig = 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((adjusts, _)) = table.coerce(&expr_ty, &fn_ptr) { + adjustments = Some(adjusts); + } 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 !is_sized(&t, table) { + return Err(CastError::IllegalCast); + } + check_ref_cast(&inner_ty, *mutbl, &t, m, table).map(|_| 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(adjustments), + (_, 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, _)) => { + check_ptr_ptr_cast(&src, &dst, table).map(|_| adjustments) + } + (CastTy::Ptr(src, _), CastTy::Int(_)) => { + check_ptr_addr_cast(&src, table).map(|_| adjustments) + } + (CastTy::Int(_), CastTy::Ptr(dst, _)) => { + check_addr_ptr_cast(&dst, table).map(|_| adjustments) + } + (CastTy::FnPtr, CastTy::Ptr(dst, _)) => { + check_fptr_ptr_cast(&dst, table).map(|_| adjustments) + } + (CastTy::Int(Int::CEnum), CastTy::Int(_)) => Ok(adjustments), + (CastTy::Int(Int::Char | Int::Bool), CastTy::Int(_)) => Ok(adjustments), + (CastTy::Int(_) | CastTy::Float, CastTy::Int(_) | CastTy::Float) => Ok(adjustments), + (CastTy::FnPtr, CastTy::Int(_)) => Ok(adjustments), + } +} + +fn check_ref_cast( + t_expr: &Ty, + m_expr: Mutability, + t_cast: &Ty, + m_cast: Mutability, + table: &mut InferenceTable<'_>, +) -> Result<(), CastError> { + // Mutability order is opposite tu rustc. `Mut < Not` + if m_expr <= m_cast { + if let TyKind::Array(ety, _) = t_expr.kind(Interner) { + // 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(src: &Ty, dst: &Ty, table: &mut InferenceTable<'_>) -> 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| { + 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(expr_ty: &Ty, table: &mut InferenceTable<'_>) -> 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(cast_ty: &Ty, table: &mut InferenceTable<'_>) -> 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(cast_ty: &Ty, table: &mut InferenceTable<'_>) -> 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), + } +} + +#[derive(PartialEq, Eq)] +enum PointerKind { + // thin pointer + Thin, + // trait object + VTable(Binders), + // slice + Length, + OfAlias, + OfParam(PlaceholderIndex), + Error, +} + +fn pointer_kind(ty: &Ty, table: &mut InferenceTable<'_>) -> Result, ()> { + let ty = table.resolve_ty_shallow(ty); + + if is_sized(&ty, table) { + 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(()) + } + } +} + +fn is_sized(ty: &Ty, table: &mut InferenceTable<'_>) -> bool { + if let Some((AdtId::StructId(id), subst)) = ty.as_adt() { + 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); + // 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 is_sized(&last_field_ty, table); + } + } + let Some(sized) = table + .db + .lang_item(table.trait_env.krate, LangItem::Sized) + .and_then(|sized| sized.as_trait()) + else { return false; }; - table.coerce(expr_elt_ty, cast_inner_ty).is_ok() + 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!(table.try_obligation(goal), Some(Solution::Unique(_))) } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 89d92ea9af0b..e20c518598ed 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -610,7 +610,7 @@ impl InferenceContext<'_> { 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(*expr, expr_ty, cast_ty.clone())); cast_ty } Expr::Ref { expr, rawness, mutability } => { diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 6825bc4862ac..595a78da10fb 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -399,7 +399,7 @@ extern "C" { 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; diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index 610fc9b6b456..74acf23b75ab 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -1286,6 +1286,7 @@ fn main() { fn method_on_dyn_impl() { check_types( r#" +//- minicore: coerce_unsized trait Foo {} impl Foo for u32 {} diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 98f30f0f6f74..4df10025f80f 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -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 { 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] //^ [(); _] diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 1c6fa62e30ce..37280f81b869 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -3572,6 +3572,7 @@ fn f(t: Ark) { fn ref_to_array_to_ptr_cast() { check_types( r#" +//- minicore: sized fn default() -> T { loop {} } fn foo() { let arr = [default()];