1use proc_macro2::{Span, TokenStream};
2use quote::ToTokens;
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
6use syn::parse::{Error, Parse, ParseStream, Result};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{braced, token, LitStr, Token};
10use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
11use wit_bindgen_core::AsyncFilterSet;
12use wit_bindgen_rust::{Opts, Ownership, WithOption};
13
14#[proc_macro]
15pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
16 syn::parse_macro_input!(input as Config)
17 .expand()
18 .unwrap_or_else(Error::into_compile_error)
19 .into()
20}
21
22fn anyhow_to_syn(span: Span, err: anyhow::Error) -> Error {
23 let err = attach_with_context(err);
24 let mut msg = err.to_string();
25 for cause in err.chain().skip(1) {
26 msg.push_str(&format!("\n\nCaused by:\n {cause}"));
27 }
28 Error::new(span, msg)
29}
30
31fn attach_with_context(err: anyhow::Error) -> anyhow::Error {
32 if let Some(e) = err.downcast_ref::<wit_bindgen_rust::MissingWith>() {
33 let option = e.0.clone();
34 return err.context(format!(
35 "missing one of:\n\
36 * `generate_all` option\n\
37 * `with: {{ \"{option}\": path::to::bindings, }}`\n\
38 * `with: {{ \"{option}\": generate, }}`\
39 "
40 ));
41 }
42 err
43}
44
45struct Config {
46 opts: Opts,
47 resolve: Resolve,
48 world: WorldId,
49 files: Vec<PathBuf>,
50 debug: bool,
51}
52
53enum Source {
55 Paths(Vec<PathBuf>),
57 Inline(String, Option<Vec<PathBuf>>),
59}
60
61impl Parse for Config {
62 fn parse(input: ParseStream<'_>) -> Result<Self> {
63 let call_site = Span::call_site();
64 let mut opts = Opts::default();
65 let mut world = None;
66 let mut source = None;
67 let mut features = Vec::new();
68 let mut async_configured = false;
69 let mut debug = false;
70
71 if input.peek(token::Brace) {
72 let content;
73 syn::braced!(content in input);
74 let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
75 for field in fields.into_pairs() {
76 match field.into_value() {
77 Opt::Path(span, p) => {
78 let paths = p.into_iter().map(|f| PathBuf::from(f.value())).collect();
79
80 source = Some(match source {
81 Some(Source::Paths(_)) | Some(Source::Inline(_, Some(_))) => {
82 return Err(Error::new(span, "cannot specify second source"));
83 }
84 Some(Source::Inline(i, None)) => Source::Inline(i, Some(paths)),
85 None => Source::Paths(paths),
86 })
87 }
88 Opt::World(s) => {
89 if world.is_some() {
90 return Err(Error::new(s.span(), "cannot specify second world"));
91 }
92 world = Some(s.value());
93 }
94 Opt::Inline(s) => {
95 source = Some(match source {
96 Some(Source::Inline(_, _)) => {
97 return Err(Error::new(s.span(), "cannot specify second source"));
98 }
99 Some(Source::Paths(p)) => Source::Inline(s.value(), Some(p)),
100 None => Source::Inline(s.value(), None),
101 })
102 }
103 Opt::UseStdFeature => opts.std_feature = true,
104 Opt::RawStrings => opts.raw_strings = true,
105 Opt::Ownership(ownership) => opts.ownership = ownership,
106 Opt::Skip(list) => opts.skip.extend(list.iter().map(|i| i.value())),
107 Opt::RuntimePath(path) => opts.runtime_path = Some(path.value()),
108 Opt::BitflagsPath(path) => opts.bitflags_path = Some(path.value()),
109 Opt::Stubs => {
110 opts.stubs = true;
111 }
112 Opt::ExportPrefix(prefix) => opts.export_prefix = Some(prefix.value()),
113 Opt::AdditionalDerives(paths) => {
114 opts.additional_derive_attributes = paths
115 .into_iter()
116 .map(|p| p.into_token_stream().to_string())
117 .collect()
118 }
119 Opt::AdditionalDerivesIgnore(list) => {
120 opts.additional_derive_ignore =
121 list.into_iter().map(|i| i.value()).collect()
122 }
123 Opt::With(with) => opts.with.extend(with),
124 Opt::GenerateAll => {
125 opts.generate_all = true;
126 }
127 Opt::TypeSectionSuffix(suffix) => {
128 opts.type_section_suffix = Some(suffix.value());
129 }
130 Opt::DisableRunCtorsOnceWorkaround(enable) => {
131 opts.disable_run_ctors_once_workaround = enable.value();
132 }
133 Opt::DefaultBindingsModule(enable) => {
134 opts.default_bindings_module = Some(enable.value());
135 }
136 Opt::ExportMacroName(name) => {
137 opts.export_macro_name = Some(name.value());
138 }
139 Opt::PubExportMacro(enable) => {
140 opts.pub_export_macro = enable.value();
141 }
142 Opt::GenerateUnusedTypes(enable) => {
143 opts.generate_unused_types = enable.value();
144 }
145 Opt::Features(f) => {
146 features.extend(f.into_iter().map(|f| f.value()));
147 }
148 Opt::DisableCustomSectionLinkHelpers(disable) => {
149 opts.disable_custom_section_link_helpers = disable.value();
150 }
151 Opt::Debug(enable) => {
152 debug = enable.value();
153 }
154 Opt::Async(val, span) => {
155 if async_configured {
156 return Err(Error::new(span, "cannot specify second async config"));
157 }
158 async_configured = true;
159 if val.any_enabled() && !cfg!(feature = "async") {
160 return Err(Error::new(
161 span,
162 "must enable `async` feature to enable async imports and/or exports",
163 ));
164 }
165 opts.async_ = val;
166 }
167 }
168 }
169 } else {
170 world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
171 if input.parse::<Option<syn::token::In>>()?.is_some() {
172 source = Some(Source::Paths(vec![PathBuf::from(
173 input.parse::<syn::LitStr>()?.value(),
174 )]));
175 }
176 }
177 let (resolve, main_packages, files) =
178 parse_source(&source, &features).map_err(|err| anyhow_to_syn(call_site, err))?;
179 let world = resolve
180 .select_world(&main_packages, world.as_deref())
181 .map_err(|e| anyhow_to_syn(call_site, e))?;
182 Ok(Config {
183 opts,
184 resolve,
185 world,
186 files,
187 debug,
188 })
189 }
190}
191
192fn parse_source(
194 source: &Option<Source>,
195 features: &[String],
196) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
197 let mut resolve = Resolve::default();
198 resolve.features.extend(features.iter().cloned());
199 let mut files = Vec::new();
200 let mut pkgs = Vec::new();
201 let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
202 let mut parse = |paths: &[PathBuf]| -> anyhow::Result<()> {
203 for path in paths {
204 let p = root.join(path);
205 let normalized_path = match std::fs::canonicalize(&p) {
209 Ok(p) => p,
210 Err(_) => p.to_path_buf(),
211 };
212 let (pkg, sources) = resolve.push_path(normalized_path)?;
213 pkgs.push(pkg);
214 files.extend(sources.paths().map(|p| p.to_owned()));
215 }
216 Ok(())
217 };
218 let default = root.join("wit");
219 match source {
220 Some(Source::Inline(s, path)) => {
221 match path {
222 Some(p) => parse(p)?,
223 None => {
228 if default.exists() {
229 parse(&[default])?;
230 }
231 }
232 }
233 pkgs.truncate(0);
234 pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", s)?)?);
235 }
236 Some(Source::Paths(p)) => parse(p)?,
237 None => parse(&[default])?,
238 };
239
240 Ok((resolve, pkgs, files))
241}
242
243impl Config {
244 fn expand(self) -> Result<TokenStream> {
245 let mut files = Default::default();
246 let mut generator = self.opts.build();
247 generator
248 .generate(&self.resolve, self.world, &mut files)
249 .map_err(|e| anyhow_to_syn(Span::call_site(), e))?;
250 let (_, src) = files.iter().next().unwrap();
251 let mut src = std::str::from_utf8(src).unwrap().to_string();
252
253 if std::env::var("WIT_BINDGEN_DEBUG").is_ok() || self.debug {
258 static INVOCATION: AtomicUsize = AtomicUsize::new(0);
259 let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
260 let world_name = &self.resolve.worlds[self.world].name;
261 let n = INVOCATION.fetch_add(1, Relaxed);
262 let path = root.join(format!("{world_name}{n}.rs"));
263
264 let contents = match fmt(&src) {
266 Ok(formatted) => formatted,
267 Err(_) => src.clone(),
268 };
269 std::fs::write(&path, contents.as_bytes()).unwrap();
270
271 src = format!("include!({path:?});");
272 }
273 let mut contents = src.parse::<TokenStream>().unwrap();
274
275 for file in self.files.iter() {
278 contents.extend(
279 format!(
280 "const _: &[u8] = include_bytes!(r#\"{}\"#);\n",
281 file.display()
282 )
283 .parse::<TokenStream>()
284 .unwrap(),
285 );
286 }
287
288 Ok(contents)
289 }
290}
291
292mod kw {
293 syn::custom_keyword!(std_feature);
294 syn::custom_keyword!(raw_strings);
295 syn::custom_keyword!(skip);
296 syn::custom_keyword!(world);
297 syn::custom_keyword!(path);
298 syn::custom_keyword!(inline);
299 syn::custom_keyword!(ownership);
300 syn::custom_keyword!(runtime_path);
301 syn::custom_keyword!(bitflags_path);
302 syn::custom_keyword!(exports);
303 syn::custom_keyword!(stubs);
304 syn::custom_keyword!(export_prefix);
305 syn::custom_keyword!(additional_derives);
306 syn::custom_keyword!(additional_derives_ignore);
307 syn::custom_keyword!(with);
308 syn::custom_keyword!(generate_all);
309 syn::custom_keyword!(type_section_suffix);
310 syn::custom_keyword!(disable_run_ctors_once_workaround);
311 syn::custom_keyword!(default_bindings_module);
312 syn::custom_keyword!(export_macro_name);
313 syn::custom_keyword!(pub_export_macro);
314 syn::custom_keyword!(generate_unused_types);
315 syn::custom_keyword!(features);
316 syn::custom_keyword!(disable_custom_section_link_helpers);
317 syn::custom_keyword!(imports);
318 syn::custom_keyword!(debug);
319}
320
321#[derive(Clone)]
322enum ExportKey {
323 World,
324 Name(syn::LitStr),
325}
326
327impl Parse for ExportKey {
328 fn parse(input: ParseStream<'_>) -> Result<Self> {
329 let l = input.lookahead1();
330 Ok(if l.peek(kw::world) {
331 input.parse::<kw::world>()?;
332 Self::World
333 } else {
334 Self::Name(input.parse()?)
335 })
336 }
337}
338
339impl From<ExportKey> for wit_bindgen_rust::ExportKey {
340 fn from(key: ExportKey) -> Self {
341 match key {
342 ExportKey::World => Self::World,
343 ExportKey::Name(s) => Self::Name(s.value()),
344 }
345 }
346}
347
348enum Opt {
349 World(syn::LitStr),
350 Path(Span, Vec<syn::LitStr>),
351 Inline(syn::LitStr),
352 UseStdFeature,
353 RawStrings,
354 Skip(Vec<syn::LitStr>),
355 Ownership(Ownership),
356 RuntimePath(syn::LitStr),
357 BitflagsPath(syn::LitStr),
358 Stubs,
359 ExportPrefix(syn::LitStr),
360 AdditionalDerives(Vec<syn::Path>),
362 AdditionalDerivesIgnore(Vec<syn::LitStr>),
363 With(HashMap<String, WithOption>),
364 GenerateAll,
365 TypeSectionSuffix(syn::LitStr),
366 DisableRunCtorsOnceWorkaround(syn::LitBool),
367 DefaultBindingsModule(syn::LitStr),
368 ExportMacroName(syn::LitStr),
369 PubExportMacro(syn::LitBool),
370 GenerateUnusedTypes(syn::LitBool),
371 Features(Vec<syn::LitStr>),
372 DisableCustomSectionLinkHelpers(syn::LitBool),
373 Async(AsyncFilterSet, Span),
374 Debug(syn::LitBool),
375}
376
377impl Parse for Opt {
378 fn parse(input: ParseStream<'_>) -> Result<Self> {
379 let l = input.lookahead1();
380 if l.peek(kw::path) {
381 input.parse::<kw::path>()?;
382 input.parse::<Token![:]>()?;
383 if input.peek(token::Bracket) {
387 let contents;
388 syn::bracketed!(contents in input);
389 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
390 Ok(Opt::Path(list.span(), list.into_iter().collect()))
391 } else {
392 let path: LitStr = input.parse()?;
393 Ok(Opt::Path(path.span(), vec![path]))
394 }
395 } else if l.peek(kw::inline) {
396 input.parse::<kw::inline>()?;
397 input.parse::<Token![:]>()?;
398 Ok(Opt::Inline(input.parse()?))
399 } else if l.peek(kw::world) {
400 input.parse::<kw::world>()?;
401 input.parse::<Token![:]>()?;
402 Ok(Opt::World(input.parse()?))
403 } else if l.peek(kw::std_feature) {
404 input.parse::<kw::std_feature>()?;
405 Ok(Opt::UseStdFeature)
406 } else if l.peek(kw::raw_strings) {
407 input.parse::<kw::raw_strings>()?;
408 Ok(Opt::RawStrings)
409 } else if l.peek(kw::ownership) {
410 input.parse::<kw::ownership>()?;
411 input.parse::<Token![:]>()?;
412 let ownership = input.parse::<syn::Ident>()?;
413 Ok(Opt::Ownership(match ownership.to_string().as_str() {
414 "Owning" => Ownership::Owning,
415 "Borrowing" => Ownership::Borrowing {
416 duplicate_if_necessary: {
417 let contents;
418 braced!(contents in input);
419 let field = contents.parse::<syn::Ident>()?;
420 match field.to_string().as_str() {
421 "duplicate_if_necessary" => {
422 contents.parse::<Token![:]>()?;
423 contents.parse::<syn::LitBool>()?.value
424 }
425 name => {
426 return Err(Error::new(
427 field.span(),
428 format!(
429 "unrecognized `Ownership::Borrowing` field: `{name}`; \
430 expected `duplicate_if_necessary`"
431 ),
432 ));
433 }
434 }
435 },
436 },
437 name => {
438 return Err(Error::new(
439 ownership.span(),
440 format!(
441 "unrecognized ownership: `{name}`; \
442 expected `Owning` or `Borrowing`"
443 ),
444 ));
445 }
446 }))
447 } else if l.peek(kw::skip) {
448 input.parse::<kw::skip>()?;
449 input.parse::<Token![:]>()?;
450 let contents;
451 syn::bracketed!(contents in input);
452 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
453 Ok(Opt::Skip(list.iter().cloned().collect()))
454 } else if l.peek(kw::runtime_path) {
455 input.parse::<kw::runtime_path>()?;
456 input.parse::<Token![:]>()?;
457 Ok(Opt::RuntimePath(input.parse()?))
458 } else if l.peek(kw::bitflags_path) {
459 input.parse::<kw::bitflags_path>()?;
460 input.parse::<Token![:]>()?;
461 Ok(Opt::BitflagsPath(input.parse()?))
462 } else if l.peek(kw::stubs) {
463 input.parse::<kw::stubs>()?;
464 Ok(Opt::Stubs)
465 } else if l.peek(kw::export_prefix) {
466 input.parse::<kw::export_prefix>()?;
467 input.parse::<Token![:]>()?;
468 Ok(Opt::ExportPrefix(input.parse()?))
469 } else if l.peek(kw::additional_derives) {
470 input.parse::<kw::additional_derives>()?;
471 input.parse::<Token![:]>()?;
472 let contents;
473 syn::bracketed!(contents in input);
474 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
475 Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
476 } else if l.peek(kw::additional_derives_ignore) {
477 input.parse::<kw::additional_derives_ignore>()?;
478 input.parse::<Token![:]>()?;
479 let contents;
480 syn::bracketed!(contents in input);
481 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
482 Ok(Opt::AdditionalDerivesIgnore(list.iter().cloned().collect()))
483 } else if l.peek(kw::with) {
484 input.parse::<kw::with>()?;
485 input.parse::<Token![:]>()?;
486 let contents;
487 let _lbrace = braced!(contents in input);
488 let fields: Punctuated<_, Token![,]> =
489 contents.parse_terminated(with_field_parse, Token![,])?;
490 Ok(Opt::With(HashMap::from_iter(fields.into_iter())))
491 } else if l.peek(kw::generate_all) {
492 input.parse::<kw::generate_all>()?;
493 Ok(Opt::GenerateAll)
494 } else if l.peek(kw::type_section_suffix) {
495 input.parse::<kw::type_section_suffix>()?;
496 input.parse::<Token![:]>()?;
497 Ok(Opt::TypeSectionSuffix(input.parse()?))
498 } else if l.peek(kw::disable_run_ctors_once_workaround) {
499 input.parse::<kw::disable_run_ctors_once_workaround>()?;
500 input.parse::<Token![:]>()?;
501 Ok(Opt::DisableRunCtorsOnceWorkaround(input.parse()?))
502 } else if l.peek(kw::default_bindings_module) {
503 input.parse::<kw::default_bindings_module>()?;
504 input.parse::<Token![:]>()?;
505 Ok(Opt::DefaultBindingsModule(input.parse()?))
506 } else if l.peek(kw::export_macro_name) {
507 input.parse::<kw::export_macro_name>()?;
508 input.parse::<Token![:]>()?;
509 Ok(Opt::ExportMacroName(input.parse()?))
510 } else if l.peek(kw::pub_export_macro) {
511 input.parse::<kw::pub_export_macro>()?;
512 input.parse::<Token![:]>()?;
513 Ok(Opt::PubExportMacro(input.parse()?))
514 } else if l.peek(kw::generate_unused_types) {
515 input.parse::<kw::generate_unused_types>()?;
516 input.parse::<Token![:]>()?;
517 Ok(Opt::GenerateUnusedTypes(input.parse()?))
518 } else if l.peek(kw::features) {
519 input.parse::<kw::features>()?;
520 input.parse::<Token![:]>()?;
521 let contents;
522 syn::bracketed!(contents in input);
523 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
524 Ok(Opt::Features(list.into_iter().collect()))
525 } else if l.peek(kw::disable_custom_section_link_helpers) {
526 input.parse::<kw::disable_custom_section_link_helpers>()?;
527 input.parse::<Token![:]>()?;
528 Ok(Opt::DisableCustomSectionLinkHelpers(input.parse()?))
529 } else if l.peek(kw::debug) {
530 input.parse::<kw::debug>()?;
531 input.parse::<Token![:]>()?;
532 Ok(Opt::Debug(input.parse()?))
533 } else if l.peek(Token![async]) {
534 let span = input.parse::<Token![async]>()?.span;
535 input.parse::<Token![:]>()?;
536 if input.peek(syn::LitBool) {
537 let enabled = input.parse::<syn::LitBool>()?.value;
538 Ok(Opt::Async(AsyncFilterSet::all(enabled), span))
539 } else {
540 let mut set = AsyncFilterSet::default();
541 let contents;
542 syn::bracketed!(contents in input);
543 for val in contents.parse_terminated(|p| p.parse::<syn::LitStr>(), Token![,])? {
544 set.push(&val.value());
545 }
546 Ok(Opt::Async(set, span))
547 }
548 } else {
549 Err(l.error())
550 }
551 }
552}
553
554fn with_field_parse(input: ParseStream<'_>) -> Result<(String, WithOption)> {
555 let interface = input.parse::<syn::LitStr>()?.value();
556 input.parse::<Token![:]>()?;
557 let start = input.span();
558 let path = input.parse::<syn::Path>()?;
559
560 let span = start
562 .join(path.segments.last().unwrap().ident.span())
563 .unwrap_or(start);
564
565 if path.is_ident("generate") {
566 return Ok((interface, WithOption::Generate));
567 }
568
569 let mut buf = String::new();
570 let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
571 if !segment.arguments.is_none() {
572 return Err(Error::new(
573 span,
574 "Module path must not contain angles or parens",
575 ));
576 }
577
578 buf.push_str(&segment.ident.to_string());
579
580 Ok(())
581 };
582
583 if path.leading_colon.is_some() {
584 buf.push_str("::");
585 }
586
587 let mut segments = path.segments.into_iter();
588
589 if let Some(segment) = segments.next() {
590 append(&mut buf, segment)?;
591 }
592
593 for segment in segments {
594 buf.push_str("::");
595 append(&mut buf, segment)?;
596 }
597
598 Ok((interface, WithOption::Path(buf)))
599}
600
601fn fmt(input: &str) -> Result<String> {
603 let syntax_tree = syn::parse_file(&input)?;
604 Ok(prettyplease::unparse(&syntax_tree))
605}