use {
darling::{
ast::{Data, Fields, NestedMeta, Style},
FromDeriveInput, FromField, FromMeta, FromVariant, Result,
},
proc_macro2::{Span, TokenStream},
quote::{quote, ToTokens as _},
std::{
borrow::Cow,
collections::VecDeque,
fmt::{self, Display},
},
syn::{
parse_quote,
spanned::Spanned,
visit::{self, Visit},
visit_mut::{self, VisitMut},
DeriveInput, Expr, ExprLit, GenericArgument, Generics, Ident, Lifetime, Lit, LitInt,
Member, Path, Type, TypeImplTrait, TypeParamBound, TypeReference, TypeTraitObject,
Visibility,
},
};
#[derive(FromField)]
#[darling(attributes(wincode), forward_attrs)]
pub(crate) struct Field {
pub(crate) ident: Option<Ident>,
pub(crate) ty: Type,
#[darling(default)]
pub(crate) with: Option<Type>,
pub(crate) skip: Option<SkipMode>,
}
#[derive(FromMeta)]
#[darling(from_word = || Ok(Self::Default))]
pub enum SkipMode {
Default,
DefaultVal(Expr),
}
impl SkipMode {
pub(crate) fn default_val_token_stream(&self) -> TokenStream {
match self {
Self::Default => quote! { Default::default() },
Self::DefaultVal(val) => val.to_token_stream(),
}
}
}
pub(crate) trait TypeExt {
fn with_lifetime(&self, ident: &str) -> Type;
fn with_infer(&self, infer: &Type) -> Type;
fn lifetimes(&self) -> Vec<&Lifetime>;
fn has_lifetime(&self) -> bool;
}
impl TypeExt for Type {
fn with_lifetime(&self, ident: &str) -> Type {
let mut this = self.clone();
ReplaceLifetimes(ident).visit_type_mut(&mut this);
this
}
fn with_infer(&self, infer: &Type) -> Type {
let mut this = self.clone();
let mut stack = GenericStack::new();
stack.visit_type(infer);
if stack.0.is_empty() {
stack.0.push_back(infer);
}
let mut infer = InferGeneric::from(stack);
infer.visit_type_mut(&mut this);
this
}
fn lifetimes(&self) -> Vec<&Lifetime> {
let mut lifetimes = Vec::new();
GatherLifetimes(&mut lifetimes).visit_type(self);
lifetimes
}
fn has_lifetime(&self) -> bool {
let mut visitor = HasLifetime(false);
visitor.visit_type(self);
visitor.0
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum TraitImpl {
SchemaRead,
SchemaWrite,
}
impl Display for TraitImpl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl Field {
pub(crate) fn target(&self) -> &Type {
if let Some(with) = &self.with {
with
} else {
&self.ty
}
}
pub(crate) fn target_resolved(&self) -> Type {
self.target().with_infer(&self.ty)
}
pub(crate) fn struct_member_ident(&self, index: usize) -> Member {
if let Some(ident) = &self.ident {
ident.clone().into()
} else {
index.into()
}
}
pub(crate) fn struct_member_ident_to_string(&self, index: usize) -> String {
if let Some(ident) = &self.ident {
ident.to_string()
} else {
index.to_string()
}
}
pub(crate) fn has_lifetime(&self) -> bool {
self.ty.has_lifetime()
}
}
pub(crate) trait FieldsExt {
fn type_meta_impl(&self, trait_impl: TraitImpl, repr: &StructRepr) -> TokenStream;
fn struct_members_iter(&self) -> impl Iterator<Item = (&Field, Member)>;
fn unskipped_anon_ident_iter(&self, prefix: Option<&str>) -> impl Iterator<Item = Ident>;
fn enum_members_iter(
&self,
prefix: Option<&str>,
) -> impl Iterator<Item = (&Field, Cow<'_, Ident>)> + Clone;
fn unskipped_iter(&self) -> impl Iterator<Item = &Field> + Clone;
fn fields_with_lifetime_iter(&self) -> impl Iterator<Item = &Field>;
}
impl FieldsExt for Fields<Field> {
fn type_meta_impl(&self, trait_impl: TraitImpl, repr: &StructRepr) -> TokenStream {
let tuple_expansion = match trait_impl {
TraitImpl::SchemaRead => {
let items = self.unskipped_iter().map(|field| {
let target = field.target_resolved().with_lifetime("de");
quote! { <#target as SchemaRead<'de, WincodeConfig>>::TYPE_META }
});
quote! { #(#items),* }
}
TraitImpl::SchemaWrite => {
let items = self.unskipped_iter().map(|field| {
let target = field.target_resolved();
quote! { <#target as SchemaWrite<WincodeConfig>>::TYPE_META }
});
quote! { #(#items),* }
}
};
let anon_idents = self.unskipped_anon_ident_iter(None).collect::<Vec<_>>();
let zero_copy_idents = self
.unskipped_anon_ident_iter(Some("zc_"))
.collect::<Vec<_>>();
let is_zero_copy_eligible = repr.is_zero_copy_eligible();
quote! {
if let (#(TypeMeta::Static { size: #anon_idents, zero_copy: #zero_copy_idents }),*) = (#tuple_expansion) {
let serialized_size = #(#anon_idents)+*;
let no_padding = serialized_size == core::mem::size_of::<Self>();
TypeMeta::Static { size: serialized_size, zero_copy: no_padding && #is_zero_copy_eligible && #(#zero_copy_idents)&&* }
} else {
TypeMeta::Dynamic
}
}
}
fn struct_members_iter(&self) -> impl Iterator<Item = (&Field, Member)> {
self.iter()
.enumerate()
.map(|(i, field)| (field, field.struct_member_ident(i)))
}
fn unskipped_anon_ident_iter(&self, prefix: Option<&str>) -> impl Iterator<Item = Ident> {
let len = self.unskipped_iter().count();
anon_ident_iter(prefix).take(len)
}
fn enum_members_iter(
&self,
prefix: Option<&str>,
) -> impl Iterator<Item = (&Field, Cow<'_, Ident>)> + Clone {
let mut alpha = anon_ident_iter(prefix);
self.iter().map(move |field| {
(
field,
if let Some(ident) = &field.ident {
Cow::Borrowed(ident)
} else {
Cow::Owned(
alpha
.next()
.expect("alpha iterator should never be exhausted"),
)
},
)
})
}
fn unskipped_iter(&self) -> impl Iterator<Item = &Field> + Clone {
self.iter().filter(|field| field.skip.is_none())
}
fn fields_with_lifetime_iter(&self) -> impl Iterator<Item = &Field> {
self.iter().filter(|field| field.has_lifetime())
}
}
fn anon_ident_iter(prefix: Option<&str>) -> impl Iterator<Item = Ident> + Clone + use<'_> {
let prefix = prefix.unwrap_or("");
('a'..='z').cycle().enumerate().map(move |(i, ch)| {
let wrap = i / 26;
let name = if wrap == 0 {
format!("{}{}", prefix, ch)
} else {
format!("{}{}{}", prefix, ch, wrap - 1)
};
Ident::new(&name, Span::call_site())
})
}
#[derive(FromVariant)]
#[darling(attributes(wincode), forward_attrs)]
pub(crate) struct Variant {
pub(crate) ident: Ident,
pub(crate) fields: Fields<Field>,
#[darling(default)]
pub(crate) tag: Option<Expr>,
}
impl Variant {
pub(crate) fn discriminant(&self, field_index: usize) -> Cow<'_, Expr> {
self.tag.as_ref().map(Cow::Borrowed).unwrap_or_else(|| {
Cow::Owned(Expr::Lit(ExprLit {
lit: Lit::Int(LitInt::new(&field_index.to_string(), Span::call_site())),
attrs: vec![],
}))
})
}
}
pub(crate) trait VariantsExt {
fn type_meta_impl(&self, trait_impl: TraitImpl, tag_encoding: &Type) -> TokenStream;
}
impl VariantsExt for &[Variant] {
fn type_meta_impl(&self, trait_impl: TraitImpl, tag_encoding: &Type) -> TokenStream {
if self.is_empty() {
return quote! { TypeMeta::Static { size: 0, zero_copy: false } };
}
let idents = anon_ident_iter(Some("variant_"))
.take(self.len())
.collect::<Vec<_>>();
let tag_expr = match trait_impl {
TraitImpl::SchemaRead => {
quote! { <#tag_encoding as SchemaRead<'de, WincodeConfig>>::TYPE_META }
}
TraitImpl::SchemaWrite => {
quote! { <#tag_encoding as SchemaWrite<WincodeConfig>>::TYPE_META }
}
};
let variant_type_metas = self
.iter()
.zip(&idents)
.map(|(variant, ident)| match variant.fields.style {
Style::Struct | Style::Tuple => {
let fields_type_meta_expansion = match trait_impl {
TraitImpl::SchemaRead => {
let items = variant.fields.unskipped_iter().map(|field| {
let target = field.target_resolved().with_lifetime("de");
quote! { <#target as SchemaRead<'de, WincodeConfig>>::TYPE_META }
});
quote! { #(#items),* }
},
TraitImpl::SchemaWrite => {
let items = variant.fields.unskipped_iter().map(|field| {
let target = field.target_resolved();
quote! { <#target as SchemaWrite<WincodeConfig>>::TYPE_META }
});
quote! { #(#items),* }
},
};
let anon_idents = variant.fields.unskipped_anon_ident_iter(None).collect::<Vec<_>>();
quote! {
let #ident = if let (TypeMeta::Static { size: disc_size, .. }, #(TypeMeta::Static { size: #anon_idents, .. }),*) = (#tag_expr, #fields_type_meta_expansion) {
TypeMeta::Static { size: disc_size + #(#anon_idents)+*, zero_copy: false }
} else {
TypeMeta::Dynamic
};
}
}
Style::Unit => {
quote! {
let #ident = match #tag_expr {
TypeMeta::Static { size, .. } => {
TypeMeta::Static { size, zero_copy: false }
}
TypeMeta::Dynamic => TypeMeta::Dynamic,
};
}
}
});
quote! {
const {
#(#variant_type_metas)*
let variant_sizes = [#(#idents),*];
const fn choose(variant_sizes: &[TypeMeta]) -> TypeMeta {
if variant_sizes.len() == 1 {
return variant_sizes[0];
}
let mut i = 1;
while i < variant_sizes.len() {
match (variant_sizes[i], variant_sizes[0]) {
(TypeMeta::Static { size: s1, .. }, TypeMeta::Static { size: s2, .. }) if s1 == s2 => {
i += 1;
}
_ => {
return TypeMeta::Dynamic;
}
}
}
variant_sizes[0]
}
choose(&variant_sizes)
}
}
}
}
pub(crate) type ImplBody = Data<Variant, Field>;
pub(crate) fn suppress_unused_fields(args: &SchemaArgs) -> TokenStream {
if args.from.is_none() || args.no_suppress_unused {
return quote! {};
}
match &args.data {
Data::Struct(fields) if !fields.is_empty() => {
let idents = fields.struct_members_iter().map(|(_, ident)| ident);
let ident = &args.ident;
let (impl_generics, ty_generics, where_clause) = args.generics.split_for_impl();
quote! {
const _: () = {
#[allow(dead_code, unused_variables)]
fn __wincode_use_fields #impl_generics (value: &#ident #ty_generics) #where_clause {
let _ = ( #( &value.#idents ),* );
}
};
}
}
_ => {
quote! {}
}
}
}
pub(crate) fn get_crate_name(args: &SchemaArgs) -> Path {
if args.internal {
parse_quote!(crate)
} else {
parse_quote!(::wincode)
}
}
pub(crate) fn get_src_dst(args: &SchemaArgs) -> Cow<'_, Type> {
if let Some(from) = args.from.as_ref() {
Cow::Borrowed(from)
} else {
Cow::Owned(parse_quote!(Self))
}
}
pub(crate) fn get_src_dst_fully_qualified(args: &SchemaArgs) -> Cow<'_, Type> {
if let Some(from) = args.from.as_ref() {
Cow::Borrowed(from)
} else {
let ident = &args.ident;
let (_, ty_generics, _) = args.generics.split_for_impl();
Cow::Owned(parse_quote!(#ident #ty_generics))
}
}
#[derive(FromDeriveInput)]
#[darling(attributes(wincode), forward_attrs)]
pub(crate) struct SchemaArgs {
pub(crate) ident: Ident,
pub(crate) generics: Generics,
pub(crate) data: ImplBody,
pub(crate) vis: Visibility,
#[darling(default)]
pub(crate) internal: bool,
#[darling(default)]
pub(crate) from: Option<Type>,
#[darling(default)]
pub(crate) no_suppress_unused: bool,
#[darling(default)]
pub(crate) struct_extensions: bool,
#[darling(default)]
pub(crate) tag_encoding: Option<Type>,
#[darling(default)]
pub(crate) assert_zero_copy: Option<AssertZeroCopyConfig>,
}
#[derive(Debug, Clone)]
pub(crate) struct AssertZeroCopyConfig(pub(crate) Option<Path>);
impl FromMeta for AssertZeroCopyConfig {
fn from_word() -> darling::Result<Self> {
Ok(AssertZeroCopyConfig(None))
}
fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
if items.len() != 1 {
return Err(darling::Error::too_many_items(1));
}
match &items[0] {
NestedMeta::Meta(syn::Meta::Path(path)) => Ok(AssertZeroCopyConfig(Some(path.clone()))),
_ => Err(darling::Error::unexpected_type("path")),
}
}
}
#[inline]
pub(crate) fn default_tag_encoding() -> Type {
parse_quote!(WincodeConfig::TagEncoding)
}
#[derive(Default)]
pub(crate) struct StructRepr {
layout: Layout,
}
#[derive(Default)]
pub(crate) enum Layout {
#[default]
Rust,
Transparent,
C,
}
impl StructRepr {
pub(crate) fn is_zero_copy_eligible(&self) -> bool {
matches!(self.layout, Layout::Transparent | Layout::C)
}
}
pub(crate) fn extract_repr(input: &DeriveInput, trait_impl: TraitImpl) -> Result<StructRepr> {
let mut struct_repr = StructRepr::default();
for attr in &input.attrs {
if !attr.path().is_ident("repr") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("packed") {
return Err(meta.error(format!(
"`{trait_impl}` cannot be derived for types annotated with `#[repr(packed)]` \
or `#[repr(packed(n))]`"
)));
}
if meta.path.is_ident("C") {
struct_repr.layout = Layout::C;
return Ok(());
}
if meta.path.is_ident("transparent") {
struct_repr.layout = Layout::Transparent;
return Ok(());
}
_ = meta.input.parse::<TokenStream>();
Ok(())
})?;
}
Ok(struct_repr)
}
struct GenericStack<'ast>(VecDeque<&'ast Type>);
impl GenericStack<'_> {
fn new() -> Self {
Self(VecDeque::new())
}
}
impl<'ast> Visit<'ast> for GenericStack<'ast> {
fn visit_generic_argument(&mut self, ga: &'ast GenericArgument) {
if let GenericArgument::Type(t) = ga {
match t {
Type::Slice(slice) => {
self.0.push_back(&slice.elem);
return;
}
Type::Array(array) => {
self.0.push_back(&array.elem);
return;
}
Type::Path(tp)
if tp.path.segments.iter().any(|seg| {
matches!(seg.arguments, syn::PathArguments::AngleBracketed(_))
}) =>
{
}
_ => self.0.push_back(t),
}
}
visit::visit_generic_argument(self, ga);
}
}
struct InferGeneric<'ast>(VecDeque<&'ast Type>);
impl<'ast> From<GenericStack<'ast>> for InferGeneric<'ast> {
fn from(stack: GenericStack<'ast>) -> Self {
Self(stack.0)
}
}
impl VisitMut for InferGeneric<'_> {
fn visit_generic_argument_mut(&mut self, ga: &mut GenericArgument) {
if let GenericArgument::Type(Type::Infer(_)) = ga {
let ty = self
.0
.pop_front()
.expect("wincode-derive: inference mismatch: not enough collected types for `_`")
.clone();
*ga = GenericArgument::Type(ty);
}
visit_mut::visit_generic_argument_mut(self, ga);
}
fn visit_type_array_mut(&mut self, array: &mut syn::TypeArray) {
if let Type::Infer(_) = &*array.elem {
let ty = self
.0
.pop_front()
.expect("wincode-derive: inference mismatch: not enough collected types for `_`")
.clone();
*array.elem = ty;
}
visit_mut::visit_type_array_mut(self, array);
}
}
struct ReplaceLifetimes<'a>(&'a str);
impl ReplaceLifetimes<'_> {
fn replace(&self, t: &mut Lifetime) {
t.ident = Ident::new(self.0, t.ident.span());
}
fn new_from_reference(&self, t: &mut TypeReference) {
t.lifetime = Some(Lifetime {
apostrophe: t.and_token.span(),
ident: Ident::new(self.0, t.and_token.span()),
})
}
}
impl VisitMut for ReplaceLifetimes<'_> {
fn visit_type_reference_mut(&mut self, t: &mut TypeReference) {
match &mut t.lifetime {
Some(l) => self.replace(l),
None => {
self.new_from_reference(t);
}
}
visit_mut::visit_type_reference_mut(self, t);
}
fn visit_generic_argument_mut(&mut self, ga: &mut GenericArgument) {
if let GenericArgument::Lifetime(l) = ga {
self.replace(l);
}
visit_mut::visit_generic_argument_mut(self, ga);
}
fn visit_type_trait_object_mut(&mut self, t: &mut TypeTraitObject) {
for bd in &mut t.bounds {
if let TypeParamBound::Lifetime(l) = bd {
self.replace(l);
}
}
visit_mut::visit_type_trait_object_mut(self, t);
}
fn visit_type_impl_trait_mut(&mut self, t: &mut TypeImplTrait) {
for bd in &mut t.bounds {
if let TypeParamBound::Lifetime(l) = bd {
self.replace(l);
}
}
visit_mut::visit_type_impl_trait_mut(self, t);
}
}
struct GatherLifetimes<'a, 'ast>(&'a mut Vec<&'ast Lifetime>);
impl<'ast> Visit<'ast> for GatherLifetimes<'_, 'ast> {
fn visit_lifetime(&mut self, l: &'ast Lifetime) {
self.0.push(l);
}
}
struct HasLifetime(bool);
impl Visit<'_> for HasLifetime {
fn visit_lifetime(&mut self, _: &Lifetime) {
self.0 = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infer_generic() {
let src: Type = parse_quote!(Foo<_>);
let infer = parse_quote!(Bar<u8>);
assert_eq!(src.with_infer(&infer), parse_quote!(Foo<u8>));
let src: Type = parse_quote!(Foo<_, _>);
let infer = parse_quote!(Bar<u8, u16>);
assert_eq!(src.with_infer(&infer), parse_quote!(Foo<u8, u16>));
let src: Type = parse_quote!(Pod<_>);
let infer = parse_quote!([u8; u64]);
assert_eq!(src.with_infer(&infer), parse_quote!(Pod<[u8; u64]>));
let src: Type = parse_quote!(containers::Vec<containers::Pod<_>>);
let infer = parse_quote!(Vec<u8>);
assert_eq!(
src.with_infer(&infer),
parse_quote!(containers::Vec<containers::Pod<u8>>)
);
let src: Type = parse_quote!(containers::Box<[Pod<_>]>);
let infer = parse_quote!(Box<[u8]>);
assert_eq!(
src.with_infer(&infer),
parse_quote!(containers::Box<[Pod<u8>]>)
);
let src: Type = parse_quote!(containers::Box<[Pod<[_; 32]>]>);
let infer = parse_quote!(Box<[u8; 32]>);
assert_eq!(
src.with_infer(&infer),
parse_quote!(containers::Box<[Pod<[u8; 32]>]>)
);
let src: Type = parse_quote!(containers::Vec<containers::Box<[containers::Pod<_>]>>);
let infer = parse_quote!(Vec<Box<[u8]>>);
assert_eq!(
src.with_infer(&infer),
parse_quote!(containers::Vec<containers::Box<[containers::Pod<u8>]>>)
);
let src: Type =
parse_quote!(Pair<containers::Box<[containers::Pod<_>]>, containers::Pod<_>>);
let infer: Type = parse_quote!(Pair<Box<[Foo<Bar<u8>>]>, u16>);
assert_eq!(
src.with_infer(&infer),
parse_quote!(
Pair<containers::Box<[containers::Pod<Foo<Bar<u8>>>]>, containers::Pod<u16>>
)
)
}
#[test]
fn test_override_ref_lifetime() {
let target: Type = parse_quote!(Foo<'a>);
assert_eq!(target.with_lifetime("de"), parse_quote!(Foo<'de>));
let target: Type = parse_quote!(&'a str);
assert_eq!(target.with_lifetime("de"), parse_quote!(&'de str));
}
#[test]
fn test_anon_ident_iter() {
let mut iter = anon_ident_iter(None);
assert_eq!(iter.next().unwrap().to_string(), "a");
assert_eq!(iter.nth(25).unwrap().to_string(), "a0");
assert_eq!(iter.next().unwrap().to_string(), "b0");
assert_eq!(iter.nth(24).unwrap().to_string(), "a1");
}
#[test]
fn test_gather_lifetimes() {
let ty: Type = parse_quote!(&'a Foo);
let lt: Lifetime = parse_quote!('a);
assert_eq!(ty.lifetimes(), vec![<]);
let ty: Type = parse_quote!(&'a Foo<'b, 'c>);
let (a, b, c) = (parse_quote!('a), parse_quote!('b), parse_quote!('c));
assert_eq!(ty.lifetimes(), vec![&a, &b, &c]);
}
#[test]
fn test_has_lifetime() {
let ty: Type = parse_quote!(&'a Foo);
assert!(ty.has_lifetime());
let ty: Type = parse_quote!(Foo<'b, 'c>);
assert!(ty.has_lifetime());
}
}