blob: 1cfde70624a7b7ce057c3b571fbf257f0b36f018 [file]
// SPDX-License-Identifier: (BSD-2-Clause OR Apache-2.0) OR MIT
//
// Copyright 2026 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_quote, spanned::Spanned as _, Data, DataEnum, DeriveInput, Error, Expr, Fields, Ident,
Index, Type,
};
use crate::{
repr::{EnumRepr, StructUnionRepr},
util::{
const_block, generate_tag_enum, Client, Ctx, DataExt, FieldBounds, FieldProjection,
ImplBlockBuilder, Trait,
},
};
pub(crate) fn tag_ident(variant: &Ident) -> Ident {
ident!(("___ZEROCOPY_TAG_{}", variant), variant.span())
}
pub(crate) fn variant_struct_ident(variant: &Ident) -> Ident {
ident!(("___ZerocopyVariantStruct_{}", variant), variant.span())
}
pub(crate) fn variants_union_field_ident(variant: &Ident) -> Ident {
ident!(("__field_{}", variant), variant.span())
}
/// Generates a constant for the tag associated with each variant of the enum.
/// When we match on the enum's tag, each arm matches one of these constants. We
/// have to use constants here because:
///
/// - The type that we're matching on is not the type of the tag, it's an
/// integer of the same size as the tag type and with the same bit patterns.
/// - We can't read the enum tag as an enum because the bytes may not represent
/// a valid variant.
/// - Patterns do not currently support const expressions, so we have to assign
/// these constants to names rather than use them inline in the `match`
/// statement.
pub(crate) fn generate_tag_consts(data: &DataEnum) -> TokenStream {
let tags = data.variants.iter().map(|variant| {
let variant_ident = &variant.ident;
let tag = tag_ident(variant_ident);
quote! {
// This casts the enum variant to its discriminant, and then
// converts the discriminant to the target integral type via a
// numeric cast [1].
//
// Because these are the same size, this is defined to be a no-op
// and therefore is a lossless conversion [2].
//
// [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
//
// Casts an enum to its discriminant.
//
// [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
//
// Casting between two integers of the same size (e.g. i32 -> u32)
// is a no-op.
const #tag: ___ZerocopyTagPrimitive =
___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
}
});
quote! {
#(#tags)*
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum Validity {
Uninit,
Initialized,
Safe,
}
impl Validity {
fn as_type(self, ctx: &Ctx) -> Type {
let zerocopy_crate = &ctx.zerocopy_crate;
match self {
Validity::Uninit => parse_quote!(#zerocopy_crate::invariant::Uninit),
Validity::Initialized => {
parse_quote!(#zerocopy_crate::invariant::Initialized)
}
Validity::Safe => parse_quote!(#zerocopy_crate::invariant::Safe),
}
}
/// Computes the output validity of projecting a struct or union field, or
/// returns `None` if that projection must not be generated.
///
/// Struct fields preserve all three validity invariants. Union fields
/// preserve `Uninit` and `Initialized`. No projection is generated from a
/// `Safe` union.
fn output_validity_for_struct_or_union(self, data: &Data) -> Option<Self> {
match (data, self) {
(Data::Struct(_), validity) => Some(validity),
(Data::Union(_), validity @ (Validity::Uninit | Validity::Initialized)) => {
Some(validity)
}
(Data::Union(_), Validity::Safe) => None,
(Data::Enum(_), _) => unreachable!(),
}
}
}
/// Returns the projection variant ID for the struct or union described by
/// `ctx`.
///
/// This intentionally does not handle enums: an enum field's variant ID
/// identifies its source variant rather than the representation of its
/// containing type.
pub(crate) fn struct_union_variant_id(ctx: &Ctx) -> Box<Expr> {
let zerocopy_crate = &ctx.zerocopy_crate;
match &ctx.ast.data {
Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
Data::Union(_) => {
if StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap() {
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
} else {
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
}
}
Data::Enum(_) => unreachable!("struct_union_variant_id called for enum"),
}
}
/// Generates field projection implementations for a struct or union.
///
/// `repr(C)` unions use `REPR_C_UNION_VARIANT_ID`, so their projections
/// additionally implement `pointer::cast::Cast`. Other unions use
/// `UNION_VARIANT_ID`.
pub(crate) fn derive_projection_struct_union(
ctx: &Ctx,
data: &dyn DataExt,
client: Client,
) -> TokenStream {
let fields = ctx.ast.data.fields();
if fields.is_empty() {
return quote! {};
}
let field_tokens = fields.iter().map(|(vis, ident, _)| {
let ident = ident!(("__z{}", ident), ident.span());
quote! {
#vis enum #ident {}
}
});
let zerocopy_crate = &ctx.zerocopy_crate;
let variant_id = struct_union_variant_id(ctx);
let core = ctx.core_path();
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag { client }, FieldBounds::None)
.inner_extras(quote! {
type Tag = ();
type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
})
.build();
let has_fields = fields.iter().map(move |(_, ident, ty)| {
let field_token = ident!(("__z{}", ident), ident.span());
let projection = FieldProjection {
variant_id: variant_id.clone(),
field: parse_quote!(#field_token),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
};
let has_field_trait = Trait::HasField { client, projection: projection.clone() };
let has_field_path = has_field_trait.crate_path(ctx);
let has_field = ImplBlockBuilder::new(ctx, data, has_field_trait, FieldBounds::None)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(
slf: #zerocopy_crate::pointer::PtrInner<'_, Self>,
) -> *mut <Self as #has_field_path>::Type {
let slf = slf.as_ptr();
// SAFETY: By invariant on `PtrInner`, `slf` is a non-null
// pointer whose referent is zero-sized or lives in a valid
// allocation. Since `#ident` is a struct or union field of
// `Self`, this projection preserves or shrinks the referent
// size, and so the resulting referent also fits in the same
// allocation.
unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
}
})
.build();
let derive_project_field = |input_validity: Validity| {
let output_validity =
match input_validity.output_validity_for_struct_or_union(&ctx.ast.data) {
Some(output_validity) => output_validity.as_type(ctx),
None => return TokenStream::new(),
};
let input_validity = input_validity.as_type(ctx);
let projection_preserves_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
.map(|repr| repr.get_packed().is_none())
.unwrap();
let output_alignment = if projection_preserves_alignment {
quote! { ___ZcAlignment }
} else {
quote! { #zerocopy_crate::invariant::Unaligned }
};
ImplBlockBuilder::new(
ctx,
data,
Trait::ProjectField {
client,
projection: projection.clone(),
invariants: parse_quote!((___ZcAliasing, ___ZcAlignment, #input_validity)),
},
FieldBounds::None,
)
.param_extras(vec![
parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Aliasing),
parse_quote!(___ZcAlignment: #zerocopy_crate::invariant::Alignment),
])
.inner_extras(quote! {
// SAFETY: Struct and union projections do not depend on the value of
// the referent, and are therefore infallible.
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
// SAFETY: Projection preserves aliasing. It also preserves
// alignment unless the containing type is packed. `Validity`
// determines whether a projection is emitted and, if so,
// computes its output validity: struct fields preserve validity;
// union fields preserve `Uninit` and `Initialized`, and no
// projection is emitted for `Safe` union input.
type Invariants = (___ZcAliasing, #output_alignment, #output_validity);
})
.build()
};
let project_uninit = derive_project_field(Validity::Uninit);
let project_initialized = derive_project_field(Validity::Initialized);
let project_safe = derive_project_field(Validity::Safe);
quote! {
#has_field
#project_uninit
#project_initialized
#project_safe
}
});
const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
}
pub(crate) fn derive_enum(
ctx: &Ctx,
data: &DataEnum,
client: Client,
) -> Result<TokenStream, Error> {
// With no fields, there are no `HasField` or `ProjectField` impls to emit,
// and therefore no representation requirement to enforce.
if data.fields().is_empty() {
return Ok(TokenStream::new());
}
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
let (outer_tag_type, inner_tag_type) = if repr.is_c() {
(quote! { ___ZerocopyTag }, quote! { () })
} else if repr.is_primitive() {
(quote! { () }, quote! { ___ZerocopyTag })
} else {
return Err(Error::new(
ctx.ast.span(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
};
let zerocopy_crate = &ctx.zerocopy_crate;
let core = &ctx.core_path();
let client_path = &client.crate_path(ctx);
let has_tag_trait = Trait::HasTag { client };
let has_tag_path = &has_tag_trait.crate_path(ctx);
let infallible =
quote! { #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible };
let tag_enum = generate_tag_enum(ctx, &repr, data);
let tag_consts = generate_tag_consts(data);
let generics = &ctx.ast.generics;
let (impl_generics, ref ty_generics, where_clause) = generics.split_for_impl();
// Synthesize a `repr(C)` struct for each fieldful variant. We must do this
// because Rust does not provide an `addr_of!` that works on enums.
let variant_structs = data.variants.iter().filter_map(move |variant| {
if matches!(variant.fields, Fields::Unit) {
return None;
}
let ident = variant_struct_ident(&variant.ident);
let field_types = variant.fields.iter().map(|field| &field.ty);
let enum_name = &ctx.ast.ident;
let variant_struct: DeriveInput = parse_quote! {
#[repr(C)]
struct #ident #impl_generics (
#core::mem::MaybeUninit<___ZerocopyInnerTag>,
#(#field_types,)*
#core::marker::PhantomData<#enum_name #ty_generics>,
) #where_clause;
};
let projections = derive_projection_struct_union(
&ctx.with_input(&variant_struct),
&variant_struct.data,
client,
);
Some(quote! {
#variant_struct
#projections
})
});
// Synthesize a `repr(C)` union encompassing all synthesized variants.
let variants_union = {
let fields = data.variants.iter().filter_map(|variant| {
if matches!(variant.fields, Fields::Unit) {
return None;
}
let field_name = variants_union_field_ident(&variant.ident);
let variant_struct = variant_struct_ident(&variant.ident);
Some(quote! {
#field_name: #core::mem::ManuallyDrop<#variant_struct #ty_generics>,
})
});
let variants_union: DeriveInput = parse_quote! {
#[repr(C)]
union ___ZerocopyVariants #generics {
#(#fields)*
// A fieldless enum produces no variant structs, but a union must
// have at least one field. This unit does not affect `repr(C)`
// layout.
__nonempty: (),
}
};
let projections = derive_projection_struct_union(
&ctx.with_input(&variants_union),
&variants_union.data,
client,
);
quote! {
#variants_union
#projections
}
};
// Synthesize a `repr(C)` struct that pairs the the tag with the synthesized
// variants union. In practice, the actual tag storage may be here, or
// inside the synthesized variant structs, depending on the `repr` of the
// enum.
let raw_enum: DeriveInput = parse_quote! {
#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
};
let raw_projections =
derive_projection_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data, client);
let has_tag = ImplBlockBuilder::new(ctx, data, has_tag_trait, FieldBounds::None)
.inner_extras(quote! {
type Tag = ___ZerocopyTag;
type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
})
.build();
let aliasing_param: &syn::GenericParam =
&parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Aliasing);
let reference_param: &syn::GenericParam =
&parse_quote!(___ZcAliasing: #zerocopy_crate::invariant::Reference);
let alignment_param: &syn::GenericParam =
&parse_quote!(___ZcAlignment: #zerocopy_crate::invariant::Alignment);
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
let variants_union_field = variants_union_field_ident(variant_ident);
let infallible = &infallible;
fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
// Rust does not presently support explicit visibility modifiers on
// enum fields. Keep this assertion so that a future language
// change cannot silently invalidate the visibility invariant.
assert!(matches!(vis, syn::Visibility::Inherited));
let projection = FieldProjection {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
field: parse_quote!(()),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
};
// The generated variant struct's first field is its inner tag.
let variant_struct_field_index = Index::from(idx + 1);
let has_field_trait = Trait::HasField {
client,
projection: projection.clone(),
};
let has_field_path = has_field_trait.crate_path(ctx);
let has_field = ImplBlockBuilder::new(ctx, data, has_field_trait, FieldBounds::None)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(
slf: #zerocopy_crate::pointer::PtrInner<'_, Self>,
) -> *mut <Self as #has_field_path>::Type {
use #zerocopy_crate::pointer::cast::{CastSized, Projection};
slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
.project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
.project::<_, Projection<#client_path, _, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field) }>>()
.project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
.project::<_, Projection<#client_path, _, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
.as_ptr()
}
})
.build();
// SAFETY: `projection` is constructed from the current field and
// `variant_ident`, and that same `variant_ident` selects the
// variants union field through which `has_field` projects. Thus the
// `HasField` impl, `ProjectField` impls, and valid projection's tag
// check all identify the same enum field. `idx` is the current
// field's zero-based position, and the generated variant struct
// prepends its inner tag before reproducing the enum fields in
// source order, so `idx + 1` selects that same field. The assertion
// above enforces that `repr` is `repr(C)` or a primitive
// representation; `___ZerocopyRawEnum` and its nested types model
// exactly the representations specified by [1] and [2]. Those
// representations use `repr(C)` structs for variant fields, so field
// projection preserves alignment. Each `ProjectField` impl preserves
// the input validity. The `Uninit` and `Initialized` projections are
// infallible because those validity invariants do not depend on the
// enum's tag. The `Safe` projection first checks that the tag
// selects `variant_ident`; its `Reference` aliasing bound prevents
// mutation of the tag between that check and field projection. The
// corresponding `HasTag` impl projects to the raw representation's
// tag using `CastSized`.
//
// [1] Per https://doc.rust-lang.org/1.56.0/reference/type-layout.html#reprc-enums-with-fields:
//
// The representation of a `repr(C)` enum with fields is a
// `repr(C)` struct with two fields [...]: a `repr(C)` version of
// the enum with all fields removed ("the tag") [and] a `repr(C)`
// union of `repr(C)` structs for the fields of each variant [...].
//
// [2] Per https://doc.rust-lang.org/1.56.0/reference/type-layout.html#primitive-representation-of-enums-with-fields:
//
// The representation of a primitive representation enum is a
// `repr(C)` union of `repr(C)` structs for each variant with a
// field. The first field of each struct in the union is [...] the
// tag and the remaining fields are the fields of that variant.
let derive_enum_project_field = |validity| {
let (error, is_projectable, aliasing_bound) = if validity == Validity::Safe {
assert!(!matches!(validity, Validity::Uninit | Validity::Initialized));
(
quote! { () },
quote! {
#[inline(always)]
fn is_projectable(
tag: #zerocopy_crate::pointer::Ptr<
'_,
<Self as #has_tag_path>::Tag,
(
#zerocopy_crate::invariant::Shared,
___ZcAlignment,
#zerocopy_crate::invariant::Safe,
),
>,
) -> #core::result::Result<(), ()> {
let tag = tag.read::<#zerocopy_crate::BecauseImmutable>();
if tag == ___ZerocopyTag::#variant_ident {
#core::result::Result::Ok(())
} else {
#core::result::Result::Err(())
}
}
},
reference_param.clone(),
)
} else {
(infallible.clone(), quote! {}, aliasing_param.clone())
};
let validity = validity.as_type(ctx);
let invariants: Box<Type> =
parse_quote!((___ZcAliasing, ___ZcAlignment, #validity));
let project_field_trait = Trait::ProjectField {
client,
projection: projection.clone(),
invariants: invariants.clone(),
};
ImplBlockBuilder::new(ctx, data, project_field_trait, FieldBounds::None)
.param_extras(vec![aliasing_bound.clone(), alignment_param.clone()])
.inner_extras(quote! {
type Error = #error;
type Invariants = #invariants;
#is_projectable
})
.build()
};
let project_uninit = derive_enum_project_field(Validity::Uninit);
let project_initialized = derive_enum_project_field(Validity::Initialized);
let project_valid = derive_enum_project_field(Validity::Safe);
quote! {
#has_field
#project_uninit
#project_initialized
#project_valid
}
})
});
Ok(quote! {
#tag_enum
type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
{ #core::mem::size_of::<___ZerocopyTag>() },
>;
#tag_consts
type ___ZerocopyOuterTag = #outer_tag_type;
type ___ZerocopyInnerTag = #inner_tag_type;
#(#variant_structs)*
#variants_union
#raw_enum
#raw_projections
#has_tag
#(#has_fields)*
})
}
pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
match &ctx.ast.data {
Data::Struct(strct) => {
Ok(derive_projection_struct_union(ctx, strct, Client::ProjectDerive))
}
Data::Union(unn) => Ok(derive_projection_struct_union(ctx, unn, Client::ProjectDerive)),
Data::Enum(enm) => {
let projections = derive_enum(ctx, enm, Client::ProjectDerive)?;
if projections.is_empty() {
Ok(projections)
} else {
Ok(const_block([Some(projections)]))
}
}
}
}