wasmtime_internal_component_macro/
bindgen.rs

1use proc_macro2::{Span, TokenStream};
2use quote::ToTokens;
3use std::collections::HashMap;
4use std::env;
5use std::path::{Path, PathBuf};
6use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
7use syn::parse::{Error, Parse, ParseStream, Result};
8use syn::punctuated::Punctuated;
9use syn::{Token, braced, token};
10use wasmtime_wit_bindgen::{
11    FunctionConfig, FunctionFilter, FunctionFlags, Opts, Ownership, TrappableError,
12};
13use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
14
15pub struct Config {
16    opts: Opts,
17    resolve: Resolve,
18    world: WorldId,
19    files: Vec<PathBuf>,
20    include_generated_code_from_file: bool,
21}
22
23pub fn expand(input: &Config) -> Result<TokenStream> {
24    let mut src = match input.opts.generate(&input.resolve, input.world) {
25        Ok(s) => s,
26        Err(e) => return Err(Error::new(Span::call_site(), e.to_string())),
27    };
28
29    if input.opts.stringify {
30        return Ok(quote::quote!(#src));
31    }
32
33    // If a magical `WASMTIME_DEBUG_BINDGEN` environment variable is set then
34    // place a formatted version of the expanded code into a file. This file
35    // will then show up in rustc error messages for any codegen issues and can
36    // be inspected manually.
37    if input.include_generated_code_from_file
38        || input.opts.debug
39        || std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok()
40    {
41        static INVOCATION: AtomicUsize = AtomicUsize::new(0);
42        let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
43        let world_name = &input.resolve.worlds[input.world].name;
44        let n = INVOCATION.fetch_add(1, Relaxed);
45        let path = root.join(format!("{world_name}{n}.rs"));
46
47        std::fs::write(&path, &src).unwrap();
48
49        // optimistically format the code but don't require success
50        drop(
51            std::process::Command::new("rustfmt")
52                .arg(&path)
53                .arg("--edition=2021")
54                .output(),
55        );
56
57        src = format!("include!({path:?});");
58    }
59    let mut contents = src.parse::<TokenStream>().unwrap();
60
61    // Include a dummy `include_str!` for any files we read so rustc knows that
62    // we depend on the contents of those files.
63    for file in input.files.iter() {
64        contents.extend(
65            format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())
66                .parse::<TokenStream>()
67                .unwrap(),
68        );
69    }
70
71    Ok(contents)
72}
73
74impl Parse for Config {
75    fn parse(input: ParseStream<'_>) -> Result<Self> {
76        let call_site = Span::call_site();
77        let mut opts = Opts::default();
78        let mut world = None;
79        let mut inline = None;
80        let mut paths = Vec::new();
81        let mut imports_configured = false;
82        let mut exports_configured = false;
83        let mut include_generated_code_from_file = false;
84
85        if input.peek(token::Brace) {
86            let content;
87            syn::braced!(content in input);
88            let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
89            for field in fields.into_pairs() {
90                match field.into_value() {
91                    Opt::Path(p) => {
92                        paths.extend(p.into_iter().map(|p| p.value()));
93                    }
94                    Opt::World(s) => {
95                        if world.is_some() {
96                            return Err(Error::new(s.span(), "cannot specify second world"));
97                        }
98                        world = Some(s.value());
99                    }
100                    Opt::Inline(s) => {
101                        if inline.is_some() {
102                            return Err(Error::new(s.span(), "cannot specify second source"));
103                        }
104                        inline = Some(s.value());
105                    }
106                    Opt::Debug(val) => opts.debug = val,
107                    Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
108                    Opt::Ownership(val) => opts.ownership = val,
109                    Opt::Interfaces(s) => {
110                        if inline.is_some() {
111                            return Err(Error::new(s.span(), "cannot specify a second source"));
112                        }
113                        inline = Some(format!(
114                            "
115                                package wasmtime:component-macro-synthesized;
116
117                                world interfaces {{
118                                    {}
119                                }}
120                            ",
121                            s.value()
122                        ));
123
124                        if world.is_some() {
125                            return Err(Error::new(
126                                s.span(),
127                                "cannot specify a world with `interfaces`",
128                            ));
129                        }
130                        world = Some("wasmtime:component-macro-synthesized/interfaces".to_string());
131
132                        opts.only_interfaces = true;
133                    }
134                    Opt::With(val) => opts.with.extend(val),
135                    Opt::AdditionalDerives(paths) => {
136                        opts.additional_derive_attributes = paths
137                            .into_iter()
138                            .map(|p| p.into_token_stream().to_string())
139                            .collect()
140                    }
141                    Opt::Stringify(val) => opts.stringify = val,
142                    Opt::SkipMutForwardingImpls(val) => opts.skip_mut_forwarding_impls = val,
143                    Opt::RequireStoreDataSend(val) => opts.require_store_data_send = val,
144                    Opt::WasmtimeCrate(f) => {
145                        opts.wasmtime_crate = Some(f.into_token_stream().to_string())
146                    }
147                    Opt::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i,
148                    Opt::Imports(config, span) => {
149                        if imports_configured {
150                            return Err(Error::new(span, "cannot specify imports configuration"));
151                        }
152                        opts.imports = config;
153                        imports_configured = true;
154                    }
155                    Opt::Exports(config, span) => {
156                        if exports_configured {
157                            return Err(Error::new(span, "cannot specify exports configuration"));
158                        }
159                        opts.exports = config;
160                        exports_configured = true;
161                    }
162                }
163            }
164        } else {
165            world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
166            if input.parse::<Option<syn::token::In>>()?.is_some() {
167                paths.push(input.parse::<syn::LitStr>()?.value());
168            }
169        }
170        let (resolve, pkgs, files) = parse_source(&paths, &inline)
171            .map_err(|err| Error::new(call_site, format!("{err:?}")))?;
172
173        let world = resolve
174            .select_world(&pkgs, world.as_deref())
175            .map_err(|e| Error::new(call_site, format!("{e:?}")))?;
176        Ok(Config {
177            opts,
178            resolve,
179            world,
180            files,
181            include_generated_code_from_file,
182        })
183    }
184}
185
186fn parse_source(
187    paths: &Vec<String>,
188    inline: &Option<String>,
189) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
190    let mut resolve = Resolve::default();
191    resolve.all_features = true;
192    let mut files = Vec::new();
193    let mut pkgs = Vec::new();
194    let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
195    let default = root.join("wit");
196
197    let parse = |resolve: &mut Resolve,
198                 files: &mut Vec<PathBuf>,
199                 pkgs: &mut Vec<PackageId>,
200                 paths: &[PathBuf]|
201     -> anyhow::Result<_> {
202        for path in paths {
203            let p = root.join(path);
204            // Try to normalize the path to make the error message more understandable when
205            // the path is not correct. Fallback to the original path if normalization fails
206            // (probably return an error somewhere else).
207            let normalized_path = match std::fs::canonicalize(&p) {
208                Ok(p) => p,
209                Err(_) => p.to_path_buf(),
210            };
211            let (pkg, sources) = resolve.push_path(normalized_path)?;
212            pkgs.push(pkg);
213            files.extend(sources.paths().map(|p| p.to_owned()));
214        }
215        Ok(())
216    };
217
218    if paths.is_empty() {
219        if default.exists() {
220            parse(&mut resolve, &mut files, &mut pkgs, &[default])?;
221        }
222    } else {
223        parse(
224            &mut resolve,
225            &mut files,
226            &mut pkgs,
227            &paths.iter().map(|s| s.into()).collect::<Vec<_>>(),
228        )?;
229    }
230
231    if let Some(inline) = inline {
232        pkgs.truncate(0);
233        pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);
234    }
235
236    Ok((resolve, pkgs, files))
237}
238
239mod kw {
240    syn::custom_keyword!(inline);
241    syn::custom_keyword!(path);
242    syn::custom_keyword!(tracing);
243    syn::custom_keyword!(verbose_tracing);
244    syn::custom_keyword!(trappable_error_type);
245    syn::custom_keyword!(world);
246    syn::custom_keyword!(ownership);
247    syn::custom_keyword!(interfaces);
248    syn::custom_keyword!(with);
249    syn::custom_keyword!(except_imports);
250    syn::custom_keyword!(only_imports);
251    syn::custom_keyword!(additional_derives);
252    syn::custom_keyword!(stringify);
253    syn::custom_keyword!(skip_mut_forwarding_impls);
254    syn::custom_keyword!(require_store_data_send);
255    syn::custom_keyword!(wasmtime_crate);
256    syn::custom_keyword!(include_generated_code_from_file);
257    syn::custom_keyword!(debug);
258    syn::custom_keyword!(imports);
259    syn::custom_keyword!(exports);
260    syn::custom_keyword!(store);
261    syn::custom_keyword!(trappable);
262    syn::custom_keyword!(ignore_wit);
263    syn::custom_keyword!(exact);
264    syn::custom_keyword!(task_exit);
265}
266
267enum Opt {
268    World(syn::LitStr),
269    Path(Vec<syn::LitStr>),
270    Inline(syn::LitStr),
271    TrappableErrorType(Vec<TrappableError>),
272    Ownership(Ownership),
273    Interfaces(syn::LitStr),
274    With(HashMap<String, String>),
275    AdditionalDerives(Vec<syn::Path>),
276    Stringify(bool),
277    SkipMutForwardingImpls(bool),
278    RequireStoreDataSend(bool),
279    WasmtimeCrate(syn::Path),
280    IncludeGeneratedCodeFromFile(bool),
281    Debug(bool),
282    Imports(FunctionConfig, Span),
283    Exports(FunctionConfig, Span),
284}
285
286impl Parse for Opt {
287    fn parse(input: ParseStream<'_>) -> Result<Self> {
288        let l = input.lookahead1();
289        if l.peek(kw::debug) {
290            input.parse::<kw::debug>()?;
291            input.parse::<Token![:]>()?;
292            Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))
293        } else if l.peek(kw::path) {
294            input.parse::<kw::path>()?;
295            input.parse::<Token![:]>()?;
296
297            let mut paths: Vec<syn::LitStr> = vec![];
298
299            let l = input.lookahead1();
300            if l.peek(syn::LitStr) {
301                paths.push(input.parse()?);
302            } else if l.peek(syn::token::Bracket) {
303                let contents;
304                syn::bracketed!(contents in input);
305                let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
306
307                paths.extend(list);
308            } else {
309                return Err(l.error());
310            };
311
312            Ok(Opt::Path(paths))
313        } else if l.peek(kw::inline) {
314            input.parse::<kw::inline>()?;
315            input.parse::<Token![:]>()?;
316            Ok(Opt::Inline(input.parse()?))
317        } else if l.peek(kw::world) {
318            input.parse::<kw::world>()?;
319            input.parse::<Token![:]>()?;
320            Ok(Opt::World(input.parse()?))
321        } else if l.peek(kw::ownership) {
322            input.parse::<kw::ownership>()?;
323            input.parse::<Token![:]>()?;
324            let ownership = input.parse::<syn::Ident>()?;
325            Ok(Opt::Ownership(match ownership.to_string().as_str() {
326                "Owning" => Ownership::Owning,
327                "Borrowing" => Ownership::Borrowing {
328                    duplicate_if_necessary: {
329                        let contents;
330                        braced!(contents in input);
331                        let field = contents.parse::<syn::Ident>()?;
332                        match field.to_string().as_str() {
333                            "duplicate_if_necessary" => {
334                                contents.parse::<Token![:]>()?;
335                                contents.parse::<syn::LitBool>()?.value
336                            }
337                            name => {
338                                return Err(Error::new(
339                                    field.span(),
340                                    format!(
341                                        "unrecognized `Ownership::Borrowing` field: `{name}`; \
342                                         expected `duplicate_if_necessary`"
343                                    ),
344                                ));
345                            }
346                        }
347                    },
348                },
349                name => {
350                    return Err(Error::new(
351                        ownership.span(),
352                        format!(
353                            "unrecognized ownership: `{name}`; \
354                             expected `Owning` or `Borrowing`"
355                        ),
356                    ));
357                }
358            }))
359        } else if l.peek(kw::trappable_error_type) {
360            input.parse::<kw::trappable_error_type>()?;
361            input.parse::<Token![:]>()?;
362            let contents;
363            let _lbrace = braced!(contents in input);
364            let fields: Punctuated<_, Token![,]> =
365                contents.parse_terminated(trappable_error_field_parse, Token![,])?;
366            Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))
367        } else if l.peek(kw::interfaces) {
368            input.parse::<kw::interfaces>()?;
369            input.parse::<Token![:]>()?;
370            Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))
371        } else if l.peek(kw::with) {
372            input.parse::<kw::with>()?;
373            input.parse::<Token![:]>()?;
374            let contents;
375            let _lbrace = braced!(contents in input);
376            let fields: Punctuated<(String, String), Token![,]> =
377                contents.parse_terminated(with_field_parse, Token![,])?;
378            Ok(Opt::With(HashMap::from_iter(fields)))
379        } else if l.peek(kw::additional_derives) {
380            input.parse::<kw::additional_derives>()?;
381            input.parse::<Token![:]>()?;
382            let contents;
383            syn::bracketed!(contents in input);
384            let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
385            Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
386        } else if l.peek(kw::stringify) {
387            input.parse::<kw::stringify>()?;
388            input.parse::<Token![:]>()?;
389            Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value))
390        } else if l.peek(kw::skip_mut_forwarding_impls) {
391            input.parse::<kw::skip_mut_forwarding_impls>()?;
392            input.parse::<Token![:]>()?;
393            Ok(Opt::SkipMutForwardingImpls(
394                input.parse::<syn::LitBool>()?.value,
395            ))
396        } else if l.peek(kw::require_store_data_send) {
397            input.parse::<kw::require_store_data_send>()?;
398            input.parse::<Token![:]>()?;
399            Ok(Opt::RequireStoreDataSend(
400                input.parse::<syn::LitBool>()?.value,
401            ))
402        } else if l.peek(kw::wasmtime_crate) {
403            input.parse::<kw::wasmtime_crate>()?;
404            input.parse::<Token![:]>()?;
405            Ok(Opt::WasmtimeCrate(input.parse()?))
406        } else if l.peek(kw::include_generated_code_from_file) {
407            input.parse::<kw::include_generated_code_from_file>()?;
408            input.parse::<Token![:]>()?;
409            Ok(Opt::IncludeGeneratedCodeFromFile(
410                input.parse::<syn::LitBool>()?.value,
411            ))
412        } else if l.peek(kw::imports) {
413            let span = input.parse::<kw::imports>()?.span;
414            input.parse::<Token![:]>()?;
415            Ok(Opt::Imports(parse_function_config(input)?, span))
416        } else if l.peek(kw::exports) {
417            let span = input.parse::<kw::exports>()?.span;
418            input.parse::<Token![:]>()?;
419            Ok(Opt::Exports(parse_function_config(input)?, span))
420        } else {
421            Err(l.error())
422        }
423    }
424}
425
426fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {
427    let wit_path = input.parse::<syn::LitStr>()?.value();
428    input.parse::<Token![=>]>()?;
429    let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
430    Ok(TrappableError {
431        wit_path,
432        rust_type_name,
433    })
434}
435
436fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {
437    let interface = input.parse::<syn::LitStr>()?.value();
438    input.parse::<Token![:]>()?;
439    let start = input.span();
440    let path = input.parse::<syn::Path>()?;
441
442    // It's not possible for the segments of a path to be empty
443    let span = start
444        .join(path.segments.last().unwrap().ident.span())
445        .unwrap_or(start);
446
447    let mut buf = String::new();
448    let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
449        if segment.arguments != syn::PathArguments::None {
450            return Err(Error::new(
451                span,
452                "Module path must not contain angles or parens",
453            ));
454        }
455
456        buf.push_str(&segment.ident.to_string());
457
458        Ok(())
459    };
460
461    if path.leading_colon.is_some() {
462        buf.push_str("::");
463    }
464
465    let mut segments = path.segments.into_iter();
466
467    if let Some(segment) = segments.next() {
468        append(&mut buf, segment)?;
469    }
470
471    for segment in segments {
472        buf.push_str("::");
473        append(&mut buf, segment)?;
474    }
475
476    Ok((interface, buf))
477}
478
479fn parse_function_config(input: ParseStream<'_>) -> Result<FunctionConfig> {
480    let content;
481    syn::braced!(content in input);
482    let mut ret = FunctionConfig::new();
483
484    let list = Punctuated::<FunctionConfigSyntax, Token![,]>::parse_terminated(&content)?;
485    for item in list.into_iter() {
486        ret.push(item.filter, item.flags);
487    }
488
489    return Ok(ret);
490
491    struct FunctionConfigSyntax {
492        filter: FunctionFilter,
493        flags: FunctionFlags,
494    }
495
496    impl Parse for FunctionConfigSyntax {
497        fn parse(input: ParseStream<'_>) -> Result<Self> {
498            let l = input.lookahead1();
499            let filter = if l.peek(syn::LitStr) {
500                FunctionFilter::Name(input.parse::<syn::LitStr>()?.value())
501            } else if l.peek(Token![default]) {
502                input.parse::<Token![default]>()?;
503                FunctionFilter::Default
504            } else {
505                return Err(l.error());
506            };
507
508            input.parse::<Token![:]>()?;
509
510            let mut flags = FunctionFlags::empty();
511            while !input.is_empty() {
512                let l = input.lookahead1();
513                if l.peek(Token![async]) {
514                    input.parse::<Token![async]>()?;
515                    flags |= FunctionFlags::ASYNC;
516                } else if l.peek(kw::tracing) {
517                    input.parse::<kw::tracing>()?;
518                    flags |= FunctionFlags::TRACING;
519                } else if l.peek(kw::verbose_tracing) {
520                    input.parse::<kw::verbose_tracing>()?;
521                    flags |= FunctionFlags::VERBOSE_TRACING;
522                } else if l.peek(kw::store) {
523                    input.parse::<kw::store>()?;
524                    flags |= FunctionFlags::STORE;
525                } else if l.peek(kw::trappable) {
526                    input.parse::<kw::trappable>()?;
527                    flags |= FunctionFlags::TRAPPABLE;
528                } else if l.peek(kw::ignore_wit) {
529                    input.parse::<kw::ignore_wit>()?;
530                    flags |= FunctionFlags::IGNORE_WIT;
531                } else if l.peek(kw::exact) {
532                    input.parse::<kw::exact>()?;
533                    flags |= FunctionFlags::EXACT;
534                } else if l.peek(kw::task_exit) {
535                    input.parse::<kw::task_exit>()?;
536                    flags |= FunctionFlags::TASK_EXIT;
537                } else {
538                    return Err(l.error());
539                }
540
541                if input.peek(Token![|]) {
542                    input.parse::<Token![|]>()?;
543                } else {
544                    break;
545                }
546            }
547
548            Ok(FunctionConfigSyntax { filter, flags })
549        }
550    }
551}