From ec5f0a29f712887c1eefc3204cc9ee89342cc7a6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 6 Feb 2018 15:19:47 -0800 Subject: [PATCH] Add a #[wasm_bindgen(constructor)] attribute This can be used for specifying that a static function should actually delegate to `new` --- DESIGN.md | 32 +++++++++---- crates/wasm-bindgen-cli-support/src/js.rs | 21 ++++----- crates/wasm-bindgen-macro/src/ast.rs | 56 +++++++++++++++++++++-- crates/wasm-bindgen-macro/src/lib.rs | 2 +- crates/wasm-bindgen-shared/src/lib.rs | 1 + tests/import-class.rs | 43 +++++++++++++++++ 6 files changed, 131 insertions(+), 24 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index d0579665..38b24deb 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -345,7 +345,7 @@ happening: * Returning strings from wasm is a little tricky as we need to return a ptr/len pair, but wasm currently only supports one return value (multiple return values [is being standardized](https://github.com/WebAssembly/design/issues/1146)). - To work around this in the meantime, we're actually returning a pointer to a + To work around this in the meantime, we're actually returning a pointer to a ptr/len pair, and then using functions to access the various fields. * Some cleanup ends up happening in wasm. The `__wbindgen_boxed_str_free` @@ -622,14 +622,16 @@ As usual though, let's dive into an example! wasm_bindgen! { #[wasm_module = "./bar"] extern struct Bar { - fn new() -> Bar; + #[wasm_bindgen(constructor)] + fn new(arg: i32) -> Bar; + fn another_function() -> i32; fn get(&self) -> i32; fn set(&self, val: i32); } } fn run() { - let bar = Bar::new(); + let bar = Bar::new(Bar::another_function()); let x = bar.get(); bar.set(x + 3); } @@ -646,7 +648,11 @@ import { Bar } from './bar'; // other support functions omitted... export function __wbg_s_Bar_new() { - return addHeapObject(Bar.new()); + return addHeapObject(new Bar()); +} + +export function __wbg_s_Bar_another_function() { + return Bar.another_function(); } export function __wbg_s_Bar_get(ptr) { @@ -659,10 +665,11 @@ export function __wbg_s_Bar_set(ptr, arg0) { ``` Like when importing functions from JS we can see a bunch of shims are generated -for all the relevant functions. The `new` static function is translated directly -to `Bar.new` (where `Bar` is imported at the top), and then when returning we're -sure to call `addHeapObject` as we're passing ownership to Rust (which just -declares `-> Bar`, no sigils). +for all the relevant functions. The `new` static function has the +`#[wasm_bindgen(constructor)]` attribute which means that instead of any +particular method it should actually invoke the `new` constructor instead (as +we see here). The static function `another_function`, however, is dispatched as +`Bar.another_function`. The `get` and `set` functions are methods so they go through `Bar.prototype`, and otherwise their first argument is implicitly the JS object itself which is @@ -687,6 +694,15 @@ impl Bar { } } + fn another_function() -> i32 { + extern { + fn __wbg_s_Bar_another_function() -> i32; + } + unsafe { + __wbg_s_Bar_another_function() + } + } + fn get(&self) -> i32 { extern { fn __wbg_s_Bar_get(ptr: u32) -> i32; diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index b337dd62..ac8b5288 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -501,27 +501,26 @@ impl<'a> Js<'a> { } for f in import.functions.iter() { - self.generate_import_struct_function(&import.name, - f.method, - &f.function); + self.generate_import_struct_function(&import.name, f); } } fn generate_import_struct_function( &mut self, class: &str, - is_method: bool, - function: &shared::Function, + f: &shared::ImportStructFunction, ) { - let delegate = if is_method { - format!("{}.prototype.{}.call", class, function.name) + let delegate = if f.method { + format!("{}.prototype.{}.call", class, f.function.name) + } else if f.js_new { + format!("new {}", class) } else { - format!("{}.{}", class, function.name) + format!("{}.{}", class, f.function.name) }; - self.gen_import_shim(&shared::mangled_import_name(Some(class), &function.name), + self.gen_import_shim(&shared::mangled_import_name(Some(class), &f.function.name), &delegate, - is_method, - function) + f.method, + &f.function) } fn gen_import_shim( diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index 8454f75c..314ceaf8 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -32,7 +32,13 @@ pub struct ImportFunction { pub struct ImportStruct { pub module: Option, pub name: syn::Ident, - pub functions: Vec<(bool, ImportFunction)>, + pub functions: Vec<(ImportFunctionKind, ImportFunction)>, +} + +pub enum ImportFunctionKind { + Method, + Static, + JsConstructor, } pub enum Type { @@ -154,7 +160,43 @@ impl Program { let functions = class.functions.iter() .map(|f| { let (f, method) = self.gen_foreign_item(f, true); - (method, f) + let kind = if method { + ImportFunctionKind::Method + } else { + let new = f.rust_attrs.iter() + .filter_map(|a| a.interpret_meta()) + .filter_map(|m| { + match m { + syn::Meta::List(i) => { + if i.ident == "wasm_bindgen" { + Some(i.nested) + } else { + None + } + } + _ => None, + } + }) + .flat_map(|a| a) + .filter_map(|a| { + match a { + syn::NestedMeta::Meta(a) => Some(a), + _ => None, + } + }) + .any(|a| { + match a { + syn::Meta::Word(a) => a == "constructor", + _ => false, + } + }); + if new { + ImportFunctionKind::JsConstructor + } else { + ImportFunctionKind::Static + } + }; + (kind, f) }) .collect(); self.imported_structs.push(ImportStruct { @@ -423,9 +465,15 @@ impl ImportStruct { ("name", &|a| a.str(self.name.as_ref())), ("functions", &|a| { a.list(&self.functions, - |&(is_method, ref f), a| { + |&(ref kind, ref f), a| { + let (method, new) = match *kind { + ImportFunctionKind::Method => (true, false), + ImportFunctionKind::JsConstructor => (false, true), + ImportFunctionKind::Static => (false, false), + }; a.fields(&[ - ("method", &|a| a.bool(is_method)), + ("method", &|a| a.bool(method)), + ("js_new", &|a| a.bool(new)), ("function", &|a| f.wasm_function.wbg_literal(a)), ]); }) diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index d9eb65c8..5e1b5969 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -367,7 +367,7 @@ fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) { let mut methods = Tokens::new(); - for &(_is_method, ref f) in import.functions.iter() { + for &(_, ref f) in import.functions.iter() { let import_name = shared::mangled_import_name( Some(&import.name.to_string()), f.wasm_function.name.as_ref(), diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 9d1078cb..9d0ba1a4 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -33,6 +33,7 @@ pub struct ImportStruct { #[derive(Serialize, Deserialize)] pub struct ImportStructFunction { pub method: bool, + pub js_new: bool, pub function: Function, } diff --git a/tests/import-class.rs b/tests/import-class.rs index 0cc89c8e..c3bbab0c 100644 --- a/tests/import-class.rs +++ b/tests/import-class.rs @@ -141,3 +141,46 @@ fn construct() { "#) .test(); } + +#[test] +fn new_constructors() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + wasm_bindgen! { + #[wasm_module = "./test"] + extern struct Foo { + #[wasm_bindgen(constructor)] + fn new(arg: i32) -> Foo; + fn get(&self) -> i32; + } + + pub fn run() { + let f = Foo::new(1); + assert_eq!(f.get(), 2); + } + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export class Foo { + constructor(private field: number) { + } + + get() { + return this.field + 1; + } + } + + export function test() { + run(); + } + "#) + .test(); +}