use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, DeriveInput, Data, Fields, Type, Attribute, Meta,
Expr, ExprLit, Lit,
};
#[proc_macro_derive(InitSpace, attributes(max_len))]
pub fn derive_init_space(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let mut total_size = quote! { 0 };
if let Data::Struct(data_struct) = input.data {
if let Fields::Named(fields_named) = data_struct.fields {
for field in &fields_named.named {
let ty = &field.ty;
let attrs = &field.attrs;
if let Some(max_len)= get_max_len(attrs) {
if is_dynamic_type(ty) {
total_size = quote! { #total_size + 4 + #max_len };
continue;
} else {
panic!("`#[max_len = N]` is only allowed on Vec or String types");
}
}
let field_size = if let Some(size) = get_fixed_size(ty) {
quote! { #size }
} else {
quote! { 4 }
};
total_size = quote! { #total_size + #field_size };
}
}
}
let expanded = quote! {
impl #name {
pub fn space() -> usize {
#total_size
}
}
};
TokenStream::from(expanded)
}
fn get_fixed_size(ty: &Type) -> Option<usize> {
if let Type::Path(typepath) = ty {
let ident = &typepath.path.segments.last()?.ident;
match ident.to_string().as_str() {
"u8" => Some(1),
"u16" => Some(2),
"u32" => Some(4),
"u64" => Some(8),
"u128" => Some(16),
"i8" => Some(1),
"i16" => Some(2),
"i32" => Some(4),
"i64" => Some(8),
"i128" => Some(16),
"bool" => Some(1),
"Pubkey" => Some(32),
_ => None,
}
} else {
None
}
}
fn get_max_len(attrs: &[Attribute]) -> Option<usize> {
for attr in attrs {
if attr.path().is_ident("max_len") {
if let Ok(meta) = attr.meta.clone().require_name_value() {
if let Expr::Lit(ExprLit {
lit: Lit::Int(lit_int),
..
}) = &meta.value
{
return lit_int.base10_parse::<usize>().ok();
}
}
}
}
None
}
fn is_dynamic_type(ty: &Type) -> bool {
if let Type::Path(typepath) = ty {
if let Some(segment) = typepath.path.segments.last() {
let ident = segment.ident.to_string();
return ident == "Vec" || ident == "String";
}
}
false
}