databake_derive/
lib.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Custom derives for `Bake`
6
7use proc_macro::TokenStream;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::quote;
10use syn::{
11    parse::{Parse, ParseStream},
12    parse_macro_input,
13    punctuated::Punctuated,
14    DeriveInput, Ident, Path, PathSegment, Token,
15};
16use synstructure::{AddBounds, Structure};
17
18/// This custom derive auto-implements the `Bake` trait on any given type that has public
19/// fields that also implement `Bake`.
20///
21/// For a type `Person` defined in the module `module` of crate `bar`, this derive
22/// can be used as follows:
23///
24/// ```rust
25/// use databake::Bake;
26///
27/// #[derive(Bake)]
28/// #[databake(path = bar::module)]
29/// pub struct Person<'a> {
30///     pub name: &'a str,
31///     pub age: u32,
32/// }
33/// ```
34#[proc_macro_derive(Bake, attributes(databake))]
35pub fn bake_derive(input: TokenStream) -> TokenStream {
36    let input = parse_macro_input!(input as DeriveInput);
37    TokenStream::from(bake_derive_impl(&input))
38}
39
40fn bake_derive_impl(input: &DeriveInput) -> TokenStream2 {
41    let mut structure = Structure::new(input);
42
43    struct PathAttr(Punctuated<PathSegment, Token![::]>);
44
45    impl Parse for PathAttr {
46        fn parse(input: ParseStream<'_>) -> syn::parse::Result<Self> {
47            let i: Ident = input.parse()?;
48            if i != "path" {
49                return Err(input.error(format!("expected token \"path\", found {i:?}")));
50            }
51            input.parse::<Token![=]>()?;
52            Ok(Self(input.parse::<Path>()?.segments))
53        }
54    }
55
56    let path = input
57        .attrs
58        .iter()
59        .find(|a| a.path().is_ident("databake"))
60        .expect("missing databake(path = ...) attribute")
61        .parse_args::<PathAttr>()
62        .unwrap()
63        .0;
64
65    let bake_body = structure.each_variant(|vi| {
66        let recursive_calls = vi.bindings().iter().map(|b| {
67            let ident = b.binding.clone();
68            quote! { let #ident =  #ident.bake(env); }
69        });
70
71        let constructor = vi.construct(|_, i| {
72            let ident = &vi.bindings()[i].binding;
73            quote! { # #ident }
74        });
75
76        quote! {
77            #(#recursive_calls)*
78            databake::quote! { #path::#constructor }
79        }
80    });
81
82    let borrows_size_body = structure.each_variant(|vi| {
83        let recursive_calls = vi.bindings().iter().map(|b| {
84            let ident = b.binding.clone();
85            quote! { #ident.borrows_size() }
86        });
87
88        quote! {
89            0 #(+ #recursive_calls)*
90        }
91    });
92
93    structure.add_bounds(AddBounds::Fields);
94
95    let crate_name = path.iter().next().unwrap();
96    let crate_name = quote!(#crate_name).to_string();
97
98    structure.gen_impl(quote! {
99        gen impl databake::Bake for @Self {
100            fn bake(&self, env: &databake::CrateEnv) -> databake::TokenStream {
101                env.insert(#crate_name);
102                match self {
103                    #bake_body
104                    &_ => unreachable!() // ZST references aren't uninhabited
105                }
106            }
107        }
108        gen impl databake::BakeSize for @Self {
109            fn borrows_size(&self) -> usize {
110                match self {
111                    #borrows_size_body
112                    &_ => unreachable!() // ZST references aren't uninhabited
113                }
114            }
115        }
116    })
117}