Classes are now working!

This commit is contained in:
Alex Crichton 2017-12-18 14:31:01 -08:00
parent 7c510a8a7e
commit 6593b5ef69
8 changed files with 396 additions and 91 deletions

View File

@ -8,3 +8,6 @@ wasm-bindgen-macro = { path = "crates/wasm-bindgen-macro" }
[dev-dependencies] [dev-dependencies]
test-support = { path = "crates/test-support" } test-support = { path = "crates/test-support" }
[workspace]
members = ["crates/wasm-bindgen-cli"]

View File

@ -9,6 +9,9 @@ use std::sync::atomic::*;
use std::sync::{Once, ONCE_INIT}; use std::sync::{Once, ONCE_INIT};
use std::time::Instant; use std::time::Instant;
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
pub struct Project { pub struct Project {
files: Vec<(String, String)>, files: Vec<(String, String)>,
} }
@ -25,10 +28,12 @@ pub fn project() -> Project {
files: vec![ files: vec![
("Cargo.toml".to_string(), format!(r#" ("Cargo.toml".to_string(), format!(r#"
[package] [package]
name = "test" name = "test{}"
version = "0.0.1" version = "0.0.1"
authors = [] authors = []
[workspace]
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
@ -37,7 +42,7 @@ pub fn project() -> Project {
[profile.dev] [profile.dev]
opt-level = 2 # TODO: decrease when upstream is not buggy opt-level = 2 # TODO: decrease when upstream is not buggy
"#, dir.display())), "#, IDX.with(|x| *x), dir.display())),
("Cargo.lock".to_string(), lockfile), ("Cargo.lock".to_string(), lockfile),
@ -60,8 +65,6 @@ pub fn project() -> Project {
} }
pub fn root() -> PathBuf { pub fn root() -> PathBuf {
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
let idx = IDX.with(|x| *x); let idx = IDX.with(|x| *x);
let mut me = env::current_exe().unwrap(); let mut me = env::current_exe().unwrap();
@ -123,7 +126,8 @@ impl Project {
.env("CARGO_TARGET_DIR", &target_dir); .env("CARGO_TARGET_DIR", &target_dir);
run(&mut cmd, "cargo"); run(&mut cmd, "cargo");
let mut out = target_dir.join("wasm32-unknown-unknown/debug/test.wasm"); let idx = IDX.with(|x| *x);
let mut out = target_dir.join(&format!("wasm32-unknown-unknown/debug/test{}.wasm", idx));
if Command::new("wasm-gc").output().is_ok() { if Command::new("wasm-gc").output().is_ok() {
let tmp = out; let tmp = out;
out = tmp.with_extension("gc.wasm"); out = tmp.with_extension("gc.wasm");

View File

@ -4,7 +4,11 @@ use shared;
pub struct Js { pub struct Js {
pub expose_global_memory: bool, pub expose_global_memory: bool,
pub expose_global_exports: bool, pub expose_global_exports: bool,
pub expose_get_string_from_wasm: bool,
pub expose_pass_string_to_wasm: bool,
pub expose_token: bool,
pub exports: Vec<(String, String)>, pub exports: Vec<(String, String)>,
pub classes: Vec<String>,
pub nodejs: bool, pub nodejs: bool,
} }
@ -14,6 +18,9 @@ impl Js {
for f in program.free_functions.iter() { for f in program.free_functions.iter() {
self.generate_free_function(f); self.generate_free_function(f);
} }
for s in program.structs.iter() {
self.generate_struct(s);
}
} }
pub fn generate_free_function(&mut self, func: &shared::Function) { pub fn generate_free_function(&mut self, func: &shared::Function) {
@ -28,12 +35,76 @@ impl Js {
return return
} }
let mut dst = format!("function {}(", func.name); let ret = self.generate_function(&format!("function {}", func.name),
&func.name,
false,
&func.arguments,
func.ret.as_ref());
self.exports.push((func.name.clone(), ret));
}
pub fn generate_struct(&mut self, s: &shared::Struct) {
let mut dst = String::new();
self.expose_token = true;
self.expose_global_exports = true;
dst.push_str(&format!("
class {} {{
constructor(ptr, sym) {{
_checkToken(sym);
this.__wasmPtr = ptr;
}}
free() {{
const ptr = this.__wasmPtr;
this.__wasmPtr = 0;
exports.{}(ptr);
}}
", s.name, s.free_function()));
for function in s.functions.iter() {
let f = self.generate_function(
&format!("static {}", function.name),
&function.struct_function_export_name(&s.name),
false,
&function.arguments,
function.ret.as_ref(),
);
dst.push_str(&f);
dst.push_str("\n");
}
for method in s.methods.iter() {
let f = self.generate_function(
&format!("{}", method.function.name),
&method.function.struct_function_export_name(&s.name),
true,
&method.function.arguments,
method.function.ret.as_ref(),
);
dst.push_str(&f);
dst.push_str("\n");
}
dst.push_str("}\n");
self.classes.push(dst);
self.exports.push((s.name.clone(), s.name.clone()));
}
fn generate_function(&mut self,
name: &str,
wasm_name: &str,
is_method: bool,
arguments: &[shared::Type],
ret: Option<&shared::Type>) -> String {
let mut dst = format!("{}(", name);
let mut passed_args = String::new(); let mut passed_args = String::new();
let mut arg_conversions = String::new(); let mut arg_conversions = String::new();
let mut destructors = String::new(); let mut destructors = String::new();
for (i, arg) in func.arguments.iter().enumerate() { if is_method {
passed_args.push_str("this.__wasmPtr");
}
for (i, arg) in arguments.iter().enumerate() {
let name = format!("arg{}", i); let name = format!("arg{}", i);
if i > 0 { if i > 0 {
dst.push_str(", "); dst.push_str(", ");
@ -50,23 +121,17 @@ impl Js {
shared::Type::Number => pass(&name), shared::Type::Number => pass(&name),
shared::Type::BorrowedStr | shared::Type::BorrowedStr |
shared::Type::String => { shared::Type::String => {
if self.nodejs { self.expose_global_exports = true;
arg_conversions.push_str(&format!("\ self.expose_pass_string_to_wasm = true;
const buf{i} = Buffer.from({arg}); arg_conversions.push_str(&format!("\
const len{i} = buf{i}.length; const [ptr{i}, len{i}] = passStringToWasm({arg});
const ptr{i} = exports.__wbindgen_malloc(len{i}); ", i = i, arg = name));
let memory{i} = new Uint8Array(memory.buffer); pass(&format!("ptr{}", i));
buf{i}.copy(memory{i}, ptr{i}); pass(&format!("len{}", i));
", i = i, arg = name)); if let shared::Type::BorrowedStr = *arg {
pass(&format!("ptr{}", i)); destructors.push_str(&format!("\n\
pass(&format!("len{}", i)); exports.__wbindgen_free(ptr{i}, len{i});\n\
if let shared::Type::BorrowedStr = *arg { ", i = i));
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::ByRef(_) |
@ -85,59 +150,107 @@ impl Js {
} }
} }
} }
let convert_ret = match func.ret { let convert_ret = match ret {
None | None |
Some(shared::Type::Number) => format!("return ret;"), Some(&shared::Type::Number) => format!("return ret;"),
Some(shared::Type::BorrowedStr) | Some(&shared::Type::BorrowedStr) |
Some(shared::Type::ByMutRef(_)) | Some(&shared::Type::ByMutRef(_)) |
Some(shared::Type::ByRef(_)) => panic!(), Some(&shared::Type::ByRef(_)) => panic!(),
Some(shared::Type::ByValue(ref name)) => { Some(&shared::Type::ByValue(ref name)) => {
format!("\ format!("\
return {name}.__wasmWrap(ret); return new {name}(ret, token);
", name = name) ", name = name)
} }
Some(shared::Type::String) => { Some(&shared::Type::String) => {
if self.nodejs { self.expose_get_string_from_wasm = true;
format!("\ format!("return getStringFromWasm(ret);")
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(") {\n ");
dst.push_str(&arg_conversions); dst.push_str(&arg_conversions);
dst.push_str(&format!("\ self.expose_global_exports = true;
try {{ if destructors.len() == 0 {
dst.push_str(&format!("\
const ret = exports.{f}({passed}); const ret = exports.{f}({passed});
{convert_ret} {convert_ret}
}} finally {{ ", f = wasm_name, passed = passed_args, convert_ret = convert_ret));
{destructors} } else {
}} dst.push_str(&format!("\
", f = func.name, passed = passed_args, destructors = destructors, try {{
convert_ret = convert_ret)); const ret = exports.{f}({passed});
dst.push_str("};"); {convert_ret}
}} finally {{
self.exports.push((func.name.clone(), dst)); {destructors}
}}
", f = wasm_name, passed = passed_args, destructors = destructors,
convert_ret = convert_ret));
}
dst.push_str("}");
return dst
} }
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
let mut globals = String::new(); let mut globals = String::new();
if self.expose_global_memory { if self.expose_global_memory ||
self.expose_pass_string_to_wasm ||
self.expose_get_string_from_wasm
{
globals.push_str("const memory = obj.instance.exports.memory;\n"); globals.push_str("const memory = obj.instance.exports.memory;\n");
} }
if self.expose_global_exports { if self.expose_global_exports ||
self.expose_pass_string_to_wasm ||
self.expose_get_string_from_wasm
{
globals.push_str("const exports = obj.instance.exports;\n"); globals.push_str("const exports = obj.instance.exports;\n");
} }
if self.expose_token {
globals.push_str("\
const token = Symbol('foo');
function _checkToken(sym) {
if (token != sym)
throw new Error('cannot invoke `new` directly');
}
");
}
if self.expose_pass_string_to_wasm {
if self.nodejs {
globals.push_str("
function passStringToWasm(arg) {
const buf = Buffer.from(arg);
const len = buf.length;
const ptr = exports.__wbindgen_malloc(len);
let array = new Uint8Array(memory.buffer);
buf.copy(array, ptr);
return [ptr, len];
}
");
} else {
panic!("browser strings not implemented yet");
}
}
if self.expose_get_string_from_wasm {
if self.nodejs {
globals.push_str("
function getStringFromWasm(ptr) {
const mem = new Uint8Array(memory.buffer);
const data = exports.__wbindgen_boxed_str_ptr(ptr);
const len = exports.__wbindgen_boxed_str_len(ptr);
const buf = Buffer.from(mem.slice(data, data + len));
const ret = buf.toString();
exports.__wbindgen_boxed_str_free(ptr);
return ret;
}
");
} else {
panic!("strings not implemented for browser");
}
}
let mut exports = String::new(); let mut exports = String::new();
for class in self.classes.iter() {
exports.push_str(class);
exports.push_str("\n");
}
for &(ref name, ref body) in self.exports.iter() { for &(ref name, ref body) in self.exports.iter() {
exports.push_str("obj."); exports.push_str("obj.");
exports.push_str(name); exports.push_str(name);
@ -146,12 +259,12 @@ impl Js {
exports.push_str(";\n"); exports.push_str(";\n");
} }
format!(" format!("
const function xform(obj) {{ function xform(obj) {{
{} {}
{} {}
return obj; return obj;
}} }}
export const function instantiate(bytes, imports) {{ export function instantiate(bytes, imports) {{
return WebAssembly.instantiate(bytes, imports).then(xform); return WebAssembly.instantiate(bytes, imports).then(xform);
}} }}
", globals, exports) ", globals, exports)

View File

@ -88,6 +88,7 @@ impl Object {
pub fn generate_js(&self) -> String { pub fn generate_js(&self) -> String {
let mut js = js::Js::default(); let mut js = js::Js::default();
js.nodejs = self.nodejs;
js.generate_program(&self.program); js.generate_program(&self.program);
js.to_string() js.to_string()
} }

View File

@ -24,7 +24,6 @@ pub enum Type {
pub struct Struct { pub struct Struct {
pub name: syn::Ident, pub name: syn::Ident,
pub ctor: Option<Function>,
pub methods: Vec<Method>, pub methods: Vec<Method>,
pub functions: Vec<Function>, pub functions: Vec<Function>,
} }
@ -118,16 +117,30 @@ impl Function {
Function { name: input.ident, arguments, ret } Function { name: input.ident, arguments, ret }
} }
pub fn export_name(&self) -> syn::Lit { pub fn free_function_export_name(&self) -> syn::Lit {
let name = self.shared().free_function_export_name();
syn::Lit { syn::Lit {
value: syn::LitKind::Other(Literal::string(self.name.sym.as_str())), value: syn::LitKind::Other(Literal::string(&name)),
span: Default::default(), span: Default::default(),
} }
} }
pub fn rust_symbol(&self) -> syn::Ident { pub fn struct_function_export_name(&self, s: syn::Ident) -> syn::Lit {
let generated_name = format!("__wasm_bindgen_generated_{}", let name = self.shared().struct_function_export_name(s.sym.as_str());
self.name.sym.as_str()); syn::Lit {
value: syn::LitKind::Other(Literal::string(&name)),
span: Default::default(),
}
}
pub fn rust_symbol(&self, namespace: Option<syn::Ident>) -> syn::Ident {
let mut generated_name = format!("__wasm_bindgen_generated");
if let Some(ns) = namespace {
generated_name.push_str("_");
generated_name.push_str(ns.sym.as_str());
}
generated_name.push_str("_");
generated_name.push_str(self.name.sym.as_str());
syn::Ident::from(generated_name) syn::Ident::from(generated_name)
} }
@ -220,12 +233,15 @@ impl Struct {
pub fn from(s: &syn::ItemStruct) -> Struct { pub fn from(s: &syn::ItemStruct) -> Struct {
Struct { Struct {
name: s.ident, name: s.ident,
ctor: None,
methods: Vec::new(), methods: Vec::new(),
functions: Vec::new(), functions: Vec::new(),
} }
} }
pub fn free_function(&self) -> syn::Ident {
syn::Ident::from(self.shared().free_function())
}
pub fn push_item(&mut self, item: &syn::ImplItem) { pub fn push_item(&mut self, item: &syn::ImplItem) {
let method = match *item { let method = match *item {
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"), syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
@ -299,7 +315,6 @@ impl Struct {
pub fn shared(&self) -> shared::Struct { pub fn shared(&self) -> shared::Struct {
shared::Struct { shared::Struct {
name: self.name.to_string(), name: self.name.to_string(),
ctor: self.ctor.as_ref().unwrap().shared(),
functions: self.functions.iter().map(|f| f.shared()).collect(), functions: self.functions.iter().map(|f| f.shared()).collect(),
methods: self.methods.iter().map(|f| f.shared()).collect(), methods: self.methods.iter().map(|f| f.shared()).collect(),
} }

View File

@ -60,6 +60,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
static CNT: AtomicUsize = ATOMIC_USIZE_INIT; static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}", let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
CNT.fetch_add(1, Ordering::SeqCst)); CNT.fetch_add(1, Ordering::SeqCst));
let generated_static_name = syn::Ident::from(generated_static_name);
let mut generated_static = String::from("wbg:"); let mut generated_static = String::from("wbg:");
generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap()); generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap());
let generated_static_value = syn::Lit { let generated_static_value = syn::Lit {
@ -75,22 +76,89 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
*#generated_static_value; *#generated_static_value;
}).to_tokens(&mut ret); }).to_tokens(&mut ret);
// println!("{}", ret);
ret.into() ret.into()
} }
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) { fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
let export_name = function.export_name(); bindgen(&function.free_function_export_name(),
let generated_name = function.rust_symbol(); function.rust_symbol(None),
Receiver::FreeFunction(function.name),
&function.arguments,
function.ret.as_ref(),
into)
}
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();
(quote! {
#[no_mangle]
pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) {
assert!(!ptr.is_null());
drop(Box::from_raw(ptr));
}
}).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::Lit,
generated_name: syn::Ident,
receiver: Receiver,
arguments: &[ast::Type],
ret_type: Option<&ast::Type>,
into: &mut Tokens) {
let mut args = vec![]; let mut args = vec![];
let mut arg_conversions = vec![]; let mut arg_conversions = vec![];
let real_name = &function.name;
let mut converted_arguments = vec![]; let mut converted_arguments = vec![];
let ret = syn::Ident::from("_ret"); let ret = syn::Ident::from("_ret");
let mut malloc = false; let mut malloc = false;
let mut boxed_str = false; let mut boxed_str = false;
for (i, ty) in function.arguments.iter().enumerate() { let mut offset = 0;
if let Receiver::StructMethod(class, _, _) = receiver {
args.push(quote! { me: *mut ::std::cell::RefCell<#class> });
arg_conversions.push(quote! {
assert!(!me.is_null());
let me = unsafe { &*me };
});
offset = 1;
}
for (i, ty) in arguments.iter().enumerate() {
let i = i + offset;
let ident = syn::Ident::from(format!("arg{}", i)); let ident = syn::Ident::from(format!("arg{}", i));
match *ty { match *ty {
ast::Type::Integer(i) => { ast::Type::Integer(i) => {
@ -153,23 +221,23 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
} }
let ret_ty; let ret_ty;
let convert_ret; let convert_ret;
match function.ret { match ret_type {
Some(ast::Type::Integer(i)) => { Some(&ast::Type::Integer(i)) => {
ret_ty = quote! { -> #i }; ret_ty = quote! { -> #i };
convert_ret = quote! { #ret }; convert_ret = quote! { #ret };
} }
Some(ast::Type::BorrowedStr) => panic!("can't return a borrowed string"), 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::ByRef(_)) => panic!("can't return a borrowed ref"),
Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"), Some(&ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),
Some(ast::Type::String) => { Some(&ast::Type::String) => {
boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst); boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst);
ret_ty = quote! { -> *mut String }; ret_ty = quote! { -> *mut String };
convert_ret = quote! { Box::into_raw(Box::new(#ret)) }; convert_ret = quote! { Box::into_raw(Box::new(#ret)) };
} }
Some(ast::Type::ByValue(name)) => { Some(&ast::Type::ByValue(name)) => {
ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> }; ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> };
convert_ret = quote! { convert_ret = quote! {
Box::into_raw(Box::new(::std::cell::RefCell<#ret>)) Box::into_raw(Box::new(::std::cell::RefCell::new(#ret)))
}; };
} }
None => { None => {
@ -224,20 +292,38 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
#malloc #malloc
#boxed_str #boxed_str
#[no_mangle]
#[export_name = #export_name] #[export_name = #export_name]
#[allow(non_snake_case)]
pub extern fn #generated_name(#(#args),*) #ret_ty { pub extern fn #generated_name(#(#args),*) #ret_ty {
#(#arg_conversions)* #(#arg_conversions)*
let #ret = #real_name(#(#converted_arguments),*); let #ret = #receiver(#(#converted_arguments),*);
#convert_ret #convert_ret
} }
}; };
// println!("{}", tokens);
tokens.to_tokens(into); tokens.to_tokens(into);
} }
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) { impl ToTokens for Receiver {
if s.ctor.is_none() { fn to_tokens(&self, tokens: &mut Tokens) {
panic!("struct `{}` needs a `new` function to construct it", s.name); match *self {
Receiver::FreeFunction(name) => name.to_tokens(tokens),
Receiver::StructFunction(s, name) => {
s.to_tokens(tokens);
syn::tokens::Colon2::default().to_tokens(tokens);
name.to_tokens(tokens);
}
Receiver::StructMethod(_, mutable, name) => {
syn::Ident::from("me").to_tokens(tokens);
syn::tokens::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_delimited("(", Default::default(), |_| ());
syn::tokens::Dot::default().to_tokens(tokens);
name.to_tokens(tokens);
}
}
} }
} }

View File

@ -10,7 +10,6 @@ pub struct Program {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Struct { pub struct Struct {
pub name: String, pub name: String,
pub ctor: Function,
pub functions: Vec<Function>, pub functions: Vec<Function>,
pub methods: Vec<Method>, pub methods: Vec<Method>,
} }
@ -28,6 +27,33 @@ pub struct Function {
pub ret: Option<Type>, pub ret: Option<Type>,
} }
impl Struct {
pub fn free_function(&self) -> String {
let mut name = format!("__wbindgen_");
name.extend(self.name
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_free");
return name
}
}
impl Function {
pub fn free_function_export_name(&self) -> String {
self.name.clone()
}
pub fn struct_function_export_name(&self, struct_: &str) -> String {
let mut name = struct_
.chars()
.flat_map(|s| s.to_lowercase())
.collect::<String>();
name.push_str("_");
name.push_str(&self.name);
return name
}
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub enum Type { pub enum Type {
Number, Number,

View File

@ -21,7 +21,7 @@ fn simple() {
} }
pub fn with_contents(a: u32) -> Foo { pub fn with_contents(a: u32) -> Foo {
Foo::with_contents(a) Foo { contents: a }
} }
pub fn add(&mut self, amt: u32) -> u32 { pub fn add(&mut self, amt: u32) -> u32 {
@ -35,15 +35,72 @@ fn simple() {
import * as assert from "assert"; import * as assert from "assert";
export function test(wasm) { export function test(wasm) {
const r = new wasm.Foo(); const r = wasm.Foo.new();
assert.strictEqual(r.add(0), 0); assert.strictEqual(r.add(0), 0);
assert.strictEqual(r.add(1), 1); assert.strictEqual(r.add(1), 1);
assert.strictEqual(r.add(1), 2); assert.strictEqual(r.add(1), 2);
r.free();
const r2 = wasm.Foo.with_contents(10); const r2 = wasm.Foo.with_contents(10);
assert.strictEqual(r.add(1), 11); assert.strictEqual(r2.add(1), 11);
assert.strictEqual(r.add(2), 13); assert.strictEqual(r2.add(2), 13);
assert.strictEqual(r.add(3), 16); assert.strictEqual(r2.add(3), 16);
r2.free();
}
"#)
.test();
}
#[test]
fn strings() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct Foo {
name: u32,
}
pub struct Bar {
contents: String,
}
impl Foo {
pub fn new() -> Foo {
Foo { name: 0 }
}
pub fn set(&mut self, amt: u32) {
self.name = amt;
}
pub fn bar(&self, mix: &str) -> Bar {
Bar { contents: format!("foo-{}-{}", mix, self.name) }
}
}
impl Bar {
pub fn name(&self) -> String {
self.contents.clone()
}
}
}
"#)
.file("test.js", r#"
import * as assert from "assert";
export function test(wasm) {
const r = wasm.Foo.new();
r.set(3);
let bar = r.bar('baz');
r.free();
assert.strictEqual(bar.name(), "foo-baz-3");
bar.free();
} }
"#) "#)
.test(); .test();