From 9183236522723b198cdb993225b205f0225af543 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 14 Feb 2018 13:16:02 -0800 Subject: [PATCH] Implement getter/setter bindings --- DESIGN.md | 39 +++++++++++++++ crates/test-support/src/lib.rs | 3 +- crates/wasm-bindgen-cli-support/src/js.rs | 34 +++++++++---- crates/wasm-bindgen-macro/src/ast.rs | 58 +++++++++++++++++++++++ crates/wasm-bindgen-shared/src/lib.rs | 2 + tests/import-class.rs | 57 ++++++++++++++++++++++ 6 files changed, 183 insertions(+), 10 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 01a42f0c..87d43c86 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -669,12 +669,20 @@ extern { #[wasm_bindgen(method)] fn set(this: &Bar, val: i32); + + #[wasm_bindgen(method, getter)] + fn property(this: &Bar) -> i32; + + #[wasm_bindgen(method, setter)] + fn set_property(this: &Bar, val: i32); } fn run() { let bar = Bar::new(Bar::another_function()); let x = bar.get(); bar.set(x + 3); + + bar.set_property(bar.property() + 6); } ``` @@ -726,6 +734,16 @@ const set_shim = Bar.prototype.set; export function __wbg_s_Bar_set(ptr, arg0) { set_shim.call(getObject(ptr), arg0) } + +const property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').get; +export function __wbg_s_Bar_property(ptr) { + return property_shim.call(getObject(ptr)); +} + +const set_property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').set; +export function __wbg_s_Bar_set_property(ptr, arg0) { + set_property_shim.call(getObject(ptr), arg0) +} ``` Like when importing functions from JS we can see a bunch of shims are generated @@ -787,6 +805,27 @@ impl Bar { __wbg_s_Bar_set(ptr, val); } } + + fn property(&self) -> i32 { + extern { + fn __wbg_s_Bar_property(ptr: u32) -> i32; + } + unsafe { + let ptr = self.obj.__get_idx(); + let ret = __wbg_s_Bar_property(ptr); + return ret + } + } + + fn set_property(&self, val: i32) { + extern { + fn __wbg_s_Bar_set_property(ptr: u32, val: i32); + } + unsafe { + let ptr = self.obj.__get_idx(); + __wbg_s_Bar_set_property(ptr, val); + } + } } impl WasmBoundary for Bar { diff --git a/crates/test-support/src/lib.rs b/crates/test-support/src/lib.rs index 7be6eedc..c9e84a42 100644 --- a/crates/test-support/src/lib.rs +++ b/crates/test-support/src/lib.rs @@ -95,7 +95,8 @@ pub fn project() -> Project { "strictFunctionTypes": true, "strictNullChecks": true, "alwaysStrict": true, - "strict": true + "strict": true, + "target": "es5" } } "#.to_string()), diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index 8c304f3b..49329105 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -957,23 +957,39 @@ impl<'a, 'b> SubContext<'a, 'b> { let invoc_args = invoc_args.join(", "); let function_name = &import.function.name; let invoc = match import.class { - Some(ref class) if import.method => { - self.cx.globals.push_str(&format!(" - const {}_target = {}.prototype.{}; - ", name, class, function_name)); - format!("{}_target.call({})", name, invoc_args) - } Some(ref class) if import.js_new => { - format!("new {}({})", class, invoc_args) + format!("new {}", class) + } + Some(ref class) if import.method => { + let target = if let Some(ref g) = import.getter { + format!( + "Object.getOwnPropertyDescriptor({}.prototype, '{}').get;", + class, + g, + ) + } else if let Some(ref s) = import.setter { + format!( + "Object.getOwnPropertyDescriptor({}.prototype, '{}').set;", + class, + s, + ) + } else { + format!("{}.prototype.{}", class, function_name) + }; + self.cx.globals.push_str(&format!(" + const {}_target = {}; + ", name, target)); + format!("{}_target.call", name) } Some(ref class) => { self.cx.globals.push_str(&format!(" const {}_target = {}.{}; ", name, class, function_name)); - format!("{}_target({})", name, invoc_args) + format!("{}_target", name) } - None => format!("{}({})", function_name, invoc_args), + None => function_name.to_string(), }; + let invoc = format!("{}({})", invoc, invoc_args); let invoc = match import.function.ret { Some(shared::TYPE_NUMBER) => format!("return {};", invoc), Some(shared::TYPE_BOOLEAN) => format!("return {} ? 1 : 0;", invoc), diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index e0491f59..552ab9e4 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -528,6 +528,16 @@ impl Import { } ImportKind::Normal => {} } + + let mut getter = None; + let mut setter = None; + + if self.function.opts.getter() { + getter = Some(self.infer_getter_property()); + } + if self.function.opts.setter() { + setter = Some(self.infer_setter_property()); + } a.fields(&[ ("module", &|a| { match self.module { @@ -539,6 +549,18 @@ impl Import { ("method", &|a| a.bool(method)), ("js_new", &|a| a.bool(js_new)), ("statik", &|a| a.bool(statik)), + ("getter", &|a| { + match getter { + Some(ref s) => a.str(s), + None => a.append("null"), + } + }), + ("setter", &|a| { + match setter { + Some(ref s) => a.str(s), + None => a.append("null"), + } + }), ("function", &|a| self.function.wbg_literal(a)), ("class", &|a| { match class_name { @@ -548,6 +570,16 @@ impl Import { }), ]); } + + fn infer_getter_property(&self) -> String { + self.function.name.as_ref().to_string() + } + + fn infer_setter_property(&self) -> String { + let name = self.function.name.as_ref(); + assert!(name.starts_with("set_"), "setters must start with `set_`"); + name[4..].to_string() + } } impl Struct { @@ -702,6 +734,26 @@ impl BindgenAttrs { }) .next() } + + fn getter(&self) -> bool { + self.attrs.iter() + .any(|a| { + match *a { + BindgenAttr::Getter => true, + _ => false, + } + }) + } + + fn setter(&self) -> bool { + self.attrs.iter() + .any(|a| { + match *a { + BindgenAttr::Setter => true, + _ => false, + } + }) + } } impl syn::synom::Synom for BindgenAttrs { @@ -725,6 +777,8 @@ enum BindgenAttr { Method, Static(syn::Type), Module(String), + Getter, + Setter, } impl syn::synom::Synom for BindgenAttr { @@ -735,6 +789,10 @@ impl syn::synom::Synom for BindgenAttr { | call!(term, "method") => { |_| BindgenAttr::Method } | + call!(term, "getter") => { |_| BindgenAttr::Getter } + | + call!(term, "setter") => { |_| BindgenAttr::Setter } + | do_parse!( call!(term, "static") >> punct!(=) >> diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index af2a3121..a47a0f95 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -19,6 +19,8 @@ pub struct Import { pub method: bool, pub js_new: bool, pub statik: bool, + pub getter: Option, + pub setter: Option, pub class: Option, pub function: Function, } diff --git a/tests/import-class.rs b/tests/import-class.rs index 19606383..50a33b79 100644 --- a/tests/import-class.rs +++ b/tests/import-class.rs @@ -278,3 +278,60 @@ fn switch_methods() { "#) .test(); } + +#[test] +fn properties() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + type Foo; + + #[wasm_bindgen(constructor)] + fn new() -> Foo; + + #[wasm_bindgen(getter, method)] + fn a(this: &Foo) -> i32; + + #[wasm_bindgen(setter, method)] + fn set_a(this: &Foo, a: i32); + } + + #[wasm_bindgen] + #[no_mangle] + pub extern fn run() { + let a = Foo::new(); + assert_eq!(a.a(), 1); + a.set_a(2); + assert_eq!(a.a(), 2); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export class Foo { + constructor(private num: number) { + this.num = 1; + } + + get a() { + return this.num; + } + + set a(val) { + this.num = val; + } + } + + export function test() { + run(); + } + "#) + .test(); +}