1use 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
118pub(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
157struct 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}