wit_bindgen_rust_macro/
lib.rs

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
53/// The source of the wit package definition
54enum Source {
55    /// A list of paths to wit directories
56    Paths(Vec<PathBuf>),
57    /// Inline sources have an optional path to a directory of their dependencies
58    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
192/// Parse the source
193fn 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            // Try to normalize the path to make the error message more understandable when
206            // the path is not correct. Fallback to the original path if normalization fails
207            // (probably return an error somewhere else).
208            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                // If no `path` is explicitly specified still parse the default
224                // `wit` directory if it exists. Don't require its existence,
225                // however, as `inline` can be used in lieu of a folder. Test
226                // whether it exists and only if there is it parsed.
227                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 a magical `WIT_BINDGEN_DEBUG` environment variable is set then
254        // place a formatted version of the expanded code into a file. This file
255        // will then show up in rustc error messages for any codegen issues and can
256        // be inspected manually.
257        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            // optimistically format the code but don't require success
265            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        // Include a dummy `include_bytes!` for any files we read so rustc knows that
276        // we depend on the contents of those files.
277        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    // Parse as paths so we can take the concrete types/macro names rather than raw strings
361    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            // the `path` supports two forms:
384            // * path: "xxx"
385            // * path: ["aaa", "bbb"]
386            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    // It's not possible for the segments of a path to be empty
561    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
601/// Format a valid Rust string
602fn fmt(input: &str) -> Result<String> {
603    let syntax_tree = syn::parse_file(&input)?;
604    Ok(prettyplease::unparse(&syntax_tree))
605}