Add a structural bindgen attribute

This attribute indicates that methods are to be accessed in a structural method
rather than through their class. This should allow direct access to properties
embedded on objects rather than forcing all objects to have a class/prototype.
This commit is contained in:
Alex Crichton 2018-03-22 17:37:27 -07:00
parent 0e1fee5ddd
commit 8830f540a9
6 changed files with 120 additions and 11 deletions

View File

@ -1,5 +1,6 @@
use std::char; use std::char;
use std::collections::{HashSet, HashMap}; use std::collections::{HashSet, HashMap};
use std::fmt::Write;
use std::mem; use std::mem;
use shared; use shared;
@ -1432,6 +1433,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
} }
} }
let nargs = invoc_args.len();
let invoc_args = invoc_args.join(", "); let invoc_args = invoc_args.join(", ");
let function_name = &import.function.name; let function_name = &import.function.name;
let invoc = match import.class { let invoc = match import.class {
@ -1441,19 +1443,50 @@ impl<'a, 'b> SubContext<'a, 'b> {
Some(ref class) if import.method => { Some(ref class) if import.method => {
let class = self.import_name(info, class); let class = self.import_name(info, class);
let target = if let Some(ref g) = import.getter { let target = if let Some(ref g) = import.getter {
format!( if import.structural {
"Object.getOwnPropertyDescriptor({}.prototype, '{}').get;", format!("function() {{ return this.{}; }}", g)
class, } else {
g, format!(
) "Object.getOwnPropertyDescriptor\
({}.prototype, '{}').get;",
class,
g,
)
}
} else if let Some(ref s) = import.setter { } else if let Some(ref s) = import.setter {
format!( if import.structural {
"Object.getOwnPropertyDescriptor({}.prototype, '{}').set;", format!("function(y) {{ this.{} = y; }}", s)
class, } else {
s, format!(
) "Object.getOwnPropertyDescriptor\
({}.prototype, '{}').set;",
class,
s,
)
}
} else { } 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!(" self.cx.globals.push_str(&format!("
const {}_target = {}; const {}_target = {};

View File

@ -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 { impl syn::synom::Synom for BindgenAttrs {
@ -695,6 +705,7 @@ enum BindgenAttr {
Module(String), Module(String),
Getter, Getter,
Setter, Setter,
Structural,
} }
impl syn::synom::Synom for BindgenAttr { impl syn::synom::Synom for BindgenAttr {
@ -709,6 +720,8 @@ impl syn::synom::Synom for BindgenAttr {
| |
call!(term, "setter") => { |_| BindgenAttr::Setter } call!(term, "setter") => { |_| BindgenAttr::Setter }
| |
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
do_parse!( do_parse!(
call!(term, "js_namespace") >> call!(term, "js_namespace") >>
punct!(=) >> punct!(=) >>

View File

@ -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 { impl From<::wasm_bindgen::JsValue> for #name {
fn from(obj: ::wasm_bindgen::JsValue) -> #name { fn from(obj: ::wasm_bindgen::JsValue) -> #name {
#name { obj } #name { obj }

View File

@ -242,6 +242,7 @@ impl Literal for ast::ImportFunction {
let mut getter = None; let mut getter = None;
let mut setter = None; let mut setter = None;
let structural = self.function.opts.structural();
if self.function.opts.getter() { if self.function.opts.getter() {
getter = Some(self.infer_getter_property()); getter = Some(self.infer_getter_property());
@ -254,6 +255,7 @@ impl Literal for ast::ImportFunction {
("catch", &|a| a.bool(self.function.opts.catch())), ("catch", &|a| a.bool(self.function.opts.catch())),
("method", &|a| a.bool(method)), ("method", &|a| a.bool(method)),
("js_new", &|a| a.bool(js_new)), ("js_new", &|a| a.bool(js_new)),
("structural", &|a| a.bool(structural)),
("getter", &|a| match getter { ("getter", &|a| match getter {
Some(ref s) => a.str(s), Some(ref s) => a.str(s),
None => a.append("null"), None => a.append("null"),

View File

@ -38,6 +38,7 @@ pub struct ImportFunction {
pub catch: bool, pub catch: bool,
pub method: bool, pub method: bool,
pub js_new: bool, pub js_new: bool,
pub structural: bool,
pub getter: Option<String>, pub getter: Option<String>,
pub setter: Option<String>, pub setter: Option<String>,
pub class: Option<String>, pub class: Option<String>,

50
tests/structural.rs Normal file
View File

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