#![warn(missing_docs)]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Data, DeriveInput, Ident, Path, PathSegment, Token, Visibility,
};
use synstructure::{AddBounds, Structure};
#[proc_macro_derive(Bake, attributes(databake))]
pub fn bake_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(bake_derive_impl(&input))
}
fn bake_derive_impl(input: &DeriveInput) -> TokenStream2 {
let mut structure = Structure::new(input);
struct PathAttr(Punctuated<PathSegment, Token![::]>);
impl Parse for PathAttr {
fn parse(input: ParseStream<'_>) -> syn::parse::Result<Self> {
let i: Ident = input.parse()?;
if i != "path" {
return Err(input.error(format!("expected token \"path\", found {i:?}")));
}
input.parse::<Token![=]>()?;
Ok(Self(input.parse::<Path>()?.segments))
}
}
let path = input
.attrs
.iter()
.find(|a| a.path().is_ident("databake"))
.expect("missing databake(path = ...) attribute")
.parse_args::<PathAttr>()
.unwrap()
.0;
let bake_body = structure.each_variant(|vi| {
let recursive_calls = vi.bindings().iter().map(|b| {
let ident = b.binding.clone();
quote! { let #ident = #ident.bake(env); }
});
let constructor = vi.construct(|f, i| {
assert!(
f.vis == syn::parse_str::<Visibility>("pub").unwrap()
|| matches!(input.data, Data::Enum(_)),
"deriving Bake requires public fields"
);
let ident = &vi.bindings()[i].binding;
quote! { # #ident }
});
quote! {
#(#recursive_calls)*
databake::quote! { #path::#constructor }
}
});
let borrows_size_body = structure.each_variant(|vi| {
let recursive_calls = vi.bindings().iter().map(|b| {
let ident = b.binding.clone();
quote! { #ident.borrows_size() }
});
quote! {
0 #(+ #recursive_calls)*
}
});
structure.add_bounds(AddBounds::Fields);
let crate_name = path.iter().next().unwrap();
let crate_name = quote!(#crate_name).to_string();
structure.gen_impl(quote! {
gen impl databake::Bake for @Self {
fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
env.insert(#crate_name);
match self {
#bake_body
&_ => unreachable!() }
}
}
gen impl databake::BakeSize for @Self {
fn borrows_size(&self) -> usize {
match self {
#borrows_size_body
&_ => unreachable!() }
}
}
})
}