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
140
141
142
143
144
145
146
147
#![feature(proc_macro_diagnostic, proc_macro_span)]
#![recursion_limit="256"]

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

use proc_macro::TokenStream;
use devise_core::*;

struct Naked(bool);

impl FromMeta for Naked {
    fn from_meta(meta: MetaItem) -> Result<Naked> {
        if let MetaItem::List(list) = meta {
            if list.iter.len() != 1 {
                return Err(list.span().error("expected exactly one parameter"));
            }

            let item = list.iter().next().unwrap();
            if let MetaItem::Ident(ident) = item {
                if ident == "naked" {
                    return Ok(Naked(true));
                }
            }

            Err(item.span().error("expected `naked`"))
        } else {
            Err(meta.span().error("malformed attribute: expected list"))
        }
    }
}

#[proc_macro_derive(FromMeta, attributes(meta))]
pub fn derive_from_meta(input: TokenStream) -> TokenStream {
    DeriveGenerator::build_for(input, quote!(impl ::devise::FromMeta))
        .data_support(DataSupport::NamedStruct)
        .function(|_, inner| quote! {
            fn from_meta(
                __meta: ::devise::MetaItem
            ) -> ::devise::Result<Self> {
                #inner
            }
        })
        .validate_fields(|_, fields| {
            for f in fields.iter() {
                Naked::from_attrs("meta", &f.attrs).unwrap_or(Ok(Naked(false)))?;
            }

            Ok(())
        })
        .map_struct(|_, data| {
            let naked = |field: &Field| -> bool {
                Naked::from_attrs("meta", &field.attrs)
                    .unwrap_or(Ok(Naked(false)))
                    .expect("checked in `validate_fields`")
                    .0
            };

            let constructors = data.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 = data.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 = data.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 = data.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)))?,
                }
            });

            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 = match __meta {
                    ::devise::MetaItem::List(__l) => __l.iter(),
                    _ => return Err(__span.error("malformed attribute")
                                    .help("expected syntax: #[attr(key = value, ..)]"))
                };

                // 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 pair"))
                    };

                    #(#named_matchers)*

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

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