diff --git a/crates/wasm-bindgen-cli-support/src/lib.rs b/crates/wasm-bindgen-cli-support/src/lib.rs index 4b835014..28677ecb 100644 --- a/crates/wasm-bindgen-cli-support/src/lib.rs +++ b/crates/wasm-bindgen-cli-support/src/lib.rs @@ -109,6 +109,7 @@ fn extract_program(module: &mut Module) -> shared::Program { structs: Vec::new(), free_functions: Vec::new(), imports: Vec::new(), + imported_structs: Vec::new(), }; let data = match data { Some(data) => data, @@ -126,10 +127,11 @@ fn extract_program(module: &mut Module) -> shared::Program { Ok(f) => f, Err(_) => continue, }; - let shared::Program { structs, free_functions, imports } = p; + let shared::Program { structs, free_functions, imports, imported_structs } = p; ret.structs.extend(structs); ret.free_functions.extend(free_functions); ret.imports.extend(imports); + ret.imported_structs.extend(imported_structs); } data.entries_mut().remove(i); } diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 8ad348e3..dddb8570 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -6,6 +6,7 @@ pub struct Program { pub structs: Vec, pub free_functions: Vec, pub imports: Vec, + pub imported_structs: Vec, } pub struct Function { @@ -16,11 +17,21 @@ pub struct Function { pub struct Import { pub module: String, - pub function: Function, - pub decl: Box, + pub function: ImportFunction, +} + +pub struct ImportFunction { pub ident: syn::Ident, - pub vis: syn::Visibility, - pub attrs: Vec, + pub wasm_function: Function, + pub rust_decl: Box, + pub rust_vis: syn::Visibility, + pub rust_attrs: Vec, +} + +pub struct ImportStruct { + pub module: Option, + pub name: syn::Ident, + pub functions: Vec<(bool, ImportFunction)>, } pub enum Type { @@ -103,23 +114,51 @@ impl Program { }) .expect("must specify `#[wasm_module = ...]` for module to import from"); for item in f.items.iter() { - self.push_foreign_item(&module, item); + let import = self.gen_foreign_item(item, false).0; + self.imports.push(Import { + module: module.clone(), + function: import, + }); } } - pub fn push_foreign_item(&mut self, module: &str, f: &syn::ForeignItem) { + pub fn gen_foreign_item(&mut self, + f: &syn::ForeignItem, + allow_self: bool) -> (ImportFunction, bool) { let f = match *f { syn::ForeignItem::Fn(ref f) => f, _ => panic!("only foreign functions allowed for now, not statics"), }; - self.imports.push(Import { - module: module.to_string(), - attrs: f.attrs.clone(), - vis: f.vis.clone(), - decl: f.decl.clone(), + let (wasm, mutable) = Function::from_decl(f.ident, &f.decl, allow_self); + let is_method = match mutable { + Some(false) => true, + None => false, + Some(true) => { + panic!("mutable self methods not allowed in extern structs"); + } + }; + + (ImportFunction { + rust_attrs: f.attrs.clone(), + rust_vis: f.vis.clone(), + rust_decl: f.decl.clone(), ident: f.ident.clone(), - function: Function::from_decl(f.ident, &f.decl), + wasm_function: wasm, + }, is_method) + } + + pub fn push_extern_class(&mut self, class: &ExternClass) { + let functions = class.functions.iter() + .map(|f| { + let (f, method) = self.gen_foreign_item(f, true); + (method, f) + }) + .collect(); + self.imported_structs.push(ImportStruct { + module: class.module.as_ref().map(|s| s.value()), + name: class.name, + functions, }); } @@ -128,7 +167,10 @@ impl Program { structs: self.structs.iter().map(|s| s.shared()).collect(), free_functions: self.free_functions.iter().map(|s| s.shared()).collect(), imports: self.imports.iter() - .map(|i| (i.module.clone(), i.function.shared())) + .map(|i| (i.module.clone(), i.function.wasm_function.shared())) + .collect(), + imported_structs: self.imported_structs.iter() + .map(|i| i.shared()) .collect(), } } @@ -153,10 +195,12 @@ impl Function { panic!("can only bindgen Rust ABI functions") } - Function::from_decl(input.ident, &input.decl) + Function::from_decl(input.ident, &input.decl, false).0 } - pub fn from_decl(name: syn::Ident, decl: &syn::FnDecl) -> Function { + pub fn from_decl(name: syn::Ident, + decl: &syn::FnDecl, + allow_self: bool) -> (Function, Option) { if decl.variadic.is_some() { panic!("can't bindgen variadic functions") } @@ -164,10 +208,19 @@ impl Function { panic!("can't bindgen functions with lifetime or type parameters") } + let mut mutable = None; let arguments = decl.inputs.iter() - .map(|arg| { + .filter_map(|arg| { match *arg { - syn::FnArg::Captured(ref c) => c, + syn::FnArg::Captured(ref c) => Some(c), + syn::FnArg::SelfValue(_) => { + panic!("by-value `self` not yet supported"); + } + syn::FnArg::SelfRef(ref a) if allow_self => { + assert!(mutable.is_none()); + mutable = Some(a.mutability.is_some()); + None + } _ => panic!("arguments cannot be `self` or ignored"), } }) @@ -179,7 +232,7 @@ impl Function { syn::ReturnType::Type(_, ref t) => Some(Type::from(t)), }; - Function { name, arguments, ret } + (Function { name, arguments, ret }, mutable) } pub fn free_function_export_name(&self) -> syn::LitStr { @@ -346,38 +399,9 @@ impl Struct { panic!("can only bindgen safe functions"); } - if method.sig.decl.variadic.is_some() { - panic!("can't bindgen variadic functions") - } - if method.sig.decl.generics.params.len() > 0 { - panic!("can't bindgen functions with lifetime or type parameters") - } - - let mut mutable = None; - let arguments = method.sig.decl.inputs.iter() - .filter_map(|arg| { - match *arg { - syn::FnArg::Captured(ref c) => Some(c), - syn::FnArg::SelfValue(_) => { - panic!("by-value `self` not yet supported"); - } - syn::FnArg::SelfRef(ref a) => { - assert!(mutable.is_none()); - mutable = Some(a.mutability.is_some()); - None - } - _ => panic!("arguments cannot be `self` or ignored"), - } - }) - .map(|arg| Type::from(&arg.ty)) - .collect::>(); - - let ret = match method.sig.decl.output { - syn::ReturnType::Default => None, - syn::ReturnType::Type(_, ref t) => Some(Type::from(t)), - }; - - let function = Function { name: method.sig.ident, arguments, ret }; + let (function, mutable) = Function::from_decl(method.sig.ident, + &method.sig.decl, + true); match mutable { Some(mutable) => { self.methods.push(Method { mutable, function }); @@ -405,3 +429,77 @@ impl Method { } } } + +impl ImportStruct { + fn shared(&self) -> shared::ImportStruct { + shared::ImportStruct { + module: self.module.clone(), + name: self.name.to_string(), + functions: self.functions.iter() + .map(|&(b, ref f)| (b, f.wasm_function.shared())) + .collect(), + } + } +} + +pub struct File { + pub items: Vec, +} + +impl syn::synom::Synom for File { + named!(parse -> Self, map!(many0!(syn!(MyItem)), |items| File { items })); +} + +pub enum MyItem { + Normal(syn::Item), + ExternClass(ExternClass), +} + +impl syn::synom::Synom for MyItem { + named!(parse -> Self, alt!( + syn!(syn::Item) => { MyItem::Normal } + | + syn!(ExternClass) => { MyItem::ExternClass } + )); +} + +pub struct ExternClass { + name: syn::Ident, + module: Option, + functions: Vec, +} + +impl syn::synom::Synom for ExternClass { + named!(parse -> Self, do_parse!( + module: option!(do_parse!( + punct!(#) >> + name: brackets!(do_parse!( + call!(term, "wasm_module") >> + punct!(=) >> + val: syn!(syn::LitStr) >> + (val) + )) >> + (name.1) + )) >> + keyword!(extern) >> + keyword!(struct) >> + name: syn!(syn::Ident) >> + items: braces!(many0!(syn!(syn::ForeignItem))) >> + (ExternClass { + name, + module, + functions: items.1, + }) + )); +} + +fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) + -> syn::synom::PResult<'a, ()> +{ + if let Some((_span, term, next)) = cursor.term() { + if term.as_str() == name { + return Ok(((), next)) + } + } + syn::parse_error() +} diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 5c58392c..a7a26953 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -1,5 +1,6 @@ #![feature(proc_macro)] +#[macro_use] extern crate syn; #[macro_use] extern crate quote; @@ -26,7 +27,7 @@ macro_rules! my_quote { #[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::(input) + let file = syn::parse::(input) .expect("expected a set of valid Rust items"); let mut ret = Tokens::new(); @@ -35,12 +36,21 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream { structs: Vec::new(), free_functions: Vec::new(), imports: Vec::new(), + imported_structs: Vec::new(), }; // Translate all input items into our own internal representation (the `ast` // module). We'll be panicking here on anything that we can't process 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); @@ -76,6 +86,9 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream { for i in program.imports.iter() { bindgen_import(i, &mut ret); } + for i in program.imported_structs.iter() { + bindgen_imported_struct(i, &mut ret); + } // 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 @@ -416,18 +429,53 @@ impl ToTokens for Receiver { } fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { - let vis = &import.vis; - let ret = &import.decl.output; - let name = &import.ident; - let fn_token = &import.decl.fn_token; - let arguments = &import.decl.inputs; + bindgen_import_function(&import.function, + &import.function.ident.to_string(), + Some(&import.module), + tokens); +} + +fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) { + let name = import.name; + (my_quote! { + pub struct #name { + obj: ::wasm_bindgen::JsObject, + } + }).to_tokens(tokens); + + let mut methods = Tokens::new(); + + for &(_is_method, ref f) in import.functions.iter() { + let import_name = format!("__wbg_{}_{}", name, f.ident); + + bindgen_import_function(f, + &import_name, + import.module.as_ref().map(|s| &**s), + &mut methods); + } + + (my_quote! { + impl #name { + #methods + } + }).to_tokens(tokens); +} + +fn bindgen_import_function(import: &ast::ImportFunction, + import_name: &str, + _import_module: Option<&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; 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 names = import.decl.inputs + let names = import.rust_decl.inputs .iter() .map(|arg| { match *arg { @@ -449,7 +497,7 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { } }); - for (ty, name) in import.function.arguments.iter().zip(names) { + for (ty, name) in import.wasm_function.arguments.iter().zip(names) { match *ty { ast::Type::Integer(i) => { abi_argument_names.push(name); @@ -507,7 +555,7 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { } let abi_ret; let convert_ret; - match import.function.ret { + match import.wasm_function.ret { Some(ast::Type::Integer(i)) => { abi_ret = my_quote! { #i }; convert_ret = my_quote! { #ret_ident }; @@ -542,14 +590,16 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { } } + let name = import.ident; + let import_name = syn::Ident::from(import_name); (quote! { #vis #fn_token #name(#arguments) #ret { extern { - fn #name(#(#abi_arguments),*) -> #abi_ret; + fn #import_name(#(#abi_arguments),*) -> #abi_ret; } unsafe { #(#arg_conversions)* - let #ret_ident = #name(#(#abi_argument_names),*); + let #ret_ident = #import_name(#(#abi_argument_names),*); #convert_ret } } diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index f8e13e53..38aa1cd4 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -6,6 +6,7 @@ pub struct Program { pub structs: Vec, pub free_functions: Vec, pub imports: Vec<(String, Function)>, + pub imported_structs: Vec, } #[derive(Serialize, Deserialize)] @@ -15,6 +16,13 @@ pub struct Struct { pub methods: Vec, } +#[derive(Serialize, Deserialize)] +pub struct ImportStruct { + pub module: Option, + pub name: String, + pub functions: Vec<(bool, Function)>, +} + #[derive(Serialize, Deserialize)] pub struct Method { pub mutable: bool, diff --git a/tests/import-class.rs b/tests/import-class.rs new file mode 100644 index 00000000..a23b174b --- /dev/null +++ b/tests/import-class.rs @@ -0,0 +1,31 @@ +extern crate test_support; + +#[test] +fn simple() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + wasm_bindgen! { + extern struct Math { + fn random() -> f64; + } + + pub fn get_random() -> f64 { + Math::random() + } + } + "#) + .file("test.ts", r#" + import * as wasm from "./out"; + + export function test() { + wasm.get_random(); + } + "#) + .test(); +}