mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-21 08:41:35 +00:00
Migrate from a macro to an attribute
This commit migrates from `wasm_bindgen!`-the-macro to `#[wasm_bindgen]`-the-attribute. The actual mechanics of the macro are relatively simple in just generating some shims here and there, but wrapping everything in one huge macro invocation can often seem intimidating as it gives off this feeling of "oh dear anything can happen here!" Using an attribute should curb expectations much more greatly of "oh there's just some extra stuff happening behind the scenes". The usage is otherwise relatively straightforward and close to what it was before, but check out the DESIGN.md/README.md changes for more info!
This commit is contained in:
@ -13,7 +13,7 @@ extern crate wasm_bindgen_shared as shared;
|
||||
use std::sync::atomic::*;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenNode, Delimiter, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use quote::{Tokens, ToTokens};
|
||||
|
||||
macro_rules! my_quote {
|
||||
@ -22,75 +22,41 @@ macro_rules! my_quote {
|
||||
|
||||
mod ast;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
// Parse the input as a list of Rust items, reusing the `syn::File` parser.
|
||||
let file = syn::parse::<ast::File>(input)
|
||||
.expect("expected a set of valid Rust items");
|
||||
#[proc_macro_attribute]
|
||||
pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let item = syn::parse::<syn::Item>(input.clone())
|
||||
.expect("expected a valid Rust item");
|
||||
let opts = syn::parse::<ast::BindgenAttrs>(attr)
|
||||
.expect("invalid arguments to #[wasm_bindgen]");
|
||||
|
||||
let mut ret = Tokens::new();
|
||||
let mut program = ast::Program::default();
|
||||
program.push_item(item, Some(opts), &mut ret);
|
||||
generate_wrappers(program, &mut ret);
|
||||
|
||||
let mut program = ast::Program {
|
||||
structs: Vec::new(),
|
||||
free_functions: Vec::new(),
|
||||
imports: Vec::new(),
|
||||
imported_structs: Vec::new(),
|
||||
};
|
||||
// println!("{}", ret);
|
||||
|
||||
// Translate all input items into our own internal representation (the `ast`
|
||||
// module). We'll be panicking here on anything that we can't process
|
||||
ret.into()
|
||||
}
|
||||
|
||||
for item in file.items.iter() {
|
||||
let item = match *item {
|
||||
ast::MyItem::ExternClass(ref c) => {
|
||||
program.push_extern_class(c);
|
||||
continue
|
||||
}
|
||||
ast::MyItem::Normal(ref item) => item,
|
||||
};
|
||||
|
||||
match *item {
|
||||
syn::Item::Fn(ref f) => {
|
||||
item.to_tokens(&mut ret);
|
||||
program.free_functions.push(ast::Function::from(f));
|
||||
}
|
||||
syn::Item::Struct(ref s) => {
|
||||
item.to_tokens(&mut ret);
|
||||
let s = ast::Struct::from(s);
|
||||
if program.structs.iter().any(|a| a.name == s.name) {
|
||||
panic!("redefinition of struct: {}", s.name);
|
||||
}
|
||||
program.structs.push(s);
|
||||
}
|
||||
syn::Item::Impl(ref i) => {
|
||||
item.to_tokens(&mut ret);
|
||||
program.push_impl(i);
|
||||
}
|
||||
syn::Item::ForeignMod(ref f) => {
|
||||
program.push_foreign_mod(f);
|
||||
}
|
||||
_ => panic!("unexpected item in bindgen macro"),
|
||||
}
|
||||
}
|
||||
|
||||
// Generate wrappers for all the items that we've found
|
||||
|
||||
for function in program.free_functions.iter() {
|
||||
bindgen_fn(function, &mut ret);
|
||||
// Generate wrappers for all the items that we've found
|
||||
fn generate_wrappers(program: ast::Program, tokens: &mut Tokens) {
|
||||
for export in program.exports.iter() {
|
||||
bindgen_export(export, tokens);
|
||||
}
|
||||
for s in program.structs.iter() {
|
||||
bindgen_struct(s, &mut ret);
|
||||
bindgen_struct(s, tokens);
|
||||
}
|
||||
for i in program.imports.iter() {
|
||||
bindgen_import(i, &mut ret);
|
||||
bindgen_import(i, tokens);
|
||||
}
|
||||
for i in program.imported_structs.iter() {
|
||||
bindgen_imported_struct(i, &mut ret);
|
||||
for &(ref vis, ref t) in program.imported_types.iter() {
|
||||
bindgen_imported_type(vis, t, tokens);
|
||||
}
|
||||
|
||||
// Finally generate a static which will eventually be what lives in a custom
|
||||
// section of the wasm executable. For now it's just a plain old static, but
|
||||
// we'll eventually have it actually in its own section.
|
||||
// Generate a static which will eventually be what lives in a custom section
|
||||
// of the wasm executable. For now it's just a plain old static, but we'll
|
||||
// eventually have it actually in its own section.
|
||||
|
||||
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
|
||||
@ -104,32 +70,12 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub static #generated_static_name: [u32; #generated_static_length] =
|
||||
[#generated_static_value];
|
||||
}).to_tokens(&mut ret);
|
||||
|
||||
// println!("{}", ret);
|
||||
|
||||
ret.into()
|
||||
}
|
||||
|
||||
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
|
||||
bindgen(&function.free_function_export_name(),
|
||||
function.rust_symbol(None),
|
||||
Receiver::FreeFunction(function.name),
|
||||
&function.arguments,
|
||||
function.ret.as_ref(),
|
||||
into)
|
||||
}).to_tokens(tokens);
|
||||
}
|
||||
|
||||
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
||||
for f in s.functions.iter() {
|
||||
bindgen_struct_fn(s, f, into);
|
||||
}
|
||||
for f in s.methods.iter() {
|
||||
bindgen_struct_method(s, f, into);
|
||||
}
|
||||
|
||||
let name = &s.name;
|
||||
let free_fn = s.free_function();
|
||||
let free_fn = syn::Ident::from(shared::free_function(s.name.as_ref()));
|
||||
let c = shared::name_to_descriptor(name.as_ref()) as u32;
|
||||
(my_quote! {
|
||||
impl ::wasm_bindgen::convert::WasmBoundary for #name {
|
||||
@ -175,43 +121,17 @@ fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
||||
}).to_tokens(into);
|
||||
}
|
||||
|
||||
fn bindgen_struct_fn(s: &ast::Struct, f: &ast::Function, into: &mut Tokens) {
|
||||
bindgen(&f.struct_function_export_name(s.name),
|
||||
f.rust_symbol(Some(s.name)),
|
||||
Receiver::StructFunction(s.name, f.name),
|
||||
&f.arguments,
|
||||
f.ret.as_ref(),
|
||||
into)
|
||||
}
|
||||
|
||||
fn bindgen_struct_method(s: &ast::Struct, m: &ast::Method, into: &mut Tokens) {
|
||||
bindgen(&m.function.struct_function_export_name(s.name),
|
||||
m.function.rust_symbol(Some(s.name)),
|
||||
Receiver::StructMethod(s.name, m.mutable, m.function.name),
|
||||
&m.function.arguments,
|
||||
m.function.ret.as_ref(),
|
||||
into)
|
||||
}
|
||||
|
||||
enum Receiver {
|
||||
FreeFunction(syn::Ident),
|
||||
StructFunction(syn::Ident, syn::Ident),
|
||||
StructMethod(syn::Ident, bool, syn::Ident),
|
||||
}
|
||||
|
||||
fn bindgen(export_name: &syn::LitStr,
|
||||
generated_name: syn::Ident,
|
||||
receiver: Receiver,
|
||||
arguments: &[ast::Type],
|
||||
ret_type: Option<&ast::Type>,
|
||||
into: &mut Tokens) {
|
||||
fn bindgen_export(export: &ast::Export, into: &mut Tokens) {
|
||||
let generated_name = export.rust_symbol();
|
||||
let export_name = export.export_name();
|
||||
let mut args = vec![];
|
||||
let mut arg_conversions = vec![];
|
||||
let mut converted_arguments = vec![];
|
||||
let ret = syn::Ident::from("_ret");
|
||||
|
||||
let mut offset = 0;
|
||||
if let Receiver::StructMethod(class, _, _) = receiver {
|
||||
if export.method {
|
||||
let class = export.class.unwrap();
|
||||
args.push(my_quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> });
|
||||
arg_conversions.push(my_quote! {
|
||||
::wasm_bindgen::__rt::assert_not_null(me);
|
||||
@ -220,7 +140,7 @@ fn bindgen(export_name: &syn::LitStr,
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
for (i, ty) in arguments.iter().enumerate() {
|
||||
for (i, ty) in export.function.arguments.iter().enumerate() {
|
||||
let i = i + offset;
|
||||
let ident = syn::Ident::from(format!("arg{}", i));
|
||||
match *ty {
|
||||
@ -288,12 +208,12 @@ fn bindgen(export_name: &syn::LitStr,
|
||||
}
|
||||
let ret_ty;
|
||||
let convert_ret;
|
||||
match ret_type {
|
||||
Some(&ast::Type::String) => {
|
||||
match export.function.ret {
|
||||
Some(ast::Type::String) => {
|
||||
ret_ty = my_quote! { -> *mut String };
|
||||
convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) };
|
||||
}
|
||||
Some(&ast::Type::ByValue(ref t)) => {
|
||||
Some(ast::Type::ByValue(ref t)) => {
|
||||
ret_ty = my_quote! {
|
||||
-> <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
|
||||
};
|
||||
@ -301,9 +221,9 @@ fn bindgen(export_name: &syn::LitStr,
|
||||
<#t as ::wasm_bindgen::convert::WasmBoundary>::into_js(#ret)
|
||||
};
|
||||
}
|
||||
Some(&ast::Type::BorrowedStr) |
|
||||
Some(&ast::Type::ByMutRef(_)) |
|
||||
Some(&ast::Type::ByRef(_)) => {
|
||||
Some(ast::Type::BorrowedStr) |
|
||||
Some(ast::Type::ByMutRef(_)) |
|
||||
Some(ast::Type::ByRef(_)) => {
|
||||
panic!("can't return a borrowed ref");
|
||||
}
|
||||
None => {
|
||||
@ -312,6 +232,19 @@ fn bindgen(export_name: &syn::LitStr,
|
||||
}
|
||||
}
|
||||
|
||||
let name = export.function.name;
|
||||
let receiver = match export.class {
|
||||
Some(_) if export.method => {
|
||||
if export.mutable {
|
||||
my_quote! { me.borrow_mut().#name }
|
||||
} else {
|
||||
my_quote! { me.borrow().#name }
|
||||
}
|
||||
}
|
||||
Some(class) => my_quote! { #class::#name },
|
||||
None => my_quote!{ #name },
|
||||
};
|
||||
|
||||
let tokens = my_quote! {
|
||||
#[export_name = #export_name]
|
||||
#[allow(non_snake_case)]
|
||||
@ -324,65 +257,14 @@ fn bindgen(export_name: &syn::LitStr,
|
||||
tokens.to_tokens(into);
|
||||
}
|
||||
|
||||
impl ToTokens for Receiver {
|
||||
fn to_tokens(&self, tokens: &mut Tokens) {
|
||||
match *self {
|
||||
Receiver::FreeFunction(name) => name.to_tokens(tokens),
|
||||
Receiver::StructFunction(s, name) => {
|
||||
s.to_tokens(tokens);
|
||||
syn::token::Colon2::default().to_tokens(tokens);
|
||||
name.to_tokens(tokens);
|
||||
}
|
||||
Receiver::StructMethod(_, mutable, name) => {
|
||||
(my_quote! { me }).to_tokens(tokens);
|
||||
syn::token::Dot::default().to_tokens(tokens);
|
||||
if mutable {
|
||||
syn::Ident::from("borrow_mut").to_tokens(tokens);
|
||||
} else {
|
||||
syn::Ident::from("borrow").to_tokens(tokens);
|
||||
}
|
||||
tokens.append(TokenTree {
|
||||
span: Span::def_site(),
|
||||
kind: TokenNode::Group(Delimiter::Parenthesis,
|
||||
proc_macro2::TokenStream::empty()),
|
||||
});
|
||||
syn::token::Dot::default().to_tokens(tokens);
|
||||
name.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
|
||||
let import_name = shared::mangled_import_name(
|
||||
None,
|
||||
import.function.wasm_function.name.as_ref(),
|
||||
);
|
||||
bindgen_import_function(&import.function, &import_name, tokens);
|
||||
}
|
||||
|
||||
fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
|
||||
let name = import.name;
|
||||
|
||||
let mut methods = Tokens::new();
|
||||
|
||||
for f in import.functions.iter() {
|
||||
let import_name = shared::mangled_import_name(
|
||||
Some(&import.name.to_string()),
|
||||
f.function.wasm_function.name.as_ref(),
|
||||
);
|
||||
bindgen_import_function(&f.function, &import_name, &mut methods);
|
||||
}
|
||||
|
||||
fn bindgen_imported_type(vis: &syn::Visibility,
|
||||
name: &syn::Ident,
|
||||
tokens: &mut Tokens) {
|
||||
(my_quote! {
|
||||
pub struct #name {
|
||||
#vis struct #name {
|
||||
obj: ::wasm_bindgen::JsValue,
|
||||
}
|
||||
|
||||
impl #name {
|
||||
#methods
|
||||
}
|
||||
|
||||
impl ::wasm_bindgen::convert::WasmBoundary for #name {
|
||||
type Js = <::wasm_bindgen::JsValue as
|
||||
::wasm_bindgen::convert::WasmBoundary>::Js;
|
||||
@ -397,42 +279,50 @@ fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
|
||||
#name { obj: ::wasm_bindgen::JsValue::from_js(js) }
|
||||
}
|
||||
}
|
||||
|
||||
impl ::wasm_bindgen::convert::ToRefWasmBoundary for #name {
|
||||
fn to_js_ref(&self) -> u32 {
|
||||
self.obj.to_js_ref()
|
||||
}
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
}
|
||||
|
||||
fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
import_name: &str,
|
||||
tokens: &mut Tokens) {
|
||||
let vis = &import.rust_vis;
|
||||
let ret = &import.rust_decl.output;
|
||||
let fn_token = &import.rust_decl.fn_token;
|
||||
let arguments = &import.rust_decl.inputs;
|
||||
|
||||
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
|
||||
let mut class_ty = None;
|
||||
let mut is_method = false;
|
||||
let mut class_name = None;
|
||||
match import.kind {
|
||||
ast::ImportKind::Method { ref ty, ref class } => {
|
||||
is_method = true;
|
||||
class_ty = Some(ty);
|
||||
class_name = Some(class);
|
||||
}
|
||||
ast::ImportKind::Static { ref ty, ref class } |
|
||||
ast::ImportKind::JsConstructor { ref ty, ref class } => {
|
||||
class_ty = Some(ty);
|
||||
class_name = Some(class);
|
||||
}
|
||||
ast::ImportKind::Normal => {}
|
||||
}
|
||||
let import_name = shared::mangled_import_name(
|
||||
class_name.map(|s| &**s),
|
||||
import.function.name.as_ref(),
|
||||
);
|
||||
let vis = &import.function.rust_vis;
|
||||
let ret = &import.function.rust_decl.output;
|
||||
let fn_token = &import.function.rust_decl.fn_token;
|
||||
|
||||
let mut abi_argument_names = Vec::new();
|
||||
let mut abi_arguments = Vec::new();
|
||||
let mut arg_conversions = Vec::new();
|
||||
let ret_ident = syn::Ident::from("_ret");
|
||||
|
||||
let inputs = import.rust_decl.inputs.iter().collect::<Vec<_>>();
|
||||
let (is_method, inputs) = match inputs.get(0) {
|
||||
Some(&&syn::FnArg::Captured(_)) => (false, &inputs[..]),
|
||||
Some(_) => (true, &inputs[1..]),
|
||||
None => (false, &inputs[..]),
|
||||
};
|
||||
|
||||
if is_method {
|
||||
let ptr = syn::Ident::from("ptr");
|
||||
abi_argument_names.push(ptr);
|
||||
abi_arguments.push(my_quote! { #ptr: u32 });
|
||||
arg_conversions.push(my_quote! {
|
||||
let #ptr = ::wasm_bindgen::convert::ToRefWasmBoundary::to_js_ref(&self.obj);
|
||||
});
|
||||
}
|
||||
|
||||
let names = inputs
|
||||
let names = import.function.rust_decl.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
match **arg {
|
||||
match *arg {
|
||||
syn::FnArg::Captured(ref c) => c,
|
||||
_ => panic!("arguments cannot be `self` or ignored"),
|
||||
}
|
||||
@ -451,7 +341,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
}
|
||||
});
|
||||
|
||||
for (ty, name) in import.wasm_function.arguments.iter().zip(names) {
|
||||
for (i, (ty, name)) in import.function.arguments.iter().zip(names).enumerate() {
|
||||
match *ty {
|
||||
ast::Type::BorrowedStr => {
|
||||
let ptr = syn::Ident::from(format!("{}_ptr", name));
|
||||
@ -470,19 +360,33 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
abi_arguments.push(my_quote! {
|
||||
#name: <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
|
||||
});
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
|
||||
::into_js(#name);
|
||||
});
|
||||
if i == 0 && is_method {
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
|
||||
::into_js(self);
|
||||
});
|
||||
} else {
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
|
||||
::into_js(#name);
|
||||
});
|
||||
}
|
||||
}
|
||||
ast::Type::ByMutRef(_) => panic!("urgh mut"),
|
||||
ast::Type::ByRef(ref t) => {
|
||||
abi_argument_names.push(name);
|
||||
abi_arguments.push(my_quote! { #name: u32 });
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
|
||||
::to_js_ref(#name);
|
||||
});
|
||||
if i == 0 && is_method {
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
|
||||
::to_js_ref(self);
|
||||
});
|
||||
} else {
|
||||
arg_conversions.push(my_quote! {
|
||||
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
|
||||
::to_js_ref(#name);
|
||||
});
|
||||
}
|
||||
}
|
||||
// TODO: need to test this
|
||||
ast::Type::String => {
|
||||
@ -502,7 +406,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
}
|
||||
let abi_ret;
|
||||
let mut convert_ret;
|
||||
match import.wasm_function.ret {
|
||||
match import.function.ret {
|
||||
Some(ast::Type::ByValue(ref t)) => {
|
||||
abi_ret = my_quote! {
|
||||
<#t as ::wasm_bindgen::convert::WasmBoundary>::Js
|
||||
@ -539,7 +443,7 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
}
|
||||
|
||||
let mut exceptional_ret = my_quote! {};
|
||||
if import.catch {
|
||||
if import.function.opts.catch() {
|
||||
let exn_data = syn::Ident::from("exn_data");
|
||||
let exn_data_ptr = syn::Ident::from("exn_data_ptr");
|
||||
abi_argument_names.push(exn_data_ptr);
|
||||
@ -557,10 +461,24 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
};
|
||||
}
|
||||
|
||||
let name = import.ident;
|
||||
let name = import.function.name;
|
||||
let import_name = syn::Ident::from(import_name);
|
||||
(quote! {
|
||||
#vis #fn_token #name(#arguments) #ret {
|
||||
let attrs = &import.function.rust_attrs;
|
||||
|
||||
let arguments = import.function.rust_decl.inputs
|
||||
.iter()
|
||||
.skip(if is_method { 1 } else { 0 })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let me = if is_method {
|
||||
my_quote! { &self, }
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let invocation = quote! {
|
||||
#(#attrs)*
|
||||
#vis extern #fn_token #name(#me #(#arguments),*) #ret {
|
||||
extern {
|
||||
fn #import_name(#(#abi_arguments),*) -> #abi_ret;
|
||||
}
|
||||
@ -571,5 +489,15 @@ fn bindgen_import_function(import: &ast::ImportFunction,
|
||||
#convert_ret
|
||||
}
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
};
|
||||
|
||||
if let Some(class) = class_ty {
|
||||
(quote! {
|
||||
impl #class {
|
||||
#invocation
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
} else {
|
||||
invocation.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user