autocxx_parser/
directives.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use indexmap::map::IndexMap as HashMap;
10
11use once_cell::sync::OnceCell;
12use proc_macro2::Ident;
13use proc_macro2::Span;
14#[cfg(feature = "reproduction_case")]
15use proc_macro2::TokenStream;
16
17#[cfg(feature = "reproduction_case")]
18use quote::{quote, ToTokens};
19use syn::parse::ParseStream;
20
21use crate::config::AllowlistErr;
22
23#[cfg(feature = "reproduction_case")]
24use crate::config::Allowlist;
25
26use crate::directive_names::{EXTERN_RUST_FUN, EXTERN_RUST_TYPE, SUBCLASS};
27use crate::{AllowlistEntry, IncludeCppConfig};
28use crate::{ParseResult, RustFun, RustPath};
29
30pub(crate) struct DirectivesMap {
31    pub(crate) need_hexathorpe: HashMap<String, Box<dyn Directive>>,
32    pub(crate) need_exclamation: HashMap<String, Box<dyn Directive>>,
33}
34
35static DIRECTIVES: OnceCell<DirectivesMap> = OnceCell::new();
36
37pub(crate) fn get_directives() -> &'static DirectivesMap {
38    DIRECTIVES.get_or_init(|| {
39        let mut need_hexathorpe: HashMap<String, Box<dyn Directive>> = HashMap::new();
40        need_hexathorpe.insert("include".into(), Box::new(Inclusion));
41        let mut need_exclamation: HashMap<String, Box<dyn Directive>> = HashMap::new();
42        need_exclamation.insert("generate".into(), Box::new(Generate(false)));
43        need_exclamation.insert("generate_pod".into(), Box::new(Generate(true)));
44        need_exclamation.insert("generate_ns".into(), Box::new(GenerateNs));
45        need_exclamation.insert("generate_all".into(), Box::new(GenerateAll));
46        need_exclamation.insert("safety".into(), Box::new(Safety));
47        need_exclamation.insert(
48            "pod".into(),
49            Box::new(StringList(
50                |config| &mut config.pod_requests,
51                |config| &config.pod_requests,
52            )),
53        );
54        need_exclamation.insert(
55            "block".into(),
56            Box::new(StringList(
57                |config| &mut config.blocklist,
58                |config| &config.blocklist,
59            )),
60        );
61        need_exclamation.insert(
62            "block_constructors".into(),
63            Box::new(StringList(
64                |config| &mut config.constructor_blocklist,
65                |config| &config.constructor_blocklist,
66            )),
67        );
68        need_exclamation.insert(
69            "instantiable".into(),
70            Box::new(StringList(
71                |config| &mut config.instantiable,
72                |config| &config.instantiable,
73            )),
74        );
75        need_exclamation.insert(
76            "parse_only".into(),
77            Box::new(BoolFlag(
78                |config| &mut config.parse_only,
79                |config| &config.parse_only,
80            )),
81        );
82        need_exclamation.insert(
83            "exclude_impls".into(),
84            Box::new(BoolFlag(
85                |config| &mut config.exclude_impls,
86                |config| &config.exclude_impls,
87            )),
88        );
89        need_exclamation.insert(
90            "exclude_utilities".into(),
91            Box::new(BoolFlag(
92                |config| &mut config.exclude_utilities,
93                |config| &config.exclude_utilities,
94            )),
95        );
96        need_exclamation.insert("name".into(), Box::new(ModName));
97        need_exclamation.insert("concrete".into(), Box::new(Concrete));
98        need_exclamation.insert("rust_type".into(), Box::new(RustType { output: false }));
99        need_exclamation.insert(EXTERN_RUST_TYPE.into(), Box::new(RustType { output: true }));
100        need_exclamation.insert(SUBCLASS.into(), Box::new(Subclass));
101        need_exclamation.insert(EXTERN_RUST_FUN.into(), Box::new(ExternRustFun));
102        need_exclamation.insert(
103            "extern_cpp_type".into(),
104            Box::new(ExternCppType { opaque: false }),
105        );
106        need_exclamation.insert(
107            "extern_cpp_opaque_type".into(),
108            Box::new(ExternCppType { opaque: true }),
109        );
110
111        DirectivesMap {
112            need_hexathorpe,
113            need_exclamation,
114        }
115    })
116}
117
118/// Trait for handling an `include_cpp!` configuration directive.
119pub(crate) trait Directive: Send + Sync {
120    fn parse(
121        &self,
122        args: ParseStream,
123        config: &mut IncludeCppConfig,
124        span: &Span,
125    ) -> ParseResult<()>;
126
127    #[cfg(feature = "reproduction_case")]
128    fn output<'a>(
129        &self,
130        config: &'a IncludeCppConfig,
131    ) -> Box<dyn Iterator<Item = TokenStream> + 'a>;
132}
133
134struct Inclusion;
135
136impl Directive for Inclusion {
137    fn parse(
138        &self,
139        args: ParseStream,
140        config: &mut IncludeCppConfig,
141        _span: &Span,
142    ) -> ParseResult<()> {
143        let hdr: syn::LitStr = args.parse()?;
144        config.inclusions.push(hdr.value());
145        Ok(())
146    }
147
148    #[cfg(feature = "reproduction_case")]
149    fn output<'a>(
150        &self,
151        config: &'a IncludeCppConfig,
152    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
153        Box::new(config.inclusions.iter().map(|val| quote! { #val }))
154    }
155}
156
157/// Directive for either `generate!` (false) or `generate_pod!` (true).
158struct Generate(bool);
159
160impl Directive for Generate {
161    fn parse(
162        &self,
163        args: ParseStream,
164        config: &mut IncludeCppConfig,
165        span: &Span,
166    ) -> ParseResult<()> {
167        let generate: syn::LitStr = args.parse()?;
168        config
169            .allowlist
170            .push(AllowlistEntry::Item(generate.value()))
171            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
172        if self.0 {
173            config.pod_requests.push(generate.value());
174        }
175        Ok(())
176    }
177
178    #[cfg(feature = "reproduction_case")]
179    fn output<'a>(
180        &self,
181        config: &'a IncludeCppConfig,
182    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
183        match &config.allowlist {
184            Allowlist::Specific(items) if !self.0 => Box::new(
185                items
186                    .iter()
187                    .flat_map(|i| match i {
188                        AllowlistEntry::Item(s) => Some(s),
189                        _ => None,
190                    })
191                    .map(|s| quote! { #s }),
192            ),
193            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
194            _ => Box::new(std::iter::empty()),
195        }
196    }
197}
198
199struct GenerateNs;
200
201impl Directive for GenerateNs {
202    fn parse(
203        &self,
204        args: ParseStream,
205        config: &mut IncludeCppConfig,
206        span: &Span,
207    ) -> ParseResult<()> {
208        let generate: syn::LitStr = args.parse()?;
209        config
210            .allowlist
211            .push(AllowlistEntry::Namespace(generate.value()))
212            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
213        Ok(())
214    }
215
216    #[cfg(feature = "reproduction_case")]
217    fn output<'a>(
218        &self,
219        config: &'a IncludeCppConfig,
220    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
221        match &config.allowlist {
222            Allowlist::Specific(items) => Box::new(
223                items
224                    .iter()
225                    .flat_map(|i| match i {
226                        AllowlistEntry::Namespace(s) => Some(s),
227                        _ => None,
228                    })
229                    .map(|s| quote! { #s }),
230            ),
231            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
232            _ => Box::new(std::iter::empty()),
233        }
234    }
235}
236
237struct GenerateAll;
238
239impl Directive for GenerateAll {
240    fn parse(
241        &self,
242        _args: ParseStream,
243        config: &mut IncludeCppConfig,
244        span: &Span,
245    ) -> ParseResult<()> {
246        config
247            .allowlist
248            .set_all()
249            .map_err(|e| allowlist_err_to_syn_err(e, span))?;
250        Ok(())
251    }
252
253    #[cfg(feature = "reproduction_case")]
254    fn output<'a>(
255        &self,
256        config: &'a IncludeCppConfig,
257    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
258        match &config.allowlist {
259            Allowlist::All => Box::new(std::iter::once(TokenStream::new())),
260            Allowlist::Unspecified(_) => panic!("Allowlist mode not yet determined"),
261            _ => Box::new(std::iter::empty()),
262        }
263    }
264}
265
266struct Safety;
267
268impl Directive for Safety {
269    fn parse(
270        &self,
271        args: ParseStream,
272        config: &mut IncludeCppConfig,
273        _ident_span: &Span,
274    ) -> ParseResult<()> {
275        config.unsafe_policy = args.parse()?;
276        Ok(())
277    }
278
279    #[cfg(feature = "reproduction_case")]
280    fn output<'a>(
281        &self,
282        config: &'a IncludeCppConfig,
283    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
284        let policy = &config.unsafe_policy;
285        match config.unsafe_policy {
286            crate::UnsafePolicy::AllFunctionsUnsafe => Box::new(std::iter::empty()),
287            _ => Box::new(std::iter::once(policy.to_token_stream())),
288        }
289    }
290}
291
292fn allowlist_err_to_syn_err(err: AllowlistErr, span: &Span) -> syn::Error {
293    syn::Error::new(*span, format!("{err}"))
294}
295
296struct StringList<SET, GET>(SET, GET)
297where
298    SET: Fn(&mut IncludeCppConfig) -> &mut Vec<String>,
299    GET: Fn(&IncludeCppConfig) -> &Vec<String>;
300
301impl<SET, GET> Directive for StringList<SET, GET>
302where
303    SET: Fn(&mut IncludeCppConfig) -> &mut Vec<String> + Sync + Send,
304    GET: Fn(&IncludeCppConfig) -> &Vec<String> + Sync + Send,
305{
306    fn parse(
307        &self,
308        args: ParseStream,
309        config: &mut IncludeCppConfig,
310        _ident_span: &Span,
311    ) -> ParseResult<()> {
312        let val: syn::LitStr = args.parse()?;
313        self.0(config).push(val.value());
314        Ok(())
315    }
316
317    #[cfg(feature = "reproduction_case")]
318    fn output<'a>(
319        &self,
320        config: &'a IncludeCppConfig,
321    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
322        Box::new(self.1(config).iter().map(|val| {
323            quote! {
324                #val
325            }
326        }))
327    }
328}
329
330struct BoolFlag<SET, GET>(SET, GET)
331where
332    SET: Fn(&mut IncludeCppConfig) -> &mut bool,
333    GET: Fn(&IncludeCppConfig) -> &bool;
334
335impl<SET, GET> Directive for BoolFlag<SET, GET>
336where
337    SET: Fn(&mut IncludeCppConfig) -> &mut bool + Sync + Send,
338    GET: Fn(&IncludeCppConfig) -> &bool + Sync + Send,
339{
340    fn parse(
341        &self,
342        _args: ParseStream,
343        config: &mut IncludeCppConfig,
344        _ident_span: &Span,
345    ) -> ParseResult<()> {
346        *self.0(config) = true;
347        Ok(())
348    }
349
350    #[cfg(feature = "reproduction_case")]
351    fn output<'a>(
352        &self,
353        config: &'a IncludeCppConfig,
354    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
355        if *self.1(config) {
356            Box::new(std::iter::once(quote! {}))
357        } else {
358            Box::new(std::iter::empty())
359        }
360    }
361}
362
363struct ModName;
364
365impl Directive for ModName {
366    fn parse(
367        &self,
368        args: ParseStream,
369        config: &mut IncludeCppConfig,
370        _ident_span: &Span,
371    ) -> ParseResult<()> {
372        let id: Ident = args.parse()?;
373        config.mod_name = Some(id);
374        Ok(())
375    }
376
377    #[cfg(feature = "reproduction_case")]
378    fn output<'a>(
379        &self,
380        config: &'a IncludeCppConfig,
381    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
382        match &config.mod_name {
383            None => Box::new(std::iter::empty()),
384            Some(id) => Box::new(std::iter::once(quote! { #id })),
385        }
386    }
387}
388
389struct Concrete;
390
391impl Directive for Concrete {
392    fn parse(
393        &self,
394        args: ParseStream,
395        config: &mut IncludeCppConfig,
396        _ident_span: &Span,
397    ) -> ParseResult<()> {
398        let definition: syn::LitStr = args.parse()?;
399        args.parse::<syn::token::Comma>()?;
400        let rust_id: syn::Ident = args.parse()?;
401        config.concretes.0.insert(definition.value(), rust_id);
402        Ok(())
403    }
404
405    #[cfg(feature = "reproduction_case")]
406    fn output<'a>(
407        &self,
408        config: &'a IncludeCppConfig,
409    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
410        Box::new(config.concretes.0.iter().map(|(k, v)| {
411            quote! {
412                #k,#v
413            }
414        }))
415    }
416}
417
418struct RustType {
419    #[allow(dead_code)]
420    output: bool,
421}
422
423impl Directive for RustType {
424    fn parse(
425        &self,
426        args: ParseStream,
427        config: &mut IncludeCppConfig,
428        _ident_span: &Span,
429    ) -> ParseResult<()> {
430        let id: Ident = args.parse()?;
431        config.rust_types.push(RustPath::new_from_ident(id));
432        Ok(())
433    }
434
435    #[cfg(feature = "reproduction_case")]
436    fn output<'a>(
437        &self,
438        config: &'a IncludeCppConfig,
439    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
440        if self.output {
441            Box::new(config.rust_types.iter().map(|rp| rp.to_token_stream()))
442        } else {
443            Box::new(std::iter::empty())
444        }
445    }
446}
447
448struct Subclass;
449
450impl Directive for Subclass {
451    fn parse(
452        &self,
453        args: ParseStream,
454        config: &mut IncludeCppConfig,
455        _ident_span: &Span,
456    ) -> ParseResult<()> {
457        let superclass: syn::LitStr = args.parse()?;
458        args.parse::<syn::token::Comma>()?;
459        let subclass: syn::Ident = args.parse()?;
460        config.subclasses.push(crate::config::Subclass {
461            superclass: superclass.value(),
462            subclass,
463        });
464        Ok(())
465    }
466
467    #[cfg(feature = "reproduction_case")]
468    fn output<'a>(
469        &self,
470        config: &'a IncludeCppConfig,
471    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
472        Box::new(config.subclasses.iter().map(|sc| {
473            let superclass = &sc.superclass;
474            let subclass = &sc.subclass;
475            quote! {
476                #superclass,#subclass
477            }
478        }))
479    }
480}
481
482struct ExternRustFun;
483
484impl Directive for ExternRustFun {
485    fn parse(
486        &self,
487        args: ParseStream,
488        config: &mut IncludeCppConfig,
489        _ident_span: &Span,
490    ) -> ParseResult<()> {
491        let path: RustPath = args.parse()?;
492        args.parse::<syn::token::Comma>()?;
493        let sig: syn::Signature = args.parse()?;
494        config.extern_rust_funs.push(RustFun {
495            path,
496            sig,
497            has_receiver: false,
498        });
499        Ok(())
500    }
501
502    #[cfg(feature = "reproduction_case")]
503    fn output<'a>(
504        &self,
505        config: &'a IncludeCppConfig,
506    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
507        Box::new(config.extern_rust_funs.iter().map(|erf| {
508            let p = &erf.path;
509            let s = &erf.sig;
510            quote! { #p,#s }
511        }))
512    }
513}
514
515struct ExternCppType {
516    opaque: bool,
517}
518
519impl Directive for ExternCppType {
520    fn parse(
521        &self,
522        args: ParseStream,
523        config: &mut IncludeCppConfig,
524        _ident_span: &Span,
525    ) -> ParseResult<()> {
526        let definition: syn::LitStr = args.parse()?;
527        args.parse::<syn::token::Comma>()?;
528        let rust_path: syn::TypePath = args.parse()?;
529        config.externs.0.insert(
530            definition.value(),
531            crate::config::ExternCppType {
532                rust_path,
533                opaque: self.opaque,
534            },
535        );
536        Ok(())
537    }
538
539    #[cfg(feature = "reproduction_case")]
540    fn output<'a>(
541        &self,
542        config: &'a IncludeCppConfig,
543    ) -> Box<dyn Iterator<Item = TokenStream> + 'a> {
544        let opaque_needed = self.opaque;
545        Box::new(
546            config
547                .externs
548                .0
549                .iter()
550                .filter_map(move |(definition, details)| {
551                    if details.opaque == opaque_needed {
552                        let rust_path = &details.rust_path;
553                        Some(quote! {
554                            #definition, #rust_path
555                        })
556                    } else {
557                        None
558                    }
559                }),
560        )
561    }
562}