diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index db9fedac..10ea9742 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -1,5 +1,6 @@ use std::char; use std::collections::{HashSet, HashMap}; +use std::fmt::Write; use std::mem; use shared; @@ -1432,6 +1433,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } } + let nargs = invoc_args.len(); let invoc_args = invoc_args.join(", "); let function_name = &import.function.name; let invoc = match import.class { @@ -1441,19 +1443,50 @@ impl<'a, 'b> SubContext<'a, 'b> { Some(ref class) if import.method => { let class = self.import_name(info, class); let target = if let Some(ref g) = import.getter { - format!( - "Object.getOwnPropertyDescriptor({}.prototype, '{}').get;", - class, - g, - ) + if import.structural { + format!("function() {{ return this.{}; }}", g) + } else { + format!( + "Object.getOwnPropertyDescriptor\ + ({}.prototype, '{}').get;", + class, + g, + ) + } } else if let Some(ref s) = import.setter { - format!( - "Object.getOwnPropertyDescriptor({}.prototype, '{}').set;", - class, - s, - ) + if import.structural { + format!("function(y) {{ this.{} = y; }}", s) + } else { + format!( + "Object.getOwnPropertyDescriptor\ + ({}.prototype, '{}').set;", + class, + s, + ) + } } else { - format!("{}.prototype.{}", class, function_name) + if import.structural { + let mut s = format!("function("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str(") { return this."); + s.push_str(function_name); + s.push_str("("); + for i in 0..nargs - 1 { + if i > 0 { + drop(write!(s, ", ")); + } + drop(write!(s, "x{}", i)); + } + s.push_str("); }"); + s + } else { + format!("{}.prototype.{}", class, function_name) + } }; self.cx.globals.push_str(&format!(" const {}_target = {}; diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index fa61e544..30a19e31 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -670,6 +670,16 @@ impl BindgenAttrs { } }) } + + pub fn structural(&self) -> bool { + self.attrs.iter() + .any(|a| { + match *a { + BindgenAttr::Structural => true, + _ => false, + } + }) + } } impl syn::synom::Synom for BindgenAttrs { @@ -695,6 +705,7 @@ enum BindgenAttr { Module(String), Getter, Setter, + Structural, } impl syn::synom::Synom for BindgenAttr { @@ -709,6 +720,8 @@ impl syn::synom::Synom for BindgenAttr { | call!(term, "setter") => { |_| BindgenAttr::Setter } | + call!(term, "structural") => { |_| BindgenAttr::Structural } + | do_parse!( call!(term, "js_namespace") >> punct!(=) >> diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index 0c691e99..9ac26d2f 100755 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -349,6 +349,16 @@ impl ToTokens for ast::ImportType { } } + impl ::wasm_bindgen::convert::FromRefWasmBoundary for #name { + type RefAnchor = ::std::mem::ManuallyDrop<#name>; + unsafe fn from_js_ref(js: Self::Js) -> Self::RefAnchor { + let obj = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::WasmBoundary> + ::from_js(js); + ::std::mem::ManuallyDrop::new(#name { obj }) + } + } + + impl From<::wasm_bindgen::JsValue> for #name { fn from(obj: ::wasm_bindgen::JsValue) -> #name { #name { obj } diff --git a/crates/wasm-bindgen-macro/src/literal.rs b/crates/wasm-bindgen-macro/src/literal.rs index 9c0f14a2..432bb062 100644 --- a/crates/wasm-bindgen-macro/src/literal.rs +++ b/crates/wasm-bindgen-macro/src/literal.rs @@ -242,6 +242,7 @@ impl Literal for ast::ImportFunction { let mut getter = None; let mut setter = None; + let structural = self.function.opts.structural(); if self.function.opts.getter() { getter = Some(self.infer_getter_property()); @@ -254,6 +255,7 @@ impl Literal for ast::ImportFunction { ("catch", &|a| a.bool(self.function.opts.catch())), ("method", &|a| a.bool(method)), ("js_new", &|a| a.bool(js_new)), + ("structural", &|a| a.bool(structural)), ("getter", &|a| match getter { Some(ref s) => a.str(s), None => a.append("null"), diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index 68c114d3..f8ae5192 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -38,6 +38,7 @@ pub struct ImportFunction { pub catch: bool, pub method: bool, pub js_new: bool, + pub structural: bool, pub getter: Option, pub setter: Option, pub class: Option, diff --git a/tests/structural.rs b/tests/structural.rs new file mode 100644 index 00000000..bd3a5494 --- /dev/null +++ b/tests/structural.rs @@ -0,0 +1,50 @@ +extern crate test_support; + +#[test] +fn works() { + test_support::project() + .detect_node(true) + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern { + pub type Foo; + + #[wasm_bindgen(method, structural)] + fn bar(this: &Foo); + #[wasm_bindgen(method, getter, structural)] + fn baz(this: &Foo) -> u32; + #[wasm_bindgen(method, setter, structural)] + fn set_baz(this: &Foo, val: u32); + } + + #[wasm_bindgen] + pub fn run(a: &Foo) { + a.bar(); + assert_eq!(a.baz(), 1); + a.set_baz(2); + assert_eq!(a.baz(), 2); + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import { run } from "./out"; + + export function test() { + let called = false; + run({ + bar() { called = true; }, + baz: 1, + }); + assert.strictEqual(called, true); + } + "#) + .test(); +} + +