diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs new file mode 100644 index 00000000..37c6e44e --- /dev/null +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -0,0 +1,159 @@ +use shared; + +#[derive(Default)] +pub struct Js { + pub expose_global_memory: bool, + pub expose_global_exports: bool, + pub exports: Vec<(String, String)>, + pub nodejs: bool, +} + +impl Js { + + pub fn generate_program(&mut self, program: &shared::Program) { + for f in program.free_functions.iter() { + self.generate_free_function(f); + } + } + + pub fn generate_free_function(&mut self, func: &shared::Function) { + let simple = func.arguments.iter().all(|t| t.is_number()) && + func.ret.as_ref().map(|t| t.is_number()).unwrap_or(true); + + if simple { + self.exports.push(( + func.name.clone(), + format!("obj.instance.exports.{}", func.name), + )); + return + } + + let mut dst = format!("function {}(", func.name); + let mut passed_args = String::new(); + let mut arg_conversions = String::new(); + let mut destructors = String::new(); + + for (i, arg) in func.arguments.iter().enumerate() { + let name = format!("arg{}", i); + if i > 0 { + dst.push_str(", "); + } + dst.push_str(&name); + + let mut pass = |arg: &str| { + if passed_args.len() > 0 { + passed_args.push_str(", "); + } + passed_args.push_str(arg); + }; + match *arg { + shared::Type::Number => pass(&name), + shared::Type::BorrowedStr | + shared::Type::String => { + if self.nodejs { + arg_conversions.push_str(&format!("\ + const buf{i} = Buffer.from({arg}); + const len{i} = buf{i}.length; + const ptr{i} = exports.__wbindgen_malloc(len{i}); + let memory{i} = new Uint8Array(memory.buffer); + buf{i}.copy(memory{i}, ptr{i}); + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + pass(&format!("len{}", i)); + if let shared::Type::BorrowedStr = *arg { + destructors.push_str(&format!("\n\ + exports.__wbindgen_free(ptr{i}, len{i});\n\ + ", i = i)); + } + } else { + panic!("strings not implemented for browser"); + } + } + shared::Type::ByRef(_) | + shared::Type::ByMutRef(_) => { + arg_conversions.push_str(&format!("\ + const ptr{i} = {arg}.__wasmPtr; + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + } + shared::Type::ByValue(_) => { + arg_conversions.push_str(&format!("\ + const ptr{i} = {arg}.__wasmPtr; + {arg}.__wasmPtr = 0; + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + } + } + } + let convert_ret = match func.ret { + None | + Some(shared::Type::Number) => format!("return ret;"), + Some(shared::Type::BorrowedStr) | + Some(shared::Type::ByMutRef(_)) | + Some(shared::Type::ByRef(_)) => panic!(), + Some(shared::Type::ByValue(ref name)) => { + format!("\ + return {name}.__wasmWrap(ret); + ", name = name) + } + Some(shared::Type::String) => { + if self.nodejs { + format!("\ + const mem = new Uint8Array(memory.buffer); + const ptr = exports.__wbindgen_boxed_str_ptr(ret); + const len = exports.__wbindgen_boxed_str_len(ret); + const buf = Buffer.from(mem.slice(ptr, ptr + len)); + const realRet = buf.toString(); + exports.__wbindgen_boxed_str_free(ret); + return realRet; + ") + } else { + panic!("strings not implemented for browser"); + } + } + }; + dst.push_str(") {\n "); + dst.push_str(&arg_conversions); + dst.push_str(&format!("\ + try {{ + const ret = exports.{f}({passed}); + {convert_ret} + }} finally {{ + {destructors} + }} + ", f = func.name, passed = passed_args, destructors = destructors, + convert_ret = convert_ret)); + dst.push_str("};"); + + self.exports.push((func.name.clone(), dst)); + } + + pub fn to_string(&self) -> String { + let mut globals = String::new(); + if self.expose_global_memory { + globals.push_str("const memory = obj.instance.exports.memory;\n"); + } + if self.expose_global_exports { + globals.push_str("const exports = obj.instance.exports;\n"); + } + + let mut exports = String::new(); + for &(ref name, ref body) in self.exports.iter() { + exports.push_str("obj."); + exports.push_str(name); + exports.push_str(" = "); + exports.push_str(body); + exports.push_str(";\n"); + } + format!(" + const function xform(obj) {{ + {} + {} + return obj; + }} + export const function instantiate(bytes, imports) {{ + return WebAssembly.instantiate(bytes, imports).then(xform); + }} + ", globals, exports) + } +} diff --git a/crates/wasm-bindgen-cli-support/src/lib.rs b/crates/wasm-bindgen-cli-support/src/lib.rs index 820d013f..4e6d8cb2 100644 --- a/crates/wasm-bindgen-cli-support/src/lib.rs +++ b/crates/wasm-bindgen-cli-support/src/lib.rs @@ -11,6 +11,8 @@ use std::io::Write; use failure::{Error, ResultExt}; use parity_wasm::elements::*; +mod js; + pub struct Bindgen { path: Option, nodejs: bool, @@ -18,7 +20,7 @@ pub struct Bindgen { pub struct Object { module: Module, - items: Vec, + program: shared::Program, nodejs: bool, } @@ -48,10 +50,10 @@ impl Bindgen { let mut module = parity_wasm::deserialize_file(input).map_err(|e| { format_err!("{:?}", e) })?; - let items = extract_items(&mut module); + let program = extract_program(&mut module); Ok(Object { module, - items, + program, nodejs: self.nodejs, }) } @@ -85,117 +87,13 @@ impl Object { } pub fn generate_js(&self) -> String { - let mut set_exports = String::new(); - for func in self.items.iter() { - self.add_export(func, &mut set_exports); - } - - format!("\ -const xform = (obj) => {{ - const exports = obj.instance.exports; - const memory = obj.instance.exports.memory; - {set_exports} - return obj; -}}; -export const instantiate = (bytes, imports) => {{ - return WebAssembly.instantiate(bytes, imports).then(xform); -}}; -", - set_exports = set_exports, -) - } - - fn add_export(&self, func: &shared::Function, dst: &mut String) { - let simple = func.arguments.iter().all(|t| t.is_number()) && - func.ret.as_ref().map(|t| t.is_number()).unwrap_or(true); - - if simple { - dst.push_str(&format!("\n\ - obj.{f} = obj.instance.exports.{f};\ - ", f = func.name)); - return - } - - dst.push_str(&format!("\n obj.{f} = function {f}(", - f = func.name)); - let mut passed_args = String::new(); - let mut arg_conversions = String::new(); - let mut destructors = String::new(); - - for (i, arg) in func.arguments.iter().enumerate() { - let name = format!("arg{}", i); - if i > 0 { - dst.push_str(", "); - } - dst.push_str(&name); - - let mut pass = |arg: &str| { - if passed_args.len() > 0 { - passed_args.push_str(", "); - } - passed_args.push_str(arg); - }; - match *arg { - shared::Type::Number => pass(&name), - shared::Type::BorrowedStr | - shared::Type::String => { - if self.nodejs { - arg_conversions.push_str(&format!("\ - const buf{i} = Buffer.from({arg}); - const len{i} = buf{i}.length; - const ptr{i} = exports.__wbindgen_malloc(len{i}); - let memory{i} = new Uint8Array(memory.buffer); - buf{i}.copy(memory{i}, ptr{i}); - ", i = i, arg = name)); - pass(&format!("ptr{}", i)); - pass(&format!("len{}", i)); - if let shared::Type::BorrowedStr = *arg { - destructors.push_str(&format!("\n\ - exports.__wbindgen_free(ptr{i}, len{i});\n\ - ", i = i)); - } - } else { - panic!("strings not implemented for browser"); - } - } - } - } - let convert_ret = match func.ret { - None | - Some(shared::Type::Number) => format!("return ret;"), - Some(shared::Type::BorrowedStr) => panic!(), - Some(shared::Type::String) => { - if self.nodejs { - format!("\ - const mem = new Uint8Array(memory.buffer); - const ptr = exports.__wbindgen_boxed_str_ptr(ret); - const len = exports.__wbindgen_boxed_str_len(ret); - const buf = Buffer.from(mem.slice(ptr, ptr + len)); - const realRet = buf.toString(); - exports.__wbindgen_boxed_str_free(ret); - return realRet; - ") - } else { - panic!("strings not implemented for browser"); - } - } - }; - dst.push_str(") {\n "); - dst.push_str(&arg_conversions); - dst.push_str(&format!("\ - try {{ - const ret = exports.{f}({passed}); - {convert_ret} - }} finally {{ - {destructors} - }} - ", f = func.name, passed = passed_args, destructors = destructors, - convert_ret = convert_ret)); - dst.push_str("};"); + let mut js = js::Js::default(); + js.generate_program(&self.program); + js.to_string() } } -fn extract_items(module: &mut Module) -> Vec { +fn extract_program(module: &mut Module) -> shared::Program { let data = module.sections_mut() .iter_mut() .filter_map(|s| { @@ -205,12 +103,16 @@ fn extract_items(module: &mut Module) -> Vec { } }) .next(); + + let mut ret = shared::Program { + structs: Vec::new(), + free_functions: Vec::new(), + }; let data = match data { Some(data) => data, - None => return Vec::new(), + None => return ret, }; - let mut ret = Vec::new(); for i in (0..data.entries().len()).rev() { { let value = data.entries()[i].value(); @@ -218,11 +120,13 @@ fn extract_items(module: &mut Module) -> Vec { continue } let json = &value[4..]; - let func: shared::Function = match serde_json::from_slice(json) { + let p = match serde_json::from_slice(json) { Ok(f) => f, Err(_) => continue, }; - ret.push(func); + let shared::Program { structs, free_functions } = p; + ret.structs.extend(structs); + ret.free_functions.extend(free_functions); } data.entries_mut().remove(i); } diff --git a/crates/wasm-bindgen-cli/src/main.rs b/crates/wasm-bindgen-cli/src/main.rs index 61c4b083..cabe1238 100644 --- a/crates/wasm-bindgen-cli/src/main.rs +++ b/crates/wasm-bindgen-cli/src/main.rs @@ -1,4 +1,4 @@ -extern crate wasm_bindgen; +extern crate wasm_bindgen_cli_support; #[macro_use] extern crate serde_derive; extern crate docopt; @@ -6,7 +6,7 @@ extern crate docopt; use std::path::PathBuf; use docopt::Docopt; -use wasm_bindgen::Bindgen; +use wasm_bindgen_cli_support::Bindgen; const USAGE: &'static str = " Generating JS bindings for a wasm file diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 672f4197..3eea2fed 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -1,9 +1,12 @@ use proc_macro2::Literal; -use quote::{ToTokens, Tokens}; -use serde_json; use syn; use wasm_bindgen_shared as shared; +pub struct Program { + pub structs: Vec, + pub free_functions: Vec, +} + pub struct Function { pub name: syn::Ident, pub arguments: Vec, @@ -14,6 +17,58 @@ pub enum Type { Integer(syn::Ident), BorrowedStr, String, + ByValue(syn::Ident), + ByRef(syn::Ident), + ByMutRef(syn::Ident), +} + +pub struct Struct { + pub name: syn::Ident, + pub ctor: Option, + pub methods: Vec, + pub functions: Vec, +} + +pub struct Method { + pub mutable: bool, + pub function: Function, +} + +impl Program { + pub fn push_impl(&mut self, item: &syn::ItemImpl) { + match item.defaultness { + syn::Defaultness::Final => {} + _ => panic!("default impls are not supported"), + } + match item.unsafety { + syn::Unsafety::Normal => {} + _ => panic!("unsafe impls are not supported"), + } + if item.trait_.is_some() { + panic!("trait impls are not supported"); + } + if item.generics.params.len() > 0 { + panic!("generic impls aren't supported"); + } + let name = match Type::from(&item.self_ty) { + Type::ByValue(ident) => ident, + _ => panic!("unsupported self type in impl"), + }; + let dst = self.structs + .iter_mut() + .find(|s| s.name == name) + .expect(&format!("failed to definition of struct for impl of `{}`", name)); + for item in item.items.iter() { + dst.push_item(item); + } + } + + pub fn shared(&self) -> shared::Program { + shared::Program { + structs: self.structs.iter().map(|s| s.shared()).collect(), + free_functions: self.free_functions.iter().map(|s| s.shared()).collect(), + } + } } impl Function { @@ -36,6 +91,7 @@ impl Function { if !input.abi.is_none() { panic!("can only bindgen Rust ABI functions") } + if input.decl.variadic { panic!("can't bindgen variadic functions") } @@ -59,11 +115,7 @@ impl Function { syn::ReturnType::Type(ref t, _) => Some(Type::from(t)), }; - Function { - name: input.ident, - arguments, - ret, - } + Function { name: input.ident, arguments, ret } } pub fn export_name(&self) -> syn::Lit { @@ -79,18 +131,6 @@ impl Function { syn::Ident::from(generated_name) } - pub fn generated_static_name(&self) -> syn::Ident { - let generated_name = format!("__WASM_BINDGEN_GENERATED_{}", - self.name.sym.as_str()); - syn::Ident::from(generated_name) - } - - pub fn generate_static(&self) -> Vec { - let mut prefix = String::from("wbg:"); - prefix.push_str(&serde_json::to_string(&self.shared()).unwrap()); - prefix.into_bytes() - } - fn shared(&self) -> shared::Function { shared::Function { name: self.name.sym.as_str().to_string(), @@ -120,16 +160,22 @@ impl Type { if r.lifetime.is_some() { panic!("can't have lifetimes on references yet"); } - match r.ty.mutability { - syn::Mutability::Immutable => {} - _ => panic!("can't have mutable references yet"), - } + let mutable = match r.ty.mutability { + syn::Mutability::Immutable => false, + syn::Mutability::Mutable(_) => true, + }; match r.ty.ty { syn::Type::Path(syn::TypePath { qself: None, ref path }) => { let ident = extract_path_ident(path); match ident.sym.as_str() { - "str" => Type::BorrowedStr, - _ => panic!("unsupported reference type"), + "str" => { + if mutable { + panic!("mutable strings not allowed"); + } + Type::BorrowedStr + } + _ if mutable => Type::ByMutRef(ident), + _ => Type::ByRef(ident), } } _ => panic!("unsupported reference type"), @@ -151,7 +197,7 @@ impl Type { Type::Integer(ident) } "String" => Type::String, - s => panic!("unsupported type: {}", s), + _ => Type::ByValue(ident), } } _ => panic!("unsupported type"), @@ -163,21 +209,108 @@ impl Type { Type::Integer(_) => shared::Type::Number, Type::BorrowedStr => shared::Type::BorrowedStr, Type::String => shared::Type::String, + Type::ByValue(n) => shared::Type::ByValue(n.to_string()), + Type::ByRef(n) => shared::Type::ByRef(n.to_string()), + Type::ByMutRef(n) => shared::Type::ByMutRef(n.to_string()), } } } -impl ToTokens for Type { - fn to_tokens(&self, tokens: &mut Tokens) { - match *self { - Type::Integer(i) => i.to_tokens(tokens), - Type::String => { - syn::Ident::from("String").to_tokens(tokens); +impl Struct { + pub fn from(s: &syn::ItemStruct) -> Struct { + Struct { + name: s.ident, + ctor: None, + methods: Vec::new(), + functions: Vec::new(), + } + } + + pub fn push_item(&mut self, item: &syn::ImplItem) { + let method = match *item { + syn::ImplItem::Const(_) => panic!("const definitions aren't supported"), + syn::ImplItem::Type(_) => panic!("type definitions in impls aren't supported"), + syn::ImplItem::Method(ref m) => m, + syn::ImplItem::Macro(_) => panic!("macros in impls aren't supported"), + }; + match method.vis { + syn::Visibility::Public(_) => {} + _ => return, + } + match method.defaultness { + syn::Defaultness::Final => {} + _ => panic!("default methods are not supported"), + } + match method.sig.constness { + syn::Constness::NotConst => {} + _ => panic!("can only bindgen non-const functions"), + } + match method.sig.unsafety { + syn::Unsafety::Normal => {} + _ => panic!("can only bindgen safe functions"), + } + + if method.sig.decl.variadic { + 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() + .map(|i| i.into_item()) + .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(match a.mutbl { + syn::Mutability::Mutable(_) => true, + syn::Mutability::Immutable => false, + }); + 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 }; + match mutable { + Some(mutable) => { + self.methods.push(Method { mutable, function }); } - Type::BorrowedStr => { - ::default().to_tokens(tokens); - syn::Ident::from("str").to_tokens(tokens); + None => { + self.functions.push(function); } } } + + pub fn shared(&self) -> shared::Struct { + shared::Struct { + name: self.name.to_string(), + ctor: self.ctor.as_ref().unwrap().shared(), + functions: self.functions.iter().map(|f| f.shared()).collect(), + methods: self.methods.iter().map(|f| f.shared()).collect(), + } + } +} + +impl Method { + pub fn shared(&self) -> shared::Method { + shared::Method { + mutable: self.mutable, + function: self.function.shared(), + } + } } diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 44db7835..e45222a4 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -1,7 +1,6 @@ #![feature(proc_macro)] extern crate syn; -#[macro_use] extern crate synom; #[macro_use] extern crate quote; @@ -28,25 +27,63 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream { let mut ret = Tokens::new(); + let mut program = ast::Program { + structs: Vec::new(), + free_functions: Vec::new(), + }; + for item in file.items.iter() { item.to_tokens(&mut ret); match *item { - syn::Item::Fn(ref f) => bindgen_fn(f, &mut ret), + syn::Item::Fn(ref f) => { + program.free_functions.push(ast::Function::from(f)); + } + syn::Item::Struct(ref s) => { + 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 s) => program.push_impl(s), _ => panic!("unexpected item in bindgen macro"), } } + for function in program.free_functions.iter() { + bindgen_fn(function, &mut ret); + } + for s in program.structs.iter() { + bindgen_struct(s, &mut ret); + } + + static CNT: AtomicUsize = ATOMIC_USIZE_INIT; + let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}", + CNT.fetch_add(1, Ordering::SeqCst)); + let mut generated_static = String::from("wbg:"); + generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap()); + let generated_static_value = syn::Lit { + value: syn::LitKind::Other(Literal::byte_string(generated_static.as_bytes())), + span: Default::default(), + }; + let generated_static_length = generated_static.len(); + + (quote! { + #[no_mangle] + #[allow(non_upper_case_globals)] + pub static #generated_static_name: [u8; #generated_static_length] = + *#generated_static_value; + }).to_tokens(&mut ret); + ret.into() } -fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) { - let function = ast::Function::from(input); - +fn bindgen_fn(function: &ast::Function, into: &mut Tokens) { let export_name = function.export_name(); let generated_name = function.rust_symbol(); let mut args = vec![]; let mut arg_conversions = vec![]; - let real_name = &input.ident; + let real_name = &function.name; let mut converted_arguments = vec![]; let ret = syn::Ident::from("_ret"); @@ -85,6 +122,32 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) { }; }); } + ast::Type::ByValue(name) => { + args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(quote! { + assert!(!#ident.is_null()); + let #ident = unsafe { + (*#ident).borrow_mut(); + Box::from_raw(#ident).into_inner() + }; + }); + } + ast::Type::ByRef(name) => { + args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(quote! { + assert!(!#ident.is_null()); + let #ident = unsafe { (*#ident).borrow() }; + let #ident = &*#ident; + }); + } + ast::Type::ByMutRef(name) => { + args.push(quote! { #ident: *mut ::std::cell::RefCell<#name> }); + arg_conversions.push(quote! { + assert!(!#ident.is_null()); + let mut #ident = unsafe { (*#ident).borrow_mut() }; + let #ident = &mut *#ident; + }); + } } converted_arguments.push(quote! { #ident }); } @@ -96,25 +159,25 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) { convert_ret = quote! { #ret }; } Some(ast::Type::BorrowedStr) => panic!("can't return a borrowed string"), + Some(ast::Type::ByRef(_)) => panic!("can't return a borrowed ref"), + Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"), Some(ast::Type::String) => { boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst); ret_ty = quote! { -> *mut String }; convert_ret = quote! { Box::into_raw(Box::new(#ret)) }; } + Some(ast::Type::ByValue(name)) => { + ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> }; + convert_ret = quote! { + Box::into_raw(Box::new(::std::cell::RefCell<#ret>)) + }; + } None => { ret_ty = quote! {}; convert_ret = quote! {}; } } - let generated_static_name = function.generated_static_name(); - let generated_static = function.generate_static(); - let generated_static_value = syn::Lit { - value: syn::LitKind::Other(Literal::byte_string(&generated_static)), - span: Default::default(), - }; - let generated_static_length = generated_static.len(); - let malloc = if malloc { quote! { #[no_mangle] @@ -161,11 +224,6 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) { #malloc #boxed_str - #[no_mangle] - #[allow(non_upper_case_globals)] - pub static #generated_static_name: [u8; #generated_static_length] = - *#generated_static_value; - #[no_mangle] #[export_name = #export_name] pub extern fn #generated_name(#(#args),*) #ret_ty { @@ -177,3 +235,9 @@ fn bindgen_fn(input: &syn::ItemFn, into: &mut Tokens) { // println!("{}", tokens); tokens.to_tokens(into); } + +fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) { + if s.ctor.is_none() { + panic!("struct `{}` needs a `new` function to construct it", s.name); + } +} diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 2f2e9f01..6adb10c8 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -1,6 +1,26 @@ #[macro_use] extern crate serde_derive; +#[derive(Serialize, Deserialize)] +pub struct Program { + pub structs: Vec, + pub free_functions: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Struct { + pub name: String, + pub ctor: Function, + pub functions: Vec, + pub methods: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Method { + pub mutable: bool, + pub function: Function, +} + #[derive(Serialize, Deserialize)] pub struct Function { pub name: String, @@ -13,6 +33,9 @@ pub enum Type { Number, BorrowedStr, String, + ByValue(String), + ByRef(String), + ByMutRef(String), } impl Type { diff --git a/tests/classes.rs b/tests/classes.rs new file mode 100644 index 00000000..24671a3e --- /dev/null +++ b/tests/classes.rs @@ -0,0 +1,50 @@ +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! { + pub struct Foo { + contents: u32, + } + + impl Foo { + pub fn new() -> Foo { + Foo::with_contents(0) + } + + pub fn with_contents(a: u32) -> Foo { + Foo::with_contents(a) + } + + pub fn add(&mut self, amt: u32) -> u32 { + self.contents += amt; + self.contents + } + } + } + "#) + .file("test.js", r#" + import * as assert from "assert"; + + export function test(wasm) { + const r = new wasm.Foo(); + assert.strictEqual(r.add(0), 0); + assert.strictEqual(r.add(1), 1); + assert.strictEqual(r.add(1), 2); + + const r2 = wasm.Foo.with_contents(10); + assert.strictEqual(r.add(1), 11); + assert.strictEqual(r.add(2), 13); + assert.strictEqual(r.add(3), 16); + } + "#) + .test(); +}