1 use proc_macro2::{Span, TokenStream};
2 use quote::ToTokens;
3 use std::collections::HashMap;
4 use std::env;
5 use std::path::{Path, PathBuf};
6 use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
7 use syn::parse::{Error, Parse, ParseStream, Result};
8 use syn::punctuated::Punctuated;
9 use syn::{Token, braced, token};
10 use wasmtime_wit_bindgen::{
11     FunctionConfig, FunctionFilter, FunctionFlags, Opts, Ownership, TrappableError,
12 };
13 use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
14 
15 pub struct Config {
16     opts: Opts,
17     resolve: Resolve,
18     world: WorldId,
19     files: Vec<PathBuf>,
20     include_generated_code_from_file: bool,
21 }
22 
23 pub 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 
74 impl 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 = select_world(&resolve, &pkgs, world.as_deref())
174             .map_err(|e| Error::new(call_site, format!("{e:?}")))?;
175         Ok(Config {
176             opts,
177             resolve,
178             world,
179             files,
180             include_generated_code_from_file,
181         })
182     }
183 }
184 
185 fn parse_source(
186     paths: &Vec<String>,
187     inline: &Option<String>,
188 ) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
189     let mut resolve = Resolve::default();
190     resolve.all_features = true;
191     let mut files = Vec::new();
192     let mut pkgs = Vec::new();
193     let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
194 
195     let parse = |resolve: &mut Resolve,
196                  files: &mut Vec<PathBuf>,
197                  pkgs: &mut Vec<PackageId>,
198                  paths: &[String]|
199      -> anyhow::Result<_> {
200         for path in paths {
201             let p = root.join(path);
202             // Try to normalize the path to make the error message more understandable when
203             // the path is not correct. Fallback to the original path if normalization fails
204             // (probably return an error somewhere else).
205             let normalized_path = match std::fs::canonicalize(&p) {
206                 Ok(p) => p,
207                 Err(_) => p.to_path_buf(),
208             };
209             let (pkg, sources) = resolve.push_path(normalized_path)?;
210             pkgs.push(pkg);
211             files.extend(sources.paths().map(|p| p.to_owned()));
212         }
213         Ok(())
214     };
215 
216     if !paths.is_empty() {
217         parse(&mut resolve, &mut files, &mut pkgs, &paths)?;
218     }
219 
220     if let Some(inline) = inline {
221         pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);
222     }
223 
224     if pkgs.is_empty() {
225         parse(&mut resolve, &mut files, &mut pkgs, &["wit".into()])?;
226     }
227 
228     Ok((resolve, pkgs, files))
229 }
230 
231 fn select_world(
232     resolve: &Resolve,
233     pkgs: &[PackageId],
234     world: Option<&str>,
235 ) -> anyhow::Result<WorldId> {
236     if pkgs.len() == 1 {
237         resolve.select_world(pkgs[0], world)
238     } else {
239         assert!(!pkgs.is_empty());
240         match world {
241             Some(name) => {
242                 if !name.contains(":") {
243                     anyhow::bail!(
244                         "with multiple packages a fully qualified \
245                          world name must be specified"
246                     )
247                 }
248 
249                 // This will ignore the package argument due to the fully
250                 // qualified name being used.
251                 resolve.select_world(pkgs[0], world)
252             }
253             None => {
254                 let worlds = pkgs
255                     .iter()
256                     .filter_map(|p| resolve.select_world(*p, None).ok())
257                     .collect::<Vec<_>>();
258                 match &worlds[..] {
259                     [] => anyhow::bail!("no packages have a world"),
260                     [world] => Ok(*world),
261                     _ => anyhow::bail!("multiple packages have a world, must specify which to use"),
262                 }
263             }
264         }
265     }
266 }
267 
268 mod kw {
269     syn::custom_keyword!(inline);
270     syn::custom_keyword!(path);
271     syn::custom_keyword!(tracing);
272     syn::custom_keyword!(verbose_tracing);
273     syn::custom_keyword!(trappable_error_type);
274     syn::custom_keyword!(world);
275     syn::custom_keyword!(ownership);
276     syn::custom_keyword!(interfaces);
277     syn::custom_keyword!(with);
278     syn::custom_keyword!(except_imports);
279     syn::custom_keyword!(only_imports);
280     syn::custom_keyword!(additional_derives);
281     syn::custom_keyword!(stringify);
282     syn::custom_keyword!(skip_mut_forwarding_impls);
283     syn::custom_keyword!(require_store_data_send);
284     syn::custom_keyword!(wasmtime_crate);
285     syn::custom_keyword!(include_generated_code_from_file);
286     syn::custom_keyword!(debug);
287     syn::custom_keyword!(imports);
288     syn::custom_keyword!(exports);
289     syn::custom_keyword!(store);
290     syn::custom_keyword!(trappable);
291     syn::custom_keyword!(ignore_wit);
292     syn::custom_keyword!(exact);
293 }
294 
295 enum Opt {
296     World(syn::LitStr),
297     Path(Vec<syn::LitStr>),
298     Inline(syn::LitStr),
299     TrappableErrorType(Vec<TrappableError>),
300     Ownership(Ownership),
301     Interfaces(syn::LitStr),
302     With(HashMap<String, String>),
303     AdditionalDerives(Vec<syn::Path>),
304     Stringify(bool),
305     SkipMutForwardingImpls(bool),
306     RequireStoreDataSend(bool),
307     WasmtimeCrate(syn::Path),
308     IncludeGeneratedCodeFromFile(bool),
309     Debug(bool),
310     Imports(FunctionConfig, Span),
311     Exports(FunctionConfig, Span),
312 }
313 
314 impl Parse for Opt {
315     fn parse(input: ParseStream<'_>) -> Result<Self> {
316         let l = input.lookahead1();
317         if l.peek(kw::debug) {
318             input.parse::<kw::debug>()?;
319             input.parse::<Token![:]>()?;
320             Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))
321         } else if l.peek(kw::path) {
322             input.parse::<kw::path>()?;
323             input.parse::<Token![:]>()?;
324 
325             let mut paths: Vec<syn::LitStr> = vec![];
326 
327             let l = input.lookahead1();
328             if l.peek(syn::LitStr) {
329                 paths.push(input.parse()?);
330             } else if l.peek(syn::token::Bracket) {
331                 let contents;
332                 syn::bracketed!(contents in input);
333                 let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
334 
335                 paths.extend(list);
336             } else {
337                 return Err(l.error());
338             };
339 
340             Ok(Opt::Path(paths))
341         } else if l.peek(kw::inline) {
342             input.parse::<kw::inline>()?;
343             input.parse::<Token![:]>()?;
344             Ok(Opt::Inline(input.parse()?))
345         } else if l.peek(kw::world) {
346             input.parse::<kw::world>()?;
347             input.parse::<Token![:]>()?;
348             Ok(Opt::World(input.parse()?))
349         } else if l.peek(kw::ownership) {
350             input.parse::<kw::ownership>()?;
351             input.parse::<Token![:]>()?;
352             let ownership = input.parse::<syn::Ident>()?;
353             Ok(Opt::Ownership(match ownership.to_string().as_str() {
354                 "Owning" => Ownership::Owning,
355                 "Borrowing" => Ownership::Borrowing {
356                     duplicate_if_necessary: {
357                         let contents;
358                         braced!(contents in input);
359                         let field = contents.parse::<syn::Ident>()?;
360                         match field.to_string().as_str() {
361                             "duplicate_if_necessary" => {
362                                 contents.parse::<Token![:]>()?;
363                                 contents.parse::<syn::LitBool>()?.value
364                             }
365                             name => {
366                                 return Err(Error::new(
367                                     field.span(),
368                                     format!(
369                                         "unrecognized `Ownership::Borrowing` field: `{name}`; \
370                                          expected `duplicate_if_necessary`"
371                                     ),
372                                 ));
373                             }
374                         }
375                     },
376                 },
377                 name => {
378                     return Err(Error::new(
379                         ownership.span(),
380                         format!(
381                             "unrecognized ownership: `{name}`; \
382                              expected `Owning` or `Borrowing`"
383                         ),
384                     ));
385                 }
386             }))
387         } else if l.peek(kw::trappable_error_type) {
388             input.parse::<kw::trappable_error_type>()?;
389             input.parse::<Token![:]>()?;
390             let contents;
391             let _lbrace = braced!(contents in input);
392             let fields: Punctuated<_, Token![,]> =
393                 contents.parse_terminated(trappable_error_field_parse, Token![,])?;
394             Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))
395         } else if l.peek(kw::interfaces) {
396             input.parse::<kw::interfaces>()?;
397             input.parse::<Token![:]>()?;
398             Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))
399         } else if l.peek(kw::with) {
400             input.parse::<kw::with>()?;
401             input.parse::<Token![:]>()?;
402             let contents;
403             let _lbrace = braced!(contents in input);
404             let fields: Punctuated<(String, String), Token![,]> =
405                 contents.parse_terminated(with_field_parse, Token![,])?;
406             Ok(Opt::With(HashMap::from_iter(fields)))
407         } else if l.peek(kw::additional_derives) {
408             input.parse::<kw::additional_derives>()?;
409             input.parse::<Token![:]>()?;
410             let contents;
411             syn::bracketed!(contents in input);
412             let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
413             Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
414         } else if l.peek(kw::stringify) {
415             input.parse::<kw::stringify>()?;
416             input.parse::<Token![:]>()?;
417             Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value))
418         } else if l.peek(kw::skip_mut_forwarding_impls) {
419             input.parse::<kw::skip_mut_forwarding_impls>()?;
420             input.parse::<Token![:]>()?;
421             Ok(Opt::SkipMutForwardingImpls(
422                 input.parse::<syn::LitBool>()?.value,
423             ))
424         } else if l.peek(kw::require_store_data_send) {
425             input.parse::<kw::require_store_data_send>()?;
426             input.parse::<Token![:]>()?;
427             Ok(Opt::RequireStoreDataSend(
428                 input.parse::<syn::LitBool>()?.value,
429             ))
430         } else if l.peek(kw::wasmtime_crate) {
431             input.parse::<kw::wasmtime_crate>()?;
432             input.parse::<Token![:]>()?;
433             Ok(Opt::WasmtimeCrate(input.parse()?))
434         } else if l.peek(kw::include_generated_code_from_file) {
435             input.parse::<kw::include_generated_code_from_file>()?;
436             input.parse::<Token![:]>()?;
437             Ok(Opt::IncludeGeneratedCodeFromFile(
438                 input.parse::<syn::LitBool>()?.value,
439             ))
440         } else if l.peek(kw::imports) {
441             let span = input.parse::<kw::imports>()?.span;
442             input.parse::<Token![:]>()?;
443             Ok(Opt::Imports(parse_function_config(input)?, span))
444         } else if l.peek(kw::exports) {
445             let span = input.parse::<kw::exports>()?.span;
446             input.parse::<Token![:]>()?;
447             Ok(Opt::Exports(parse_function_config(input)?, span))
448         } else {
449             Err(l.error())
450         }
451     }
452 }
453 
454 fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {
455     let wit_path = input.parse::<syn::LitStr>()?.value();
456     input.parse::<Token![=>]>()?;
457     let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
458     Ok(TrappableError {
459         wit_path,
460         rust_type_name,
461     })
462 }
463 
464 fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {
465     let interface = input.parse::<syn::LitStr>()?.value();
466     input.parse::<Token![:]>()?;
467     let start = input.span();
468     let path = input.parse::<syn::Path>()?;
469 
470     // It's not possible for the segments of a path to be empty
471     let span = start
472         .join(path.segments.last().unwrap().ident.span())
473         .unwrap_or(start);
474 
475     let mut buf = String::new();
476     let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
477         if segment.arguments != syn::PathArguments::None {
478             return Err(Error::new(
479                 span,
480                 "Module path must not contain angles or parens",
481             ));
482         }
483 
484         buf.push_str(&segment.ident.to_string());
485 
486         Ok(())
487     };
488 
489     if path.leading_colon.is_some() {
490         buf.push_str("::");
491     }
492 
493     let mut segments = path.segments.into_iter();
494 
495     if let Some(segment) = segments.next() {
496         append(&mut buf, segment)?;
497     }
498 
499     for segment in segments {
500         buf.push_str("::");
501         append(&mut buf, segment)?;
502     }
503 
504     Ok((interface, buf))
505 }
506 
507 fn parse_function_config(input: ParseStream<'_>) -> Result<FunctionConfig> {
508     let content;
509     syn::braced!(content in input);
510     let mut ret = FunctionConfig::new();
511 
512     let list = Punctuated::<FunctionConfigSyntax, Token![,]>::parse_terminated(&content)?;
513     for item in list.into_iter() {
514         ret.push(item.filter, item.flags);
515     }
516 
517     return Ok(ret);
518 
519     struct FunctionConfigSyntax {
520         filter: FunctionFilter,
521         flags: FunctionFlags,
522     }
523 
524     impl Parse for FunctionConfigSyntax {
525         fn parse(input: ParseStream<'_>) -> Result<Self> {
526             let l = input.lookahead1();
527             let filter = if l.peek(syn::LitStr) {
528                 FunctionFilter::Name(input.parse::<syn::LitStr>()?.value())
529             } else if l.peek(Token![default]) {
530                 input.parse::<Token![default]>()?;
531                 FunctionFilter::Default
532             } else {
533                 return Err(l.error());
534             };
535 
536             input.parse::<Token![:]>()?;
537 
538             let mut flags = FunctionFlags::empty();
539             while !input.is_empty() {
540                 let l = input.lookahead1();
541                 if l.peek(Token![async]) {
542                     input.parse::<Token![async]>()?;
543                     flags |= FunctionFlags::ASYNC;
544                 } else if l.peek(kw::tracing) {
545                     input.parse::<kw::tracing>()?;
546                     flags |= FunctionFlags::TRACING;
547                 } else if l.peek(kw::verbose_tracing) {
548                     input.parse::<kw::verbose_tracing>()?;
549                     flags |= FunctionFlags::VERBOSE_TRACING;
550                 } else if l.peek(kw::store) {
551                     input.parse::<kw::store>()?;
552                     flags |= FunctionFlags::STORE;
553                 } else if l.peek(kw::trappable) {
554                     input.parse::<kw::trappable>()?;
555                     flags |= FunctionFlags::TRAPPABLE;
556                 } else if l.peek(kw::ignore_wit) {
557                     input.parse::<kw::ignore_wit>()?;
558                     flags |= FunctionFlags::IGNORE_WIT;
559                 } else if l.peek(kw::exact) {
560                     input.parse::<kw::exact>()?;
561                     flags |= FunctionFlags::EXACT;
562                 } else {
563                     return Err(l.error());
564                 }
565 
566                 if input.peek(Token![|]) {
567                     input.parse::<Token![|]>()?;
568                 } else {
569                     break;
570                 }
571             }
572 
573             Ok(FunctionConfigSyntax { filter, flags })
574         }
575     }
576 }
577