bakery_derive/
lib.rs

1extern crate proc_macro2;
2use proc_macro2::TokenStream;
3use quote::{quote, quote_spanned};
4use syn::{
5    parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam,
6    Generics,
7};
8
9/// Implements bakery::Recipe trait for the derived type
10#[proc_macro_derive(Recipe)]
11pub fn derive_bakery(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12    // This code is derived from the heapsize_derive example from the syn crate:
13    // https://github.com/dtolnay/syn/blob/master/examples/heapsize/heapsize_derive/src/lib.rs
14    let input = parse_macro_input!(input as DeriveInput);
15    let name = input.ident;
16    // add_trait_bounds will mark generic types to impl the trait `bakery::Recipe`
17    let generics = add_trait_bounds(input.generics);
18    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
19    let fields = recipe_token_stream(&input.data);
20
21    let expanded = quote! {
22        impl #impl_generics bakery::Recipe for #name #ty_generics #where_clause {
23            fn recipe(tree: &mut bakery::NodeTree) -> u32 {
24                let nid = tree.create_struct(None, "S");
25                #fields
26                nid
27            }
28        }
29    };
30
31    proc_macro::TokenStream::from(expanded)
32}
33
34// Add a bound `T: bakery::Recipe` to every type parameter T
35fn add_trait_bounds(mut generics: Generics) -> Generics {
36    for param in &mut generics.params {
37        if let GenericParam::Type(ref mut type_param) = *param {
38            type_param.bounds.push(parse_quote!(bakery::Recipe));
39        }
40    }
41    generics
42}
43
44fn recipe_token_stream(data: &Data) -> TokenStream {
45    match *data {
46        Data::Struct(ref data) => {
47            match data.fields {
48                Fields::Named(ref fields) => {
49                    let quotes = fields.named.iter().map(|f| {
50                        let name = &f.ident;
51                        let ty = &f.ty;
52                        quote_spanned! {
53                            f.span() =>
54                                // To comply with borrow checker, those two lines cannot be merged.
55                                let nid_ty = <#ty> :: recipe(tree);
56                                tree.create_struct_member(nid, stringify!(#name), nid_ty);
57                        }
58                    });
59                    quote! {
60                        #( #quotes )*
61                    }
62                }
63                Fields::Unnamed(_) | Fields::Unit => unimplemented!(),
64            }
65        }
66        Data::Enum(_) | Data::Union(_) => unimplemented!(),
67    }
68}