1 use proc_macro2::{Span, TokenStream};
2 use quote::ToTokens;
3 use std::collections::HashMap;
4 use std::collections::HashSet;
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::{braced, token, Token};
10 use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError, TrappableImports};
11 use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
12 
13 pub struct Config {
14     opts: Opts,
15     resolve: Resolve,
16     world: WorldId,
17     files: Vec<PathBuf>,
18 }
19 
20 pub fn expand(input: &Config) -> Result<TokenStream> {
21     if !cfg!(feature = "async") && input.opts.async_.maybe_async() {
22         return Err(Error::new(
23             Span::call_site(),
24             "cannot enable async bindings unless `async` crate feature is active",
25         ));
26     }
27 
28     let mut src = match input.opts.generate(&input.resolve, input.world) {
29         Ok(s) => s,
30         Err(e) => return Err(Error::new(Span::call_site(), e.to_string())),
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 std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok() {
38         static INVOCATION: AtomicUsize = AtomicUsize::new(0);
39         let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
40         let world_name = &input.resolve.worlds[input.world].name;
41         let n = INVOCATION.fetch_add(1, Relaxed);
42         let path = root.join(format!("{world_name}{n}.rs"));
43 
44         std::fs::write(&path, &src).unwrap();
45 
46         // optimistically format the code but don't require success
47         drop(
48             std::process::Command::new("rustfmt")
49                 .arg(&path)
50                 .arg("--edition=2021")
51                 .output(),
52         );
53 
54         src = format!("include!({path:?});");
55     }
56     let mut contents = src.parse::<TokenStream>().unwrap();
57 
58     // Include a dummy `include_str!` for any files we read so rustc knows that
59     // we depend on the contents of those files.
60     for file in input.files.iter() {
61         contents.extend(
62             format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())
63                 .parse::<TokenStream>()
64                 .unwrap(),
65         );
66     }
67 
68     Ok(contents)
69 }
70 
71 impl Parse for Config {
72     fn parse(input: ParseStream<'_>) -> Result<Self> {
73         let call_site = Span::call_site();
74         let mut opts = Opts::default();
75         let mut world = None;
76         let mut inline = None;
77         let mut path = None;
78         let mut async_configured = false;
79 
80         if input.peek(token::Brace) {
81             let content;
82             syn::braced!(content in input);
83             let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
84             for field in fields.into_pairs() {
85                 match field.into_value() {
86                     Opt::Path(s) => {
87                         if path.is_some() {
88                             return Err(Error::new(s.span(), "cannot specify second path"));
89                         }
90                         path = Some(s.value());
91                     }
92                     Opt::World(s) => {
93                         if world.is_some() {
94                             return Err(Error::new(s.span(), "cannot specify second world"));
95                         }
96                         world = Some(s.value());
97                     }
98                     Opt::Inline(s) => {
99                         if inline.is_some() {
100                             return Err(Error::new(s.span(), "cannot specify second source"));
101                         }
102                         inline = Some(s.value());
103                     }
104                     Opt::Tracing(val) => opts.tracing = val,
105                     Opt::Async(val, span) => {
106                         if async_configured {
107                             return Err(Error::new(span, "cannot specify second async config"));
108                         }
109                         async_configured = true;
110                         opts.async_ = val;
111                     }
112                     Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
113                     Opt::TrappableImports(val) => opts.trappable_imports = val,
114                     Opt::Ownership(val) => opts.ownership = val,
115                     Opt::Interfaces(s) => {
116                         if inline.is_some() {
117                             return Err(Error::new(s.span(), "cannot specify a second source"));
118                         }
119                         inline = Some(format!(
120                             "
121                                 package wasmtime:component-macro-synthesized;
122 
123                                 world interfaces {{
124                                     {}
125                                 }}
126                             ",
127                             s.value()
128                         ));
129 
130                         if world.is_some() {
131                             return Err(Error::new(
132                                 s.span(),
133                                 "cannot specify a world with `interfaces`",
134                             ));
135                         }
136                         world = Some("interfaces".to_string());
137 
138                         opts.only_interfaces = true;
139                     }
140                     Opt::With(val) => opts.with.extend(val),
141                     Opt::AdditionalDerives(paths) => {
142                         opts.additional_derive_attributes = paths
143                             .into_iter()
144                             .map(|p| p.into_token_stream().to_string())
145                             .collect()
146                     }
147                 }
148             }
149         } else {
150             world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
151             if input.parse::<Option<syn::token::In>>()?.is_some() {
152                 path = Some(input.parse::<syn::LitStr>()?.value());
153             }
154         }
155         let (resolve, pkg, files) = parse_source(&path, &inline)
156             .map_err(|err| Error::new(call_site, format!("{err:?}")))?;
157 
158         let world = resolve
159             .select_world(pkg, world.as_deref())
160             .map_err(|e| Error::new(call_site, format!("{e:?}")))?;
161         Ok(Config {
162             opts,
163             resolve,
164             world,
165             files,
166         })
167     }
168 }
169 
170 fn parse_source(
171     path: &Option<String>,
172     inline: &Option<String>,
173 ) -> anyhow::Result<(Resolve, PackageId, Vec<PathBuf>)> {
174     let mut resolve = Resolve::default();
175     let mut files = Vec::new();
176     let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
177 
178     let mut parse = |resolve: &mut Resolve, path: &Path| -> anyhow::Result<_> {
179         let (pkg, sources) = resolve.push_path(path)?;
180         files.extend(sources);
181         Ok(pkg)
182     };
183 
184     let path_pkg = if let Some(path) = path {
185         Some(parse(&mut resolve, &root.join(path))?)
186     } else {
187         None
188     };
189 
190     let inline_pkg = if let Some(inline) = inline {
191         Some(resolve.push(UnresolvedPackage::parse("macro-input".as_ref(), inline)?)?)
192     } else {
193         None
194     };
195 
196     let pkg = inline_pkg
197         .or(path_pkg)
198         .map_or_else(|| parse(&mut resolve, &root.join("wit")), Ok)?;
199 
200     Ok((resolve, pkg, files))
201 }
202 
203 mod kw {
204     syn::custom_keyword!(inline);
205     syn::custom_keyword!(path);
206     syn::custom_keyword!(tracing);
207     syn::custom_keyword!(trappable_error_type);
208     syn::custom_keyword!(world);
209     syn::custom_keyword!(ownership);
210     syn::custom_keyword!(interfaces);
211     syn::custom_keyword!(with);
212     syn::custom_keyword!(except_imports);
213     syn::custom_keyword!(only_imports);
214     syn::custom_keyword!(trappable_imports);
215     syn::custom_keyword!(additional_derives);
216 }
217 
218 enum Opt {
219     World(syn::LitStr),
220     Path(syn::LitStr),
221     Inline(syn::LitStr),
222     Tracing(bool),
223     Async(AsyncConfig, Span),
224     TrappableErrorType(Vec<TrappableError>),
225     Ownership(Ownership),
226     Interfaces(syn::LitStr),
227     With(HashMap<String, String>),
228     TrappableImports(TrappableImports),
229     AdditionalDerives(Vec<syn::Path>),
230 }
231 
232 impl Parse for Opt {
233     fn parse(input: ParseStream<'_>) -> Result<Self> {
234         let l = input.lookahead1();
235         if l.peek(kw::path) {
236             input.parse::<kw::path>()?;
237             input.parse::<Token![:]>()?;
238             Ok(Opt::Path(input.parse()?))
239         } else if l.peek(kw::inline) {
240             input.parse::<kw::inline>()?;
241             input.parse::<Token![:]>()?;
242             Ok(Opt::Inline(input.parse()?))
243         } else if l.peek(kw::world) {
244             input.parse::<kw::world>()?;
245             input.parse::<Token![:]>()?;
246             Ok(Opt::World(input.parse()?))
247         } else if l.peek(kw::tracing) {
248             input.parse::<kw::tracing>()?;
249             input.parse::<Token![:]>()?;
250             Ok(Opt::Tracing(input.parse::<syn::LitBool>()?.value))
251         } else if l.peek(Token![async]) {
252             let span = input.parse::<Token![async]>()?.span;
253             input.parse::<Token![:]>()?;
254             if input.peek(syn::LitBool) {
255                 match input.parse::<syn::LitBool>()?.value {
256                     true => Ok(Opt::Async(AsyncConfig::All, span)),
257                     false => Ok(Opt::Async(AsyncConfig::None, span)),
258                 }
259             } else {
260                 let contents;
261                 syn::braced!(contents in input);
262 
263                 let l = contents.lookahead1();
264                 let ctor: fn(HashSet<String>) -> AsyncConfig = if l.peek(kw::except_imports) {
265                     contents.parse::<kw::except_imports>()?;
266                     contents.parse::<Token![:]>()?;
267                     AsyncConfig::AllExceptImports
268                 } else if l.peek(kw::only_imports) {
269                     contents.parse::<kw::only_imports>()?;
270                     contents.parse::<Token![:]>()?;
271                     AsyncConfig::OnlyImports
272                 } else {
273                     return Err(l.error());
274                 };
275 
276                 let list;
277                 syn::bracketed!(list in contents);
278                 let fields: Punctuated<syn::LitStr, Token![,]> =
279                     list.parse_terminated(Parse::parse, Token![,])?;
280 
281                 if contents.peek(Token![,]) {
282                     contents.parse::<Token![,]>()?;
283                 }
284                 Ok(Opt::Async(
285                     ctor(fields.iter().map(|s| s.value()).collect()),
286                     span,
287                 ))
288             }
289         } else if l.peek(kw::ownership) {
290             input.parse::<kw::ownership>()?;
291             input.parse::<Token![:]>()?;
292             let ownership = input.parse::<syn::Ident>()?;
293             Ok(Opt::Ownership(match ownership.to_string().as_str() {
294                 "Owning" => Ownership::Owning,
295                 "Borrowing" => Ownership::Borrowing {
296                     duplicate_if_necessary: {
297                         let contents;
298                         braced!(contents in input);
299                         let field = contents.parse::<syn::Ident>()?;
300                         match field.to_string().as_str() {
301                             "duplicate_if_necessary" => {
302                                 contents.parse::<Token![:]>()?;
303                                 contents.parse::<syn::LitBool>()?.value
304                             }
305                             name => {
306                                 return Err(Error::new(
307                                     field.span(),
308                                     format!(
309                                         "unrecognized `Ownership::Borrowing` field: `{name}`; \
310                                          expected `duplicate_if_necessary`"
311                                     ),
312                                 ));
313                             }
314                         }
315                     },
316                 },
317                 name => {
318                     return Err(Error::new(
319                         ownership.span(),
320                         format!(
321                             "unrecognized ownership: `{name}`; \
322                              expected `Owning` or `Borrowing`"
323                         ),
324                     ));
325                 }
326             }))
327         } else if l.peek(kw::trappable_error_type) {
328             input.parse::<kw::trappable_error_type>()?;
329             input.parse::<Token![:]>()?;
330             let contents;
331             let _lbrace = braced!(contents in input);
332             let fields: Punctuated<_, Token![,]> =
333                 contents.parse_terminated(trappable_error_field_parse, Token![,])?;
334             Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))
335         } else if l.peek(kw::interfaces) {
336             input.parse::<kw::interfaces>()?;
337             input.parse::<Token![:]>()?;
338             Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))
339         } else if l.peek(kw::with) {
340             input.parse::<kw::with>()?;
341             input.parse::<Token![:]>()?;
342             let contents;
343             let _lbrace = braced!(contents in input);
344             let fields: Punctuated<(String, String), Token![,]> =
345                 contents.parse_terminated(with_field_parse, Token![,])?;
346             Ok(Opt::With(HashMap::from_iter(fields)))
347         } else if l.peek(kw::trappable_imports) {
348             input.parse::<kw::trappable_imports>()?;
349             input.parse::<Token![:]>()?;
350             let config = if input.peek(syn::LitBool) {
351                 match input.parse::<syn::LitBool>()?.value {
352                     true => TrappableImports::All,
353                     false => TrappableImports::None,
354                 }
355             } else {
356                 let contents;
357                 syn::bracketed!(contents in input);
358                 let fields: Punctuated<syn::LitStr, Token![,]> =
359                     contents.parse_terminated(Parse::parse, Token![,])?;
360                 TrappableImports::Only(fields.iter().map(|s| s.value()).collect())
361             };
362             Ok(Opt::TrappableImports(config))
363         } else if l.peek(kw::additional_derives) {
364             input.parse::<kw::additional_derives>()?;
365             input.parse::<Token![:]>()?;
366             let contents;
367             syn::bracketed!(contents in input);
368             let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
369             Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
370         } else {
371             Err(l.error())
372         }
373     }
374 }
375 
376 fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {
377     let wit_path = input.parse::<syn::LitStr>()?.value();
378     input.parse::<Token![=>]>()?;
379     let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
380     Ok(TrappableError {
381         wit_path,
382         rust_type_name,
383     })
384 }
385 
386 fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {
387     let interface = input.parse::<syn::LitStr>()?.value();
388     input.parse::<Token![:]>()?;
389     let start = input.span();
390     let path = input.parse::<syn::Path>()?;
391 
392     // It's not possible for the segments of a path to be empty
393     let span = start
394         .join(path.segments.last().unwrap().ident.span())
395         .unwrap_or(start);
396 
397     let mut buf = String::new();
398     let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
399         if segment.arguments != syn::PathArguments::None {
400             return Err(Error::new(
401                 span,
402                 "Module path must not contain angles or parens",
403             ));
404         }
405 
406         buf.push_str(&segment.ident.to_string());
407 
408         Ok(())
409     };
410 
411     if path.leading_colon.is_some() {
412         buf.push_str("::");
413     }
414 
415     let mut segments = path.segments.into_iter();
416 
417     if let Some(segment) = segments.next() {
418         append(&mut buf, segment)?;
419     }
420 
421     for segment in segments {
422         buf.push_str("::");
423         append(&mut buf, segment)?;
424     }
425 
426     Ok((interface, buf))
427 }
428