devise_codegen/
lib.rs

1#![recursion_limit="256"]
2
3#[macro_use] extern crate quote;
4extern crate proc_macro;
5extern crate devise_core;
6
7use proc_macro::TokenStream;
8
9use devise_core::*;
10use devise_core::ext::SpanDiagnosticExt;
11
12#[derive(Default)]
13struct Naked(bool);
14
15impl FromMeta for Naked {
16    fn from_meta(meta: &MetaItem) -> Result<Naked> {
17        if let Some(meta) = meta.list()?.next() {
18            if meta.path()?.is_ident("naked") {
19                return Ok(Naked(true));
20            }
21        }
22
23        Err(meta.span().error("expected `naked`"))
24    }
25}
26
27#[proc_macro_derive(FromMeta, attributes(meta))]
28pub fn derive_from_meta(input: TokenStream) -> TokenStream {
29    DeriveGenerator::build_for(input, quote!(impl ::devise::FromMeta))
30        .support(Support::NamedStruct)
31        .inner_mapper(MapperBuild::new()
32            .with_output(|_, output| quote! {
33                fn from_meta(
34                    __meta: &::devise::MetaItem
35                ) -> ::devise::Result<Self> {
36                    #[allow(unused_imports)]
37                    use ::devise::ext::SpanDiagnosticExt;
38
39                    #output
40                }
41            })
42            .try_fields_map(|_, fields| {
43                let naked = |field: &Field| -> bool {
44                    Naked::one_from_attrs("meta", &field.attrs)
45                        .unwrap()
46                        .unwrap_or_default()
47                        .0
48                };
49
50                // We do this just to emit errors.
51                for field in fields.iter() {
52                    Naked::one_from_attrs("meta", &field.attrs)?;
53                }
54
55                let constructors = fields.iter().map(|f| {
56                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
57                    quote_spanned!(span => #[allow(unused_assignments)] let mut #ident = None;)
58                });
59
60                let naked_matchers = fields.iter().filter(naked).map(|f| {
61                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
62                    let (name, ty) = (ident.to_string(), &f.ty);
63
64                    quote_spanned! { span =>
65                        match __list.next() {
66                            Some(__i) if __i.is_bare() => {
67                                #ident = Some(<#ty>::from_meta(__i)?)
68                            },
69                            Some(__i) => return Err(__i.span().error(
70                                "unexpected keyed parameter: expected literal or identifier")),
71                            None => return Err(__span.error(
72                                format!("missing expected parameter: `{}`", #name))),
73                        };
74                    }
75                });
76
77                let named_matchers = fields.iter().filter(|f| !naked(f)).map(|f| {
78                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
79                    let (name, ty) = (ident.to_string(), &f.ty);
80
81                    quote_spanned! { span =>
82                        if __name == #name {
83                            if #ident.is_some() {
84                                return Err(__span.error(
85                                    format!("duplicate attribute parameter: {}", #name)));
86                            }
87
88                            #ident = Some(<#ty>::from_meta(__meta)?);
89                            continue;
90                        }
91                    }
92                });
93
94                let builders = fields.iter().map(|f| {
95                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
96                    let name = ident.to_string();
97
98                    quote_spanned! { span =>
99                        #ident: #ident.or_else(::devise::FromMeta::default)
100                        .ok_or_else(|| __span.error(
101                            format!("missing required attribute parameter: `{}`", #name)))?,
102                    }
103                });
104
105                Ok(quote! {
106                    use ::devise::Spanned;
107
108                    // First, check that the attribute is a list: name(list, ..) and
109                    // generate __list: iterator over the items in the attribute.
110                    let __span = __meta.span();
111                    let mut __list = __meta.list()?;
112
113                    // Set up the constructors for all the variables.
114                    #(#constructors)*
115
116                    // Then, parse all of the naked meta items.
117                    #(#naked_matchers)*
118
119                    // Parse the rest as non-naked meta items.
120                    for __meta in __list {
121                        let __span = __meta.span();
122                        let __name = match __meta.name() {
123                            Some(__ident) => __ident,
124                            None => return Err(__span.error("expected key/value `key = value`")),
125                        };
126
127                        #(#named_matchers)*
128
129                        let __msg = format!("unexpected attribute parameter: `{}`", __name);
130                        return Err(__span.error(__msg));
131                    }
132
133                    // Finally, build up the structure.
134                    Ok(Self { #(#builders)* })
135                })
136            })
137        )
138        .to_tokens()
139}