Skip to content

Commit

Permalink
MetadataCast
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukas Markeffsky committed Jan 18, 2024
1 parent 2457c02 commit afb5668
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 1 deletion.
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ language_item_table! {
PointeeTrait, sym::pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None;
Metadata, sym::metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None;
DynMetadata, sym::dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None;
MetadataCast, sym::metadata_cast, metadata_cast_trait, Target::Trait, GenericRequirement::Minimum(1);

Freeze, sym::freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0);

Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_middle/src/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,20 @@ pub enum SelectionCandidate<'tcx> {
/// A builtin implementation for some specific traits, used in cases
/// where we cannot rely an ordinary library implementations.
///
/// The most notable examples are `sized`, `Copy` and `Clone`. This is also
/// The most notable examples are `Sized`, `Copy` and `Clone`. This is also
/// used for the `DiscriminantKind` and `Pointee` trait, both of which have
/// an associated type.
BuiltinCandidate {
/// `false` if there are no *further* obligations.
has_nested: bool,
},

/// Implementation of the `MetadataCast<T>` trait.
MetadataCastCandidate {
/// Whether we should check for `Self == T`.
require_eq: bool,
},

/// Implementation of transmutability trait.
TransmutabilityCandidate,

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 @@ -1015,6 +1015,7 @@ symbols! {
memtag,
message,
meta,
metadata_cast,
metadata_type,
min_align_of,
min_align_of_val,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
self.assemble_candidate_for_pointer_like(obligation, &mut candidates);
} else if lang_items.fn_ptr_trait() == Some(def_id) {
self.assemble_candidates_for_fn_ptr_trait(obligation, &mut candidates);
} else if lang_items.metadata_cast_trait() == Some(def_id) {
self.assemble_candidates_for_metadata_cast(obligation, &mut candidates);
} else {
if lang_items.clone_trait() == Some(def_id) {
// Same builtin conditions as `Copy`, i.e., every type which has builtin support
Expand Down Expand Up @@ -1107,4 +1109,48 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
}
}

fn assemble_candidates_for_metadata_cast(
&mut self,
obligation: &PolyTraitObligation<'tcx>,
candidates: &mut SelectionCandidateSet<'tcx>,
) {
let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1);
let target = self.infcx.shallow_resolve(target);

if let ty::Tuple(tys) = target.kind()
&& tys.is_empty()
{
candidates.vec.push(MetadataCastCandidate { require_eq: false });
return;
}

let source = obligation.self_ty().skip_binder();
let source = self.infcx.shallow_resolve(source);

if let ty::Adt(src_def, src_args) = source.kind()
&& let ty::Adt(tgt_def, tgt_args) = target.kind()
&& src_def == tgt_def
&& Some(src_def.did()) == self.tcx().lang_items().dyn_metadata()
{
let src_dyn = src_args.type_at(0);
let tgt_dyn = tgt_args.type_at(0);

// We could theoretically allow casting the principal away, but `as` casts
// don't allow that, so neither does `MetadataCast` for now.
if let ty::Dynamic(src_pred, _, ty::Dyn) = src_dyn.kind()
&& let ty::Dynamic(tgt_pred, _, ty::Dyn) = tgt_dyn.kind()
&& src_pred.principal_def_id() == tgt_pred.principal_def_id()
{
candidates.vec.push(MetadataCastCandidate { require_eq: false });
return;
}
}

if source.has_non_region_infer() || target.has_non_region_infer() {
candidates.ambiguous = true;
} else if self.infcx.can_eq(obligation.param_env, source, target) {
candidates.vec.push(MetadataCastCandidate { require_eq: true });
}
}
}
19 changes: 19 additions & 0 deletions compiler/rustc_trait_selection/src/traits/select/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSource::Builtin(BuiltinImplSource::Misc, data)
}

MetadataCastCandidate { require_eq } => {
let data = if require_eq {
let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1);
let source = obligation.self_ty().skip_binder();

let InferOk { obligations, .. } = self
.infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::No, target, source)
.map_err(|_| Unimplemented)?;

obligations
} else {
Vec::new()
};

ImplSource::Builtin(BuiltinImplSource::Misc, data)
}

TransmutabilityCandidate => {
let data = self.confirm_transmutability_candidate(obligation)?;
ImplSource::Builtin(BuiltinImplSource::Misc, data)
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_trait_selection/src/traits/select/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,9 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
DropVictim::No
}

(MetadataCastCandidate { require_eq: false }, _) => DropVictim::Yes,
(_, MetadataCastCandidate { require_eq: false }) => DropVictim::No,

(ParamCandidate(other), ParamCandidate(victim)) => {
let same_except_bound_vars = other.skip_binder().trait_ref
== victim.skip_binder().trait_ref
Expand Down Expand Up @@ -1873,6 +1876,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| MetadataCastCandidate { .. }
| TraitAliasCandidate
| ObjectCandidate(_)
| ProjectionCandidate(_),
Expand Down Expand Up @@ -1903,6 +1907,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| MetadataCastCandidate { require_eq: true }
| TraitAliasCandidate,
ParamCandidate(ref victim_cand),
) => {
Expand Down Expand Up @@ -1939,6 +1944,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| MetadataCastCandidate { .. }
| TraitAliasCandidate,
) => DropVictim::Yes,

Expand All @@ -1955,6 +1961,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { .. }
| MetadataCastCandidate { .. }
| TraitAliasCandidate,
ObjectCandidate(_) | ProjectionCandidate(_),
) => DropVictim::No,
Expand Down Expand Up @@ -2063,6 +2070,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| MetadataCastCandidate { require_eq: true }
| TraitAliasCandidate,
ImplCandidate(_)
| ClosureCandidate { .. }
Expand All @@ -2075,6 +2083,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
| BuiltinUnsizeCandidate
| TraitUpcastingUnsizeCandidate(_)
| BuiltinCandidate { has_nested: true }
| MetadataCastCandidate { require_eq: true }
| TraitAliasCandidate,
) => DropVictim::No,
}
Expand Down
9 changes: 9 additions & 0 deletions library/core/src/ptr/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ pub trait Pointee {
// NOTE: don’t stabilize this before trait aliases are stable in the language?
pub trait Thin = Pointee<Metadata = ()>;

/// Provides information about legal pointer-to-pointer casts.
///
/// If a type `T` implements `MetadataCast<U>`, then a pointer with a metadata
/// of `T` can be cast to a pointer with a metadata of `U`.
#[unstable(feature = "ptr_metadata_cast", issue = "none")]
#[cfg_attr(not(bootstrap), lang = "metadata_cast")]
#[rustc_deny_explicit_impl(implement_via_object = false)]
pub trait MetadataCast<T: ?Sized> {}

/// Extract the metadata component of a pointer.
///
/// Values of type `*mut T`, `&T`, or `&mut T` can be passed directly to this function
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ pub use crate::intrinsics::copy;
pub use crate::intrinsics::write_bytes;

mod metadata;
#[unstable(feature = "ptr_metadata_cast", issue = "none")]
pub use metadata::MetadataCast;
#[unstable(feature = "ptr_metadata", issue = "81513")]
pub use metadata::{from_raw_parts, from_raw_parts_mut, metadata, DynMetadata, Pointee, Thin};

Expand Down
56 changes: 56 additions & 0 deletions tests/ui/traits/metadata-cast.lifetimes.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
error: lifetime may not live long enough
--> $DIR/metadata-cast.rs:63:5
|
LL | fn lifetimes_err1<'a, 'b>() {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | check::<DynMetadata<&'a ()>, DynMetadata<&'b ()>>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'b`
|
= help: consider adding the following bound: `'a: 'b`

error: lifetime may not live long enough
--> $DIR/metadata-cast.rs:63:5
|
LL | fn lifetimes_err1<'a, 'b>() {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | check::<DynMetadata<&'a ()>, DynMetadata<&'b ()>>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a`
|
= help: consider adding the following bound: `'b: 'a`

help: `'a` and `'b` must be the same: replace one with the other

error: lifetime may not live long enough
--> $DIR/metadata-cast.rs:68:5
|
LL | fn lifetimes_err2<'a, 'b>() {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | check::<&'a (), &'b ()>();
| ^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'b`
|
= help: consider adding the following bound: `'a: 'b`

error: lifetime may not live long enough
--> $DIR/metadata-cast.rs:68:5
|
LL | fn lifetimes_err2<'a, 'b>() {
| -- -- lifetime `'b` defined here
| |
| lifetime `'a` defined here
LL | check::<&'a (), &'b ()>();
| ^^^^^^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'a`
|
= help: consider adding the following bound: `'b: 'a`

help: `'a` and `'b` must be the same: replace one with the other
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 4 previous errors

92 changes: 92 additions & 0 deletions tests/ui/traits/metadata-cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// revisions: traits lifetimes
#![feature(ptr_metadata, ptr_metadata_cast)]

use std::ptr::{Pointee, MetadataCast, DynMetadata};

fn cast<T: ?Sized, U: ?Sized>(_: *mut T) -> *mut U
where
<T as Pointee>::Metadata: MetadataCast<<U as Pointee>::Metadata>,
{
todo!()
}

fn check<T: ?Sized, U: ?Sized>() where T: MetadataCast<U> {}

trait Trait {}
trait Trait2 {}

fn main() {
// any to () is OK
check::<(), ()>();
check::<usize, ()>();
check::<DynMetadata<dyn Trait>, ()>();

// same types are OK
check::<usize, usize>();
check::<i32, i32>();
check::<DynMetadata<dyn Trait>, DynMetadata<dyn Trait>>();

// changing auto traits of trait object in DynMetadata is OK
check::<DynMetadata<dyn Send>, DynMetadata<dyn Sync>>();
check::<DynMetadata<dyn Trait + Send>, DynMetadata<dyn Trait + Sync>>();

#[cfg(traits)]
{
check::<(), usize>(); //[traits]~ ERROR not satisfied
check::<(), DynMetadata<dyn Trait>>(); //[traits]~ ERROR not satisfied
check::<usize, DynMetadata<dyn Trait>>(); //[traits]~ ERROR not satisfied
check::<DynMetadata<dyn Trait>, usize>(); //[traits]~ ERROR not satisfied
check::<DynMetadata<dyn Trait>, DynMetadata<dyn Trait2>>(); //[traits]~ ERROR not satisfied
check::<dyn Trait + Send, dyn Trait + Sync>(); //[traits]~ ERROR not satisfied
}

// `dyn MetadataCast<usize>` should not implement `MetadataCast<usize>`
check::<dyn MetadataCast<usize>, ()>();
check::<dyn MetadataCast<usize>, dyn MetadataCast<usize>>();
#[cfg(traits)]
check::<dyn MetadataCast<usize>, usize>(); //[traits]~ ERROR not satisfied

// this could pass in the future, but for now it matches the behavior of `as` casts
#[cfg(traits)]
check::<DynMetadata<dyn Trait>, DynMetadata<dyn Send>>(); //[traits]~ ERROR not satisfied
}

fn lifetimes_ok<'a, 'b>() {
// changing lifetimes of trait object in DynMetadata is OK
check::<DynMetadata<dyn Trait + 'a>, DynMetadata<dyn Trait + 'b>>();
check::<DynMetadata<dyn Trait + 'b>, DynMetadata<dyn Trait + 'a>>();

// otherwise, require equal lifetimes
check::<&'a (), &'a ()>();
}
fn lifetimes_err1<'a, 'b>() {
check::<DynMetadata<&'a ()>, DynMetadata<&'b ()>>();
//[lifetimes]~^ ERROR may not live long enough
//[lifetimes]~| ERROR may not live long enough
}
fn lifetimes_err2<'a, 'b>() {
check::<&'a (), &'b ()>();
//[lifetimes]~^ ERROR may not live long enough
//[lifetimes]~| ERROR may not live long enough
}

fn do_casts<'a>(thin: &mut i32, slice: &mut [i32], trait_object: &mut dyn Trait) {
let _: *mut u8 = cast(thin);
let _: *mut u8 = cast(slice);
let _: *mut [u8] = cast(slice);
let _: *mut u8 = cast(trait_object);
let _: *mut (dyn Trait + Send + 'a) = cast(trait_object);


#[cfg(traits)]
{
let _: *mut [u8] = cast(thin); //[traits]~ ERROR not satisfied
let _: *mut dyn Trait = cast(thin); //[traits]~ ERROR not satisfied
let _: *mut [u8] = cast(trait_object); //[traits]~ ERROR not satisfied
let _: *mut dyn Trait = cast(slice); //[traits]~ ERROR not satisfied
let _: *mut dyn Trait2 = cast(trait_object); //[traits]~ ERROR not satisfied

// may be allowed in the future
let _: *mut dyn Send = cast(trait_object); //[traits]~ ERROR not satisfied
}
}
Loading

0 comments on commit afb5668

Please sign in to comment.