Introduce vtable_for intrinsic and use it to implement try_as_dyn and try_as_dyn_mut for fallible coercion from &T / &mut T to &dyn Trait.

This commit is contained in:
Ivar Flakstad 2025-07-13 15:32:28 +02:00 committed by theiz
parent 0160933b1d
commit d5bf1a4c9a
11 changed files with 299 additions and 4 deletions

View file

@ -6,14 +6,17 @@ mod simd;
use std::assert_matches::assert_matches;
use rustc_abi::{FieldIdx, HasDataLayout, Size, VariantIdx};
use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx};
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::{FloatTy, Ty, TyCtxt};
use rustc_middle::ty::{FloatTy, PolyExistentialPredicate, Ty, TyCtxt, TypeFoldable};
use rustc_middle::{bug, span_bug, ty};
use rustc_span::{Symbol, sym};
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
use tracing::trace;
use super::memory::MemoryKind;
@ -219,6 +222,49 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.write_scalar(Scalar::from_target_usize(offset, self), dest)?;
}
sym::vtable_for => {
let tp_ty = instance.args.type_at(0);
let result_ty = instance.args.type_at(1);
ensure_monomorphic_enough(tcx, tp_ty)?;
ensure_monomorphic_enough(tcx, result_ty)?;
let ty::Dynamic(preds, _) = result_ty.kind() else {
span_bug!(
self.find_closest_untracked_caller_location(),
"Invalid type provided to vtable_for::<T, U>. U must be dyn Trait, got {result_ty}."
);
};
let (infcx, param_env) =
self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);
let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| {
let pred = pred.with_self_ty(tcx, tp_ty);
// 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.evaluate_obligations_error_on_ambiguity().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 regions_are_valid && type_impls_trait {
let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?;
// Writing a non-null pointer into an `Option<NonNull>` will automatically make it `Some`.
self.write_pointer(vtable_ptr, dest)?;
} else {
// Write `None`
self.write_discriminant(FIRST_VARIANT, dest)?;
}
}
sym::variant_count => {
let tp_ty = instance.args.type_at(0);
let ty = match tp_ty.kind() {

View file

@ -215,6 +215,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::type_name
| sym::ub_checks
| sym::variant_count
| sym::vtable_for
| sym::wrapping_add
| sym::wrapping_mul
| sym::wrapping_sub
@ -643,6 +644,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)),

View file

@ -2473,6 +2473,7 @@ symbols! {
vsreg,
vsx,
vtable_align,
vtable_for,
vtable_size,
warn,
wasip2,

View file

@ -291,6 +291,13 @@ pub enum ExistentialPredicate<I: Interner> {
impl<I: Interner> Eq for ExistentialPredicate<I> {}
impl<I: Interner> ty::Binder<I, ExistentialPredicate<I>> {
pub fn def_id(&self) -> I::DefId {
match self.skip_binder() {
ExistentialPredicate::Trait(tr) => tr.def_id.into(),
ExistentialPredicate::Projection(p) => p.def_id.into(),
ExistentialPredicate::AutoTrait(did) => did.into(),
}
}
/// Given an existential predicate like `?Self: PartialEq<u32>` (e.g., derived from `dyn PartialEq<u32>`),
/// 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<u32>`, in our example).

View file

@ -86,7 +86,7 @@
#![stable(feature = "rust1", since = "1.0.0")]
use crate::{fmt, hash, intrinsics};
use crate::{fmt, hash, intrinsics, ptr};
///////////////////////////////////////////////////////////////////////////////
// Any trait
@ -906,3 +906,109 @@ pub const fn type_name<T: ?Sized>() -> &'static str {
pub const fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
type_name::<T>()
}
/// Returns `Some(&U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`.
///
/// # Compile-time failures
/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution.
/// In some cases, that resolution can exceed the recursion limit,
/// and compilation will fail instead of this function returning `None`.
/// # Examples
///
/// ```rust
/// #![feature(try_as_dyn)]
///
/// use core::any::try_as_dyn;
///
/// trait Animal {
/// fn speak(&self) -> &'static str;
/// }
///
/// struct Dog;
/// impl Animal for Dog {
/// fn speak(&self) -> &'static str { "woof" }
/// }
///
/// struct Rock; // does not implement Animal
///
/// let dog = Dog;
/// let rock = Rock;
///
/// let as_animal: Option<&dyn Animal> = try_as_dyn::<Dog, dyn Animal>(&dog);
/// assert_eq!(as_animal.unwrap().speak(), "woof");
///
/// let not_an_animal: Option<&dyn Animal> = try_as_dyn::<Rock, dyn Animal>(&rock);
/// assert!(not_an_animal.is_none());
/// ```
#[must_use]
#[unstable(feature = "try_as_dyn", issue = "144361")]
pub const fn try_as_dyn<
T: Any + 'static,
U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static,
>(
t: &T,
) -> Option<&U> {
let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() };
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,
}
}
/// Returns `Some(&mut U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`.
///
/// # Compile-time failures
/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution.
/// In some cases, that resolution can exceed the recursion limit,
/// and compilation will fail instead of this function returning `None`.
/// # Examples
///
/// ```rust
/// #![feature(try_as_dyn)]
///
/// use core::any::try_as_dyn_mut;
///
/// trait Animal {
/// fn speak(&self) -> &'static str;
/// }
///
/// struct Dog;
/// impl Animal for Dog {
/// fn speak(&self) -> &'static str { "woof" }
/// }
///
/// struct Rock; // does not implement Animal
///
/// let mut dog = Dog;
/// let mut rock = Rock;
///
/// let as_animal: Option<&mut dyn Animal> = try_as_dyn_mut::<Dog, dyn Animal>(&mut dog);
/// assert_eq!(as_animal.unwrap().speak(), "woof");
///
/// let not_an_animal: Option<&mut dyn Animal> = try_as_dyn_mut::<Rock, dyn Animal>(&mut rock);
/// assert!(not_an_animal.is_none());
/// ```
#[must_use]
#[unstable(feature = "try_as_dyn", issue = "144361")]
pub const fn try_as_dyn_mut<
T: Any + 'static,
U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static,
>(
t: &mut T,
) -> Option<&mut U> {
let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() };
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,
}
}

View file

@ -2754,6 +2754,22 @@ pub unsafe fn vtable_size(ptr: *const ()) -> usize;
#[rustc_intrinsic]
pub unsafe fn vtable_align(ptr: *const ()) -> usize;
/// The intrinsic returns the `U` vtable for `T` if `T` can be coerced to the trait object type `U`.
///
/// # Compile-time failures
/// Determining whether `T` can be coerced to the trait object type `U` requires trait resolution by the compiler.
/// In some cases, that resolution can exceed the recursion limit,
/// and compilation will fail instead of this function returning `None`.
///
/// # Safety
///
/// `ptr` must point to a vtable.
#[rustc_nounwind]
#[unstable(feature = "core_intrinsics", issue = "none")]
#[rustc_intrinsic]
pub const fn vtable_for<T, U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized>()
-> Option<ptr::DynMetadata<U>>;
/// The size of a type in bytes.
///
/// Note that, unlike most intrinsics, this is safe to call;

View file

@ -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() {
@ -193,3 +196,17 @@ fn carrying_mul_add_fallback_i128() {
(u128::MAX - 1, -(i128::MIN / 2)),
);
}
#[test]
fn test_vtable_for() {
#[derive(Debug)]
struct A {}
struct B {}
const A_VTABLE: Option<DynMetadata<dyn Debug>> = vtable_for::<A, dyn Debug>();
assert!(A_VTABLE.is_some());
const B_VTABLE: Option<DynMetadata<dyn Debug>> = vtable_for::<B, dyn Debug>();
assert!(B_VTABLE.is_none());
}

View file

@ -0,0 +1,29 @@
//@ run-pass
#![feature(try_as_dyn)]
use std::fmt::Debug;
// Look ma, no `T: Debug`
fn debug_format_with_try_as_dyn<T: 'static>(t: &T) -> String {
match std::any::try_as_dyn::<_, 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 = debug_format_with_try_as_dyn(&a);
assert_eq!("A { index: 42 }", result);
struct B {}
let b = B {};
let result = debug_format_with_try_as_dyn(&b);
assert_eq!("default", result);
}

View file

@ -0,0 +1,20 @@
//@ run-pass
#![feature(try_as_dyn)]
use std::fmt::{Error, Write};
// Look ma, no `T: Write`
fn try_as_dyn_mut_write<T: 'static>(t: &mut T, s: &str) -> Result<(), Error> {
match std::any::try_as_dyn_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();
try_as_dyn_mut_write(&mut buf, " world!").unwrap();
assert_eq!(buf, "Hello world!");
}

View file

@ -0,0 +1,22 @@
//@ run-pass
#![feature(try_as_dyn)]
use std::any::try_as_dyn;
trait Trait {
}
impl Trait for for<'a> fn(&'a Box<i32>) {
}
fn store(_: &'static Box<i32>) {
}
fn main() {
let fn_ptr: fn(&'static Box<i32>) = store;
let dt = try_as_dyn::<_, dyn Trait>(&fn_ptr);
assert!(dt.is_none());
}

View file

@ -0,0 +1,16 @@
//@ run-pass
#![feature(try_as_dyn)]
use std::any::try_as_dyn;
trait Trait<T> {
}
impl Trait<for<'a> fn(&'a Box<i32>)> for () {
}
fn main() {
let dt = try_as_dyn::<_, dyn Trait<fn(&'static Box<i32>)>>(&());
assert!(dt.is_none());
}