Skip to content

Commit a4d2ae8

Browse files
committed
[project] Support projecting enum tags
gherrit-pr-id: G06bb22c59364654d5c0b44cdcb9b70ad58fe8092
1 parent 1f5eea2 commit a4d2ae8

File tree

10 files changed

+535
-16
lines changed

10 files changed

+535
-16
lines changed

src/impls.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,30 @@ const _: () = {
760760
)]
761761
pub enum value {}
762762

763+
// SAFETY: See safety comment on `ProjectToTag`.
764+
unsafe impl<T: ?Sized> HasTag for ManuallyDrop<T> {
765+
#[inline]
766+
fn only_derive_is_allowed_to_implement_this_trait()
767+
where
768+
Self: Sized,
769+
{
770+
}
771+
772+
type Tag = ();
773+
774+
// SAFETY: It is trivially sound to project any pointer to a pointer to
775+
// a type of size zero and alignment 1 (which `()` is [1]). Such a
776+
// pointer will trivially satisfy its aliasing and validity requirements
777+
// (since it has a zero-sized referent), and its alignment requirement
778+
// (since it is aligned to 1).
779+
//
780+
// [1] Per https://doc.rust-lang.org/1.92.0/reference/type-layout.html#r-layout.tuple.unit:
781+
//
782+
// [T]he unit tuple (`()`)... is guaranteed as a zero-sized type to
783+
// have a size of 0 and an alignment of 1.
784+
type ProjectToTag = crate::pointer::cast::CastToUnit;
785+
}
786+
763787
// SAFETY: `ManuallyDrop<T>` has a field of type `T` at offset `0` without
764788
// any safety invariants beyond those of `T`. Its existence is not
765789
// explicitly documented, but it can be inferred; per [1] `ManuallyDrop<T>`
@@ -779,15 +803,15 @@ const _: () = {
779803
HasField<value, { crate::STRUCT_VARIANT_ID }, { crate::ident_id!(value) }>
780804
for ManuallyDrop<T>
781805
{
782-
type Type = T;
783-
784806
#[inline]
785807
fn only_derive_is_allowed_to_implement_this_trait()
786808
where
787809
Self: Sized,
788810
{
789811
}
790812

813+
type Type = T;
814+
791815
#[inline(always)]
792816
fn project(slf: PtrInner<'_, Self>) -> *mut T {
793817
// SAFETY: `ManuallyDrop<T>` has the same layout and bit validity as
@@ -1016,6 +1040,29 @@ mod tuples {
10161040
// SAFETY: If all fields in `Self` are `FromBytes`, so too is `Self`.
10171041
unsafe_impl!($($head_T: FromBytes,)* $next_T: FromBytes => FromBytes for ($($head_T,)* $next_T,));
10181042

1043+
// SAFETY: See safety comment on `ProjectToTag`.
1044+
unsafe impl<$($head_T,)* $next_T> crate::HasTag for ($($head_T,)* $next_T,) {
1045+
#[inline]
1046+
fn only_derive_is_allowed_to_implement_this_trait()
1047+
where
1048+
Self: Sized
1049+
{}
1050+
1051+
type Tag = ();
1052+
1053+
// SAFETY: It is trivially sound to project any pointer to a
1054+
// pointer to a type of size zero and alignment 1 (which `()` is
1055+
// [1]). Such a pointer will trivially satisfy its aliasing and
1056+
// validity requirements (since it has a zero-sized referent),
1057+
// and its alignment requirement (since it is aligned to 1).
1058+
//
1059+
// [1] Per https://doc.rust-lang.org/1.92.0/reference/type-layout.html#r-layout.tuple.unit:
1060+
//
1061+
// [T]he unit tuple (`()`)... is guaranteed as a zero-sized
1062+
// type to have a size of 0 and an alignment of 1.
1063+
type ProjectToTag = crate::pointer::cast::CastToUnit;
1064+
}
1065+
10191066
// Generate impls that depend on tuple index.
10201067
impl_tuple!(@variants
10211068
[$($head_T $head_I)* $next_T $next_I]

src/lib.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,27 @@ pub const STRUCT_VARIANT_ID: i128 = -1;
11051105
#[doc(hidden)]
11061106
pub const UNION_VARIANT_ID: i128 = -2;
11071107

1108+
/// # Safety
1109+
///
1110+
/// `Self::ProjectToTag` must satisfy its safety invariant.
1111+
#[doc(hidden)]
1112+
pub unsafe trait HasTag {
1113+
fn only_derive_is_allowed_to_implement_this_trait()
1114+
where
1115+
Self: Sized;
1116+
1117+
/// The type's enum tag, or `()` for non-enum types.
1118+
type Tag: Immutable;
1119+
1120+
/// A pointer projection from `Self` to its tag.
1121+
///
1122+
/// # Safety
1123+
///
1124+
/// It must be the case that, for all `slf: Ptr<'_, Self, I>`, it is sound
1125+
/// to project from `slf` to `Ptr<'_, Self::Tag, I>` using this projection.
1126+
type ProjectToTag: pointer::cast::Project<Self, Self::Tag>;
1127+
}
1128+
11081129
/// Projects a given field from `Self`.
11091130
///
11101131
/// All implementations of `HasField` for a particular field `f` in `Self`
@@ -1131,7 +1152,9 @@ pub const UNION_VARIANT_ID: i128 = -2;
11311152
///
11321153
/// The implementation of `project` must satisfy its safety post-condition.
11331154
#[doc(hidden)]
1134-
pub unsafe trait HasField<Field, const VARIANT_ID: i128, const FIELD_ID: i128> {
1155+
pub unsafe trait HasField<Field, const VARIANT_ID: i128, const FIELD_ID: i128>:
1156+
HasTag
1157+
{
11351158
fn only_derive_is_allowed_to_implement_this_trait()
11361159
where
11371160
Self: Sized;
@@ -1163,7 +1186,7 @@ pub unsafe trait HasField<Field, const VARIANT_ID: i128, const FIELD_ID: i128> {
11631186
/// # Safety
11641187
///
11651188
/// `T: ProjectField<Field, I, VARIANT_ID, FIELD_ID>` if, for a
1166-
/// `slf: Ptr<'_, T, I>` such that `if let Ok(ptr) = T::is_projectable(slf)`,
1189+
/// `ptr: Ptr<'_, T, I>` such that `T::is_projectable(ptr).is_ok()`,
11671190
/// `<T as HasField<Field, VARIANT_ID, FIELD_ID>>::project(ptr.as_inner())`
11681191
/// conforms to `T::Invariants`.
11691192
#[doc(hidden)]
@@ -1194,7 +1217,7 @@ where
11941217
/// This method must be overriden if the field's projectability depends on
11951218
/// the value of the bytes in `ptr`.
11961219
#[inline(always)]
1197-
fn is_projectable<'a>(ptr: Ptr<'a, Self, I>) -> Result<Ptr<'a, Self, I>, Self::Error> {
1220+
fn is_projectable<'a>(_ptr: Ptr<'a, Self::Tag, I>) -> Result<(), Self::Error> {
11981221
trait IsInfallible {
11991222
const IS_INFALLIBLE: bool;
12001223
}
@@ -1248,7 +1271,7 @@ where
12481271
<Projection<Self, Field, I, VARIANT_ID, FIELD_ID> as IsInfallible>::IS_INFALLIBLE
12491272
);
12501273

1251-
Ok(ptr)
1274+
Ok(())
12521275
}
12531276
}
12541277

src/pointer/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,20 @@ pub mod cast {
347347

348348
// SAFETY: The `Project::project` impl preserves the set of referent bytes.
349349
unsafe impl<T: ?Sized + KnownLayout> CastExact<T, [u8]> for AsBytesCast {}
350+
351+
/// A cast from any type to `()`.
352+
#[allow(missing_copy_implementations, missing_debug_implementations)]
353+
pub struct CastToUnit;
354+
355+
// SAFETY: The `project` implementation projects to a subset of its
356+
// argument's referent using provenance-preserving operations.
357+
unsafe impl<T: ?Sized> Project<T, ()> for CastToUnit {
358+
#[inline(always)]
359+
fn project(src: PtrInner<'_, T>) -> *mut () {
360+
src.as_ptr().cast::<()>()
361+
}
362+
}
363+
364+
// SAFETY: The `project` implementation preserves referent address.
365+
unsafe impl<T: ?Sized> Cast<T, ()> for CastToUnit {}
350366
}

src/pointer/ptr.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ mod _casts {
819819
use super::*;
820820
use crate::{
821821
pointer::cast::{AsBytesCast, Cast},
822-
ProjectField,
822+
HasTag, ProjectField,
823823
};
824824

825825
impl<'a, T, I> Ptr<'a, T, I>
@@ -886,15 +886,16 @@ mod _casts {
886886

887887
#[inline(always)]
888888
pub fn project<F, const VARIANT_ID: i128, const FIELD_ID: i128>(
889-
self,
889+
mut self,
890890
) -> Result<Ptr<'a, T::Type, T::Invariants>, T::Error>
891891
where
892892
T: ProjectField<F, I, VARIANT_ID, FIELD_ID>,
893+
I::Aliasing: Reference,
893894
{
894895
use crate::pointer::cast::Projection;
895-
match T::is_projectable(self) {
896-
Ok(ptr) => {
897-
let inner = ptr.as_inner();
896+
match T::is_projectable(self.reborrow().project_tag()) {
897+
Ok(()) => {
898+
let inner = self.as_inner();
898899
let projected = inner.project::<_, Projection<F, VARIANT_ID, FIELD_ID>>();
899900
// SAFETY: By `T: ProjectField<F, I, VARIANT_ID, FIELD_ID>`,
900901
// for `self: Ptr<'_, T, I>` such that `T::is_projectable`
@@ -914,6 +915,21 @@ mod _casts {
914915
Err(err) => Err(err),
915916
}
916917
}
918+
919+
#[must_use]
920+
#[inline(always)]
921+
pub(crate) fn project_tag(self) -> Ptr<'a, T::Tag, I>
922+
where
923+
T: HasTag,
924+
{
925+
// SAFETY: By invariant on `Self::ProjectToTag`, this is a sound
926+
// projection.
927+
let tag = unsafe { self.project_transmute_unchecked::<_, _, T::ProjectToTag>() };
928+
// SAFETY: By invariant on `Self::ProjectToTag`, the projected
929+
// pointer has the same alignment as `ptr`.
930+
let tag = unsafe { tag.assume_alignment() };
931+
tag.unify_invariants()
932+
}
917933
}
918934

919935
impl<'a, T, I> Ptr<'a, T, I>

zerocopy-derive/src/derive/try_from_bytes.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ pub(crate) fn derive_is_bit_valid(
218218
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
219219

220220
let zerocopy_crate = &ctx.zerocopy_crate;
221+
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
222+
.inner_extras(quote! {
223+
type Tag = ___ZerocopyTag;
224+
type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
225+
})
226+
.build();
221227
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
222228
let variant_ident = &variant.unwrap().ident;
223229
let variants_union_field_ident = variants_union_field_ident(variant_ident);
@@ -384,6 +390,8 @@ pub(crate) fn derive_is_bit_valid(
384390

385391
#raw_enum
386392

393+
#has_tag
394+
387395
#(#has_fields)*
388396

389397
let tag = {
@@ -464,6 +472,12 @@ fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
464472
Data::Enum(..) | Data::Struct(..) => false,
465473
};
466474
let core = ctx.core_path();
475+
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
476+
.inner_extras(quote! {
477+
type Tag = ();
478+
type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
479+
})
480+
.build();
467481
let has_fields = fields.iter().map(move |(_, ident, ty)| {
468482
let field_token = ident!(("ẕ{}", ident), ident.span());
469483
let field: Box<Type> = parse_quote!(#field_token);
@@ -581,7 +595,7 @@ fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
581595
.build()
582596
});
583597

584-
const_block(field_tokens.into_iter().chain(has_fields).map(Some))
598+
const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
585599
}
586600
fn derive_try_from_bytes_struct(
587601
ctx: &Ctx,

zerocopy-derive/src/output_tests/expected/from_bytes_union.expected.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ const _: () = {
5959
)]
6060
#[deny(ambiguous_associated_items)]
6161
#[automatically_derived]
62+
const _: () = {
63+
unsafe impl ::zerocopy::HasTag for Foo {
64+
fn only_derive_is_allowed_to_implement_this_trait() {}
65+
type Tag = ();
66+
type ProjectToTag = ::zerocopy::pointer::cast::CastToUnit;
67+
}
68+
};
69+
#[allow(
70+
deprecated,
71+
private_bounds,
72+
non_local_definitions,
73+
non_camel_case_types,
74+
non_upper_case_globals,
75+
non_snake_case,
76+
non_ascii_idents,
77+
clippy::missing_inline_in_public_items,
78+
)]
79+
#[deny(ambiguous_associated_items)]
80+
#[automatically_derived]
6281
const _: () = {
6382
unsafe impl ::zerocopy::HasField<
6483
ẕa,

0 commit comments

Comments
 (0)