mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-10 19:41:25 +00:00
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:
32
DESIGN.md
32
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;
|
||||
|
@ -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(
|
||||
|
@ -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)),
|
||||
]);
|
||||
})
|
||||
|
@ -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(),
|
||||
|
@ -33,6 +33,7 @@ pub struct ImportStruct {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ImportStructFunction {
|
||||
pub method: bool,
|
||||
pub js_new: bool,
|
||||
pub function: Function,
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user