derive_deftly_macros/
derive.rs

1//! Macro impl for capturing the driver `#[derive(Deftly)]`
2
3use super::prelude::*;
4
5/// Contents of an entry in a `#[derive_deftly(..)]` attribute
6enum InvocationEntry {
7    Precanned(TemplatePath, UnprocessedOptions),
8}
9
10// (CannedName, CannedName, ...)
11struct InvocationAttr {
12    entries: Punctuated<InvocationEntry, token::Comma>,
13}
14
15/// Contents of an entry in a `#[derive_deftly_adhoc(..)]` attribute
16#[derive(Default)]
17struct AdhocAttr {
18    pub_: Option<MacroExport>,
19}
20
21impl Parse for InvocationEntry {
22    fn parse(input: ParseStream) -> syn::Result<Self> {
23        let entry = if input.lookahead1().peek(Token![pub]) {
24            return Err(input.error("`pub` must be in #[derive_deftly_adhoc]"));
25        } else {
26            let path = input.parse()?;
27            let options = if input.peek(syn::token::Bracket) {
28                let tokens;
29                let _bracket = bracketed!(tokens in input);
30                UnprocessedOptions::parse(
31                    &tokens,
32                    OpContext::DriverApplicationCapture,
33                )?
34            } else {
35                UnprocessedOptions::default()
36            };
37            InvocationEntry::Precanned(path, options)
38        };
39        Ok(entry)
40    }
41}
42
43impl Parse for InvocationAttr {
44    fn parse(input: ParseStream) -> syn::Result<Self> {
45        let entries = Punctuated::parse_terminated(input)?;
46        Ok(InvocationAttr { entries })
47    }
48}
49
50fn check_for_misplaced_atrs(data: &syn::Data) -> syn::Result<()> {
51    let attrs = |attrs: &[syn::Attribute]| {
52        for attr in attrs {
53            if let Some(_) = ["derive_deftly", "derive_deftly_adhoc"]
54                .iter()
55                .find(|forbidden| attr.path().is_ident(forbidden))
56            {
57                return Err(attr.error(
58 "attribute is only meaningful at the data structure toplevel"
59                ));
60            }
61        }
62        Ok(())
63    };
64
65    let fields = |fs: &Punctuated<syn::Field, _>| {
66        for f in fs.iter() {
67            attrs(&f.attrs)?;
68        }
69        Ok(())
70    };
71
72    let variantish = |fs: &syn::Fields| match fs {
73        syn::Fields::Unit => Ok(()),
74        syn::Fields::Named(n) => fields(&n.named),
75        syn::Fields::Unnamed(u) => fields(&u.unnamed),
76    };
77
78    let variants = |vs: &Punctuated<syn::Variant, _>| {
79        for v in vs.iter() {
80            attrs(&v.attrs)?;
81            variantish(&v.fields)?;
82        }
83        Ok(())
84    };
85
86    match data {
87        syn::Data::Struct(s) => variantish(&s.fields),
88        syn::Data::Union(u) => fields(&u.fields.named),
89        syn::Data::Enum(e) => variants(&e.variants),
90    }
91}
92
93/// This is #[derive(Deftly)]
94pub fn derive_deftly(
95    driver_stream: TokenStream,
96) -> Result<TokenStream, syn::Error> {
97    use engine::ChainNext;
98
99    let driver: syn::DeriveInput = syn::parse2(driver_stream.clone())?;
100
101    dprint_block!(&driver_stream, "#[derive(Deftly)] input");
102
103    let driver_mac_name =
104        format_ident!("derive_deftly_driver_{}", &driver.ident);
105
106    let precanned_paths: Vec<(TemplatePath, UnprocessedOptions)> = driver
107        .attrs
108        .iter()
109        .map(|attr| {
110            if !attr.path().is_ident("derive_deftly") {
111                return Ok(None);
112            }
113            let InvocationAttr { entries } = attr.parse_in_parens()?;
114            Ok(Some(entries))
115        })
116        .flatten_ok()
117        .flatten_ok()
118        .filter_map(|entry| match entry {
119            Err(e) => Some(Err(e)),
120            Ok(InvocationEntry::Precanned(path, options)) => {
121                Some(Ok((path, options)))
122            }
123        })
124        .collect::<syn::Result<Vec<_>>>()?;
125
126    let adhoc: Option<AdhocAttr> = driver
127        .attrs
128        .iter()
129        .filter(|attr| attr.path().is_ident("derive_deftly_adhoc"))
130        .inspect(|_: &&syn::Attribute| ())
131        .map(|attr| {
132            let adhoc = match &attr.meta {
133                syn::Meta::Path(_) => AdhocAttr { pub_: None },
134                syn::Meta::NameValue(nv) => {
135                    return Err(nv
136                        .eq_token
137                        .error("arguments (if any) must be in parens"))
138                }
139                syn::Meta::List(syn::MetaList {
140                    path: _,
141                    delimiter,
142                    tokens,
143                }) => {
144                    match delimiter {
145                        syn::MacroDelimiter::Paren(_) => Ok(()),
146                        syn::MacroDelimiter::Brace(t) => Err(t.span),
147                        syn::MacroDelimiter::Bracket(t) => Err(t.span),
148                    }
149                    .map_err(|span| span.error("expected parentheses"))?;
150                    let pub_ = Parser::parse2(
151                        MacroExport::parse_option,
152                        tokens.clone(),
153                    )?;
154                    AdhocAttr { pub_ }
155                }
156            };
157            Ok::<AdhocAttr, syn::Error>(adhoc)
158        })
159        .inspect(|_: &Result<AdhocAttr, _>| ())
160        // allow this attr to be repeated; any pub makes it pub
161        .reduce(|a, b| {
162            let pub_ = chain!(a?.pub_, b?.pub_).next();
163            Ok(AdhocAttr { pub_ })
164        })
165        .transpose()?;
166
167    check_for_misplaced_atrs(&driver.data)?;
168
169    let engine_macro = engine_macro_name()?;
170
171    // If the driver contains any $ tokens, we must do something about them.
172    // Otherwise, they might get mangled by the macro_rules expander.
173    // In particular, the following cause trouble:
174    //   `$template`, `$passthrough` - taken as references to the
175    //      macro arguments.
176    //  `$$` - taken as a reference to the nightly `$$` macro rules feature
177    //     (which we would love to use here, but can't yet)
178    //
179    // `$orig_dollar` is a literal dollar which comes from the driver
180    // invocation in invocation.rs.  This technique doesn't get the span
181    // right.  But getting the span right here is hard without having
182    // a whole new quoting scheme - see the discussion in the doc comment
183    // for `escape_dollars`.
184    //
185    // We can't use the technique we use for the template, because that
186    // technique relies on the fact that it's *us* that parses the template.
187    // But the driver is parsed for us by `syn`.
188    //
189    // Actual `$` in drivers will be very rare.  They could only appear in
190    // attributes or the like.  So, unlike with templates (which are
191    // full of important `$`s) we can probably live with the wrong spans.
192    let driver_escaped = escape_dollars(driver_stream);
193
194    let mut output = TokenStream::new();
195
196    let mut accum_start = TokenStream::new();
197
198    if let Some(adhoc) = adhoc {
199        accum_start.extend(quote!( _meta_used * ));
200
201        let macro_export = adhoc
202            .pub_
203            .map(|export| {
204                let macro_export =
205                    quote_spanned!(export.span()=> #[macro_export]);
206                Ok::<_, syn::Error>(macro_export)
207            })
208            .transpose()?;
209
210        output.extend(quote! {
211            #[allow(unused_macros)]
212            #macro_export
213            macro_rules! #driver_mac_name {
214                {
215                    { $($template:tt)* }
216                    { ($orig_dollar:tt) $(future:tt)* }
217                    $($dpassthrough:tt)*
218                } => {
219                    #engine_macro!{
220                        { #driver_escaped }
221                        ( )
222                        { $($template)* }
223                        $($dpassthrough)*
224                    }
225                };
226                { $($wrong:tt)* } => {
227                    compile_error!{concat!(
228                        "wrong input to derive-deftly driver inner macro ",
229                        stringify!(#driver_mac_name),
230                     "; might be due to incompatible derive-deftly versions(s)",
231                    )}
232                };
233            }
234        });
235    }
236
237    let (chain_next, chain_rest);
238    {
239        let mut errs = ErrorAccumulator::default();
240        let mut chain = chain!(
241            precanned_paths
242                .into_iter()
243                .map(|(templ_path, aoptions)| {
244                    let call = templ_path.macro_path().to_token_stream();
245                    let ao_versions = OpCompatVersions::ours();
246                    let after_driver = quote!( [ #ao_versions #aoptions ] () );
247                    Ok(ChainNext { call, after_driver })
248                })
249                .filter_map(|r| errs.handle(r)),
250            [ChainNext {
251                call: engine_macro.clone(),
252                after_driver: quote!( . () ),
253            }],
254        );
255        chain_next = chain.next().expect("should have been nonempty!");
256        chain_rest = {
257            let mut rest = TokenStream::new();
258            for c in chain {
259                c.to_tokens(&mut rest);
260            }
261            rest
262        };
263
264        errs.finish()?;
265    }
266    let ChainNext { call, after_driver } = chain_next;
267
268    output.extend(quote! {
269        #call !{
270            { #driver }
271            #after_driver
272            [ #chain_rest ]
273            [ #accum_start ]
274        }
275    });
276
277    dprint_block!(&output, "#[derive(Deftly)] output for {}", &driver.ident);
278
279    Ok(output)
280}