#![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro-support/0.2")]
#[macro_use]
mod error;
mod ast;
mod codegen;
mod encode;
mod generics;
mod hash;
mod parser;
use codegen::TryToTokens;
use error::Diagnostic;
pub use parser::BindgenAttrs;
use parser::{ConvertToAst, MacroParse};
use proc_macro2::TokenStream;
use quote::quote;
use quote::ToTokens;
use quote::TokenStreamExt;
use syn::parse::Parser;
use syn::parse::{Parse, ParseStream, Result as SynResult};
use syn::Token;
fn cfg_gate_conditions(meta: &syn::Meta) -> Vec<TokenStream> {
if meta.path().is_ident("cfg") {
return match meta {
syn::Meta::List(list) => vec![list.tokens.clone()],
_ => Vec::new(),
};
}
let syn::Meta::List(list) = meta else {
return Vec::new();
};
if !list.path.is_ident("cfg_attr") {
return Vec::new();
}
let Ok(args) = syn::punctuated::Punctuated::<syn::Meta, Token![,]>::parse_terminated
.parse2(list.tokens.clone())
else {
return Vec::new();
};
let mut args = args.iter();
let Some(predicate) = args.next() else {
return Vec::new();
};
args.flat_map(cfg_gate_conditions)
.map(|condition| quote! { any(not(#predicate), #condition) })
.collect()
}
pub(crate) fn cfg_gate_attrs(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()
.flat_map(|attr| cfg_gate_conditions(&attr.meta))
.map(|condition| syn::parse_quote! { #[cfg(#condition)] })
.collect()
}
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let item = syn::parse2::<syn::Item>(input)?;
if let syn::Item::Struct(mut s) = item {
let opts: BindgenAttrs = syn::parse2(attr.clone())?;
if strip_retry_marker(&mut s.attrs) {
bail_span!(
s.ident,
"cannot resolve a path to the `wasm_bindgen` crate: add `wasm-bindgen` \
as a direct dependency, or specify an explicit path with \
`#[wasm_bindgen(wasm_bindgen = path::to::wasm_bindgen)]`"
);
}
let wasm_bindgen = opts
.wasm_bindgen()
.cloned()
.unwrap_or_else(|| syn::parse_quote! { ::wasm_bindgen });
let extends_path = opts.attrs.iter().find_map(|(_, a)| match a {
parser::BindgenAttr::Extends(_, path) => Some(path.clone()),
_ => None,
});
parser::inject_parent_field(&mut s, extends_path.as_ref(), &wasm_bindgen)?;
let item = quote! {
#[derive(#wasm_bindgen::__rt::BindgenedStruct)]
#[wasm_bindgen(#attr)]
#[__wasm_bindgen_retried]
#s
};
return Ok(item);
}
let opts = syn::parse2(attr)?;
let mut tokens = proc_macro2::TokenStream::new();
let mut program = ast::Program::default();
item.macro_parse(&mut program, (Some(opts), &mut tokens))?;
program.try_to_tokens(&mut tokens)?;
parser::check_unused_attrs(&mut tokens);
Ok(tokens)
}
fn strip_retry_marker(attrs: &mut Vec<syn::Attribute>) -> bool {
let before = attrs.len();
attrs.retain(|attr| !attr.path().is_ident("__wasm_bindgen_retried"));
before != attrs.len()
}
pub fn expand_link_to(input: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let opts = syn::parse2(input)?;
let mut tokens = proc_macro2::TokenStream::new();
let link = parser::link_to(opts)?;
link.try_to_tokens(&mut tokens)?;
Ok(tokens)
}
pub fn expand_class_marker(
attr: TokenStream,
input: TokenStream,
) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let mut item = syn::parse2::<syn::ImplItemFn>(input)?;
let opts: ClassMarker = syn::parse2(attr)?;
let mut program = ast::Program::default();
item.macro_parse(&mut program, &opts)?;
let mut tokens = proc_macro2::TokenStream::new();
tokens.append_all(
item.attrs
.iter()
.filter(|attr| matches!(attr.style, syn::AttrStyle::Outer)),
);
item.vis.to_tokens(&mut tokens);
item.sig.to_tokens(&mut tokens);
let mut err = None;
item.block.brace_token.surround(&mut tokens, |tokens| {
if let Err(e) = program.try_to_tokens(tokens) {
err = Some(e);
}
parser::check_unused_attrs(tokens); tokens.append_all(
item.attrs
.iter()
.filter(|attr| matches!(attr.style, syn::AttrStyle::Inner(_))),
);
tokens.append_all(&item.block.stmts);
});
if let Some(err) = err {
return Err(err);
}
Ok(tokens)
}
struct ClassMarker {
class: syn::Ident,
js_class: String,
js_namespace: Option<Vec<String>>,
wasm_bindgen: syn::Path,
wasm_bindgen_futures: syn::Path,
js_sys: syn::Path,
}
impl Parse for ClassMarker {
fn parse(input: ParseStream) -> SynResult<Self> {
let class = input.parse::<syn::Ident>()?;
input.parse::<Token![=]>()?;
let mut js_class = input.parse::<syn::LitStr>()?.value();
js_class = js_class
.strip_prefix("r#")
.map(String::from)
.unwrap_or(js_class);
let mut js_namespace: Option<Vec<String>> = None;
let mut wasm_bindgen = None;
let mut wasm_bindgen_futures = None;
let mut js_sys = None;
loop {
if input.parse::<Option<Token![,]>>()?.is_some() {
let ident = input.parse::<syn::Ident>()?;
if ident == "js_namespace" {
if js_namespace.is_some() {
return Err(syn::Error::new(
ident.span(),
"found duplicate `js_namespace`",
));
}
input.parse::<Token![=]>()?;
let content;
syn::bracketed!(content in input);
let segs: syn::punctuated::Punctuated<syn::LitStr, Token![,]> = content
.parse_terminated(|p: ParseStream| p.parse::<syn::LitStr>(), Token![,])?;
js_namespace = Some(segs.into_iter().map(|s| s.value()).collect());
} else if ident == "wasm_bindgen" {
if wasm_bindgen.is_some() {
return Err(syn::Error::new(
ident.span(),
"found duplicate `wasm_bindgen`",
));
}
input.parse::<Token![=]>()?;
wasm_bindgen = Some(input.parse::<syn::Path>()?);
} else if ident == "wasm_bindgen_futures" {
if wasm_bindgen_futures.is_some() {
return Err(syn::Error::new(
ident.span(),
"found duplicate `wasm_bindgen_futures`",
));
}
input.parse::<Token![=]>()?;
wasm_bindgen_futures = Some(input.parse::<syn::Path>()?);
} else if ident == "js_sys" {
if js_sys.is_some() {
return Err(syn::Error::new(ident.span(), "found duplicate `js_sys`"));
}
input.parse::<Token![=]>()?;
js_sys = Some(input.parse::<syn::Path>()?);
} else {
return Err(syn::Error::new(
ident.span(),
"expected `js_namespace`, `wasm_bindgen`, `wasm_bindgen_futures`, or `js_sys`",
));
}
} else {
break;
}
}
Ok(ClassMarker {
class,
js_class,
js_namespace,
wasm_bindgen: wasm_bindgen.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen }),
wasm_bindgen_futures: wasm_bindgen_futures
.unwrap_or_else(|| syn::parse_quote! { wasm_bindgen_futures }),
js_sys: js_sys.unwrap_or_else(|| syn::parse_quote! { js_sys }),
})
}
}
pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let mut s: syn::ItemStruct = syn::parse2(item)?;
let mut program = ast::Program::default();
program.structs.push((&mut s).convert(&program)?);
let mut tokens = proc_macro2::TokenStream::new();
program.try_to_tokens(&mut tokens)?;
parser::check_unused_attrs(&mut tokens);
Ok(tokens)
}