Path: blob/main/crates/component-macro/src/bindgen.rs
3067 views
use proc_macro2::{Span, TokenStream};1use quote::ToTokens;2use std::collections::HashMap;3use std::env;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::{Token, braced, token};9use wasmtime_wit_bindgen::{10FunctionConfig, FunctionFilter, FunctionFlags, Opts, Ownership, TrappableError,11};12use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};1314pub struct Config {15opts: Opts,16resolve: Resolve,17world: WorldId,18files: Vec<PathBuf>,19include_generated_code_from_file: bool,20}2122pub fn expand(input: &Config) -> Result<TokenStream> {23let mut src = match input.opts.generate(&input.resolve, input.world) {24Ok(s) => s,25Err(e) => return Err(Error::new(Span::call_site(), e.to_string())),26};2728if input.opts.stringify {29return Ok(quote::quote!(#src));30}3132// If a magical `WASMTIME_DEBUG_BINDGEN` environment variable is set then33// place a formatted version of the expanded code into a file. This file34// will then show up in rustc error messages for any codegen issues and can35// be inspected manually.36if input.include_generated_code_from_file37|| input.opts.debug38|| std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok()39{40static INVOCATION: AtomicUsize = AtomicUsize::new(0);41let root = Path::new(env!("DEBUG_OUTPUT_DIR"));42let world_name = &input.resolve.worlds[input.world].name;43let n = INVOCATION.fetch_add(1, Relaxed);44let path = root.join(format!("{world_name}{n}.rs"));4546std::fs::write(&path, &src).unwrap();4748// optimistically format the code but don't require success49drop(50std::process::Command::new("rustfmt")51.arg(&path)52.arg("--edition=2021")53.output(),54);5556src = format!("include!({path:?});");57}58let mut contents = src.parse::<TokenStream>().unwrap();5960// Include a dummy `include_str!` for any files we read so rustc knows that61// we depend on the contents of those files.62for file in input.files.iter() {63contents.extend(64format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())65.parse::<TokenStream>()66.unwrap(),67);68}6970Ok(contents)71}7273impl Parse for Config {74fn parse(input: ParseStream<'_>) -> Result<Self> {75let call_site = Span::call_site();76let mut opts = Opts::default();77let mut world = None;78let mut inline = None;79let mut paths = Vec::new();80let mut imports_configured = false;81let mut exports_configured = false;82let mut include_generated_code_from_file = false;8384if input.peek(token::Brace) {85let content;86syn::braced!(content in input);87let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;88for field in fields.into_pairs() {89match field.into_value() {90Opt::Path(p) => {91paths.extend(p.into_iter().map(|p| p.value()));92}93Opt::World(s) => {94if world.is_some() {95return Err(Error::new(s.span(), "cannot specify second world"));96}97world = Some(s.value());98}99Opt::Inline(s) => {100if inline.is_some() {101return Err(Error::new(s.span(), "cannot specify second source"));102}103inline = Some(s.value());104}105Opt::Debug(val) => opts.debug = val,106Opt::TrappableErrorType(val) => opts.trappable_error_type = val,107Opt::Ownership(val) => opts.ownership = val,108Opt::Interfaces(s) => {109if inline.is_some() {110return Err(Error::new(s.span(), "cannot specify a second source"));111}112inline = Some(format!(113"114package wasmtime:component-macro-synthesized;115116world interfaces {{117{}118}}119",120s.value()121));122123if world.is_some() {124return Err(Error::new(125s.span(),126"cannot specify a world with `interfaces`",127));128}129world = Some("wasmtime:component-macro-synthesized/interfaces".to_string());130131opts.only_interfaces = true;132}133Opt::With(val) => opts.with.extend(val),134Opt::AdditionalDerives(paths) => {135opts.additional_derive_attributes = paths136.into_iter()137.map(|p| p.into_token_stream().to_string())138.collect()139}140Opt::Stringify(val) => opts.stringify = val,141Opt::SkipMutForwardingImpls(val) => opts.skip_mut_forwarding_impls = val,142Opt::RequireStoreDataSend(val) => opts.require_store_data_send = val,143Opt::WasmtimeCrate(f) => {144opts.wasmtime_crate = Some(f.into_token_stream().to_string())145}146Opt::Anyhow(val) => {147opts.anyhow = val;148}149Opt::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i,150Opt::Imports(config, span) => {151if imports_configured {152return Err(Error::new(span, "cannot specify imports configuration"));153}154opts.imports = config;155imports_configured = true;156}157Opt::Exports(config, span) => {158if exports_configured {159return Err(Error::new(span, "cannot specify exports configuration"));160}161opts.exports = config;162exports_configured = true;163}164}165}166} else {167world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());168if input.parse::<Option<syn::token::In>>()?.is_some() {169paths.push(input.parse::<syn::LitStr>()?.value());170}171}172let (resolve, pkgs, files) = parse_source(&paths, &inline)173.map_err(|err| Error::new(call_site, format!("{err:?}")))?;174175let world = resolve176.select_world(&pkgs, world.as_deref())177.map_err(|e| Error::new(call_site, format!("{e:?}")))?;178Ok(Config {179opts,180resolve,181world,182files,183include_generated_code_from_file,184})185}186}187188fn parse_source(189paths: &Vec<String>,190inline: &Option<String>,191) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {192let mut resolve = Resolve::default();193resolve.all_features = true;194let mut files = Vec::new();195let mut pkgs = Vec::new();196let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());197let default = root.join("wit");198199let parse = |resolve: &mut Resolve,200files: &mut Vec<PathBuf>,201pkgs: &mut Vec<PackageId>,202paths: &[PathBuf]|203-> anyhow::Result<_> {204for path in paths {205let p = root.join(path);206// Try to normalize the path to make the error message more understandable when207// the path is not correct. Fallback to the original path if normalization fails208// (probably return an error somewhere else).209let normalized_path = match std::fs::canonicalize(&p) {210Ok(p) => p,211Err(_) => p.to_path_buf(),212};213let (pkg, sources) = resolve.push_path(normalized_path)?;214pkgs.push(pkg);215files.extend(sources.paths().map(|p| p.to_owned()));216}217Ok(())218};219220if paths.is_empty() {221if default.exists() {222parse(&mut resolve, &mut files, &mut pkgs, &[default])?;223}224} else {225parse(226&mut resolve,227&mut files,228&mut pkgs,229&paths.iter().map(|s| s.into()).collect::<Vec<_>>(),230)?;231}232233if let Some(inline) = inline {234pkgs.truncate(0);235pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);236}237238Ok((resolve, pkgs, files))239}240241mod kw {242syn::custom_keyword!(inline);243syn::custom_keyword!(path);244syn::custom_keyword!(tracing);245syn::custom_keyword!(verbose_tracing);246syn::custom_keyword!(trappable_error_type);247syn::custom_keyword!(world);248syn::custom_keyword!(ownership);249syn::custom_keyword!(interfaces);250syn::custom_keyword!(with);251syn::custom_keyword!(except_imports);252syn::custom_keyword!(only_imports);253syn::custom_keyword!(additional_derives);254syn::custom_keyword!(stringify);255syn::custom_keyword!(skip_mut_forwarding_impls);256syn::custom_keyword!(require_store_data_send);257syn::custom_keyword!(wasmtime_crate);258syn::custom_keyword!(anyhow);259syn::custom_keyword!(include_generated_code_from_file);260syn::custom_keyword!(debug);261syn::custom_keyword!(imports);262syn::custom_keyword!(exports);263syn::custom_keyword!(store);264syn::custom_keyword!(trappable);265syn::custom_keyword!(ignore_wit);266syn::custom_keyword!(exact);267syn::custom_keyword!(task_exit);268}269270enum Opt {271World(syn::LitStr),272Path(Vec<syn::LitStr>),273Inline(syn::LitStr),274TrappableErrorType(Vec<TrappableError>),275Ownership(Ownership),276Interfaces(syn::LitStr),277With(HashMap<String, String>),278AdditionalDerives(Vec<syn::Path>),279Stringify(bool),280SkipMutForwardingImpls(bool),281RequireStoreDataSend(bool),282WasmtimeCrate(syn::Path),283Anyhow(bool),284IncludeGeneratedCodeFromFile(bool),285Debug(bool),286Imports(FunctionConfig, Span),287Exports(FunctionConfig, Span),288}289290impl Parse for Opt {291fn parse(input: ParseStream<'_>) -> Result<Self> {292let l = input.lookahead1();293if l.peek(kw::debug) {294input.parse::<kw::debug>()?;295input.parse::<Token![:]>()?;296Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))297} else if l.peek(kw::path) {298input.parse::<kw::path>()?;299input.parse::<Token![:]>()?;300301let mut paths: Vec<syn::LitStr> = vec![];302303let l = input.lookahead1();304if l.peek(syn::LitStr) {305paths.push(input.parse()?);306} else if l.peek(syn::token::Bracket) {307let contents;308syn::bracketed!(contents in input);309let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;310311paths.extend(list);312} else {313return Err(l.error());314};315316Ok(Opt::Path(paths))317} else if l.peek(kw::inline) {318input.parse::<kw::inline>()?;319input.parse::<Token![:]>()?;320Ok(Opt::Inline(input.parse()?))321} else if l.peek(kw::world) {322input.parse::<kw::world>()?;323input.parse::<Token![:]>()?;324Ok(Opt::World(input.parse()?))325} else if l.peek(kw::ownership) {326input.parse::<kw::ownership>()?;327input.parse::<Token![:]>()?;328let ownership = input.parse::<syn::Ident>()?;329Ok(Opt::Ownership(match ownership.to_string().as_str() {330"Owning" => Ownership::Owning,331"Borrowing" => Ownership::Borrowing {332duplicate_if_necessary: {333let contents;334braced!(contents in input);335let field = contents.parse::<syn::Ident>()?;336match field.to_string().as_str() {337"duplicate_if_necessary" => {338contents.parse::<Token![:]>()?;339contents.parse::<syn::LitBool>()?.value340}341name => {342return Err(Error::new(343field.span(),344format!(345"unrecognized `Ownership::Borrowing` field: `{name}`; \346expected `duplicate_if_necessary`"347),348));349}350}351},352},353name => {354return Err(Error::new(355ownership.span(),356format!(357"unrecognized ownership: `{name}`; \358expected `Owning` or `Borrowing`"359),360));361}362}))363} else if l.peek(kw::trappable_error_type) {364input.parse::<kw::trappable_error_type>()?;365input.parse::<Token![:]>()?;366let contents;367let _lbrace = braced!(contents in input);368let fields: Punctuated<_, Token![,]> =369contents.parse_terminated(trappable_error_field_parse, Token![,])?;370Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))371} else if l.peek(kw::interfaces) {372input.parse::<kw::interfaces>()?;373input.parse::<Token![:]>()?;374Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))375} else if l.peek(kw::with) {376input.parse::<kw::with>()?;377input.parse::<Token![:]>()?;378let contents;379let _lbrace = braced!(contents in input);380let fields: Punctuated<(String, String), Token![,]> =381contents.parse_terminated(with_field_parse, Token![,])?;382Ok(Opt::With(HashMap::from_iter(fields)))383} else if l.peek(kw::additional_derives) {384input.parse::<kw::additional_derives>()?;385input.parse::<Token![:]>()?;386let contents;387syn::bracketed!(contents in input);388let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;389Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))390} else if l.peek(kw::stringify) {391input.parse::<kw::stringify>()?;392input.parse::<Token![:]>()?;393Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value))394} else if l.peek(kw::skip_mut_forwarding_impls) {395input.parse::<kw::skip_mut_forwarding_impls>()?;396input.parse::<Token![:]>()?;397Ok(Opt::SkipMutForwardingImpls(398input.parse::<syn::LitBool>()?.value,399))400} else if l.peek(kw::require_store_data_send) {401input.parse::<kw::require_store_data_send>()?;402input.parse::<Token![:]>()?;403Ok(Opt::RequireStoreDataSend(404input.parse::<syn::LitBool>()?.value,405))406} else if l.peek(kw::wasmtime_crate) {407input.parse::<kw::wasmtime_crate>()?;408input.parse::<Token![:]>()?;409Ok(Opt::WasmtimeCrate(input.parse()?))410} else if l.peek(kw::anyhow) {411input.parse::<kw::anyhow>()?;412input.parse::<Token![:]>()?;413Ok(Opt::Anyhow(input.parse::<syn::LitBool>()?.value))414} else if l.peek(kw::include_generated_code_from_file) {415input.parse::<kw::include_generated_code_from_file>()?;416input.parse::<Token![:]>()?;417Ok(Opt::IncludeGeneratedCodeFromFile(418input.parse::<syn::LitBool>()?.value,419))420} else if l.peek(kw::imports) {421let span = input.parse::<kw::imports>()?.span;422input.parse::<Token![:]>()?;423Ok(Opt::Imports(parse_function_config(input)?, span))424} else if l.peek(kw::exports) {425let span = input.parse::<kw::exports>()?.span;426input.parse::<Token![:]>()?;427Ok(Opt::Exports(parse_function_config(input)?, span))428} else {429Err(l.error())430}431}432}433434fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {435let wit_path = input.parse::<syn::LitStr>()?.value();436input.parse::<Token![=>]>()?;437let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();438Ok(TrappableError {439wit_path,440rust_type_name,441})442}443444fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {445let interface = input.parse::<syn::LitStr>()?.value();446input.parse::<Token![:]>()?;447let start = input.span();448let path = input.parse::<syn::Path>()?;449450// It's not possible for the segments of a path to be empty451let span = start452.join(path.segments.last().unwrap().ident.span())453.unwrap_or(start);454455let mut buf = String::new();456let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {457if segment.arguments != syn::PathArguments::None {458return Err(Error::new(459span,460"Module path must not contain angles or parens",461));462}463464buf.push_str(&segment.ident.to_string());465466Ok(())467};468469if path.leading_colon.is_some() {470buf.push_str("::");471}472473let mut segments = path.segments.into_iter();474475if let Some(segment) = segments.next() {476append(&mut buf, segment)?;477}478479for segment in segments {480buf.push_str("::");481append(&mut buf, segment)?;482}483484Ok((interface, buf))485}486487fn parse_function_config(input: ParseStream<'_>) -> Result<FunctionConfig> {488let content;489syn::braced!(content in input);490let mut ret = FunctionConfig::new();491492let list = Punctuated::<FunctionConfigSyntax, Token![,]>::parse_terminated(&content)?;493for item in list.into_iter() {494ret.push(item.filter, item.flags);495}496497return Ok(ret);498499struct FunctionConfigSyntax {500filter: FunctionFilter,501flags: FunctionFlags,502}503504impl Parse for FunctionConfigSyntax {505fn parse(input: ParseStream<'_>) -> Result<Self> {506let l = input.lookahead1();507let filter = if l.peek(syn::LitStr) {508FunctionFilter::Name(input.parse::<syn::LitStr>()?.value())509} else if l.peek(Token![default]) {510input.parse::<Token![default]>()?;511FunctionFilter::Default512} else {513return Err(l.error());514};515516input.parse::<Token![:]>()?;517518let mut flags = FunctionFlags::empty();519while !input.is_empty() {520let l = input.lookahead1();521if l.peek(Token![async]) {522input.parse::<Token![async]>()?;523flags |= FunctionFlags::ASYNC;524} else if l.peek(kw::tracing) {525input.parse::<kw::tracing>()?;526flags |= FunctionFlags::TRACING;527} else if l.peek(kw::verbose_tracing) {528input.parse::<kw::verbose_tracing>()?;529flags |= FunctionFlags::VERBOSE_TRACING;530} else if l.peek(kw::store) {531input.parse::<kw::store>()?;532flags |= FunctionFlags::STORE;533} else if l.peek(kw::trappable) {534input.parse::<kw::trappable>()?;535flags |= FunctionFlags::TRAPPABLE;536} else if l.peek(kw::ignore_wit) {537input.parse::<kw::ignore_wit>()?;538flags |= FunctionFlags::IGNORE_WIT;539} else if l.peek(kw::exact) {540input.parse::<kw::exact>()?;541flags |= FunctionFlags::EXACT;542} else if l.peek(kw::task_exit) {543input.parse::<kw::task_exit>()?;544flags |= FunctionFlags::TASK_EXIT;545} else {546return Err(l.error());547}548549if input.peek(Token![|]) {550input.parse::<Token![|]>()?;551} else {552break;553}554}555556Ok(FunctionConfigSyntax { filter, flags })557}558}559}560561562