Add a #[wasm_bindgen(constructor)] attribute

This can be used for specifying that a static function should actually delegate
to `new`
This commit is contained in:
Alex Crichton
2018-02-06 15:19:47 -08:00
parent e60aa6a990
commit ec5f0a29f7
6 changed files with 131 additions and 24 deletions

View File

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

View File

@ -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(

View File

@ -32,7 +32,13 @@ pub struct ImportFunction {
pub struct ImportStruct {
pub module: Option<String>,
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)),
]);
})

View File

@ -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(),

View File

@ -33,6 +33,7 @@ pub struct ImportStruct {
#[derive(Serialize, Deserialize)]
pub struct ImportStructFunction {
pub method: bool,
pub js_new: bool,
pub function: Function,
}

View File

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