use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Expr, Ident, Token};
struct Fields {
fields: Punctuated<Field, Token![;]>,
}
struct Field {
name: Ident,
value: Expr,
}
impl Parse for Field {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse()?;
input.parse::<Token![=]>()?;
let value = input.parse()?;
Ok(Field { name, value })
}
}
impl Parse for Fields {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fields = Punctuated::parse_terminated(input)?;
if fields.is_empty() {
return Err(syn::Error::new(
input.span(),
"alloc_once! needs at least one `name = value` field to allocate",
));
}
Ok(Fields { fields })
}
}
#[proc_macro]
pub fn alloc_once(input: TokenStream) -> TokenStream {
let Fields { fields } = syn::parse_macro_input!(input as Fields);
let names = fields.iter().map(|f| &f.name);
let values = fields.iter().map(|f| &f.value);
let guard = Ident::new("__alloc_once_guard", proc_macro2::Span::mixed_site());
let expanded = quote! {
let mut #guard = {
let __alloc_once_value = ( #(#values,)* );
let __alloc_once_cap = ::core::mem::size_of_val(&__alloc_once_value);
let __alloc_once_arena =
::alloc_once::__private::Bump::with_capacity(__alloc_once_cap);
let __alloc_once_ptr: *mut _ = __alloc_once_arena.alloc(__alloc_once_value);
unsafe { ::alloc_once::AllocOnce::__from_parts(__alloc_once_arena, __alloc_once_ptr) }
};
let ( #(#names,)* ) = &mut *#guard;
};
expanded.into()
}