1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#![recursion_limit="256"]

#[macro_use] extern crate quote;
extern crate proc_macro;
extern crate devise_core;

use proc_macro::TokenStream;

use devise_core::*;
use devise_core::ext::SpanDiagnosticExt;

#[derive(Default)]
struct Naked(bool);

impl FromMeta for Naked {
    fn from_meta(meta: &MetaItem) -> Result<Naked> {
        if let Some(meta) = meta.list()?.next() {
            if meta.path()?.is_ident("naked") {
                return Ok(Naked(true));
            }
        }

        Err(meta.span().error("expected `naked`"))
    }
}

#[proc_macro_derive(FromMeta, attributes(meta))]
pub fn derive_from_meta(input: TokenStream) -> TokenStream {
    DeriveGenerator::build_for(input, quote!(impl ::devise::FromMeta))
        .support(Support::NamedStruct)
        .inner_mapper(MapperBuild::new()
            .with_output(|_, output| quote! {
                fn from_meta(
                    __meta: &::devise::MetaItem
                ) -> ::devise::Result<Self> {
                    #[allow(unused_imports)]
                    use ::devise::ext::SpanDiagnosticExt;

                    #output
                }
            })
            .try_fields_map(|_, fields| {
                let naked = |field: &Field| -> bool {
                    Naked::one_from_attrs("meta", &field.attrs)
                        .unwrap()
                        .unwrap_or_default()
                        .0
                };

                // We do this just to emit errors.
                for field in fields.iter() {
                    Naked::one_from_attrs("meta", &field.attrs)?;
                }

                let constructors = fields.iter().map(|f| {
                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
                    quote_spanned!(span => #[allow(unused_assignments)] let mut #ident = None;)
                });

                let naked_matchers = fields.iter().filter(naked).map(|f| {
                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
                    let (name, ty) = (ident.to_string(), &f.ty);

                    quote_spanned! { span =>
                        match __list.next() {
                            Some(__i) if __i.is_bare() => {
                                #ident = Some(<#ty>::from_meta(__i)?)
                            },
                            Some(__i) => return Err(__i.span().error(
                                "unexpected keyed parameter: expected literal or identifier")),
                            None => return Err(__span.error(
                                format!("missing expected parameter: `{}`", #name))),
                        };
                    }
                });

                let named_matchers = fields.iter().filter(|f| !naked(f)).map(|f| {
                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
                    let (name, ty) = (ident.to_string(), &f.ty);

                    quote_spanned! { span =>
                        if __name == #name {
                            if #ident.is_some() {
                                return Err(__span.error(
                                    format!("duplicate attribute parameter: {}", #name)));
                            }

                            #ident = Some(<#ty>::from_meta(__meta)?);
                            continue;
                        }
                    }
                });

                let builders = fields.iter().map(|f| {
                    let (ident, span) = (f.ident.as_ref().unwrap(), f.span().into());
                    let name = ident.to_string();

                    quote_spanned! { span =>
                        #ident: #ident.or_else(::devise::FromMeta::default)
                        .ok_or_else(|| __span.error(
                            format!("missing required attribute parameter: `{}`", #name)))?,
                    }
                });

                Ok(quote! {
                    use ::devise::Spanned;

                    // First, check that the attribute is a list: name(list, ..) and
                    // generate __list: iterator over the items in the attribute.
                    let __span = __meta.span();
                    let mut __list = __meta.list()?;

                    // Set up the constructors for all the variables.
                    #(#constructors)*

                    // Then, parse all of the naked meta items.
                    #(#naked_matchers)*

                    // Parse the rest as non-naked meta items.
                    for __meta in __list {
                        let __span = __meta.span();
                        let __name = match __meta.name() {
                            Some(__ident) => __ident,
                            None => return Err(__span.error("expected key/value `key = value`")),
                        };

                        #(#named_matchers)*

                        let __msg = format!("unexpected attribute parameter: `{}`", __name);
                        return Err(__span.error(__msg));
                    }

                    // Finally, build up the structure.
                    Ok(Self { #(#builders)* })
                })
            })
        )
        .to_tokens()
}