Path: blob/main/crates/component-macro/src/bindgen.rs
1692 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::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i,147Opt::Imports(config, span) => {148if imports_configured {149return Err(Error::new(span, "cannot specify imports configuration"));150}151opts.imports = config;152imports_configured = true;153}154Opt::Exports(config, span) => {155if exports_configured {156return Err(Error::new(span, "cannot specify exports configuration"));157}158opts.exports = config;159exports_configured = true;160}161}162}163} else {164world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());165if input.parse::<Option<syn::token::In>>()?.is_some() {166paths.push(input.parse::<syn::LitStr>()?.value());167}168}169let (resolve, pkgs, files) = parse_source(&paths, &inline)170.map_err(|err| Error::new(call_site, format!("{err:?}")))?;171172let world = resolve173.select_world(&pkgs, world.as_deref())174.map_err(|e| Error::new(call_site, format!("{e:?}")))?;175Ok(Config {176opts,177resolve,178world,179files,180include_generated_code_from_file,181})182}183}184185fn parse_source(186paths: &Vec<String>,187inline: &Option<String>,188) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {189let mut resolve = Resolve::default();190resolve.all_features = true;191let mut files = Vec::new();192let mut pkgs = Vec::new();193let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());194let default = root.join("wit");195196let parse = |resolve: &mut Resolve,197files: &mut Vec<PathBuf>,198pkgs: &mut Vec<PackageId>,199paths: &[PathBuf]|200-> anyhow::Result<_> {201for path in paths {202let p = root.join(path);203// Try to normalize the path to make the error message more understandable when204// the path is not correct. Fallback to the original path if normalization fails205// (probably return an error somewhere else).206let normalized_path = match std::fs::canonicalize(&p) {207Ok(p) => p,208Err(_) => p.to_path_buf(),209};210let (pkg, sources) = resolve.push_path(normalized_path)?;211pkgs.push(pkg);212files.extend(sources.paths().map(|p| p.to_owned()));213}214Ok(())215};216217if paths.is_empty() {218if default.exists() {219parse(&mut resolve, &mut files, &mut pkgs, &[default])?;220}221} else {222parse(223&mut resolve,224&mut files,225&mut pkgs,226&paths.iter().map(|s| s.into()).collect::<Vec<_>>(),227)?;228}229230if let Some(inline) = inline {231pkgs.truncate(0);232pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);233}234235Ok((resolve, pkgs, files))236}237238mod kw {239syn::custom_keyword!(inline);240syn::custom_keyword!(path);241syn::custom_keyword!(tracing);242syn::custom_keyword!(verbose_tracing);243syn::custom_keyword!(trappable_error_type);244syn::custom_keyword!(world);245syn::custom_keyword!(ownership);246syn::custom_keyword!(interfaces);247syn::custom_keyword!(with);248syn::custom_keyword!(except_imports);249syn::custom_keyword!(only_imports);250syn::custom_keyword!(additional_derives);251syn::custom_keyword!(stringify);252syn::custom_keyword!(skip_mut_forwarding_impls);253syn::custom_keyword!(require_store_data_send);254syn::custom_keyword!(wasmtime_crate);255syn::custom_keyword!(include_generated_code_from_file);256syn::custom_keyword!(debug);257syn::custom_keyword!(imports);258syn::custom_keyword!(exports);259syn::custom_keyword!(store);260syn::custom_keyword!(trappable);261syn::custom_keyword!(ignore_wit);262syn::custom_keyword!(exact);263}264265enum Opt {266World(syn::LitStr),267Path(Vec<syn::LitStr>),268Inline(syn::LitStr),269TrappableErrorType(Vec<TrappableError>),270Ownership(Ownership),271Interfaces(syn::LitStr),272With(HashMap<String, String>),273AdditionalDerives(Vec<syn::Path>),274Stringify(bool),275SkipMutForwardingImpls(bool),276RequireStoreDataSend(bool),277WasmtimeCrate(syn::Path),278IncludeGeneratedCodeFromFile(bool),279Debug(bool),280Imports(FunctionConfig, Span),281Exports(FunctionConfig, Span),282}283284impl Parse for Opt {285fn parse(input: ParseStream<'_>) -> Result<Self> {286let l = input.lookahead1();287if l.peek(kw::debug) {288input.parse::<kw::debug>()?;289input.parse::<Token![:]>()?;290Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))291} else if l.peek(kw::path) {292input.parse::<kw::path>()?;293input.parse::<Token![:]>()?;294295let mut paths: Vec<syn::LitStr> = vec![];296297let l = input.lookahead1();298if l.peek(syn::LitStr) {299paths.push(input.parse()?);300} else if l.peek(syn::token::Bracket) {301let contents;302syn::bracketed!(contents in input);303let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;304305paths.extend(list);306} else {307return Err(l.error());308};309310Ok(Opt::Path(paths))311} else if l.peek(kw::inline) {312input.parse::<kw::inline>()?;313input.parse::<Token![:]>()?;314Ok(Opt::Inline(input.parse()?))315} else if l.peek(kw::world) {316input.parse::<kw::world>()?;317input.parse::<Token![:]>()?;318Ok(Opt::World(input.parse()?))319} else if l.peek(kw::ownership) {320input.parse::<kw::ownership>()?;321input.parse::<Token![:]>()?;322let ownership = input.parse::<syn::Ident>()?;323Ok(Opt::Ownership(match ownership.to_string().as_str() {324"Owning" => Ownership::Owning,325"Borrowing" => Ownership::Borrowing {326duplicate_if_necessary: {327let contents;328braced!(contents in input);329let field = contents.parse::<syn::Ident>()?;330match field.to_string().as_str() {331"duplicate_if_necessary" => {332contents.parse::<Token![:]>()?;333contents.parse::<syn::LitBool>()?.value334}335name => {336return Err(Error::new(337field.span(),338format!(339"unrecognized `Ownership::Borrowing` field: `{name}`; \340expected `duplicate_if_necessary`"341),342));343}344}345},346},347name => {348return Err(Error::new(349ownership.span(),350format!(351"unrecognized ownership: `{name}`; \352expected `Owning` or `Borrowing`"353),354));355}356}))357} else if l.peek(kw::trappable_error_type) {358input.parse::<kw::trappable_error_type>()?;359input.parse::<Token![:]>()?;360let contents;361let _lbrace = braced!(contents in input);362let fields: Punctuated<_, Token![,]> =363contents.parse_terminated(trappable_error_field_parse, Token![,])?;364Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))365} else if l.peek(kw::interfaces) {366input.parse::<kw::interfaces>()?;367input.parse::<Token![:]>()?;368Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))369} else if l.peek(kw::with) {370input.parse::<kw::with>()?;371input.parse::<Token![:]>()?;372let contents;373let _lbrace = braced!(contents in input);374let fields: Punctuated<(String, String), Token![,]> =375contents.parse_terminated(with_field_parse, Token![,])?;376Ok(Opt::With(HashMap::from_iter(fields)))377} else if l.peek(kw::additional_derives) {378input.parse::<kw::additional_derives>()?;379input.parse::<Token![:]>()?;380let contents;381syn::bracketed!(contents in input);382let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;383Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))384} else if l.peek(kw::stringify) {385input.parse::<kw::stringify>()?;386input.parse::<Token![:]>()?;387Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value))388} else if l.peek(kw::skip_mut_forwarding_impls) {389input.parse::<kw::skip_mut_forwarding_impls>()?;390input.parse::<Token![:]>()?;391Ok(Opt::SkipMutForwardingImpls(392input.parse::<syn::LitBool>()?.value,393))394} else if l.peek(kw::require_store_data_send) {395input.parse::<kw::require_store_data_send>()?;396input.parse::<Token![:]>()?;397Ok(Opt::RequireStoreDataSend(398input.parse::<syn::LitBool>()?.value,399))400} else if l.peek(kw::wasmtime_crate) {401input.parse::<kw::wasmtime_crate>()?;402input.parse::<Token![:]>()?;403Ok(Opt::WasmtimeCrate(input.parse()?))404} else if l.peek(kw::include_generated_code_from_file) {405input.parse::<kw::include_generated_code_from_file>()?;406input.parse::<Token![:]>()?;407Ok(Opt::IncludeGeneratedCodeFromFile(408input.parse::<syn::LitBool>()?.value,409))410} else if l.peek(kw::imports) {411let span = input.parse::<kw::imports>()?.span;412input.parse::<Token![:]>()?;413Ok(Opt::Imports(parse_function_config(input)?, span))414} else if l.peek(kw::exports) {415let span = input.parse::<kw::exports>()?.span;416input.parse::<Token![:]>()?;417Ok(Opt::Exports(parse_function_config(input)?, span))418} else {419Err(l.error())420}421}422}423424fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {425let wit_path = input.parse::<syn::LitStr>()?.value();426input.parse::<Token![=>]>()?;427let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();428Ok(TrappableError {429wit_path,430rust_type_name,431})432}433434fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {435let interface = input.parse::<syn::LitStr>()?.value();436input.parse::<Token![:]>()?;437let start = input.span();438let path = input.parse::<syn::Path>()?;439440// It's not possible for the segments of a path to be empty441let span = start442.join(path.segments.last().unwrap().ident.span())443.unwrap_or(start);444445let mut buf = String::new();446let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {447if segment.arguments != syn::PathArguments::None {448return Err(Error::new(449span,450"Module path must not contain angles or parens",451));452}453454buf.push_str(&segment.ident.to_string());455456Ok(())457};458459if path.leading_colon.is_some() {460buf.push_str("::");461}462463let mut segments = path.segments.into_iter();464465if let Some(segment) = segments.next() {466append(&mut buf, segment)?;467}468469for segment in segments {470buf.push_str("::");471append(&mut buf, segment)?;472}473474Ok((interface, buf))475}476477fn parse_function_config(input: ParseStream<'_>) -> Result<FunctionConfig> {478let content;479syn::braced!(content in input);480let mut ret = FunctionConfig::new();481482let list = Punctuated::<FunctionConfigSyntax, Token![,]>::parse_terminated(&content)?;483for item in list.into_iter() {484ret.push(item.filter, item.flags);485}486487return Ok(ret);488489struct FunctionConfigSyntax {490filter: FunctionFilter,491flags: FunctionFlags,492}493494impl Parse for FunctionConfigSyntax {495fn parse(input: ParseStream<'_>) -> Result<Self> {496let l = input.lookahead1();497let filter = if l.peek(syn::LitStr) {498FunctionFilter::Name(input.parse::<syn::LitStr>()?.value())499} else if l.peek(Token![default]) {500input.parse::<Token![default]>()?;501FunctionFilter::Default502} else {503return Err(l.error());504};505506input.parse::<Token![:]>()?;507508let mut flags = FunctionFlags::empty();509while !input.is_empty() {510let l = input.lookahead1();511if l.peek(Token![async]) {512input.parse::<Token![async]>()?;513flags |= FunctionFlags::ASYNC;514} else if l.peek(kw::tracing) {515input.parse::<kw::tracing>()?;516flags |= FunctionFlags::TRACING;517} else if l.peek(kw::verbose_tracing) {518input.parse::<kw::verbose_tracing>()?;519flags |= FunctionFlags::VERBOSE_TRACING;520} else if l.peek(kw::store) {521input.parse::<kw::store>()?;522flags |= FunctionFlags::STORE;523} else if l.peek(kw::trappable) {524input.parse::<kw::trappable>()?;525flags |= FunctionFlags::TRAPPABLE;526} else if l.peek(kw::ignore_wit) {527input.parse::<kw::ignore_wit>()?;528flags |= FunctionFlags::IGNORE_WIT;529} else if l.peek(kw::exact) {530input.parse::<kw::exact>()?;531flags |= FunctionFlags::EXACT;532} else {533return Err(l.error());534}535536if input.peek(Token![|]) {537input.parse::<Token![|]>()?;538} else {539break;540}541}542543Ok(FunctionConfigSyntax { filter, flags })544}545}546}547548549