use quote::{quote, ToTokens};
use proc_macro2::Span;
use proc_macro2::TokenStream as TokenStream2;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Attribute, Error, Field, Fields, Ident, Index, Result, Token};
#[derive(Default)]
pub struct ReprInfo {
pub c: bool,
pub transparent: bool,
pub u8: bool,
pub packed: bool,
}
impl ReprInfo {
pub fn compute(attrs: &[Attribute]) -> Self {
let mut info = ReprInfo::default();
for attr in attrs.iter().filter(|a| a.path().is_ident("repr")) {
if let Ok(pieces) = attr.parse_args::<IdentListAttribute>() {
for piece in pieces.idents.iter() {
if piece == "C" || piece == "c" {
info.c = true;
} else if piece == "transparent" {
info.transparent = true;
} else if piece == "packed" {
info.packed = true;
} else if piece == "u8" {
info.u8 = true;
}
}
}
}
info
}
pub fn cpacked_or_transparent(self) -> bool {
(self.c && self.packed) || self.transparent
}
}
struct IdentListAttribute {
idents: Punctuated<Ident, Token![,]>,
}
impl Parse for IdentListAttribute {
fn parse(input: ParseStream) -> Result<Self> {
Ok(IdentListAttribute {
idents: input.parse_terminated(Ident::parse, Token![,])?,
})
}
}
pub fn wrap_field_inits(streams: &[TokenStream2], fields: &Fields) -> TokenStream2 {
match *fields {
Fields::Named(_) => quote!( { #(#streams),* } ),
Fields::Unnamed(_) => quote!( ( #(#streams),* ) ),
Fields::Unit => {
unreachable!("#[make_(var)ule] should have already checked that there are fields")
}
}
}
pub fn semi_for(f: &Fields) -> TokenStream2 {
if let Fields::Unnamed(..) = *f {
quote!(;)
} else {
quote!()
}
}
pub fn repr_for(f: &Fields) -> TokenStream2 {
if f.len() == 1 {
quote!(transparent)
} else {
quote!(C, packed)
}
}
fn suffixed_ident(name: &str, suffix: usize, s: Span) -> Ident {
Ident::new(&format!("{name}_{suffix}"), s)
}
pub(crate) fn generate_per_field_offsets<'a>(
fields: &[FieldInfo<'a>],
fields_are_asule: bool,
mut per_field_code: impl FnMut(&FieldInfo<'a>, &Ident, &Ident) -> TokenStream2, ) -> (TokenStream2, syn::Ident) {
let mut prev_offset_ident = Ident::new("ZERO", Span::call_site());
let mut code = quote!(
const ZERO: usize = 0;
);
for (i, field_info) in fields.iter().enumerate() {
let field = &field_info.field;
let ty = &field.ty;
let ty = if fields_are_asule {
quote!(<#ty as zerovec::ule::AsULE>::ULE)
} else {
quote!(#ty)
};
let new_offset_ident = suffixed_ident("OFFSET", i, field.span());
let size_ident = suffixed_ident("SIZE", i, field.span());
let pf_code = per_field_code(field_info, &prev_offset_ident, &size_ident);
code = quote! {
#code;
const #size_ident: usize = ::core::mem::size_of::<#ty>();
const #new_offset_ident: usize = #prev_offset_ident + #size_ident;
#pf_code;
};
prev_offset_ident = new_offset_ident;
}
(code, prev_offset_ident)
}
#[derive(Clone, Debug)]
pub(crate) struct FieldInfo<'a> {
pub accessor: TokenStream2,
pub field: &'a Field,
pub index: usize,
}
impl<'a> FieldInfo<'a> {
pub fn make_list(iter: impl Iterator<Item = &'a Field>) -> Vec<Self> {
iter.enumerate()
.map(|(i, field)| Self::new_for_field(field, i))
.collect()
}
pub fn new_for_field(f: &'a Field, index: usize) -> Self {
if let Some(ref i) = f.ident {
FieldInfo {
accessor: quote!(#i),
field: f,
index,
}
} else {
let idx = Index::from(index);
FieldInfo {
accessor: quote!(#idx),
field: f,
index,
}
}
}
pub fn setter(&self) -> TokenStream2 {
if let Some(ref i) = self.field.ident {
quote!(#i: )
} else {
quote!()
}
}
pub fn getter(&self) -> TokenStream2 {
if let Some(ref i) = self.field.ident {
quote!(#i)
} else {
suffixed_ident("field", self.index, self.field.span()).into_token_stream()
}
}
pub fn getter_doc_name(&self) -> String {
if let Some(ref i) = self.field.ident {
format!("the unsized `{i}` field")
} else {
format!("tuple struct field #{}", self.index)
}
}
}
pub fn extract_parenthetical_zerovec_attrs(
attrs: &mut Vec<Attribute>,
name: &str,
) -> Result<Vec<Ident>> {
let mut ret = vec![];
let mut error = None;
attrs.retain(|a| {
let second_segment = a.path().segments.iter().nth(1);
if let Some(second) = second_segment {
if second.ident == name {
let list = match a.parse_args::<IdentListAttribute>() {
Ok(l) => l,
Err(_) => {
error = Some(Error::new(
a.span(),
format!("#[zerovec::{name}(..)] takes in a comma separated list of identifiers"),
));
return false;
}
};
ret.extend(list.idents.iter().cloned());
return false;
}
}
true
});
if let Some(error) = error {
return Err(error);
}
Ok(ret)
}
pub fn extract_zerovec_attributes(attrs: &mut Vec<Attribute>) -> Vec<Attribute> {
let mut ret = vec![];
attrs.retain(|a| {
if a.path().segments.len() == 2 && a.path().segments[0].ident == "zerovec" {
ret.push(a.clone());
return false;
}
true
});
ret
}
pub fn extract_field_attributes(attrs: &mut Vec<Attribute>) -> Result<Option<Ident>> {
let mut zerovec_attrs = extract_zerovec_attributes(attrs);
let varule = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "varule")?;
if varule.len() > 1 {
return Err(Error::new(
varule[1].span(),
"Found multiple #[zerovec::varule()] on one field",
));
}
if !zerovec_attrs.is_empty() {
return Err(Error::new(
zerovec_attrs[1].span(),
"Found unusable #[zerovec::] attrs on field, only #[zerovec::varule()] supported",
));
}
Ok(varule.first().cloned())
}
#[derive(Default, Copy, Clone)]
pub struct ZeroVecAttrs {
pub skip_kv: bool,
pub skip_ord: bool,
pub serialize: bool,
pub deserialize: bool,
pub debug: bool,
pub hash: bool,
}
pub fn extract_attributes_common(
attrs: &mut Vec<Attribute>,
span: Span,
is_var: bool,
) -> Result<ZeroVecAttrs> {
let mut zerovec_attrs = extract_zerovec_attributes(attrs);
let derive = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "derive")?;
let skip = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "skip_derive")?;
let name = if is_var { "make_varule" } else { "make_ule" };
if let Some(attr) = zerovec_attrs.first() {
return Err(Error::new(
attr.span(),
format!("Found unknown or duplicate attribute for #[{name}]"),
));
}
let mut attrs = ZeroVecAttrs::default();
for ident in derive {
if ident == "Serialize" {
attrs.serialize = true;
} else if ident == "Deserialize" {
attrs.deserialize = true;
} else if ident == "Debug" {
attrs.debug = true;
} else if ident == "Hash" {
attrs.hash = true;
} else {
return Err(Error::new(
ident.span(),
format!(
"Found unknown derive attribute for #[{name}]: #[zerovec::derive({ident})]"
),
));
}
}
for ident in skip {
if ident == "ZeroMapKV" {
attrs.skip_kv = true;
} else if ident == "Ord" {
attrs.skip_ord = true;
} else {
return Err(Error::new(
ident.span(),
format!("Found unknown derive attribute for #[{name}]: #[zerovec::skip_derive({ident})]"),
));
}
}
if (attrs.serialize || attrs.deserialize) && !is_var {
return Err(Error::new(
span,
"#[make_ule] does not support #[zerovec::derive(Serialize, Deserialize)]",
));
}
Ok(attrs)
}