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:
Alex Crichton
2018-02-07 16:41:33 -08:00
parent 70614f808e
commit 29771b574c
15 changed files with 1400 additions and 1273 deletions

View File

@ -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);
}
}