use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use syn::Result;
use syn::spanned::Spanned;
fn storage_domain_impl(input: &syn::DeriveInput) -> Result<TokenStream> {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) =
input.generics.split_for_impl();
let mut domain: Option<syn::LitStr> = None;
for attr in &input.attrs {
if attr.path().is_ident("storage_domain") {
let lit = match &attr.meta {
syn::Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}),
..
}) => s.clone(),
_ => {
return Err(syn::Error::new_spanned(
attr,
"#[storage_domain] must be `storage_domain = \"...\"`",
));
}
};
domain = Some(lit);
}
}
let domain_lit = match domain {
Some(s) => quote! { #s },
None => {
let name = ident.to_string();
quote! { #name }
}
};
Ok(quote! {
const _: () = {
use ::thruput_sdk::storage::StorageDomain;
impl #impl_generics StorageDomain for #ident #ty_generics
#where_clause
{
const DOMAIN: ::thruput_sdk::storage::FramedDomain = {
const N: usize =
::thruput_sdk::storage::framed_domain_len(#domain_lit);
const BYTES: [u8; N] =
::thruput_sdk::storage::frame_domain::<N>(#domain_lit);
::thruput_sdk::storage::FramedDomain::new(&BYTES)
};
}
};
})
}
pub fn generate_impls(input: &syn::DeriveInput) -> Result<TokenStream> {
let item = &input.data;
let storage_impls = generate_from_data(input, item)?;
let domain_impl = storage_domain_impl(input)?;
let ident = &input.ident;
let ident_lower =
Ident::new(&ident.to_string().to_lowercase(), ident.span());
let impl_mod = format_ident!("_{}_storage_impls", ident_lower);
Ok(quote! {
mod #impl_mod {
use super::*;
#storage_impls
#domain_impl
}
})
}
fn generate_from_data(
input: &syn::DeriveInput,
item: &syn::Data,
) -> Result<TokenStream> {
match item {
syn::Data::Struct(item) => generate_from_struct(input, item),
_ => err(input, "Can only derive Storage for structs")?,
}
}
fn generate_from_struct(
input: &syn::DeriveInput,
item: &syn::DataStruct,
) -> Result<TokenStream> {
let ident = &input.ident;
let generics = &input.generics;
let fields = &item.fields;
impl_from_keypath(ident, generics, fields)
}
fn impl_from_keypath(
ident: &syn::Ident,
generics: &syn::Generics,
fields: &syn::Fields,
) -> Result<TokenStream> {
let named_fields = match fields {
syn::Fields::Named(fields_named) => &fields_named.named,
syn::Fields::Unit => {
let (impl_generics, ty_generics, where_clause) =
generics.split_for_impl();
return Ok(quote! {
const _: () = {
use ::thruput_sdk::storage::*;
impl #impl_generics From<Keypath> for #ident #ty_generics #where_clause {
fn from(_keypath: Keypath) -> Self {
Self
}
}
};
});
}
_ => return err(fields, "Can only handle named or unit structs"),
};
let mut generics = generics.clone();
let mut field_names = TokenStream::new();
let mut field_initializers = TokenStream::new();
let mut resolved: Vec<(u32, proc_macro2::Span)> = Vec::new();
let generic_idents: Vec<syn::Ident> = generics
.params
.iter()
.map(|p| match p {
syn::GenericParam::Type(t) => t.ident.clone(),
syn::GenericParam::Const(c) => c.ident.clone(),
syn::GenericParam::Lifetime(l) => l.lifetime.ident.clone(),
})
.collect();
for (i, field) in named_fields.iter().enumerate() {
let field_name = &field.ident;
let field_type = &field.ty;
field_names.extend(quote! { #field_name, });
let mut idx: u32 = i as u32;
let mut slot_attr_span: Option<proc_macro2::Span> = None;
for attr in &field.attrs {
if attr.path().is_ident("slot") {
if let Some(prev) = slot_attr_span {
let mut err = syn::Error::new(
attr.span(),
"duplicate #[slot] attribute on the same field",
);
err.combine(syn::Error::new(prev, "first #[slot] here"));
return Err(err);
}
let lit: syn::LitInt = attr.parse_args().map_err(|e| {
syn::Error::new(
attr.span(),
format!("#[slot(N)] expects an integer: {e}"),
)
})?;
idx = lit.base10_parse::<u32>().map_err(|_| {
syn::Error::new(
lit.span(),
"#[slot(N)] index must fit in a u32",
)
})?;
slot_attr_span = Some(attr.span());
}
}
if let Some((_, prev)) = resolved.iter().find(|(r, _)| *r == idx) {
let mut err = syn::Error::new(
field.span(),
format!(
"storage slot {idx} is assigned to more than one field"
),
);
err.combine(syn::Error::new(*prev, "previously assigned here"));
return Err(err);
}
resolved.push((idx, field.span()));
field_initializers.extend(quote! {
let #field_name = {
const __SLOT_FRAME: ::thruput_sdk::storage::FramedComponent =
::thruput_sdk::storage::FramedComponent::from_u32(#idx);
::thruput_sdk::storage::__field_from_keypath::<#field_type>(
keypath.child_framed(&__SLOT_FRAME),
)
};
});
if type_mentions_generic(field_type, &generic_idents) {
generics
.make_where_clause()
.predicates
.push(syn::parse_quote!(#field_type: From<Keypath>));
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
const _: () = {
use ::thruput_sdk::storage::*;
impl #impl_generics From<Keypath> for #ident #ty_generics
#where_clause
{
fn from(keypath: Keypath) -> Self {
#field_initializers
Self {
#field_names
}
}
}
};
})
}
fn err<T, S, M>(spanned: &S, msg: M) -> Result<T>
where
S: Spanned,
M: AsRef<str>,
{
Err(syn::Error::new(spanned.span(), msg.as_ref()))
}
fn type_mentions_generic(ty: &syn::Type, params: &[syn::Ident]) -> bool {
if params.is_empty() {
return false;
}
quote!(#ty)
.into_iter()
.any(|tt| tokens_mention(&tt, params))
}
fn tokens_mention(tt: &proc_macro2::TokenTree, params: &[syn::Ident]) -> bool {
match tt {
proc_macro2::TokenTree::Ident(id) => params.contains(id),
proc_macro2::TokenTree::Group(g) => {
g.stream().into_iter().any(|t| tokens_mention(&t, params))
}
_ => false,
}
}