#![allow(clippy::collapsible_if)]
use proc_macro_error::{emit_warning, proc_macro_error};
use quote::{ToTokens, quote};
use std::{collections::HashSet, vec};
use syn::{
BoundLifetimes, Data, DeriveInput, GenericParam, ImplGenerics, LifetimeParam, PredicateType,
TypeGenerics, TypeParamBound, WhereClause, WherePredicate, parse_macro_input,
punctuated::Punctuated,
token::{self, Plus},
};
fn empty_where_clause() -> WhereClause {
WhereClause {
where_token: token::Where::default(),
predicates: Punctuated::new(),
}
}
fn get_field_name(field: &syn::Field, field_idx: usize) -> proc_macro2::TokenStream {
field
.ident
.to_owned()
.map(|x| x.to_token_stream())
.unwrap_or_else(|| syn::Index::from(field_idx).to_token_stream())
}
fn get_ident(ty: &syn::Type) -> Option<&syn::Ident> {
if let syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path {
leading_colon: None,
segments,
},
}) = ty
{
if segments.len() == 1 {
return Some(&segments[0].ident);
}
}
None
}
fn gen_eps_deser_method_call(
field_name: &proc_macro2::TokenStream,
field_type: &syn::Type,
type_params: &HashSet<&syn::Ident>,
) -> proc_macro2::TokenStream {
if let syn::Type::Path(syn::TypePath {
qself: None,
path: syn::Path {
leading_colon: None,
segments,
},
}) = field_type
{
if let Some(segment) = segments.last() {
if segment.ident == "PhantomDeserData" {
return syn::parse_quote!(#field_name: unsafe { <#field_type>::_deser_eps_inner_special(backend)? });
}
}
if segments.len() == 1 && type_params.contains(&segments[0].ident) {
return syn::parse_quote!(#field_name: unsafe { <#field_type as DeserInner>::_deser_eps_inner(backend)? });
}
}
syn::parse_quote!(#field_name: unsafe { <#field_type as DeserInner>::_deser_full_inner(backend)? })
}
fn gen_is_zero_copy_expr(is_repr_c: bool, field_types: &[&syn::Type]) -> proc_macro2::TokenStream {
if field_types.is_empty() {
quote!(#is_repr_c)
} else {
quote!(#is_repr_c #(&& <#field_types>::IS_ZERO_COPY)*)
}
}
fn get_type_const_params(
input: &DeriveInput,
) -> (Vec<&syn::Ident>, HashSet<&syn::Ident>, Vec<&syn::Ident>) {
let mut type_const_params = vec![];
let mut type_params = HashSet::new();
let mut const_params = vec![];
for param in &input.generics.params {
match param {
syn::GenericParam::Type(t) => {
type_const_params.push(&t.ident);
type_params.insert(&t.ident);
}
syn::GenericParam::Const(c) => {
type_const_params.push(&c.ident);
const_params.push(&c.ident);
}
syn::GenericParam::Lifetime(_) => {
panic!("Lifetime generics are not supported")
}
};
}
(type_const_params, type_params, const_params)
}
fn check_and_warn(path: &syn::Path, attr_name: &str) -> bool {
if path.is_ident(attr_name) {
emit_warning!(
path,
format!(
"Attribute `{}` is deprecated, please use `epserde_{}` instead",
attr_name, attr_name
)
);
true
} else {
false
}
}
fn check_attrs(input: &DeriveInput) -> (bool, bool, bool) {
let is_repr_c = input.attrs.iter().any(|x| {
x.meta.path().is_ident("repr") && x.meta.require_list().unwrap().tokens.to_string() == "C"
});
let is_zero_copy = input.attrs.iter().any(|x| {
check_and_warn(x.meta.path(), "zero_copy") || x.meta.path().is_ident("epserde_zero_copy")
});
let is_deep_copy = input.attrs.iter().any(|x| {
check_and_warn(x.meta.path(), "deep_copy") || x.meta.path().is_ident("epserde_deep_copy")
});
if is_zero_copy && !is_repr_c {
panic!(
"Type {} is declared as zero-copy, but it is not repr(C)",
input.ident
);
}
if is_zero_copy && is_deep_copy {
panic!(
"Type {} is declared as both zero-copy and deep-copy",
input.ident
);
}
(is_repr_c, is_zero_copy, is_deep_copy)
}
fn bound_ser_deser_types(
derive_input: &DeriveInput,
repl_params: &HashSet<&syn::Ident>,
ser_where_clause: &mut WhereClause,
deser_where_clause: &mut WhereClause,
) {
for param in &derive_input.generics.params {
if let syn::GenericParam::Type(t) = param {
let ident = &t.ident;
if !t.bounds.is_empty() && repl_params.contains(ident) {
let mut lifetimes = Punctuated::new();
lifetimes.push(GenericParam::Lifetime(LifetimeParam {
attrs: vec![],
lifetime: syn::Lifetime::new(
"'__epserde_desertype",
proc_macro2::Span::call_site(),
),
colon_token: None,
bounds: Punctuated::new(),
}));
deser_where_clause
.predicates
.push(WherePredicate::Type(PredicateType {
lifetimes: Some(BoundLifetimes {
for_token: token::For::default(),
lt_token: token::Lt::default(),
lifetimes,
gt_token: token::Gt::default(),
}),
bounded_ty: syn::parse_quote!(
::epserde::deser::DeserType<'__epserde_desertype, #ident>
),
colon_token: token::Colon::default(),
bounds: t.bounds.clone(),
}));
ser_where_clause
.predicates
.push(WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: syn::parse_quote!(
::epserde::ser::SerType<#ident>
),
colon_token: token::Colon::default(),
bounds: t.bounds.clone(),
}));
}
}
}
}
fn add_ser_deser_trait_bounds(
ty: &syn::Type,
is_zero_copy: bool,
ser_where_clause: &mut syn::WhereClause,
deser_where_clause: &mut syn::WhereClause,
) {
if is_zero_copy {
ser_where_clause.predicates.push(syn::parse_quote!(
#ty: ::epserde::traits::copy_type::ZeroCopy
));
} else {
ser_where_clause.predicates.push(syn::parse_quote!(
#ty: ::epserde::ser::SerInner
));
}
deser_where_clause.predicates.push(syn::parse_quote!(
#ty: ::epserde::deser::DeserInner
));
}
fn gen_generics_for_deser_type(
ctx: &EpserdeContext,
repl_params: &HashSet<&syn::Ident>,
) -> Vec<proc_macro2::TokenStream> {
ctx.type_const_params
.iter()
.map(|ident| {
if repl_params.contains(ident) {
quote!(::epserde::deser::DeserType<'__epserde_desertype, #ident>)
} else {
quote!(#ident)
}
})
.collect()
}
fn gen_generics_for_ser_type(
ctx: &EpserdeContext,
repl_params: &HashSet<&syn::Ident>,
) -> Vec<proc_macro2::TokenStream> {
ctx.type_const_params
.iter()
.map(|ident| {
if repl_params.contains(ident) {
quote!(::epserde::ser::SerType<#ident>)
} else {
quote!(#ident)
}
})
.collect()
}
fn gen_ser_deser_where_clauses(
field_types: &[&syn::Type],
is_zero_copy: bool,
) -> (WhereClause, WhereClause) {
let mut ser_where_clause = empty_where_clause();
let mut deser_where_clause = empty_where_clause();
for field_type in field_types {
add_ser_deser_trait_bounds(
field_type,
is_zero_copy,
&mut ser_where_clause,
&mut deser_where_clause,
);
}
(ser_where_clause, deser_where_clause)
}
fn gen_type_info_where_clauses(
base_clause: &WhereClause,
is_zero_copy: bool,
field_types: &[&syn::Type],
) -> (WhereClause, WhereClause, WhereClause) {
let gen_type_info_where_clause = |trait_bound: Punctuated<TypeParamBound, Plus>| {
let mut where_clause = base_clause.clone();
for &field_type in field_types {
if is_zero_copy {
where_clause
.predicates
.push(WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: field_type.clone(),
colon_token: token::Colon::default(),
bounds: trait_bound.clone(),
}));
} else {
where_clause
.predicates
.push(WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: field_type.clone(),
colon_token: token::Colon::default(),
bounds: syn::parse_quote!(::epserde::ser::SerInner<SerType: #trait_bound>),
}));
}
}
where_clause
};
let mut bound_type_hash = Punctuated::new();
bound_type_hash.push(syn::parse_quote!(::epserde::traits::TypeHash));
let type_hash = gen_type_info_where_clause(bound_type_hash);
let mut bound_align_hash = Punctuated::new();
bound_align_hash.push(syn::parse_quote!(::epserde::traits::AlignHash));
let align_hash = gen_type_info_where_clause(bound_align_hash);
let mut bound_align_to = Punctuated::new();
bound_align_to.push(syn::parse_quote!(::epserde::traits::AlignTo));
let align_to = gen_type_info_where_clause(bound_align_to);
(type_hash, align_hash, align_to)
}
struct EpserdeContext<'a> {
derive_input: &'a DeriveInput,
type_const_params: Vec<&'a syn::Ident>,
type_params: HashSet<&'a syn::Ident>,
generics_for_impl: ImplGenerics<'a>,
generics_for_type: TypeGenerics<'a>,
where_clause: &'a WhereClause,
is_repr_c: bool,
is_zero_copy: bool,
is_deep_copy: bool,
}
fn gen_epserde_struct_impl(ctx: &EpserdeContext, s: &syn::DataStruct) -> proc_macro2::TokenStream {
let mut field_names = vec![];
let mut field_types = vec![];
let mut method_calls = vec![];
let mut repl_params = HashSet::new();
for (field_idx, field) in s.fields.iter().enumerate() {
let field_name = get_field_name(field, field_idx);
let field_type = &field.ty;
if let Some(field_type_id) = get_ident(field_type) {
if ctx.type_params.contains(field_type_id) {
repl_params.insert(field_type_id);
}
}
method_calls.push(gen_eps_deser_method_call(
&field_name,
field_type,
&ctx.type_params,
));
field_names.push(field_name);
field_types.push(field_type);
}
let generics_for_deser_type = gen_generics_for_deser_type(ctx, &repl_params);
let generics_for_ser_type = gen_generics_for_ser_type(ctx, &repl_params);
let is_zero_copy_expr = gen_is_zero_copy_expr(ctx.is_repr_c, &field_types);
let (mut ser_where_clause, mut deser_where_clause) =
gen_ser_deser_where_clauses(&field_types, ctx.is_zero_copy);
let name = &ctx.derive_input.ident;
let generics_for_impl = &ctx.generics_for_impl;
let generics_for_type = &ctx.generics_for_type;
let where_clause = &ctx.where_clause;
if ctx.is_zero_copy {
quote! {
#[automatically_derived]
unsafe impl #generics_for_impl ::epserde::traits::CopyType for #name #generics_for_type #where_clause {
type Copy = ::epserde::traits::Zero;
}
#[automatically_derived]
impl #generics_for_impl ::epserde::ser::SerInner for #name #generics_for_type #ser_where_clause {
type SerType = Self;
const IS_ZERO_COPY: bool = #is_zero_copy_expr;
unsafe fn _ser_inner(&self, backend: &mut impl ::epserde::ser::WriteWithNames) -> ::epserde::ser::Result<()> {
::epserde::ser::helpers::ser_zero(backend, self)
}
}
#[automatically_derived]
impl #generics_for_impl ::epserde::deser::DeserInner for #name #generics_for_type #deser_where_clause
{
unsafe fn _deser_full_inner(
backend: &mut impl ::epserde::deser::ReadWithPos,
) -> ::core::result::Result<Self, ::epserde::deser::Error> {
unsafe { ::epserde::deser::helpers::deser_full_zero::<Self>(backend) }
}
type DeserType<'__epserde_desertype> = &'__epserde_desertype Self;
unsafe fn _deser_eps_inner<'deser_eps_inner_lifetime>(
backend: &mut ::epserde::deser::SliceWithPos<'deser_eps_inner_lifetime>,
) -> ::core::result::Result<Self::DeserType<'deser_eps_inner_lifetime>, ::epserde::deser::Error>
{
unsafe { ::epserde::deser::helpers::deser_eps_zero::<Self>(backend) }
}
}
}
} else {
bound_ser_deser_types(
ctx.derive_input,
&repl_params,
&mut ser_where_clause,
&mut deser_where_clause,
);
let is_deep_copy = ctx.is_deep_copy;
let name_str = name.to_string();
quote! {
#[automatically_derived]
unsafe impl #generics_for_impl ::epserde::traits::CopyType for #name #generics_for_type #where_clause {
type Copy = ::epserde::traits::Deep;
}
#[automatically_derived]
impl #generics_for_impl ::epserde::ser::SerInner for #name #generics_for_type #ser_where_clause {
type SerType = #name<#(#generics_for_ser_type,)*>;
const IS_ZERO_COPY: bool = #is_zero_copy_expr;
unsafe fn _ser_inner(&self, backend: &mut impl ::epserde::ser::WriteWithNames) -> ::epserde::ser::Result<()> {
use ::epserde::ser::WriteWithNames;
const { assert!(!(! #is_deep_copy #(&& <#field_types>::IS_ZERO_COPY)*), concat!("Type ", #name_str, " could be zero-copy, but it has not declared as such; use the #[epserde_deep_copy] attribute to silence this error")); }
#(
unsafe { WriteWithNames::write(backend, stringify!(#field_names), &self.#field_names)?; }
)*
Ok(())
}
}
#[automatically_derived]
impl #generics_for_impl ::epserde::deser::DeserInner for #name #generics_for_type #deser_where_clause {
unsafe fn _deser_full_inner(
backend: &mut impl ::epserde::deser::ReadWithPos,
) -> ::core::result::Result<Self, ::epserde::deser::Error> {
use ::epserde::deser::DeserInner;
Ok(#name{
#( #field_names: unsafe { <#field_types as DeserInner>::_deser_full_inner(backend)? }, )*
})
}
type DeserType<'__epserde_desertype> = #name<#(#generics_for_deser_type,)*>;
unsafe fn _deser_eps_inner<'deser_eps_inner_lifetime>(
backend: &mut ::epserde::deser::SliceWithPos<'deser_eps_inner_lifetime>,
) -> ::core::result::Result<Self::DeserType<'deser_eps_inner_lifetime>, ::epserde::deser::Error>
{
use ::epserde::deser::DeserInner;
Ok(#name{
#( #method_calls, )*
})
}
}
}
}
}
fn gen_epserde_enum_impl(ctx: &EpserdeContext, e: &syn::DataEnum) -> proc_macro2::TokenStream {
let mut variant_ids = vec![];
let mut variant_arm = vec![];
let mut variant_ser = vec![];
let mut variant_full_des = vec![];
let mut variant_eps_des = vec![];
let mut all_repl_params = HashSet::new();
let mut all_fields_types = vec![];
for (variant_id, variant) in e.variants.iter().enumerate() {
let ident = &variant.ident;
variant_ids.push(ident);
match &variant.fields {
syn::Fields::Unit => {
variant_arm.push(quote! { #ident });
variant_ser.push(quote! {{
WriteWithNames::write(backend, "tag", &#variant_id)?;
}});
variant_full_des.push(quote! {});
variant_eps_des.push(quote! {});
}
syn::Fields::Named(fields) => {
let mut field_names = vec![];
let mut field_types = vec![];
let mut method_calls = vec![];
for field in &fields.named {
let field_name = field.ident.as_ref().unwrap();
let field_type = &field.ty;
if let Some(field_type_id) = get_ident(field_type) {
if ctx.type_params.contains(field_type_id) {
all_repl_params.insert(field_type_id);
}
}
method_calls.push(gen_eps_deser_method_call(
&field_name.to_token_stream(),
field_type,
&all_repl_params,
));
field_names.push(quote! { #field_name });
field_types.push(field_type);
}
all_fields_types.extend(&field_types);
variant_arm.push(quote! {
#ident{ #( #field_names, )* }
});
variant_ser.push(quote! {
WriteWithNames::write(backend, "tag", &#variant_id)?;
#(
WriteWithNames::write(backend, stringify!(#field_names), #field_names)?;
)*
});
variant_full_des.push(quote! {
#(
#field_names: unsafe { <#field_types as DeserInner>::_deser_full_inner(backend)? },
)*
});
variant_eps_des.push(quote! {
#(
#method_calls,
)*
});
}
syn::Fields::Unnamed(fields) => {
let mut field_indices = vec![];
let mut field_types = vec![];
let mut field_names_in_arm = vec![];
let mut method_calls: Vec<proc_macro2::TokenStream> = vec![];
for (field_idx, field) in fields.unnamed.iter().enumerate() {
let field_name = syn::Index::from(field_idx);
let field_type = &field.ty;
if let Some(field_type_id) = get_ident(field_type) {
if ctx.type_params.contains(field_type_id) {
all_repl_params.insert(field_type_id);
}
}
field_indices.push(
syn::Ident::new(&format!("v{}", field_idx), proc_macro2::Span::call_site())
.to_token_stream(),
);
method_calls.push(gen_eps_deser_method_call(
&field_name.to_token_stream(),
field_type,
&all_repl_params,
));
field_types.push(field_type);
field_names_in_arm.push(field_name);
}
all_fields_types.extend(&field_types);
variant_arm.push(quote! {
#ident( #( #field_indices, )* )
});
variant_ser.push(quote! {
WriteWithNames::write(backend, "tag", &#variant_id)?;
#(
unsafe { WriteWithNames::write(backend, stringify!(#field_indices), #field_indices)? };
)*
});
variant_full_des.push(quote! {
#(
#field_names_in_arm : unsafe { <#field_types as DeserInner>::_deser_full_inner(backend)? },
)*
});
variant_eps_des.push(quote! {
#(
#method_calls,
)*
});
}
}
}
let generics_for_deser_type = gen_generics_for_deser_type(ctx, &all_repl_params);
let generics_for_ser_type = gen_generics_for_ser_type(ctx, &all_repl_params);
let tag = (0..variant_arm.len()).collect::<Vec<_>>();
let is_zero_copy_expr = gen_is_zero_copy_expr(ctx.is_repr_c, &all_fields_types);
let (mut ser_where_clause, mut deser_where_clause) =
gen_ser_deser_where_clauses(&all_fields_types, ctx.is_zero_copy);
let name = &ctx.derive_input.ident;
let is_deep_copy = ctx.is_deep_copy;
let generics_for_impl = &ctx.generics_for_impl;
let generics_for_type = &ctx.generics_for_type;
let where_clause = &ctx.where_clause;
if ctx.is_zero_copy {
quote! {
#[automatically_derived]
unsafe impl #generics_for_impl ::epserde::traits::CopyType for #name #generics_for_type #where_clause {
type Copy = ::epserde::traits::Zero;
}
#[automatically_derived]
impl #generics_for_impl ::epserde::ser::SerInner for #name #generics_for_type #ser_where_clause {
type SerType = Self;
const IS_ZERO_COPY: bool = #is_zero_copy_expr;
unsafe fn _ser_inner(&self, backend: &mut impl ::epserde::ser::WriteWithNames) -> ::epserde::ser::Result<()> {
unsafe { ::epserde::ser::helpers::ser_zero(backend, self) }
}
}
#[automatically_derived]
impl #generics_for_impl ::epserde::deser::DeserInner for #name #generics_for_type #deser_where_clause {
unsafe fn _deser_full_inner(
backend: &mut impl ::epserde::deser::ReadWithPos,
) -> ::core::result::Result<Self, ::epserde::deser::Error> {
unsafe { ::epserde::deser::helpers::deser_full_zero::<Self>(backend) }
}
type DeserType<'__epserde_desertype> = &'__epserde_desertype Self;
unsafe fn _deser_eps_inner<'deser_eps_inner_lifetime>(
backend: &mut ::epserde::deser::SliceWithPos<'deser_eps_inner_lifetime>,
) -> ::core::result::Result<Self::DeserType<'deser_eps_inner_lifetime>, ::epserde::deser::Error>
{
unsafe { ::epserde::deser::helpers::deser_eps_zero::<Self>(backend) }
}
}
}
} else {
bound_ser_deser_types(
ctx.derive_input,
&all_repl_params,
&mut ser_where_clause,
&mut deser_where_clause,
);
let name_str = name.to_string();
quote! {
#[automatically_derived]
unsafe impl #generics_for_impl ::epserde::traits::CopyType for #name #generics_for_type #where_clause {
type Copy = ::epserde::traits::Deep;
}
#[automatically_derived]
impl #generics_for_impl ::epserde::ser::SerInner for #name #generics_for_type #ser_where_clause {
type SerType = #name<#(#generics_for_ser_type,)*>;
const IS_ZERO_COPY: bool = #is_zero_copy_expr;
unsafe fn _ser_inner(&self, backend: &mut impl ::epserde::ser::WriteWithNames) -> ::epserde::ser::Result<()> {
use ::epserde::ser::WriteWithNames;
const { assert!(!(! #is_deep_copy #(&& <#all_fields_types>::IS_ZERO_COPY)*), concat!("Type ", #name_str, " could be zero-copy, but it has not declared as such; use the #[epserde_deep_copy] attribute to silence this error")); }
match self {
#(
Self::#variant_arm => { #variant_ser }
)*
}
Ok(())
}
}
#[automatically_derived]
impl #generics_for_impl ::epserde::deser::DeserInner for #name #generics_for_type #deser_where_clause {
unsafe fn _deser_full_inner(
backend: &mut impl ::epserde::deser::ReadWithPos,
) -> ::core::result::Result<Self, ::epserde::deser::Error> {
use ::epserde::deser::DeserInner;
use ::epserde::deser::Error;
match unsafe { <usize as DeserInner>::_deser_full_inner(backend)? } {
#(
#tag => Ok(Self::#variant_ids{ #variant_full_des }),
)*
tag => Err(Error::InvalidTag(tag)),
}
}
type DeserType<'__epserde_desertype> = #name<#(#generics_for_deser_type,)*>;
unsafe fn _deser_eps_inner<'deser_eps_inner_lifetime>(
backend: &mut ::epserde::deser::SliceWithPos<'deser_eps_inner_lifetime>,
) -> ::core::result::Result<Self::DeserType<'deser_eps_inner_lifetime>, ::epserde::deser::Error>
{
use ::epserde::deser::DeserInner;
use ::epserde::deser::Error;
match unsafe { <usize as DeserInner>::_deser_full_inner(backend)? } {
#(
#tag => Ok(Self::DeserType::<'_>::#variant_ids{ #variant_eps_des }),
)*
tag => Err(Error::InvalidTag(tag)),
}
}
}
}
}
}
#[proc_macro_error(proc_macro_hack)]
#[proc_macro_derive(
Epserde,
attributes(zero_copy, deep_copy, epserde_zero_copy, epserde_deep_copy)
)]
pub fn epserde_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut derive_input = parse_macro_input!(input as DeriveInput);
if derive_input.generics.where_clause.is_some() {
panic!("The derive macros do not support where clauses on the original type.");
}
derive_input.generics.make_where_clause();
let (generics_for_impl, generics_for_type, where_clause) =
derive_input.generics.split_for_impl();
let where_clause = where_clause.unwrap();
let (is_repr_c, is_zero_copy, is_deep_copy) = check_attrs(&derive_input);
let (type_const_params, type_params, const_params) = get_type_const_params(&derive_input);
let ctx = EpserdeContext {
derive_input: &derive_input,
type_const_params,
type_params,
generics_for_impl,
generics_for_type,
where_clause,
is_repr_c,
is_zero_copy,
is_deep_copy,
};
let mut out: proc_macro::TokenStream = match &derive_input.data {
Data::Struct(s) => gen_epserde_struct_impl(&ctx, s),
Data::Enum(e) => gen_epserde_enum_impl(&ctx, e),
_ => todo!("Union types are not currently supported"),
}
.into();
out.extend(_type_info_derive(
&derive_input,
ctx.type_params,
const_params,
ctx.generics_for_impl,
ctx.generics_for_type,
ctx.where_clause,
ctx.is_zero_copy,
));
out
}
struct TypeInfoContext<'a> {
name: &'a syn::Ident,
type_params: HashSet<&'a syn::Ident>,
const_params: Vec<&'a syn::Ident>,
generics_for_impl: ImplGenerics<'a>,
generics_for_type: TypeGenerics<'a>,
where_clause: &'a WhereClause,
is_zero_copy: bool,
repr_attrs: Vec<String>,
}
fn gen_type_hash_body(
ctx: &TypeInfoContext,
field_hashes: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let copy_type = if ctx.is_zero_copy {
"ZeroCopy"
} else {
"DeepCopy"
};
let name = &ctx.name;
let const_params = &ctx.const_params;
quote! {
use ::core::hash::Hash;
use ::epserde::traits::TypeHash;
use ::epserde::ser::SerType;
Hash::hash(#copy_type, hasher);
#(
Hash::hash(&#const_params, hasher);
)*
#(
Hash::hash(stringify!(#const_params), hasher);
)*
Hash::hash(stringify!(#name), hasher);
#(
#field_hashes
)*
}
}
fn gen_struct_align_hash_body(
ctx: &TypeInfoContext,
fields_types: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let repr_attrs = &ctx.repr_attrs;
if ctx.is_zero_copy {
quote! {
use ::core::hash::Hash;
use ::epserde::traits::AlignHash;
use ::epserde::ser::SerType;
Hash::hash(&::core::mem::size_of::<Self>(), hasher);
#(
Hash::hash(#repr_attrs, hasher);
)*
#(
<#fields_types as AlignHash>::align_hash(
hasher,
offset_of,
);
)*
}
} else {
quote! {
use ::epserde::traits::AlignHash;
use ::epserde::ser::SerType;
#(
<#fields_types as AlignHash>::align_hash(hasher, &mut 0);
)*
}
}
}
fn gen_enum_align_hash_body(
ctx: &TypeInfoContext,
all_align_hashes: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let repr_attrs = &ctx.repr_attrs;
if ctx.is_zero_copy {
quote! {
use ::core::hash::Hash;
use ::epserde::traits::AlignHash;
use ::epserde::ser::SerType;
Hash::hash(&::core::mem::size_of::<Self>(), hasher);
#(
Hash::hash(#repr_attrs, hasher);
)*
let old_offset_of = *offset_of;
#(
*offset_of = old_offset_of;
#all_align_hashes
)*
}
} else {
quote! {
use ::epserde::traits::AlignHash;
use ::epserde::ser::SerType;
#(
*offset_of = 0;
#all_align_hashes
)*
}
}
}
fn gen_struct_align_to_body(fields_types: &[&syn::Type]) -> proc_macro2::TokenStream {
quote! {
use ::epserde::traits::AlignTo;
let mut align_to = ::core::mem::align_of::<Self>();
#(
if align_to < <#fields_types as AlignTo>::align_to() {
align_to = <#fields_types as AlignTo>::align_to();
}
)*
align_to
}
}
fn gen_type_info_traits(
ctx: TypeInfoContext,
type_hash_where_clause: syn::WhereClause,
align_hash_where_clause: syn::WhereClause,
align_to_where_clause: syn::WhereClause,
type_hash_body: proc_macro2::TokenStream,
align_hash_body: proc_macro2::TokenStream,
align_to_body: Option<proc_macro2::TokenStream>,
) -> proc_macro2::TokenStream {
let name = &ctx.name;
let generics_for_impl = &ctx.generics_for_impl;
let generics_for_type = &ctx.generics_for_type;
let align_to_impl = if let Some(align_to_body) = align_to_body {
quote! {
#[automatically_derived]
impl #generics_for_impl ::epserde::traits::AlignTo for #name #generics_for_type #align_to_where_clause {
fn align_to() -> usize {
#align_to_body
}
}
}
} else {
quote! {}
};
quote! {
#[automatically_derived]
impl #generics_for_impl ::epserde::traits::TypeHash for #name #generics_for_type #type_hash_where_clause {
fn type_hash(hasher: &mut impl ::core::hash::Hasher) {
#type_hash_body
}
}
#[automatically_derived]
impl #generics_for_impl ::epserde::traits::AlignHash for #name #generics_for_type #align_hash_where_clause {
fn align_hash(
hasher: &mut impl ::core::hash::Hasher,
offset_of: &mut usize,
) {
#align_hash_body
}
}
#align_to_impl
}
}
fn gen_struct_type_info_impl(
ctx: TypeInfoContext,
s: &syn::DataStruct,
) -> proc_macro2::TokenStream {
let mut field_names = vec![];
let mut field_types = vec![];
let mut field_types_ts = vec![];
for (field_idx, field) in s.fields.iter().enumerate() {
let field_type = &field.ty;
field_names.push(get_field_name(field, field_idx));
field_types.push(field_type);
field_types_ts.push(quote! { SerType<#field_type> });
}
let (type_hash_where_clause, align_hash_where_clause, align_to_where_clause) =
gen_type_info_where_clauses(ctx.where_clause, ctx.is_zero_copy, &field_types);
let mut field_hashes: Vec<_> = field_names
.iter()
.map(|name| quote! { Hash::hash(stringify!(#name), hasher); })
.collect();
field_hashes.extend(field_types_ts.iter().map(|field_type_ts| {
quote! { <#field_type_ts as TypeHash>::type_hash(hasher); }
}));
let type_hash_body = gen_type_hash_body(&ctx, &field_hashes);
let align_hash_body = gen_struct_align_hash_body(&ctx, &field_types_ts);
let align_to_body = if ctx.is_zero_copy {
Some(gen_struct_align_to_body(&field_types))
} else {
None
};
gen_type_info_traits(
ctx,
type_hash_where_clause,
align_hash_where_clause,
align_to_where_clause,
type_hash_body,
align_hash_body,
align_to_body,
)
}
fn gen_enum_type_info_impl(ctx: TypeInfoContext, e: &syn::DataEnum) -> proc_macro2::TokenStream {
let mut all_type_hashes = vec![];
let mut all_align_hashes = vec![];
let mut all_align_tos = vec![];
let mut all_field_types = vec![];
let mut all_repl_params = HashSet::new();
for variant in &e.variants {
let ident = &variant.ident;
let mut type_hash = quote! { Hash::hash(stringify!(#ident), hasher); };
let mut field_types = vec![];
let mut align_hash = quote! {};
let mut align_to = quote! {};
match &variant.fields {
syn::Fields::Unit => {}
syn::Fields::Named(fields) => {
for field in &fields.named {
let field_name = field.ident.as_ref().unwrap();
let field_type = &field.ty;
field_types.push(field_type);
let field_type_ts = quote! { SerType<#field_type> };
type_hash.extend([quote! {
Hash::hash(stringify!(#field_name), hasher);
<#field_type_ts as TypeHash>::type_hash(hasher);
}]);
align_hash.extend([quote! {
<#field_type_ts as AlignHash>::align_hash(hasher, offset_of);
}]);
align_to.extend([quote! {
if align_to < <#field_type as AlignTo>::align_to() {
align_to = <#field_type as AlignTo>::align_to();
}
}]);
if !ctx.is_zero_copy {
if let Some(field_type_id) = get_ident(field_type) {
if ctx.type_params.contains(field_type_id) {
all_repl_params.insert(field_type_id);
}
}
}
}
}
syn::Fields::Unnamed(fields) => {
for (field_idx, field) in fields.unnamed.iter().enumerate() {
let field_name = field_idx.to_string();
let field_type = &field.ty;
field_types.push(field_type);
let field_type_ts = quote! { SerType<#field_type> };
type_hash.extend([quote! {
Hash::hash(#field_name, hasher);
<#field_type_ts as TypeHash>::type_hash(hasher);
}]);
align_hash.extend([quote! {
<#field_type_ts as AlignHash>::align_hash(hasher, offset_of);
}]);
align_to.extend([quote! {
if align_to < <#field_type as AlignTo>::align_to() {
align_to = <#field_type as AlignTo>::align_to();
}
}]);
if !ctx.is_zero_copy {
if let Some(field_type_id) = get_ident(field_type) {
if ctx.type_params.contains(field_type_id) {
all_repl_params.insert(field_type_id);
}
}
}
}
}
}
all_type_hashes.push(type_hash);
all_align_hashes.push(align_hash);
all_align_tos.push(align_to);
all_field_types.extend(field_types);
}
let (where_clause_type_hash, where_clause_align_hash, where_clause_align_to) =
gen_type_info_where_clauses(ctx.where_clause, ctx.is_zero_copy, &all_field_types);
let type_hash_body = gen_type_hash_body(&ctx, &all_type_hashes);
let align_hash_body = gen_enum_align_hash_body(&ctx, &all_align_hashes);
let align_to_body = quote! {
let mut align_to = core::mem::align_of::<Self>();
#(
#all_align_tos
)*
align_to
};
let align_to_body = if ctx.is_zero_copy {
Some(align_to_body)
} else {
None
};
gen_type_info_traits(
ctx,
where_clause_type_hash,
where_clause_align_hash,
where_clause_align_to,
type_hash_body,
align_hash_body,
align_to_body,
)
}
#[proc_macro_error(proc_macro_hack)]
#[proc_macro_derive(
TypeInfo,
attributes(zero_copy, deep_copy, epserde_zero_copy, epserde_deep_copy)
)]
pub fn type_info_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut derive_input = parse_macro_input!(input as DeriveInput);
if derive_input.generics.where_clause.is_some() {
panic!("The derive macros do not support where clauses on the original type.");
}
derive_input.generics.make_where_clause();
let (generics_for_impl, generics_for_type, where_clause) =
derive_input.generics.split_for_impl();
let where_clause = where_clause.unwrap();
let (_, is_zero_copy, _) = check_attrs(&derive_input);
let (_, type_params, const_params) = get_type_const_params(&derive_input);
_type_info_derive(
&derive_input,
type_params,
const_params,
generics_for_impl,
generics_for_type,
where_clause,
is_zero_copy,
)
}
fn _type_info_derive(
derive_input: &DeriveInput,
type_params: HashSet<&syn::Ident>,
const_params: Vec<&syn::Ident>,
generics_for_impl: ImplGenerics<'_>,
generics_for_type: TypeGenerics<'_>,
where_clause: &WhereClause,
is_zero_copy: bool,
) -> proc_macro::TokenStream {
let mut repr_attrs = derive_input
.attrs
.iter()
.filter(|x| x.meta.path().is_ident("repr"))
.map(|x| x.meta.require_list().unwrap().tokens.to_string())
.collect::<Vec<_>>();
repr_attrs.sort();
let ctx = TypeInfoContext {
name: &derive_input.ident,
type_params,
const_params,
generics_for_type,
generics_for_impl,
where_clause,
is_zero_copy,
repr_attrs,
};
match &derive_input.data {
Data::Struct(s) => gen_struct_type_info_impl(ctx, s),
Data::Enum(e) => gen_enum_type_info_impl(ctx, e),
_ => todo!("Union types are not currently supported"),
}
.into()
}