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:
parent
0160933b1d
commit
d5bf1a4c9a
11 changed files with 299 additions and 4 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -2473,6 +2473,7 @@ symbols! {
|
|||
vsreg,
|
||||
vsx,
|
||||
vtable_align,
|
||||
vtable_for,
|
||||
vtable_size,
|
||||
warn,
|
||||
wasip2,
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
29
tests/ui/any/try_as_dyn.rs
Normal file
29
tests/ui/any/try_as_dyn.rs
Normal 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);
|
||||
}
|
||||
20
tests/ui/any/try_as_dyn_mut.rs
Normal file
20
tests/ui/any/try_as_dyn_mut.rs
Normal 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!");
|
||||
}
|
||||
22
tests/ui/any/try_as_dyn_soundness_test1.rs
Normal file
22
tests/ui/any/try_as_dyn_soundness_test1.rs
Normal 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());
|
||||
}
|
||||
16
tests/ui/any/try_as_dyn_soundness_test2.rs
Normal file
16
tests/ui/any/try_as_dyn_soundness_test2.rs
Normal 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());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue