use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
Index, Path,
};
use crate::{
derive_has_field_struct_union, derive_try_from_bytes_inner, repr::EnumRepr, DataExt,
FieldBounds, ImplBlockBuilder, Trait,
};
pub(crate) fn generate_tag_enum(repr: &EnumRepr, data: &DataEnum) -> TokenStream {
let variants = data.variants.iter().map(|v| {
let ident = &v.ident;
if let Some((eq, discriminant)) = &v.discriminant {
quote! { #ident #eq #discriminant }
} else {
quote! { #ident }
}
});
let repr = match repr {
EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
EnumRepr::Compound(c, _) => quote! { #c },
};
quote! {
#repr
#[allow(dead_code, non_camel_case_types)]
enum ___ZerocopyTag {
#(#variants,)*
}
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
}
}
fn tag_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
}
fn generate_tag_consts(data: &DataEnum) -> TokenStream {
let tags = data.variants.iter().map(|v| {
let variant_ident = &v.ident;
let tag_ident = tag_ident(variant_ident);
quote! {
#[allow(non_upper_case_globals)]
const #tag_ident: ___ZerocopyTagPrimitive =
___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
}
});
quote! {
#(#tags)*
}
}
fn variant_struct_ident(variant_ident: &Ident) -> Ident {
ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
}
fn generate_variant_structs(
enum_name: &Ident,
generics: &Generics,
data: &DataEnum,
zerocopy_crate: &Path,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let phantom_ty = quote! {
core_reexport::marker::PhantomData<#enum_name #ty_generics>
};
let variant_structs = data.variants.iter().filter_map(|variant| {
if matches!(variant.fields, Fields::Unit) {
return None;
}
let variant_struct_ident = variant_struct_ident(&variant.ident);
let field_types = variant.fields.iter().map(|f| &f.ty);
let variant_struct = parse_quote! {
#[repr(C)]
#[allow(non_snake_case)]
struct #variant_struct_ident #impl_generics (
core_reexport::mem::MaybeUninit<___ZerocopyInnerTag>,
#(#field_types,)*
#phantom_ty,
) #where_clause;
};
let try_from_bytes_impl =
derive_try_from_bytes_inner(&variant_struct, Trait::TryFromBytes, zerocopy_crate)
.expect("derive_try_from_bytes_inner should not fail on synthesized type");
Some(quote! {
#variant_struct
#try_from_bytes_impl
})
});
quote! {
#(#variant_structs)*
}
}
fn variants_union_field_ident(ident: &Ident) -> Ident {
ident!(("__field_{}", ident), ident.span())
}
fn generate_variants_union(
generics: &Generics,
data: &DataEnum,
zerocopy_crate: &Path,
) -> TokenStream {
let (_, ty_generics, _) = generics.split_for_impl();
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_ident = variant_struct_ident(&variant.ident);
Some(quote! {
#field_name: core_reexport::mem::ManuallyDrop<
#variant_struct_ident #ty_generics
>,
})
});
let variants_union = parse_quote! {
#[repr(C)]
#[allow(non_snake_case)]
union ___ZerocopyVariants #generics {
#(#fields)*
__nonempty: (),
}
};
let has_field =
derive_has_field_struct_union(&variants_union, &variants_union.data, zerocopy_crate);
quote! {
#variants_union
#has_field
}
}
pub(crate) fn derive_is_bit_valid(
ast: &DeriveInput,
enum_ident: &Ident,
repr: &EnumRepr,
generics: &Generics,
data: &DataEnum,
zerocopy_crate: &Path,
) -> Result<TokenStream, Error> {
let trait_path = Trait::TryFromBytes.crate_path(zerocopy_crate);
let tag_enum = generate_tag_enum(repr, data);
let tag_consts = generate_tag_consts(data);
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(
ast.span(),
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
));
};
let variant_structs = generate_variant_structs(enum_ident, generics, data, zerocopy_crate);
let variants_union = generate_variants_union(generics, data, zerocopy_crate);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
let variant_ident = &variant.unwrap().ident;
let variants_union_field_ident = variants_union_field_ident(variant_ident);
let field: Box<syn::Type> = parse_quote!(());
fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
assert!(matches!(vis, syn::Visibility::Inherited));
let variant_struct_field_index = Index::from(idx + 1);
let (_, ty_generics, _) = generics.split_for_impl();
ImplBlockBuilder::new(
ast,
data,
Trait::HasField {
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
field: field.clone(),
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
},
FieldBounds::None,
zerocopy_crate,
)
.inner_extras(quote! {
type Type = #ty;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut Self::Type {
use #zerocopy_crate::pointer::cast::{CastSized, Projection};
slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
.project::<_, Projection<_, { #zerocopy_crate::UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
.as_ptr()
}
})
.build()
})
});
let match_arms = data.variants.iter().map(|variant| {
let tag_ident = tag_ident(&variant.ident);
let variant_struct_ident = variant_struct_ident(&variant.ident);
let variants_union_field_ident = variants_union_field_ident(&variant.ident);
if matches!(variant.fields, Fields::Unit) {
quote! {
#tag_ident => true
}
} else {
quote! {
#tag_ident => {
let variant_md = unsafe { variants.cast_unchecked::<
core_reexport::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
#zerocopy_crate::pointer::cast::Projection<
_,
{ #zerocopy_crate::UNION_VARIANT_ID },
{ #zerocopy_crate::ident_id!(#variants_union_field_ident) }
>
>() };
let variant = variant_md.cast::<
#variant_struct_ident #ty_generics,
#zerocopy_crate::pointer::cast::CastSized,
#zerocopy_crate::pointer::BecauseInvariantsEq
>();
<
#variant_struct_ident #ty_generics as #trait_path
>::is_bit_valid(variant)
}
}
}
});
let raw_enum: DeriveInput = parse_quote! {
#[repr(C)]
struct ___ZerocopyRawEnum #generics {
tag: ___ZerocopyOuterTag,
variants: ___ZerocopyVariants #ty_generics,
}
};
let self_ident = &ast.ident;
let invariants_eq_impl = quote! {
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
};
let raw_enum_projections =
derive_has_field_struct_union(&raw_enum, &raw_enum.data, zerocopy_crate);
let raw_enum = quote! {
#raw_enum
#invariants_eq_impl
#raw_enum_projections
};
Ok(quote! {
fn is_bit_valid<___ZerocopyAliasing>(
candidate: #zerocopy_crate::Maybe<'_, Self, ___ZerocopyAliasing>,
) -> #zerocopy_crate::util::macro_util::core_reexport::primitive::bool
where
___ZerocopyAliasing: #zerocopy_crate::pointer::invariant::Reference,
{
use #zerocopy_crate::util::macro_util::core_reexport;
#tag_enum
type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
{ core_reexport::mem::size_of::<___ZerocopyTag>() },
>;
#tag_consts
type ___ZerocopyOuterTag = #outer_tag_type;
type ___ZerocopyInnerTag = #inner_tag_type;
unsafe impl #generics #zerocopy_crate::HasField<(), { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(tag) }> for ___ZerocopyRawEnum #ty_generics {
fn only_derive_is_allowed_to_implement_this_trait() {}
type Type = ___ZerocopyTag;
#[inline(always)]
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut Self::Type {
slf.as_ptr().cast()
}
}
#variant_structs
#variants_union
#raw_enum
#(#has_fields)*
let mut raw_enum = candidate.cast::<
___ZerocopyRawEnum #ty_generics,
#zerocopy_crate::pointer::cast::CastSized,
#zerocopy_crate::pointer::BecauseInvariantsEq
>();
let tag = {
let tag_ptr = raw_enum.reborrow().project::<
(),
{ #zerocopy_crate::ident_id!(tag) }
>().cast::<
___ZerocopyTagPrimitive,
#zerocopy_crate::pointer::cast::CastSized,
_
>();
tag_ptr.recall_validity::<_, (_, (_, _))>().read_unaligned::<#zerocopy_crate::BecauseImmutable>()
};
let variants = raw_enum.project::<_, { #zerocopy_crate::ident_id!(variants) }>();
#[allow(non_upper_case_globals)]
match tag {
#(#match_arms,)*
_ => false,
}
}
})
}