Skip to main content

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