Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,7 @@ symbols! {
vsreg,
vsx,
vtable_align,
vtable_for,
vtable_size,
warn,
wasip2,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_type_ir/src/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
108 changes: 107 additions & 1 deletion library/core/src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

#![stable(feature = "rust1", since = "1.0.0")]

use crate::{fmt, hash, intrinsics};
use crate::{fmt, hash, intrinsics, ptr};

///////////////////////////////////////////////////////////////////////////////
// Any trait
Expand Down Expand Up @@ -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,
}
}
16 changes: 16 additions & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion library/coretests/tests/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -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 changes: 29 additions & 0 deletions tests/ui/any/try_as_dyn.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions tests/ui/any/try_as_dyn_mut.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 22 additions & 0 deletions tests/ui/any/try_as_dyn_soundness_test1.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions tests/ui/any/try_as_dyn_soundness_test2.rs
Original file line number Diff line number Diff line change
@@ -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