diff --git a/Cargo.lock b/Cargo.lock index b7fc2de20b555..25425acea3529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1890,9 +1890,9 @@ dependencies = [ [[package]] name = "ipc-channel" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1c98b70019c830a1fc39cecfe1f60ff99c4122f0a189697c810c90ec545c14" +checksum = "1700f6b8b9f00cdd675f32fbb3a5be882213140dfe045805273221ca266c43f8" dependencies = [ "bincode", "crossbeam-channel", diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 5e3d0a15d8bc1..d0f44c7a0e849 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -4,14 +4,17 @@ 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_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::{bug, ty}; +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}; use tracing::trace; use super::memory::MemoryKind; @@ -149,6 +152,49 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let b_ty = self.read_type_id(&args[1])?; self.write_scalar(Scalar::from_bool(a_ty == b_ty), 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, _, ty::Dyn) = result_ty.kind() else { + 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 ocx = ObligationCtxt::new(&infcx); + ocx.register_obligations(preds.iter().map(|pred| { + 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.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 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)?; + } 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() { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 6e5fe3823ab51..02acbbc953575 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -94,6 +94,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::discriminant_value | sym::type_id | sym::type_id_eq + | sym::vtable_for | sym::select_unpredictable | sym::cold_path | sym::ptr_guaranteed_cmp @@ -553,6 +554,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 d54175548e30e..969e0c453934c 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2350,6 +2350,7 @@ symbols! { vreg_low16, vsx, vtable_align, + vtable_for, vtable_size, warn, wasip2, 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). diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 38393379a78a7..a655aec4739e2 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,45 @@ 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 = "144361")] +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); + // 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, + } +} + +#[allow(missing_docs)] +#[must_use] +#[unstable(feature = "downcast_trait", issue = "144361")] +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); + // 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, + } +} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 106cc725fee2c..c6a8ca119c41b 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2657,6 +2657,18 @@ 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> + ?Sized>() +-> 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..c6d841b8383a8 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() { @@ -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> = vtable_for::(); + assert!(A_VTABLE.is_some()); + + const B_VTABLE: Option> = vtable_for::(); + assert!(B_VTABLE.is_none()); +} 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); +} diff --git a/tests/ui/any/downcast_trait_err1.rs b/tests/ui/any/downcast_trait_err1.rs new file mode 100644 index 0000000000000..95e8286218401 --- /dev/null +++ b/tests/ui/any/downcast_trait_err1.rs @@ -0,0 +1,34 @@ +//@ 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; + 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 new file mode 100644 index 0000000000000..f2a1fd289684b --- /dev/null +++ b/tests/ui/any/downcast_trait_err2.rs @@ -0,0 +1,32 @@ +//@ 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)); + 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") + } +} 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!"); +}