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