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())
}
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! {
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),
}
}
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!(),
}
}
}
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"),
}
}
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!(("ẕ{}", 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!(("ẕ{}", 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();
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! {
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
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> {
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();
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
})
});
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)*
__nonempty: (),
}
};
let projections = derive_projection_struct_union(
&ctx.with_input(&variants_union),
&variants_union.data,
client,
);
quote! {
#variants_union
#projections
}
};
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))| {
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) }),
};
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();
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)]))
}
}
}
}