From ca8e62d73b839410c2c512ff062c94a2c7a76213 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Sun, 13 Jul 2025 15:32:28 +0200 Subject: [PATCH 01/21] Introduce vtable_for intrinsic. Passes hir analysis, far from functional --- .../src/interpret/intrinsics.rs | 16 ++++++++++++++++ .../rustc_hir_analysis/src/check/intrinsic.rs | 15 +++++++++++++++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics/mod.rs | 11 +++++++++++ library/coretests/tests/intrinsics.rs | 17 +++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 378ed6d0e1030..d72b42cee8658 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -67,6 +67,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let val = self.const_val_to_op(val, dest.layout.ty, Some(dest.layout))?; self.copy_op(&val, dest)?; } + sym::vtable_for => { + let tp_ty = instance.args.type_at(0); + let result_ty = instance.args.type_at(1); + //let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span); + + ensure_monomorphic_enough(tcx, tp_ty)?; + ensure_monomorphic_enough(tcx, result_ty)?; + + // Get vtable + //let vtable_ptr = self.get_vtable_ptr(tp_ty, result_ty.into())?; + + //let dyn_metadata = metadata(vtable_ptr); + //let val = ConstValue::from_u128(tcx.type_id_hash(tp_ty).as_u128()); + //let val = self.const_val_to_op(val, dest.layout.ty, Some(dest.layout))?; + //self.copy_op(&val, dest)?; + } sym::variant_count => { let tp_ty = instance.args.type_at(0); let ty = match tp_ty.kind() { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index cebf7d1b5324e..a94d3fd75e8ff 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -93,6 +93,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::three_way_compare | sym::discriminant_value | sym::type_id + | sym::vtable_for | sym::select_unpredictable | sym::cold_path | sym::ptr_guaranteed_cmp @@ -543,6 +544,20 @@ pub(crate) fn check_intrinsic_type( (0, 0, vec![Ty::new_imm_ptr(tcx, tcx.types.unit)], tcx.types.usize) } + sym::vtable_for => { + let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span); + let dyn_metadata_adt_ref = tcx.adt_def(dyn_metadata); + let dyn_metadata_args = tcx.mk_args(&[param(1).into()]); + let dyn_ty = Ty::new_adt(tcx, dyn_metadata_adt_ref, dyn_metadata_args); + + let option_did = tcx.require_lang_item(LangItem::Option, span); + let option_adt_ref = tcx.adt_def(option_did); + let option_args = tcx.mk_args(&[dyn_ty.into()]); + let ret_ty = Ty::new_adt(tcx, option_adt_ref, option_args); + + (2, 0, vec![], ret_ty) + } + // This type check is not particularly useful, but the `where` bounds // on the definition in `core` do the heavy lifting for checking it. sym::aggregate_raw_ptr => (3, 0, vec![param(1), param(2)], param(0)), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d6537d49be777..9ba0dc48af87d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2345,6 +2345,7 @@ symbols! { vreg_low16, vsx, vtable_align, + vtable_for, vtable_size, warn, wasip2, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index ab99492638ec0..a34d3a75b28d8 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2626,6 +2626,17 @@ pub unsafe fn vtable_size(ptr: *const ()) -> usize; #[rustc_intrinsic] pub unsafe fn vtable_align(ptr: *const ()) -> usize; +/// FIXME: write actual docs (ivarflakstad) +/// The intrinsic will return the vtable of `t` through the lens of `U`. +/// +/// # Safety +/// +/// `ptr` must point to a vtable. +#[rustc_nounwind] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_intrinsic] +pub const fn vtable_for() -> Option>; + /// The size of a type in bytes. /// /// Note that, unlike most intrinsics, this is safe to call; diff --git a/library/coretests/tests/intrinsics.rs b/library/coretests/tests/intrinsics.rs index 744a6a0d2dd8f..f5dbc39e6c699 100644 --- a/library/coretests/tests/intrinsics.rs +++ b/library/coretests/tests/intrinsics.rs @@ -193,3 +193,20 @@ fn carrying_mul_add_fallback_i128() { (u128::MAX - 1, -(i128::MIN / 2)), ); } + +#[test] +fn test_vtable_for() { + use std::fmt::Debug; + use std::intrinsics::vtable_for; + use std::option::Option; + use std::ptr::DynMetadata; + + #[allow(dead_code)] + #[derive(Debug)] + struct A { + index: usize, + } + const debug_vtable: Option> = vtable_for::(); + + println!("{debug_vtable:?}"); +} From 17b54023f76c458dba2b613479c2298fbb69bde8 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:52:01 +0200 Subject: [PATCH 02/21] Add def_id() method to ty::Binder<.., ExistentialPredicate> --- compiler/rustc_type_ir/src/predicate.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index 4643cd0ab850f..9af4c498dee9e 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -275,6 +275,13 @@ pub enum ExistentialPredicate { } impl ty::Binder> { + pub fn def_id(&self) -> I::DefId { + match self.skip_binder() { + ExistentialPredicate::Trait(tr) => tr.def_id, + ExistentialPredicate::Projection(p) => p.def_id, + ExistentialPredicate::AutoTrait(did) => did, + } + } /// Given an existential predicate like `?Self: PartialEq` (e.g., derived from `dyn PartialEq`), /// and a concrete type `self_ty`, returns a full predicate where the existentially quantified variable `?Self` /// has been replaced with `self_ty` (e.g., `self_ty: PartialEq`, in our example). From d40408ef5b9cd29d6dcea43209abd8b71df93630 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:53:06 +0200 Subject: [PATCH 03/21] Verify that type implements trait in vtable_for. Return None if not. --- .../src/interpret/intrinsics.rs | 45 ++++++++++++++----- library/coretests/tests/intrinsics.rs | 25 ++++++----- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 7e03f7c960045..f519fbb764560 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -4,14 +4,16 @@ use std::assert_matches::assert_matches; -use rustc_abi::{FieldIdx, HasDataLayout, Size}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; +use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{read_target_uint, write_target_uint}; use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{Ty, TyCtxt}; +use rustc_middle::ty::{Ty, TyCtxt, Upcast}; use rustc_middle::{bug, ty}; use rustc_span::{Symbol, sym}; +use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; use tracing::trace; use super::memory::MemoryKind; @@ -19,7 +21,7 @@ use super::util::ensure_monomorphic_enough; use super::{ Allocation, CheckInAllocMsg, ConstAllocation, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, - interp_ok, throw_inval, throw_ub_custom, throw_ub_format, + interp_ok, throw_inval, throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::fluent_generated as fluent; @@ -151,18 +153,41 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::vtable_for => { let tp_ty = instance.args.type_at(0); let result_ty = instance.args.type_at(1); - //let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span); ensure_monomorphic_enough(tcx, tp_ty)?; ensure_monomorphic_enough(tcx, result_ty)?; - // Get vtable - //let vtable_ptr = self.get_vtable_ptr(tp_ty, result_ty.into())?; + let ty::Dynamic(preds, _, ty::Dyn) = result_ty.kind() else { + throw_unsup_format!( + "Invalid type provided to vtable_for::. U must be dyn Trait, got {result_ty}." + ); + }; - //let dyn_metadata = metadata(vtable_ptr); - //let val = ConstValue::from_u128(tcx.type_id_hash(tp_ty).as_u128()); - //let val = self.const_val_to_op(val, dest.layout.ty, Some(dest.layout))?; - //self.copy_op(&val, dest)?; + let (infcx, param_env) = self + .tcx + .infer_ctxt() + .build_with_typing_env(ty::TypingEnv::fully_monomorphized()); + + let type_impls_trait = preds.iter().all(|pred| { + let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); + let pred: ty::Predicate<'tcx> = trait_ref.upcast(tcx); + + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligation(Obligation::new( + tcx, + ObligationCause::dummy(), + param_env, + pred, + )); + ocx.select_all_or_error().is_empty() + }); + + if type_impls_trait { + let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; + self.write_pointer(vtable_ptr, dest)?; + } else { + self.write_discriminant(FIRST_VARIANT, dest)?; + } } sym::variant_count => { let tp_ty = instance.args.type_at(0); diff --git a/library/coretests/tests/intrinsics.rs b/library/coretests/tests/intrinsics.rs index f5dbc39e6c699..f471637347d80 100644 --- a/library/coretests/tests/intrinsics.rs +++ b/library/coretests/tests/intrinsics.rs @@ -1,5 +1,8 @@ use core::any::TypeId; -use core::intrinsics::assume; +use core::intrinsics::{assume, vtable_for}; +use std::fmt::Debug; +use std::option::Option; +use std::ptr::DynMetadata; #[test] fn test_typeid_sized_types() { @@ -195,18 +198,16 @@ fn carrying_mul_add_fallback_i128() { } #[test] +#[allow(dead_code)] fn test_vtable_for() { - use std::fmt::Debug; - use std::intrinsics::vtable_for; - use std::option::Option; - use std::ptr::DynMetadata; - - #[allow(dead_code)] #[derive(Debug)] - struct A { - index: usize, - } - const debug_vtable: Option> = vtable_for::(); + struct A {} + + struct B {} + + const A_VTABLE: Option> = vtable_for::(); + assert!(A_VTABLE.is_some()); - println!("{debug_vtable:?}"); + const B_VTABLE: Option> = vtable_for::(); + assert!(B_VTABLE.is_none()); } From 7a46b59d6b2fa0ae406b3332a5c49624087d7f92 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:17:39 +0200 Subject: [PATCH 04/21] Add initial downcast_trait impl --- library/core/src/any.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 39cdf6efda07a..4b8194814b5e7 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -86,7 +86,7 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::{fmt, hash, intrinsics}; +use crate::{fmt, hash, intrinsics, ptr}; /////////////////////////////////////////////////////////////////////////////// // Any trait @@ -896,3 +896,22 @@ pub const fn type_name() -> &'static str { pub const fn type_name_of_val(_val: &T) -> &'static str { type_name::() } + +#[allow(missing_docs)] +#[must_use] +#[unstable(feature = "downcast_trait", issue = "69420")] +pub const fn downcast_trait< + T: Any + 'static, + U: ptr::Pointee> + ?Sized + 'static, +>( + t: &T, +) -> Option<&U> { + let vtable: Option> = const { intrinsics::vtable_for::() }; + match vtable { + Some(dyn_metadata) => { + let pointer = ptr::from_raw_parts(t, dyn_metadata); + Some(unsafe { &*pointer }) + } + None => None, + } +} From 46224d5cf723d7fcb315d5912a4be4eb8e2ab053 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:34:49 +0200 Subject: [PATCH 05/21] Add downcast_trait ui tests --- tests/ui/any/downcast_trait.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/ui/any/downcast_trait.rs diff --git a/tests/ui/any/downcast_trait.rs b/tests/ui/any/downcast_trait.rs new file mode 100644 index 0000000000000..9de44496ceea9 --- /dev/null +++ b/tests/ui/any/downcast_trait.rs @@ -0,0 +1,29 @@ +//@ run-pass +#![feature(downcast_trait)] + +use std::fmt::Debug; + +// Look ma, no `T: Debug` +fn downcast_debug_format(t: &T) -> String { + match std::any::downcast_trait::<_, dyn Debug>(t) { + Some(d) => format!("{d:?}"), + None => "default".to_string() + } +} + +// Test that downcasting to a dyn trait works as expected +fn main() { + #[allow(dead_code)] + #[derive(Debug)] + struct A { + index: usize + } + let a = A { index: 42 }; + let result = downcast_debug_format(&a); + assert_eq!("A { index: 42 }", result); + + struct B {} + let b = B {}; + let result = downcast_debug_format(&b); + assert_eq!("default", result); +} From b16ea1546e0a8386689f59df9e1c6374041ba3e1 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:45:02 +0200 Subject: [PATCH 06/21] Add initial downcast_trait_mut impl --- library/core/src/any.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 4b8194814b5e7..d62a6151c830a 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -915,3 +915,21 @@ pub const fn downcast_trait< None => None, } } +#[allow(missing_docs)] +#[must_use] +#[unstable(feature = "downcast_trait", issue = "69420")] +pub const fn downcast_trait_mut< + T: Any + 'static, + U: ptr::Pointee> + ?Sized + 'static, +>( + t: &mut T, +) -> Option<&mut U> { + let vtable: Option> = const { intrinsics::vtable_for::() }; + match vtable { + Some(dyn_metadata) => { + let pointer = ptr::from_raw_parts_mut(t, dyn_metadata); + Some(unsafe { &mut *pointer }) + } + None => None, + } +} From ff13405110569d869224fd23e01c11c0bb9fad57 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:45:25 +0200 Subject: [PATCH 07/21] Add downcast_trait_mut ui tests --- tests/ui/any/downcast_trait_mut.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/ui/any/downcast_trait_mut.rs diff --git a/tests/ui/any/downcast_trait_mut.rs b/tests/ui/any/downcast_trait_mut.rs new file mode 100644 index 0000000000000..da4f0abda4bbf --- /dev/null +++ b/tests/ui/any/downcast_trait_mut.rs @@ -0,0 +1,20 @@ +//@ run-pass +#![feature(downcast_trait)] + +use std::fmt::{Error, Write}; + +// Look ma, no `T: Write` +fn downcast_mut_write(t: &mut T, s: &str) -> Result<(), Error> { + match std::any::downcast_trait_mut::<_, dyn Write>(t) { + Some(w) => w.write_str(s), + None => Ok(()) + } +} + +// Test that downcasting to a mut dyn trait works as expected +fn main() { + let mut buf = "Hello".to_string(); + + downcast_mut_write(&mut buf, " world!").unwrap(); + assert_eq!(buf, "Hello world!"); +} From 93aa4f268de570aef4e13e939ab212f854c2fd2d Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:09:32 +0200 Subject: [PATCH 08/21] Add actual tracking issue reference --- library/core/src/any.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/core/src/any.rs b/library/core/src/any.rs index d62a6151c830a..6091e3f50c3c2 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -899,7 +899,7 @@ pub const fn type_name_of_val(_val: &T) -> &'static str { #[allow(missing_docs)] #[must_use] -#[unstable(feature = "downcast_trait", issue = "69420")] +#[unstable(feature = "downcast_trait", issue = "144361")] pub const fn downcast_trait< T: Any + 'static, U: ptr::Pointee> + ?Sized + 'static, @@ -915,9 +915,10 @@ pub const fn downcast_trait< None => None, } } + #[allow(missing_docs)] #[must_use] -#[unstable(feature = "downcast_trait", issue = "69420")] +#[unstable(feature = "downcast_trait", issue = "144361")] pub const fn downcast_trait_mut< T: Any + 'static, U: ptr::Pointee> + ?Sized + 'static, From 1249f38729398ddcf566a4ecd7608d32ff3fcb53 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:46:13 +0200 Subject: [PATCH 09/21] Remove redundant #[allow(dead_code)] --- library/coretests/tests/intrinsics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/coretests/tests/intrinsics.rs b/library/coretests/tests/intrinsics.rs index f471637347d80..c6d841b8383a8 100644 --- a/library/coretests/tests/intrinsics.rs +++ b/library/coretests/tests/intrinsics.rs @@ -198,7 +198,6 @@ fn carrying_mul_add_fallback_i128() { } #[test] -#[allow(dead_code)] fn test_vtable_for() { #[derive(Debug)] struct A {} From ad5fa57f02914ff677d2b546d69a5cf3ae40884c Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:54:41 +0200 Subject: [PATCH 10/21] update vtable_for U bounds --- library/core/src/intrinsics/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 542d2891e9fe5..a5aa063fdb23f 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2646,7 +2646,8 @@ pub unsafe fn vtable_align(ptr: *const ()) -> usize; #[rustc_nounwind] #[unstable(feature = "core_intrinsics", issue = "none")] #[rustc_intrinsic] -pub const fn vtable_for() -> Option>; +pub const fn vtable_for> + ?Sized>() +-> Option>; /// The size of a type in bytes. /// From 0da23e7c6c8b196a56331ac419ae9c66307beab9 Mon Sep 17 00:00:00 2001 From: ivarflakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:56:31 +0200 Subject: [PATCH 11/21] Use existing typing env when building infer context Co-authored-by: Oli Scherer --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index f519fbb764560..3316411c3ad32 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -166,7 +166,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let (infcx, param_env) = self .tcx .infer_ctxt() - .build_with_typing_env(ty::TypingEnv::fully_monomorphized()); + .build_with_typing_env(self.typing_env); let type_impls_trait = preds.iter().all(|pred| { let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); From d9b8269dc4cb63df78ade6009e09b1743c3fe263 Mon Sep 17 00:00:00 2001 From: ivarflakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:56:46 +0200 Subject: [PATCH 12/21] Remove redundant upcast Co-authored-by: Oli Scherer --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 3316411c3ad32..d39fe43f6802a 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -170,7 +170,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let type_impls_trait = preds.iter().all(|pred| { let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); - let pred: ty::Predicate<'tcx> = trait_ref.upcast(tcx); let ocx = ObligationCtxt::new(&infcx); ocx.register_obligation(Obligation::new( From b08d57fad3656c095947faaa66fd94ee8d81aacb Mon Sep 17 00:00:00 2001 From: ivarflakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:58:01 +0200 Subject: [PATCH 13/21] Document which discriminant we write Co-authored-by: Oli Scherer --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index d39fe43f6802a..dc135cba49f44 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -185,6 +185,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; self.write_pointer(vtable_ptr, dest)?; } else { + // Write `None` self.write_discriminant(FIRST_VARIANT, dest)?; } } From db579d30c90b8821ed674e2d2abfe741348bb383 Mon Sep 17 00:00:00 2001 From: ivarflakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:59:21 +0200 Subject: [PATCH 14/21] Document behaviour of writing Option pointer Co-authored-by: Oli Scherer --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index dc135cba49f44..4462df3f0f821 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -183,6 +183,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if type_impls_trait { let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; + // Writing a non-null pointer into an `Option` will automatically make it `Some`. self.write_pointer(vtable_ptr, dest)?; } else { // Write `None` From 852c332c3679f8a4e38a9c32409a8b20e0fba73b Mon Sep 17 00:00:00 2001 From: ivarflakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:59:57 +0200 Subject: [PATCH 15/21] Safety comment Co-authored-by: Oli Scherer --- library/core/src/any.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 6091e3f50c3c2..d2a2e92553191 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -910,6 +910,8 @@ pub const fn downcast_trait< match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts(t, dyn_metadata); + // SAFETY: `t` is a reference to a type, so we know it is valid. + // `dyn_metadata` is a vtable for T, implementing the trait of `U`. Some(unsafe { &*pointer }) } None => None, From 8298c8425caa126a1887defaa2bdfe9a0db46d26 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:21:53 +0200 Subject: [PATCH 16/21] Make unreachable path trigger ICE with span --- .../rustc_const_eval/src/interpret/intrinsics.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 4462df3f0f821..59b3f9da31742 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -11,7 +11,7 @@ use rustc_middle::mir::interpret::{read_target_uint, write_target_uint}; use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic}; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{Ty, TyCtxt, Upcast}; -use rustc_middle::{bug, ty}; +use rustc_middle::{bug, span_bug, ty}; use rustc_span::{Symbol, sym}; use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; use tracing::trace; @@ -21,7 +21,7 @@ use super::util::ensure_monomorphic_enough; use super::{ Allocation, CheckInAllocMsg, ConstAllocation, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, - interp_ok, throw_inval, throw_ub_custom, throw_ub_format, throw_unsup_format, + interp_ok, throw_inval, throw_ub_custom, throw_ub_format, }; use crate::fluent_generated as fluent; @@ -156,17 +156,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ensure_monomorphic_enough(tcx, tp_ty)?; ensure_monomorphic_enough(tcx, result_ty)?; - let ty::Dynamic(preds, _, ty::Dyn) = result_ty.kind() else { - throw_unsup_format!( + span_bug!( + self.find_closest_untracked_caller_location(), "Invalid type provided to vtable_for::. U must be dyn Trait, got {result_ty}." ); }; - let (infcx, param_env) = self - .tcx - .infer_ctxt() - .build_with_typing_env(self.typing_env); + let (infcx, param_env) = + self.tcx.infer_ctxt().build_with_typing_env(self.typing_env); let type_impls_trait = preds.iter().all(|pred| { let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); From d9cb96548fd83c2a0f0f7d0303489b1789bbda86 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:47:30 +0200 Subject: [PATCH 17/21] Improve type impls trait check --- .../src/interpret/intrinsics.rs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 59b3f9da31742..3f9e830eed859 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -10,7 +10,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{read_target_uint, write_target_uint}; use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{Ty, TyCtxt, Upcast}; +use rustc_middle::ty::{Ty, TyCtxt}; use rustc_middle::{bug, span_bug, ty}; use rustc_span::{Symbol, sym}; use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; @@ -166,18 +166,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let (infcx, param_env) = self.tcx.infer_ctxt().build_with_typing_env(self.typing_env); - let type_impls_trait = preds.iter().all(|pred| { - let trait_ref = ty::TraitRef::new(tcx, pred.def_id(), [tp_ty]); - - let ocx = ObligationCtxt::new(&infcx); - ocx.register_obligation(Obligation::new( - tcx, - ObligationCause::dummy(), - param_env, - pred, - )); - ocx.select_all_or_error().is_empty() - }); + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligations(preds.iter().map(|pred| { + let pred = pred.with_self_ty(tcx, tp_ty); + let pred = tcx.erase_regions(pred); + Obligation::new(tcx, ObligationCause::dummy(), param_env, pred) + })); + let type_impls_trait = ocx.select_all_or_error().is_empty(); if type_impls_trait { let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; From 2919430a2aec686d71ccd7c0d6e21a1f05ad2e3d Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:48:37 +0200 Subject: [PATCH 18/21] Add SAFETY comment to downcast_trait_mut --- library/core/src/any.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/core/src/any.rs b/library/core/src/any.rs index d2a2e92553191..3e6108bf9bb25 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -931,6 +931,8 @@ pub const fn downcast_trait_mut< match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts_mut(t, dyn_metadata); + // SAFETY: `t` is a reference to a type, so we know it is valid. + // `dyn_metadata` is a vtable for T, implementing the trait of `U`. Some(unsafe { &mut *pointer }) } None => None, From 5b438f842b741ca3e4bf0763dcfc0635d8e81aef Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Thu, 24 Jul 2025 20:58:15 +0200 Subject: [PATCH 19/21] Turn predicate 'erased lifetimes into 'static --- .../src/interpret/intrinsics.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 1fe719fede38c..d0f44c7a0e849 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -6,11 +6,12 @@ use std::assert_matches::assert_matches; use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; +use rustc_hir::def_id::CRATE_DEF_ID; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint}; use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic}; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{Ty, TyCtxt}; +use rustc_middle::ty::{Ty, TyCtxt, TypeFoldable}; use rustc_middle::{bug, span_bug, ty}; use rustc_span::{Symbol, sym}; use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt}; @@ -170,12 +171,22 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let ocx = ObligationCtxt::new(&infcx); ocx.register_obligations(preds.iter().map(|pred| { let pred = pred.with_self_ty(tcx, tp_ty); - let pred = tcx.erase_regions(pred); + // Lifetimes can only be 'static because of the bound on T + let pred = pred.fold_with(&mut ty::BottomUpFolder { + tcx, + ty_op: |ty| ty, + lt_op: |lt| { + if lt == tcx.lifetimes.re_erased { tcx.lifetimes.re_static } else { lt } + }, + ct_op: |ct| ct, + }); Obligation::new(tcx, ObligationCause::dummy(), param_env, pred) })); let type_impls_trait = ocx.select_all_or_error().is_empty(); + // Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default" + let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty(); - if type_impls_trait { + if regions_are_valid && type_impls_trait { let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?; // Writing a non-null pointer into an `Option` will automatically make it `Some`. self.write_pointer(vtable_ptr, dest)?; From 11748bf220069b12060e200fc2e60bfb01f07fef Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Thu, 24 Jul 2025 20:59:35 +0200 Subject: [PATCH 20/21] Add ui tests that were unsound previously and now fail on downcast_trait().unwrap() as expected --- tests/ui/any/downcast_trait_err1.rs | 30 +++++++++++++++++++++++++++++ tests/ui/any/downcast_trait_err2.rs | 28 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/ui/any/downcast_trait_err1.rs create mode 100644 tests/ui/any/downcast_trait_err2.rs diff --git a/tests/ui/any/downcast_trait_err1.rs b/tests/ui/any/downcast_trait_err1.rs new file mode 100644 index 0000000000000..52796c4ef94cd --- /dev/null +++ b/tests/ui/any/downcast_trait_err1.rs @@ -0,0 +1,30 @@ +//@ run-pass +#![feature(downcast_trait)] + +use std::{any::downcast_trait, sync::OnceLock}; + +trait Trait { + fn call(&self, x: &Box); +} + +impl Trait for for<'a> fn(&'a Box) { + fn call(&self, x: &Box) { + self(x); + } +} + +static STORAGE: OnceLock<&'static Box> = OnceLock::new(); + +fn store(x: &'static Box) { + STORAGE.set(x).unwrap(); +} + +fn main() { + let data = Box::new(Box::new(1i32)); + let fn_ptr: fn(&'static Box) = store; + downcast_trait::<_, dyn Trait>(&fn_ptr) + .unwrap() + .call(&*data); + drop(data); + println!("{}", STORAGE.get().unwrap()); +} diff --git a/tests/ui/any/downcast_trait_err2.rs b/tests/ui/any/downcast_trait_err2.rs new file mode 100644 index 0000000000000..580b46c1a999a --- /dev/null +++ b/tests/ui/any/downcast_trait_err2.rs @@ -0,0 +1,28 @@ +//@ run-pass +#![feature(downcast_trait)] +use std::{any::downcast_trait, sync::OnceLock}; + +trait Trait { + fn call(&self, t: T, x: &Box); +} + +impl Trait fn(&'a Box)> for () { + fn call(&self, f: for<'a> fn(&'a Box), x: &Box) { + f(x); + } +} + +static STORAGE: OnceLock<&'static Box> = OnceLock::new(); + +fn store(x: &'static Box) { + STORAGE.set(x).unwrap(); +} + +fn main() { + let data = Box::new(Box::new(1i32)); + downcast_trait::<_, dyn Trait)>>(&()) + .unwrap() + .call(store, &*data); + drop(data); + println!("{}", STORAGE.get().unwrap()); +} From c38ca5b244ab058bf9462bcf86e7f613d5c45281 Mon Sep 17 00:00:00 2001 From: Ivar Flakstad <69173633+ivarflakstad@users.noreply.github.com> Date: Thu, 24 Jul 2025 21:16:16 +0200 Subject: [PATCH 21/21] Restructure ui tests to highlight unsound path and how we no longer hit it --- tests/ui/any/downcast_trait_err1.rs | 14 +++++++++----- tests/ui/any/downcast_trait_err2.rs | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/ui/any/downcast_trait_err1.rs b/tests/ui/any/downcast_trait_err1.rs index 52796c4ef94cd..95e8286218401 100644 --- a/tests/ui/any/downcast_trait_err1.rs +++ b/tests/ui/any/downcast_trait_err1.rs @@ -22,9 +22,13 @@ fn store(x: &'static Box) { fn main() { let data = Box::new(Box::new(1i32)); let fn_ptr: fn(&'static Box) = store; - downcast_trait::<_, dyn Trait>(&fn_ptr) - .unwrap() - .call(&*data); - drop(data); - println!("{}", STORAGE.get().unwrap()); + let dt = downcast_trait::<_, dyn Trait>(&fn_ptr); + if let Some(dt) = dt { + // unsound path + dt.call(&*data); + drop(data); + println!("{}", STORAGE.get().unwrap()); + } else { + println!("success") + } } diff --git a/tests/ui/any/downcast_trait_err2.rs b/tests/ui/any/downcast_trait_err2.rs index 580b46c1a999a..f2a1fd289684b 100644 --- a/tests/ui/any/downcast_trait_err2.rs +++ b/tests/ui/any/downcast_trait_err2.rs @@ -20,9 +20,13 @@ fn store(x: &'static Box) { fn main() { let data = Box::new(Box::new(1i32)); - downcast_trait::<_, dyn Trait)>>(&()) - .unwrap() - .call(store, &*data); - drop(data); - println!("{}", STORAGE.get().unwrap()); + let dt = downcast_trait::<_, dyn Trait)>>(&()); + if let Some(dt) = dt { + // unsound path + dt.call(store, &*data); + drop(data); + println!("{}", STORAGE.get().unwrap()); + } else { + println!("success") + } }