Skip to content

Commit 25b73c4

Browse files
Rollup merge of #150033 - izagawd:try_as_dyn, r=oli-obk
Add try_as_dyn and try_as_dyn_mut Tracking issue: #144361 Continuation of: #144363
2 parents f3fa567 + d5bf1a4 commit 25b73c4

File tree

11 files changed

+299
-4
lines changed

11 files changed

+299
-4
lines changed

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ mod simd;
66

77
use std::assert_matches::assert_matches;
88

9-
use rustc_abi::{FieldIdx, HasDataLayout, Size, VariantIdx};
9+
use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx};
1010
use rustc_apfloat::ieee::{Double, Half, Quad, Single};
11+
use rustc_hir::def_id::CRATE_DEF_ID;
12+
use rustc_infer::infer::TyCtxtInferExt;
1113
use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint};
1214
use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic};
1315
use rustc_middle::ty::layout::TyAndLayout;
14-
use rustc_middle::ty::{FloatTy, Ty, TyCtxt};
16+
use rustc_middle::ty::{FloatTy, PolyExistentialPredicate, Ty, TyCtxt, TypeFoldable};
1517
use rustc_middle::{bug, span_bug, ty};
1618
use rustc_span::{Symbol, sym};
19+
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
1720
use tracing::trace;
1821

1922
use super::memory::MemoryKind;
@@ -219,6 +222,49 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
219222

220223
self.write_scalar(Scalar::from_target_usize(offset, self), dest)?;
221224
}
225+
sym::vtable_for => {
226+
let tp_ty = instance.args.type_at(0);
227+
let result_ty = instance.args.type_at(1);
228+
229+
ensure_monomorphic_enough(tcx, tp_ty)?;
230+
ensure_monomorphic_enough(tcx, result_ty)?;
231+
let ty::Dynamic(preds, _) = result_ty.kind() else {
232+
span_bug!(
233+
self.find_closest_untracked_caller_location(),
234+
"Invalid type provided to vtable_for::<T, U>. U must be dyn Trait, got {result_ty}."
235+
);
236+
};
237+
238+
let (infcx, param_env) =
239+
self.tcx.infer_ctxt().build_with_typing_env(self.typing_env);
240+
241+
let ocx = ObligationCtxt::new(&infcx);
242+
ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| {
243+
let pred = pred.with_self_ty(tcx, tp_ty);
244+
// Lifetimes can only be 'static because of the bound on T
245+
let pred = pred.fold_with(&mut ty::BottomUpFolder {
246+
tcx,
247+
ty_op: |ty| ty,
248+
lt_op: |lt| {
249+
if lt == tcx.lifetimes.re_erased { tcx.lifetimes.re_static } else { lt }
250+
},
251+
ct_op: |ct| ct,
252+
});
253+
Obligation::new(tcx, ObligationCause::dummy(), param_env, pred)
254+
}));
255+
let type_impls_trait = ocx.evaluate_obligations_error_on_ambiguity().is_empty();
256+
// Since `assumed_wf_tys=[]` the choice of LocalDefId is irrelevant, so using the "default"
257+
let regions_are_valid = ocx.resolve_regions(CRATE_DEF_ID, param_env, []).is_empty();
258+
259+
if regions_are_valid && type_impls_trait {
260+
let vtable_ptr = self.get_vtable_ptr(tp_ty, preds)?;
261+
// Writing a non-null pointer into an `Option<NonNull>` will automatically make it `Some`.
262+
self.write_pointer(vtable_ptr, dest)?;
263+
} else {
264+
// Write `None`
265+
self.write_discriminant(FIRST_VARIANT, dest)?;
266+
}
267+
}
222268
sym::variant_count => {
223269
let tp_ty = instance.args.type_at(0);
224270
let ty = match tp_ty.kind() {

compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
215215
| sym::type_name
216216
| sym::ub_checks
217217
| sym::variant_count
218+
| sym::vtable_for
218219
| sym::wrapping_add
219220
| sym::wrapping_mul
220221
| sym::wrapping_sub
@@ -643,6 +644,20 @@ pub(crate) fn check_intrinsic_type(
643644
(0, 0, vec![Ty::new_imm_ptr(tcx, tcx.types.unit)], tcx.types.usize)
644645
}
645646

647+
sym::vtable_for => {
648+
let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span);
649+
let dyn_metadata_adt_ref = tcx.adt_def(dyn_metadata);
650+
let dyn_metadata_args = tcx.mk_args(&[param(1).into()]);
651+
let dyn_ty = Ty::new_adt(tcx, dyn_metadata_adt_ref, dyn_metadata_args);
652+
653+
let option_did = tcx.require_lang_item(LangItem::Option, span);
654+
let option_adt_ref = tcx.adt_def(option_did);
655+
let option_args = tcx.mk_args(&[dyn_ty.into()]);
656+
let ret_ty = Ty::new_adt(tcx, option_adt_ref, option_args);
657+
658+
(2, 0, vec![], ret_ty)
659+
}
660+
646661
// This type check is not particularly useful, but the `where` bounds
647662
// on the definition in `core` do the heavy lifting for checking it.
648663
sym::aggregate_raw_ptr => (3, 0, vec![param(1), param(2)], param(0)),

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,7 @@ symbols! {
24742474
vsreg,
24752475
vsx,
24762476
vtable_align,
2477+
vtable_for,
24772478
vtable_size,
24782479
warn,
24792480
wasip2,

compiler/rustc_type_ir/src/predicate.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ pub enum ExistentialPredicate<I: Interner> {
293293
impl<I: Interner> Eq for ExistentialPredicate<I> {}
294294

295295
impl<I: Interner> ty::Binder<I, ExistentialPredicate<I>> {
296+
pub fn def_id(&self) -> I::DefId {
297+
match self.skip_binder() {
298+
ExistentialPredicate::Trait(tr) => tr.def_id.into(),
299+
ExistentialPredicate::Projection(p) => p.def_id.into(),
300+
ExistentialPredicate::AutoTrait(did) => did.into(),
301+
}
302+
}
296303
/// Given an existential predicate like `?Self: PartialEq<u32>` (e.g., derived from `dyn PartialEq<u32>`),
297304
/// and a concrete type `self_ty`, returns a full predicate where the existentially quantified variable `?Self`
298305
/// has been replaced with `self_ty` (e.g., `self_ty: PartialEq<u32>`, in our example).

library/core/src/any.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
8787
#![stable(feature = "rust1", since = "1.0.0")]
8888

89-
use crate::{fmt, hash, intrinsics};
89+
use crate::{fmt, hash, intrinsics, ptr};
9090

9191
///////////////////////////////////////////////////////////////////////////////
9292
// Any trait
@@ -906,3 +906,109 @@ pub const fn type_name<T: ?Sized>() -> &'static str {
906906
pub const fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
907907
type_name::<T>()
908908
}
909+
910+
/// Returns `Some(&U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`.
911+
///
912+
/// # Compile-time failures
913+
/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution.
914+
/// In some cases, that resolution can exceed the recursion limit,
915+
/// and compilation will fail instead of this function returning `None`.
916+
/// # Examples
917+
///
918+
/// ```rust
919+
/// #![feature(try_as_dyn)]
920+
///
921+
/// use core::any::try_as_dyn;
922+
///
923+
/// trait Animal {
924+
/// fn speak(&self) -> &'static str;
925+
/// }
926+
///
927+
/// struct Dog;
928+
/// impl Animal for Dog {
929+
/// fn speak(&self) -> &'static str { "woof" }
930+
/// }
931+
///
932+
/// struct Rock; // does not implement Animal
933+
///
934+
/// let dog = Dog;
935+
/// let rock = Rock;
936+
///
937+
/// let as_animal: Option<&dyn Animal> = try_as_dyn::<Dog, dyn Animal>(&dog);
938+
/// assert_eq!(as_animal.unwrap().speak(), "woof");
939+
///
940+
/// let not_an_animal: Option<&dyn Animal> = try_as_dyn::<Rock, dyn Animal>(&rock);
941+
/// assert!(not_an_animal.is_none());
942+
/// ```
943+
#[must_use]
944+
#[unstable(feature = "try_as_dyn", issue = "144361")]
945+
pub const fn try_as_dyn<
946+
T: Any + 'static,
947+
U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static,
948+
>(
949+
t: &T,
950+
) -> Option<&U> {
951+
let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() };
952+
match vtable {
953+
Some(dyn_metadata) => {
954+
let pointer = ptr::from_raw_parts(t, dyn_metadata);
955+
// SAFETY: `t` is a reference to a type, so we know it is valid.
956+
// `dyn_metadata` is a vtable for T, implementing the trait of `U`.
957+
Some(unsafe { &*pointer })
958+
}
959+
None => None,
960+
}
961+
}
962+
963+
/// Returns `Some(&mut U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`.
964+
///
965+
/// # Compile-time failures
966+
/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution.
967+
/// In some cases, that resolution can exceed the recursion limit,
968+
/// and compilation will fail instead of this function returning `None`.
969+
/// # Examples
970+
///
971+
/// ```rust
972+
/// #![feature(try_as_dyn)]
973+
///
974+
/// use core::any::try_as_dyn_mut;
975+
///
976+
/// trait Animal {
977+
/// fn speak(&self) -> &'static str;
978+
/// }
979+
///
980+
/// struct Dog;
981+
/// impl Animal for Dog {
982+
/// fn speak(&self) -> &'static str { "woof" }
983+
/// }
984+
///
985+
/// struct Rock; // does not implement Animal
986+
///
987+
/// let mut dog = Dog;
988+
/// let mut rock = Rock;
989+
///
990+
/// let as_animal: Option<&mut dyn Animal> = try_as_dyn_mut::<Dog, dyn Animal>(&mut dog);
991+
/// assert_eq!(as_animal.unwrap().speak(), "woof");
992+
///
993+
/// let not_an_animal: Option<&mut dyn Animal> = try_as_dyn_mut::<Rock, dyn Animal>(&mut rock);
994+
/// assert!(not_an_animal.is_none());
995+
/// ```
996+
#[must_use]
997+
#[unstable(feature = "try_as_dyn", issue = "144361")]
998+
pub const fn try_as_dyn_mut<
999+
T: Any + 'static,
1000+
U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized + 'static,
1001+
>(
1002+
t: &mut T,
1003+
) -> Option<&mut U> {
1004+
let vtable: Option<ptr::DynMetadata<U>> = const { intrinsics::vtable_for::<T, U>() };
1005+
match vtable {
1006+
Some(dyn_metadata) => {
1007+
let pointer = ptr::from_raw_parts_mut(t, dyn_metadata);
1008+
// SAFETY: `t` is a reference to a type, so we know it is valid.
1009+
// `dyn_metadata` is a vtable for T, implementing the trait of `U`.
1010+
Some(unsafe { &mut *pointer })
1011+
}
1012+
None => None,
1013+
}
1014+
}

library/core/src/intrinsics/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,22 @@ pub unsafe fn vtable_size(ptr: *const ()) -> usize;
27542754
#[rustc_intrinsic]
27552755
pub unsafe fn vtable_align(ptr: *const ()) -> usize;
27562756

2757+
/// The intrinsic returns the `U` vtable for `T` if `T` can be coerced to the trait object type `U`.
2758+
///
2759+
/// # Compile-time failures
2760+
/// Determining whether `T` can be coerced to the trait object type `U` requires trait resolution by the compiler.
2761+
/// In some cases, that resolution can exceed the recursion limit,
2762+
/// and compilation will fail instead of this function returning `None`.
2763+
///
2764+
/// # Safety
2765+
///
2766+
/// `ptr` must point to a vtable.
2767+
#[rustc_nounwind]
2768+
#[unstable(feature = "core_intrinsics", issue = "none")]
2769+
#[rustc_intrinsic]
2770+
pub const fn vtable_for<T, U: ptr::Pointee<Metadata = ptr::DynMetadata<U>> + ?Sized>()
2771+
-> Option<ptr::DynMetadata<U>>;
2772+
27572773
/// The size of a type in bytes.
27582774
///
27592775
/// Note that, unlike most intrinsics, this is safe to call;

library/coretests/tests/intrinsics.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use core::any::TypeId;
2-
use core::intrinsics::assume;
2+
use core::intrinsics::{assume, vtable_for};
3+
use std::fmt::Debug;
4+
use std::option::Option;
5+
use std::ptr::DynMetadata;
36

47
#[test]
58
fn test_typeid_sized_types() {
@@ -193,3 +196,17 @@ fn carrying_mul_add_fallback_i128() {
193196
(u128::MAX - 1, -(i128::MIN / 2)),
194197
);
195198
}
199+
200+
#[test]
201+
fn test_vtable_for() {
202+
#[derive(Debug)]
203+
struct A {}
204+
205+
struct B {}
206+
207+
const A_VTABLE: Option<DynMetadata<dyn Debug>> = vtable_for::<A, dyn Debug>();
208+
assert!(A_VTABLE.is_some());
209+
210+
const B_VTABLE: Option<DynMetadata<dyn Debug>> = vtable_for::<B, dyn Debug>();
211+
assert!(B_VTABLE.is_none());
212+
}

tests/ui/any/try_as_dyn.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//@ run-pass
2+
#![feature(try_as_dyn)]
3+
4+
use std::fmt::Debug;
5+
6+
// Look ma, no `T: Debug`
7+
fn debug_format_with_try_as_dyn<T: 'static>(t: &T) -> String {
8+
match std::any::try_as_dyn::<_, dyn Debug>(t) {
9+
Some(d) => format!("{d:?}"),
10+
None => "default".to_string()
11+
}
12+
}
13+
14+
// Test that downcasting to a dyn trait works as expected
15+
fn main() {
16+
#[allow(dead_code)]
17+
#[derive(Debug)]
18+
struct A {
19+
index: usize
20+
}
21+
let a = A { index: 42 };
22+
let result = debug_format_with_try_as_dyn(&a);
23+
assert_eq!("A { index: 42 }", result);
24+
25+
struct B {}
26+
let b = B {};
27+
let result = debug_format_with_try_as_dyn(&b);
28+
assert_eq!("default", result);
29+
}

tests/ui/any/try_as_dyn_mut.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ run-pass
2+
#![feature(try_as_dyn)]
3+
4+
use std::fmt::{Error, Write};
5+
6+
// Look ma, no `T: Write`
7+
fn try_as_dyn_mut_write<T: 'static>(t: &mut T, s: &str) -> Result<(), Error> {
8+
match std::any::try_as_dyn_mut::<_, dyn Write>(t) {
9+
Some(w) => w.write_str(s),
10+
None => Ok(())
11+
}
12+
}
13+
14+
// Test that downcasting to a mut dyn trait works as expected
15+
fn main() {
16+
let mut buf = "Hello".to_string();
17+
18+
try_as_dyn_mut_write(&mut buf, " world!").unwrap();
19+
assert_eq!(buf, "Hello world!");
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ run-pass
2+
#![feature(try_as_dyn)]
3+
4+
use std::any::try_as_dyn;
5+
6+
trait Trait {
7+
8+
}
9+
10+
impl Trait for for<'a> fn(&'a Box<i32>) {
11+
12+
}
13+
14+
fn store(_: &'static Box<i32>) {
15+
16+
}
17+
18+
fn main() {
19+
let fn_ptr: fn(&'static Box<i32>) = store;
20+
let dt = try_as_dyn::<_, dyn Trait>(&fn_ptr);
21+
assert!(dt.is_none());
22+
}

0 commit comments

Comments
 (0)