mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Merge pull request #1019 from alexcrichton/rfc-5
Implement rustwasm/rfcs#5, implement `Deref` for imports and `structural` by default
This commit is contained in:
commit
dc4e78550a
@ -535,7 +535,7 @@ impl ToTokens for ast::ImportType {
|
||||
use wasm_bindgen::convert::RefFromWasmAbi;
|
||||
use wasm_bindgen::describe::WasmDescribe;
|
||||
use wasm_bindgen::{JsValue, JsCast};
|
||||
use wasm_bindgen::__rt::core::mem::ManuallyDrop;
|
||||
use wasm_bindgen::__rt::core;
|
||||
|
||||
impl WasmDescribe for #rust_name {
|
||||
fn describe() {
|
||||
@ -589,13 +589,13 @@ impl ToTokens for ast::ImportType {
|
||||
|
||||
impl RefFromWasmAbi for #rust_name {
|
||||
type Abi = <JsValue as RefFromWasmAbi>::Abi;
|
||||
type Anchor = ManuallyDrop<#rust_name>;
|
||||
type Anchor = core::mem::ManuallyDrop<#rust_name>;
|
||||
|
||||
#[inline]
|
||||
unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor {
|
||||
let tmp = <JsValue as RefFromWasmAbi>::ref_from_abi(js, extra);
|
||||
ManuallyDrop::new(#rust_name {
|
||||
obj: ManuallyDrop::into_inner(tmp),
|
||||
core::mem::ManuallyDrop::new(#rust_name {
|
||||
obj: core::mem::ManuallyDrop::into_inner(tmp),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -657,6 +657,20 @@ impl ToTokens for ast::ImportType {
|
||||
};
|
||||
}).to_tokens(tokens);
|
||||
|
||||
let deref_target = match self.extends.first() {
|
||||
Some(target) => quote! { #target },
|
||||
None => quote! { JsValue },
|
||||
};
|
||||
(quote! {
|
||||
impl core::ops::Deref for #rust_name {
|
||||
type Target = #deref_target;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &#deref_target {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
for superclass in self.extends.iter() {
|
||||
(quote! {
|
||||
impl From<#rust_name> for #superclass {
|
||||
|
@ -413,7 +413,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
name = name
|
||||
));
|
||||
}
|
||||
self.rust_arguments.push(format!("{} ? 1 : 0", name));
|
||||
self.rust_arguments.push(format!("{}", name));
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.js_arguments.push((name.clone(), "string".to_string()));
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::mem;
|
||||
|
||||
use decode;
|
||||
@ -65,6 +64,18 @@ pub struct SubContext<'a, 'b: 'a> {
|
||||
pub vendor_prefixes: HashMap<&'b str, Vec<&'b str>>,
|
||||
}
|
||||
|
||||
pub enum ImportTarget {
|
||||
Function(String),
|
||||
Method(String),
|
||||
Constructor(String),
|
||||
StructuralMethod(String),
|
||||
StructuralGetter(Option<String>, String),
|
||||
StructuralSetter(Option<String>, String),
|
||||
StructuralIndexingGetter(Option<String>),
|
||||
StructuralIndexingSetter(Option<String>),
|
||||
StructuralIndexingDeleter(Option<String>),
|
||||
}
|
||||
|
||||
const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
@ -1944,7 +1955,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
Some(d) => d,
|
||||
};
|
||||
|
||||
let target = self.generated_import_target(info, import, &descriptor)?;
|
||||
let target = self.generated_import_target(info, import)?;
|
||||
|
||||
let js = Rust2Js::new(self.cx)
|
||||
.catch(import.catch)
|
||||
@ -1959,137 +1970,103 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
&mut self,
|
||||
info: &decode::Import<'b>,
|
||||
import: &decode::ImportFunction,
|
||||
descriptor: &Descriptor,
|
||||
) -> Result<String, Error> {
|
||||
) -> Result<ImportTarget, Error> {
|
||||
let method_data = match &import.method {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
let name = self.import_name(info, &import.function.name)?;
|
||||
return Ok(if name.contains(".") {
|
||||
self.cx.global(&format!(
|
||||
"
|
||||
const {}_target = {};
|
||||
",
|
||||
import.shim, name
|
||||
));
|
||||
format!("{}_target", import.shim)
|
||||
} else {
|
||||
name
|
||||
});
|
||||
if import.structural || !name.contains(".") {
|
||||
return Ok(ImportTarget::Function(name))
|
||||
}
|
||||
self.cx.global(&format!("const {}_target = {};", import.shim, name));
|
||||
let target = format!("{}_target", import.shim);
|
||||
return Ok(ImportTarget::Function(target))
|
||||
}
|
||||
};
|
||||
|
||||
let class = self.import_name(info, &method_data.class)?;
|
||||
let op = match &method_data.kind {
|
||||
decode::MethodKind::Constructor => return Ok(format!("new {}", class)),
|
||||
decode::MethodKind::Constructor => {
|
||||
return Ok(ImportTarget::Constructor(class.to_string()))
|
||||
}
|
||||
decode::MethodKind::Operation(op) => op,
|
||||
};
|
||||
let target = if import.structural {
|
||||
let location = if op.is_static { &class } else { "this" };
|
||||
if import.structural {
|
||||
let class = if op.is_static { Some(class.clone()) } else { None };
|
||||
|
||||
match &op.kind {
|
||||
return Ok(match &op.kind {
|
||||
decode::OperationKind::Regular => {
|
||||
let nargs = descriptor.unwrap_function().arguments.len();
|
||||
let mut s = format!("function(");
|
||||
for i in 0..nargs - 1 {
|
||||
if i > 0 {
|
||||
drop(write!(s, ", "));
|
||||
}
|
||||
drop(write!(s, "x{}", i));
|
||||
let name = import.function.name.to_string();
|
||||
match class {
|
||||
Some(c) => ImportTarget::Function(format!("{}.{}", c, name)),
|
||||
None => ImportTarget::StructuralMethod(name),
|
||||
}
|
||||
s.push_str(") { \nreturn this.");
|
||||
s.push_str(&import.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(");\n}");
|
||||
s
|
||||
}
|
||||
decode::OperationKind::Getter(g) => format!(
|
||||
"function() {{
|
||||
return {}.{};
|
||||
}}",
|
||||
location, g
|
||||
),
|
||||
decode::OperationKind::Setter(s) => format!(
|
||||
"function(y) {{
|
||||
{}.{} = y;
|
||||
}}",
|
||||
location, s
|
||||
),
|
||||
decode::OperationKind::IndexingGetter => format!(
|
||||
"function(y) {{
|
||||
return {}[y];
|
||||
}}",
|
||||
location
|
||||
),
|
||||
decode::OperationKind::IndexingSetter => format!(
|
||||
"function(y, z) {{
|
||||
{}[y] = z;
|
||||
}}",
|
||||
location
|
||||
),
|
||||
decode::OperationKind::IndexingDeleter => format!(
|
||||
"function(y) {{
|
||||
delete {}[y];
|
||||
}}",
|
||||
location
|
||||
),
|
||||
}
|
||||
} else {
|
||||
let target = format!("typeof {0} === 'undefined' ? null : {}{}",
|
||||
class,
|
||||
if op.is_static { "" } else { ".prototype" });
|
||||
let (mut target, name) = match &op.kind {
|
||||
decode::OperationKind::Regular => {
|
||||
(format!("{}.{}", target, import.function.name), &import.function.name)
|
||||
}
|
||||
decode::OperationKind::Getter(g) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
(format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}, '{}').get",
|
||||
target, g,
|
||||
), g)
|
||||
ImportTarget::StructuralGetter(class, g.to_string())
|
||||
}
|
||||
decode::OperationKind::Setter(s) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
(format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}, '{}').set",
|
||||
target, s,
|
||||
), s)
|
||||
ImportTarget::StructuralSetter(class, s.to_string())
|
||||
}
|
||||
decode::OperationKind::IndexingGetter => {
|
||||
panic!("indexing getter should be structural")
|
||||
ImportTarget::StructuralIndexingGetter(class)
|
||||
}
|
||||
decode::OperationKind::IndexingSetter => {
|
||||
panic!("indexing setter should be structural")
|
||||
ImportTarget::StructuralIndexingSetter(class)
|
||||
}
|
||||
decode::OperationKind::IndexingDeleter => {
|
||||
panic!("indexing deleter should be structural")
|
||||
ImportTarget::StructuralIndexingDeleter(class)
|
||||
}
|
||||
};
|
||||
target.push_str(&format!(" || function() {{
|
||||
throw new Error(`wasm-bindgen: {}.{} does not exist`);
|
||||
}}", class, name));
|
||||
if op.is_static {
|
||||
target.insert(0, '(');
|
||||
target.push_str(").bind(");
|
||||
target.push_str(&class);
|
||||
target.push_str(")");
|
||||
})
|
||||
}
|
||||
|
||||
let target = format!("typeof {0} === 'undefined' ? null : {}{}",
|
||||
class,
|
||||
if op.is_static { "" } else { ".prototype" });
|
||||
let (mut target, name) = match &op.kind {
|
||||
decode::OperationKind::Regular => {
|
||||
(format!("{}.{}", target, import.function.name), &import.function.name)
|
||||
}
|
||||
decode::OperationKind::Getter(g) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
(format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}, '{}').get",
|
||||
target, g,
|
||||
), g)
|
||||
}
|
||||
decode::OperationKind::Setter(s) => {
|
||||
self.cx.expose_get_inherited_descriptor();
|
||||
(format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}, '{}').set",
|
||||
target, s,
|
||||
), s)
|
||||
}
|
||||
decode::OperationKind::IndexingGetter => {
|
||||
panic!("indexing getter should be structural")
|
||||
}
|
||||
decode::OperationKind::IndexingSetter => {
|
||||
panic!("indexing setter should be structural")
|
||||
}
|
||||
decode::OperationKind::IndexingDeleter => {
|
||||
panic!("indexing deleter should be structural")
|
||||
}
|
||||
target
|
||||
};
|
||||
target.push_str(&format!(" || function() {{
|
||||
throw new Error(`wasm-bindgen: {}.{} does not exist`);
|
||||
}}", class, name));
|
||||
if op.is_static {
|
||||
target.insert(0, '(');
|
||||
target.push_str(").bind(");
|
||||
target.push_str(&class);
|
||||
target.push_str(")");
|
||||
}
|
||||
|
||||
self.cx.global(&format!("const {}_target = {};", import.shim, target));
|
||||
Ok(format!(
|
||||
"{}_target{}",
|
||||
import.shim,
|
||||
if op.is_static { "" } else { ".call" }
|
||||
))
|
||||
Ok(if op.is_static {
|
||||
ImportTarget::Function(format!("{}_target", import.shim))
|
||||
} else {
|
||||
ImportTarget::Method(format!("{}_target", import.shim))
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_import_type(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use failure::{self, Error};
|
||||
use failure::Error;
|
||||
|
||||
use super::{Context, Js2Rust};
|
||||
use super::{Context, Js2Rust, ImportTarget};
|
||||
use descriptor::{Descriptor, Function};
|
||||
|
||||
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
||||
@ -489,7 +489,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
}
|
||||
|
||||
self.ret_expr = match *ty {
|
||||
Descriptor::Boolean => "return JS ? 1 : 0;".to_string(),
|
||||
Descriptor::Boolean => "return JS;".to_string(),
|
||||
Descriptor::Char => "return JS.codePointAt(0);".to_string(),
|
||||
_ => bail!(
|
||||
"unsupported return type for calling JS function from Rust: {:?}",
|
||||
@ -499,7 +499,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish(&self, invoc: &str) -> Result<String, Error> {
|
||||
pub fn finish(&self, invoc: &ImportTarget) -> Result<String, Error> {
|
||||
let mut ret = String::new();
|
||||
ret.push_str("function(");
|
||||
ret.push_str(&self.shim_arguments.join(", "));
|
||||
@ -512,35 +512,92 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
ret.push_str(") {\n");
|
||||
ret.push_str(&self.prelude);
|
||||
|
||||
let mut invoc = if self.variadic {
|
||||
if self.js_arguments.is_empty() {
|
||||
return Err(failure::err_msg(
|
||||
"a function with no arguments cannot be variadic",
|
||||
));
|
||||
}
|
||||
let last_arg = self.js_arguments.len() - 1; // check implies >= 0
|
||||
if self.js_arguments.len() != 1 {
|
||||
self.ret_expr.replace(
|
||||
"JS",
|
||||
&format!(
|
||||
"{}({}, ...{})",
|
||||
invoc,
|
||||
self.js_arguments[..last_arg].join(", "),
|
||||
self.js_arguments[last_arg],
|
||||
),
|
||||
)
|
||||
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
||||
let ret = if self.variadic {
|
||||
let (last_arg, args) = match js_arguments.split_last() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("a function with no arguments cannot be variadic"),
|
||||
};
|
||||
if args.len() > 0 {
|
||||
self.ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}({}, ...{})", invoc, args.join(", "), last_arg),
|
||||
)
|
||||
} else {
|
||||
self.ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}(...{})", invoc, last_arg),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
self.ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}(...{})", invoc, self.js_arguments[last_arg],),
|
||||
&format!("{}({})", invoc, js_arguments.join(", ")),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
self.ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}({})", invoc, self.js_arguments.join(", ")),
|
||||
)
|
||||
};
|
||||
Ok(ret)
|
||||
};
|
||||
|
||||
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
||||
if self.variadic {
|
||||
bail!("{} cannot be variadic", desc);
|
||||
}
|
||||
match (class, self.js_arguments.len()) {
|
||||
(None, n) if n == amt + 1 => {
|
||||
Ok((self.js_arguments[0].clone(), &self.js_arguments[1..]))
|
||||
}
|
||||
(None, _) => bail!("setters must have {} arguments", amt + 1),
|
||||
(Some(class), n) if n == amt => {
|
||||
Ok((class.clone(), &self.js_arguments[..]))
|
||||
}
|
||||
(Some(_), _) => bail!("static setters must have {} arguments", amt),
|
||||
}
|
||||
};
|
||||
|
||||
let mut invoc = match invoc {
|
||||
ImportTarget::Function(f) => {
|
||||
handle_variadic(&f, &self.js_arguments)?
|
||||
}
|
||||
ImportTarget::Constructor(c) => {
|
||||
handle_variadic(&format!("new {}", c), &self.js_arguments)?
|
||||
}
|
||||
ImportTarget::Method(f) => {
|
||||
handle_variadic(&format!("{}.call", f), &self.js_arguments)?
|
||||
}
|
||||
ImportTarget::StructuralMethod(f) => {
|
||||
let (receiver, args) = match self.js_arguments.split_first() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("methods must have at least one argument"),
|
||||
};
|
||||
handle_variadic(&format!("{}.{}", receiver, f), args)?
|
||||
}
|
||||
ImportTarget::StructuralGetter(class, field) => {
|
||||
let (receiver, _) = fixed("getter", class, 0)?;
|
||||
let expr = format!("{}.{}", receiver, field);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
}
|
||||
ImportTarget::StructuralSetter(class, field) => {
|
||||
let (receiver, val) = fixed("setter", class, 1)?;
|
||||
let expr = format!("{}.{} = {}", receiver, field, val[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
}
|
||||
ImportTarget::StructuralIndexingGetter(class) => {
|
||||
let (receiver, field) = fixed("indexing getter", class, 1)?;
|
||||
let expr = format!("{}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
}
|
||||
ImportTarget::StructuralIndexingSetter(class) => {
|
||||
let (receiver, field) = fixed("indexing setter", class, 2)?;
|
||||
let expr = format!("{}[{}] = {}", receiver, field[0], field[1]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
}
|
||||
ImportTarget::StructuralIndexingDeleter(class) => {
|
||||
let (receiver, field) = fixed("indexing deleter", class, 1)?;
|
||||
let expr = format!("delete {}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
}
|
||||
};
|
||||
|
||||
if self.catch {
|
||||
let catch = "\
|
||||
const view = getUint32Memory();\n\
|
||||
|
@ -19,17 +19,17 @@
|
||||
logs.innerHTML += `${msg}\n`;
|
||||
}
|
||||
};
|
||||
console.log = function() {
|
||||
console.log = function(...args) {
|
||||
if (window.console_log_redirect)
|
||||
window.console_log_redirect(orig_console_log, arguments);
|
||||
window.console_log_redirect(orig_console_log, args);
|
||||
else
|
||||
orig_console_log.apply(this, arguments);
|
||||
orig_console_log.apply(this, args);
|
||||
};
|
||||
console.error = function() {
|
||||
console.error = function(...args) {
|
||||
if (window.console_error_redirect)
|
||||
window.console_error_redirect(orig_console_error, arguments);
|
||||
window.console_error_redirect(orig_console_error, args);
|
||||
else
|
||||
orig_console_error.apply(this, arguments);
|
||||
orig_console_error.apply(this, args);
|
||||
};
|
||||
window.__wbg_test_invoke = f => f();
|
||||
</script>
|
||||
|
@ -23,19 +23,19 @@ pub fn execute(
|
||||
// ensure they're bound correctly in wasm. This'll allow us to intercept
|
||||
// all these calls and capture the output of tests
|
||||
const prev_log = console.log;
|
||||
console.log = function() {{
|
||||
console.log = function(...args) {{
|
||||
if (console_log_redirect === null) {{
|
||||
prev_log.apply(null, arguments);
|
||||
prev_log.apply(null, args);
|
||||
}} else {{
|
||||
console_log_redirect(prev_log, arguments);
|
||||
console_log_redirect(prev_log, args);
|
||||
}}
|
||||
}};
|
||||
const prev_error = console.error;
|
||||
console.error = function() {{
|
||||
console.error = function(...args) {{
|
||||
if (console_error_redirect === null) {{
|
||||
prev_error.apply(null, arguments);
|
||||
prev_error.apply(null, args);
|
||||
}} else {{
|
||||
console_error_redirect(prev_error, arguments);
|
||||
console_error_redirect(prev_error, args);
|
||||
}}
|
||||
}};
|
||||
|
||||
|
@ -129,7 +129,7 @@ fn get_own_property_descriptor() {
|
||||
let desc = Object::get_own_property_descriptor(&foo, &"foo".into());
|
||||
assert_eq!(PropertyDescriptor::from(desc).value(), 42);
|
||||
let desc = Object::get_own_property_descriptor(&foo, &"bar".into());
|
||||
assert!(PropertyDescriptor::from(desc).value().is_undefined());
|
||||
assert!(desc.is_undefined());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@ -114,7 +114,7 @@ fn get_own_property_descriptor() {
|
||||
let desc = Reflect::get_own_property_descriptor(&obj, &"x".into()).unwrap();
|
||||
assert_eq!(PropertyDescriptor::from(desc).value(), 10);
|
||||
let desc = Reflect::get_own_property_descriptor(&obj, &"foo".into()).unwrap();
|
||||
assert!(PropertyDescriptor::from(desc).value().is_undefined());
|
||||
assert!(desc.is_undefined());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@ -149,6 +149,14 @@ impl BindgenAttrs {
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the `final` attribute is present
|
||||
fn final_(&self) -> Option<&Ident> {
|
||||
self.attrs.iter().filter_map(|a| match a {
|
||||
BindgenAttr::Final(i) => Some(i),
|
||||
_ => None,
|
||||
}).next()
|
||||
}
|
||||
|
||||
/// Whether the readonly attributes is present
|
||||
fn readonly(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
@ -229,6 +237,7 @@ pub enum BindgenAttr {
|
||||
IndexingSetter,
|
||||
IndexingDeleter,
|
||||
Structural,
|
||||
Final(Ident),
|
||||
Readonly,
|
||||
JsName(String, Span),
|
||||
JsClass(String),
|
||||
@ -240,7 +249,8 @@ pub enum BindgenAttr {
|
||||
impl Parse for BindgenAttr {
|
||||
fn parse(input: ParseStream) -> SynResult<Self> {
|
||||
let original = input.fork();
|
||||
let attr: Ident = input.parse()?;
|
||||
let attr: AnyIdent = input.parse()?;
|
||||
let attr = attr.0;
|
||||
if attr == "catch" {
|
||||
return Ok(BindgenAttr::Catch);
|
||||
}
|
||||
@ -262,6 +272,9 @@ impl Parse for BindgenAttr {
|
||||
if attr == "structural" {
|
||||
return Ok(BindgenAttr::Structural);
|
||||
}
|
||||
if attr == "final" {
|
||||
return Ok(BindgenAttr::Final(attr))
|
||||
}
|
||||
if attr == "readonly" {
|
||||
return Ok(BindgenAttr::Readonly);
|
||||
}
|
||||
@ -543,13 +556,18 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option<String>)> for syn::ForeignItemFn
|
||||
ShortHash(data)
|
||||
)
|
||||
};
|
||||
if let Some(ident) = opts.final_() {
|
||||
if opts.structural() {
|
||||
bail_span!(ident, "cannot specify both `structural` and `final`");
|
||||
}
|
||||
}
|
||||
Ok(ast::ImportKind::Function(ast::ImportFunction {
|
||||
function: wasm,
|
||||
kind,
|
||||
js_ret,
|
||||
catch,
|
||||
variadic,
|
||||
structural: opts.structural(),
|
||||
structural: opts.structural() || opts.final_().is_none(),
|
||||
rust_name: self.ident.clone(),
|
||||
shim: Ident::new(&shim, Span::call_site()),
|
||||
doc_comment: None,
|
||||
|
11
crates/macro/ui-tests/structural-and-final.rs
Normal file
11
crates/macro/ui-tests/structural-and-final.rs
Normal file
@ -0,0 +1,11 @@
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type Foo;
|
||||
|
||||
#[wasm_bindgen(method, structural, final)]
|
||||
fn bar(this: &Foo);
|
||||
}
|
8
crates/macro/ui-tests/structural-and-final.stderr
Normal file
8
crates/macro/ui-tests/structural-and-final.stderr
Normal file
@ -0,0 +1,8 @@
|
||||
error: cannot specify both `structural` and `final`
|
||||
--> $DIR/structural-and-final.rs:9:40
|
||||
|
|
||||
9 | #[wasm_bindgen(method, structural, final)]
|
||||
| ^^^^^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
@ -7,7 +7,7 @@ extern crate wasm_bindgen_futures;
|
||||
extern crate wasm_bindgen_test;
|
||||
extern crate web_sys;
|
||||
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
@ -56,3 +56,13 @@ pub mod table_element;
|
||||
pub mod title_element;
|
||||
pub mod xpath_result;
|
||||
pub mod indexeddb;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn deref_works() {
|
||||
fn _check(a: &web_sys::XmlHttpRequestUpload) {
|
||||
let _x: &web_sys::XmlHttpRequestEventTarget = a;
|
||||
let _x: &web_sys::EventTarget = a;
|
||||
let _x: &js_sys::Object = a;
|
||||
let _x: &wasm_bindgen::JsValue = a;
|
||||
}
|
||||
}
|
||||
|
@ -735,11 +735,17 @@ impl<'src> FirstPass<'src, ()> for weedle::CallbackInterfaceDefinition<'src> {
|
||||
impl<'a> FirstPassRecord<'a> {
|
||||
pub fn all_superclasses<'me>(&'me self, interface: &str) -> impl Iterator<Item = String> + 'me {
|
||||
let mut set = BTreeSet::new();
|
||||
self.fill_superclasses(interface, &mut set);
|
||||
set.into_iter()
|
||||
let mut list = Vec::new();
|
||||
self.fill_superclasses(interface, &mut set, &mut list);
|
||||
list.into_iter()
|
||||
}
|
||||
|
||||
fn fill_superclasses(&self, interface: &str, set: &mut BTreeSet<String>) {
|
||||
fn fill_superclasses(
|
||||
&self,
|
||||
interface: &str,
|
||||
set: &mut BTreeSet<&'a str>,
|
||||
list: &mut Vec<String>,
|
||||
) {
|
||||
let data = match self.interfaces.get(interface) {
|
||||
Some(data) => data,
|
||||
None => return,
|
||||
@ -749,8 +755,9 @@ impl<'a> FirstPassRecord<'a> {
|
||||
None => return,
|
||||
};
|
||||
if self.interfaces.contains_key(superclass) {
|
||||
if set.insert(camel_case_ident(superclass)) {
|
||||
self.fill_superclasses(superclass, set);
|
||||
if set.insert(superclass) {
|
||||
list.push(camel_case_ident(superclass));
|
||||
self.fill_superclasses(superclass, set, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,7 @@ pub fn run() -> Result<(), JsValue> {
|
||||
let val = document.create_element("p")?;
|
||||
val.set_inner_html("Hello from Rust!");
|
||||
|
||||
// Right now the class inheritance hierarchy of the DOM isn't super
|
||||
// ergonomic, so we manually cast `val: Element` to `&Node` to call the
|
||||
// `append_child` method.
|
||||
AsRef::<web_sys::Node>::as_ref(&body).append_child(val.as_ref())?;
|
||||
body.append_child(&val)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -8,29 +8,19 @@ use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn main() {
|
||||
pub fn main() -> Result<(), JsValue> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document
|
||||
.create_element("canvas")
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
(document.body().unwrap().as_ref() as &web_sys::Node)
|
||||
.append_child(canvas.as_ref() as &web_sys::Node)
|
||||
.unwrap();
|
||||
.create_element("canvas")?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()?;
|
||||
document.body().unwrap().append_child(&canvas)?;
|
||||
canvas.set_width(640);
|
||||
canvas.set_height(480);
|
||||
(canvas.as_ref() as &web_sys::HtmlElement)
|
||||
.style()
|
||||
.set_property("border", "solid")
|
||||
.unwrap();
|
||||
canvas.style().set_property("border", "solid")?;
|
||||
let context = canvas
|
||||
.get_context("2d")
|
||||
.get_context("2d")?
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
|
||||
let context = Rc::new(context);
|
||||
let pressed = Rc::new(Cell::new(false));
|
||||
{
|
||||
@ -41,9 +31,10 @@ pub fn main() {
|
||||
context.move_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
pressed.set(true);
|
||||
}) as Box<FnMut(_)>);
|
||||
(canvas.as_ref() as &web_sys::EventTarget)
|
||||
.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
canvas.add_event_listener_with_callback(
|
||||
"mousedown",
|
||||
closure.as_ref().unchecked_ref(),
|
||||
)?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
@ -57,9 +48,10 @@ pub fn main() {
|
||||
context.move_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
}
|
||||
}) as Box<FnMut(_)>);
|
||||
(canvas.as_ref() as &web_sys::EventTarget)
|
||||
.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
canvas.add_event_listener_with_callback(
|
||||
"mousemove",
|
||||
closure.as_ref().unchecked_ref(),
|
||||
)?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
@ -70,9 +62,12 @@ pub fn main() {
|
||||
context.line_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
context.stroke();
|
||||
}) as Box<FnMut(_)>);
|
||||
(canvas.as_ref() as &web_sys::EventTarget)
|
||||
.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
canvas.add_event_listener_with_callback(
|
||||
"mouseup",
|
||||
closure.as_ref().unchecked_ref(),
|
||||
)?;
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ impl WorkerPool {
|
||||
// thread?
|
||||
// * Need to handle the `?` on `post_message` as well.
|
||||
array.push(&wasm_bindgen::memory());
|
||||
worker.post_message(array.as_ref())?;
|
||||
worker.post_message(&array)?;
|
||||
worker.set_onmessage(Some(callback.as_ref().unchecked_ref()));
|
||||
worker.set_onerror(Some(callback.as_ref().unchecked_ref()));
|
||||
workers.push(worker);
|
||||
@ -355,10 +355,10 @@ impl Shared {
|
||||
self.scene.height as f64,
|
||||
)?;
|
||||
let arr = Array::new();
|
||||
arr.push(data.as_ref());
|
||||
arr.push(&data);
|
||||
arr.push(&JsValue::from(done));
|
||||
arr.push(&JsValue::from(self.id as f64));
|
||||
global.post_message(arr.as_ref())?;
|
||||
global.post_message(&arr)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ extern crate wasm_bindgen;
|
||||
extern crate web_sys;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{AudioContext, AudioNode, OscillatorType};
|
||||
use web_sys::{AudioContext, OscillatorType};
|
||||
|
||||
/// Converts a midi note to frequency
|
||||
///
|
||||
@ -60,34 +60,24 @@ impl FmOsc {
|
||||
fm_osc.set_type(OscillatorType::Sine);
|
||||
fm_osc.frequency().set_value(0.0);
|
||||
|
||||
// Create base class references:
|
||||
{
|
||||
let primary_node: &AudioNode = primary.as_ref();
|
||||
let gain_node: &AudioNode = gain.as_ref();
|
||||
let fm_osc_node: &AudioNode = fm_osc.as_ref();
|
||||
let fm_gain_node: &AudioNode = fm_gain.as_ref();
|
||||
let destination = ctx.destination();
|
||||
let destination_node: &AudioNode = destination.as_ref();
|
||||
// Connect the nodes up!
|
||||
|
||||
// Connect the nodes up!
|
||||
// The primary oscillator is routed through the gain node, so that
|
||||
// it can control the overall output volume.
|
||||
primary.connect_with_audio_node(&gain)?;
|
||||
|
||||
// The primary oscillator is routed through the gain node, so that
|
||||
// it can control the overall output volume.
|
||||
primary_node.connect_with_audio_node(gain.as_ref())?;
|
||||
// Then connect the gain node to the AudioContext destination (aka
|
||||
// your speakers).
|
||||
gain.connect_with_audio_node(&ctx.destination())?;
|
||||
|
||||
// Then connect the gain node to the AudioContext destination (aka
|
||||
// your speakers).
|
||||
gain_node.connect_with_audio_node(destination_node)?;
|
||||
|
||||
// The FM oscillator is connected to its own gain node, so it can
|
||||
// control the amount of modulation.
|
||||
fm_osc_node.connect_with_audio_node(fm_gain.as_ref())?;
|
||||
// The FM oscillator is connected to its own gain node, so it can
|
||||
// control the amount of modulation.
|
||||
fm_osc.connect_with_audio_node(&fm_gain)?;
|
||||
|
||||
|
||||
// Connect the FM oscillator to the frequency parameter of the main
|
||||
// oscillator, so that the FM node can modulate its frequency.
|
||||
fm_gain_node.connect_with_audio_param(&primary.frequency())?;
|
||||
}
|
||||
// Connect the FM oscillator to the frequency parameter of the main
|
||||
// oscillator, so that the FM node can modulate its frequency.
|
||||
fm_gain.connect_with_audio_param(&primary.frequency())?;
|
||||
|
||||
// Start the oscillators!
|
||||
primary.start()?;
|
||||
|
@ -8,20 +8,16 @@ use web_sys::{WebGlProgram, WebGlRenderingContext, WebGlShader};
|
||||
use js_sys::{WebAssembly};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn draw() {
|
||||
pub fn draw() -> Result<(), JsValue> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document.get_element_by_id("canvas").unwrap();
|
||||
let canvas: web_sys::HtmlCanvasElement = canvas
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()
|
||||
.map_err(|_| ())
|
||||
.unwrap();
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()?;
|
||||
|
||||
let context = canvas
|
||||
.get_context("webgl")
|
||||
.get_context("webgl")?
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<WebGlRenderingContext>()
|
||||
.unwrap();
|
||||
.dyn_into::<WebGlRenderingContext>()?;
|
||||
|
||||
let vert_shader = compile_shader(
|
||||
&context,
|
||||
@ -32,7 +28,7 @@ pub fn draw() {
|
||||
gl_Position = position;
|
||||
}
|
||||
"#,
|
||||
).unwrap();
|
||||
)?;
|
||||
let frag_shader = compile_shader(
|
||||
&context,
|
||||
WebGlRenderingContext::FRAGMENT_SHADER,
|
||||
@ -41,23 +37,23 @@ pub fn draw() {
|
||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
"#,
|
||||
).unwrap();
|
||||
let program = link_program(&context, [vert_shader, frag_shader].iter()).unwrap();
|
||||
)?;
|
||||
let program = link_program(&context, [vert_shader, frag_shader].iter())?;
|
||||
context.use_program(Some(&program));
|
||||
|
||||
let vertices: [f32; 9] = [-0.7, -0.7, 0.0, 0.7, -0.7, 0.0, 0.0, 0.7, 0.0];
|
||||
let memory_buffer = wasm_bindgen::memory().dyn_into::<WebAssembly::Memory>().unwrap().buffer();
|
||||
let memory_buffer = wasm_bindgen::memory().dyn_into::<WebAssembly::Memory>()?.buffer();
|
||||
let vertices_location = vertices.as_ptr() as u32 / 4;
|
||||
let vert_array = js_sys::Float32Array::new(&memory_buffer).subarray(
|
||||
vertices_location,
|
||||
vertices_location + vertices.len() as u32,
|
||||
);
|
||||
|
||||
let buffer = context.create_buffer().unwrap();
|
||||
let buffer = context.create_buffer().ok_or("failed to create buffer")?;
|
||||
context.bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer));
|
||||
context.buffer_data_with_array_buffer_view(
|
||||
WebGlRenderingContext::ARRAY_BUFFER,
|
||||
vert_array.as_ref(),
|
||||
&vert_array,
|
||||
WebGlRenderingContext::STATIC_DRAW,
|
||||
);
|
||||
context.vertex_attrib_pointer_with_i32(0, 3, WebGlRenderingContext::FLOAT, false, 0, 0);
|
||||
@ -71,6 +67,7 @@ pub fn draw() {
|
||||
0,
|
||||
(vertices.len() / 3) as i32,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_shader(
|
||||
|
@ -61,6 +61,7 @@
|
||||
- [`constructor`](./reference/attributes/on-js-imports/constructor.md)
|
||||
- [`extends`](./reference/attributes/on-js-imports/extends.md)
|
||||
- [`getter` and `setter`](./reference/attributes/on-js-imports/getter-and-setter.md)
|
||||
- [`final`](./reference/attributes/on-js-imports/final.md)
|
||||
- [`indexing_getter`, `indexing_setter`, and `indexing_deleter`](./reference/attributes/on-js-imports/indexing-getter-setter-deleter.md)
|
||||
- [`js_class = "Blah"`](./reference/attributes/on-js-imports/js_class.md)
|
||||
- [`js_name`](./reference/attributes/on-js-imports/js_name.md)
|
||||
@ -83,6 +84,7 @@
|
||||
- [Cargo Features](./web-sys/cargo-features.md)
|
||||
- [Function Overloads](./web-sys/function-overloads.md)
|
||||
- [Type Translations](./web-sys/type-translations.md)
|
||||
- [Inheritance](./web-sys/inheritance.md)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
154
guide/src/reference/attributes/on-js-imports/final.md
Normal file
154
guide/src/reference/attributes/on-js-imports/final.md
Normal file
@ -0,0 +1,154 @@
|
||||
# `final`
|
||||
|
||||
The `final` attribute is the converse of the [`structural`
|
||||
attribute](structural.html). It configures how `wasm-bindgen` will generate JS
|
||||
imports to call the imported function. Notably a function imported by `final`
|
||||
never changes after it was imported, whereas a function imported by default (or
|
||||
with `structural`) is subject to runtime lookup rules such as walking the
|
||||
prototype chain of an object.
|
||||
|
||||
[host-bindings]: https://github.com/WebAssembly/host-bindings
|
||||
[reference-types]: https://github.com/WebAssembly/reference-types
|
||||
|
||||
The `final` attribute is intended to be purely related to performance. It
|
||||
ideally has no user-visible effect, and `structural` imports (the default)
|
||||
should be able to transparently switch to `final` eventually.
|
||||
|
||||
The eventual performance aspect is that with the [host bindings
|
||||
proposal][host-bindings] then `wasm-bindgen` will need to generate far fewer JS
|
||||
functino shims to import than it does today. For example, consider this import
|
||||
today:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
type Foo;
|
||||
#[wasm_bindgen(method)]
|
||||
fn bar(this: &Foo, argument: &str) -> JsValue;
|
||||
}
|
||||
```
|
||||
|
||||
**Without the `final` attribute** the generated JS looks like this:
|
||||
|
||||
```js
|
||||
// without `final`
|
||||
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
|
||||
let varg1 = getStringFromWasm(arg1, arg2);
|
||||
return addHeapObject(getObject(arg0).bar(varg1));
|
||||
}
|
||||
```
|
||||
|
||||
We can see here that this JS function shim is required, but it's all relatively
|
||||
self-contained. It does, however, execute the `bar` method in a duck-type-y
|
||||
fashion in the sense that it never validates `getObject(arg0)` is of type `Foo`
|
||||
to actually call the `Foo.prototype.bar` method.
|
||||
|
||||
If we instead, however, write this:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
type Foo;
|
||||
#[wasm_bindgen(method, final)] // note the change here
|
||||
fn bar(this: &Foo, argument: &str) -> JsValue;
|
||||
}
|
||||
```
|
||||
|
||||
it generates this JS glue (roughly):
|
||||
|
||||
```js
|
||||
const __wbg_bar_target = Foo.prototype.bar;
|
||||
|
||||
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
|
||||
let varg1 = getStringFromWasm(arg1, arg2);
|
||||
return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
|
||||
}
|
||||
```
|
||||
|
||||
The difference here is pretty subtle, but we can see how the function being
|
||||
called is hoisted out of the generated shim and is bound to always be
|
||||
`Foo.prototype.bar`. This then uses the `Function.call` method to invoke that
|
||||
function with `getObject(arg0)` as the receiver.
|
||||
|
||||
But wait, there's still a JS function shim here even with `final`! That's true,
|
||||
and this is simply a fact of future WebAssembly proposals not being implemented
|
||||
yet. The semantics, though, match the future [host bindings
|
||||
proposal][host-bindings] because the method being called is determined exactly
|
||||
once, and it's located on the prototype chain rather than being resolved at
|
||||
runtime when the function is called.
|
||||
|
||||
## Interaction with future proposals
|
||||
|
||||
If you're curious to see how our JS function shim will be eliminated entirely,
|
||||
let's take a look at the generated bindings. We're starting off with this:
|
||||
|
||||
```js
|
||||
const __wbg_bar_target = Foo.prototype.bar;
|
||||
|
||||
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
|
||||
let varg1 = getStringFromWasm(arg1, arg2);
|
||||
return addHeapObject(__wbg_bar_target.call(getObject(arg0), varg1));
|
||||
}
|
||||
```
|
||||
|
||||
... and once the [reference types proposal][reference-types] is implemented then
|
||||
we won't need some of these pesky functions. That'll transform our generated JS
|
||||
shim to look like:
|
||||
|
||||
```js
|
||||
const __wbg_bar_target = Foo.prototype.bar;
|
||||
|
||||
export function __wbg_bar_a81456386e6b526f(arg0, arg1, arg2) {
|
||||
let varg1 = getStringFromWasm(arg1, arg2);
|
||||
return __wbg_bar_target.call(arg0, varg1);
|
||||
}
|
||||
```
|
||||
|
||||
Getting better! Next up we need the host bindings proposal. Note that the
|
||||
proposal is undergoing some changes right now so it's tough to link to reference
|
||||
documentation, but it suffices to say that it'll empower us with at least two
|
||||
different features.
|
||||
|
||||
First, host bindings promises to provide the concept of "argument conversions".
|
||||
The `arg1` and `arg2` values here are actually a pointer and a length to a utf-8
|
||||
encoded string, and with host bindings we'll be able to annotate that this
|
||||
import should take those two arguments and convert them to a JS string (that is,
|
||||
the *host* should do this, the WebAssembly engine). Using that feature we can
|
||||
futher trim this down to:
|
||||
|
||||
```js
|
||||
const __wbg_bar_target = Foo.prototype.bar;
|
||||
|
||||
export function __wbg_bar_a81456386e6b526f(arg0, varg1) {
|
||||
return __wbg_bar_target.call(arg0, varg1);
|
||||
}
|
||||
```
|
||||
|
||||
And finally, the second promise of the host bindings proposal is that we can
|
||||
flag a function call to indicate the first argument is the `this` binding of the
|
||||
function call. Today the `this` value of all called imported functions is
|
||||
`undefined`, and this flag (configured with host bindings) will indicate the
|
||||
first argument here is actually the `this`.
|
||||
|
||||
With that in mind we can further transform this to:
|
||||
|
||||
```js
|
||||
export const __wbg_bar_a81456386e6b526f = Foo.prototype.bar;
|
||||
```
|
||||
|
||||
and voila! We, with [reference types][reference-types] and [host
|
||||
bindings][host-bindings], now have no JS function shim at all necessary to call
|
||||
the imported function. Additionally future wasm proposals to the ES module
|
||||
system may also mean that don't even need the `export const ...` here too.
|
||||
|
||||
It's also worth pointing out that with all these wasm proposals implemented the
|
||||
default way to import the `bar` function (aka `structural`) would generate a JS
|
||||
function shim that looks like:
|
||||
|
||||
```js
|
||||
export function __wbg_bar_a81456386e6b526f(varg1) {
|
||||
return this.bar(varg1);
|
||||
}
|
||||
```
|
||||
|
||||
where this import is still subject to runtime prototype chain lookups and such.
|
@ -1,5 +1,15 @@
|
||||
# `structural`
|
||||
|
||||
> **Note**: As of [RFC 5] this attribute is the default for all imported
|
||||
> functions. This attribute is largely ignored today and is only retained for
|
||||
> backwards compatibility and learning purposes.
|
||||
>
|
||||
> The inverse of this attribute, [the `final`
|
||||
> attribute](final.html) is more functionally interesting than
|
||||
> `structural` (as `structural` is simply the default)
|
||||
|
||||
[RFC 5]: https://rustwasm.github.io/rfcs/005-structural-and-deref.html
|
||||
|
||||
The `structural` flag can be added to `method` annotations, indicating that the
|
||||
method being accessed (or property with getters/setters) should be accessed in a
|
||||
structural, duck-type-y fashion. Rather than walking the constructor's prototype
|
||||
@ -36,15 +46,3 @@ function quack(duck) {
|
||||
duck.quack();
|
||||
}
|
||||
```
|
||||
|
||||
## Why don't we always use the `structural` behavior?
|
||||
|
||||
In theory, it is faster since the prototype chain doesn't need to be traversed
|
||||
every time the method or property is accessed, but today's optimizing JIT
|
||||
compilers are really good about eliminating that cost. The real reason is to be
|
||||
future compatible with the ["host bindings" proposal][host-bindings], which
|
||||
requires that there be no JavaScript shim between the caller and the native host
|
||||
function. In this scenario, the properties and methods *must* be resolved before
|
||||
the wasm is instantiated.
|
||||
|
||||
[host-bindings]: https://github.com/WebAssembly/host-bindings/blob/master/proposals/host-bindings/Overview.md
|
||||
|
55
guide/src/web-sys/inheritance.md
Normal file
55
guide/src/web-sys/inheritance.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Inheritance in `web-sys`
|
||||
|
||||
Inheritance between JS classes is the bread and butter of how the DOM works on
|
||||
the web, and as a result it's quite important for `web-sys` to provide access to
|
||||
this inheritance hierarchy as well! There are few ways you can access the
|
||||
inheritance hierarchy when using `web-sys`.
|
||||
|
||||
### Accessing parent classes using `Deref`
|
||||
|
||||
Like smart pointers in Rust, all types in `web_sys` implement `Deref` to their
|
||||
parent JS class. This means, for example, if you have a `web_sys::Element` you
|
||||
can create a `web_sys::Node` from that implicitly:
|
||||
|
||||
```rust
|
||||
let element: &Element = ...;
|
||||
|
||||
element.append_child(..); // call a method on `Node`
|
||||
|
||||
method_expecting_a_node(&element); // coerce to `&Node` implicitly
|
||||
|
||||
let node: &Node = &element; // explicitly coerce to `&Node`
|
||||
```
|
||||
|
||||
Using `Deref` allows ergonomic transitioning up the inheritance hierarchy to the
|
||||
parent class and beyond, giving you access to all the methods using the `.`
|
||||
operator.
|
||||
|
||||
### Accessing parent classes using `AsRef`
|
||||
|
||||
In addition to `Deref`, the `AsRef` trait is implemented for all types in
|
||||
`web_sys` for all types in the inheritance hierarchy. For example for the
|
||||
`HtmlAnchorElement` type you'll find:
|
||||
|
||||
```rust
|
||||
impl AsRef<HtmlElement> for HtmlAnchorElement
|
||||
impl AsRef<Element> for HtmlAnchorElement
|
||||
impl AsRef<Node> for HtmlAnchorElement
|
||||
impl AsRef<EventTarget> for HtmlAnchorElement
|
||||
impl AsRef<Object> for HtmlAnchorElement
|
||||
impl AsRef<JsValue> for HtmlAnchorElement
|
||||
```
|
||||
|
||||
You can use `.as_ref()` to explicitly get a reference to any parent class from
|
||||
from a type in `web_sys`. Note that because of the number of `AsRef`
|
||||
implementations you'll likely need to have type inference guidance as well.
|
||||
|
||||
### Accessing child clases using `JsCast`
|
||||
|
||||
Finally the `wasm_bindgen::JsCast` trait can be used to implement all manner of
|
||||
casts between types. It supports static unchecked casts between types as well as
|
||||
dynamic runtime-checked casts (using `instanceof`) between types.
|
||||
|
||||
More documentation about this can be found [on the trait itself][jscast]
|
||||
|
||||
[jscast]: https://docs.rs/wasm-bindgen/0.2/wasm_bindgen/trait.JsCast.html
|
25
tests/wasm/final.js
Normal file
25
tests/wasm/final.js
Normal file
@ -0,0 +1,25 @@
|
||||
const assert = require('assert');
|
||||
|
||||
exports.MyType = class {
|
||||
static foo(y) {
|
||||
assert.equal(y, 'x');
|
||||
return y + 'y';
|
||||
}
|
||||
|
||||
constructor(x) {
|
||||
assert.equal(x, 2);
|
||||
this._a = 1;
|
||||
}
|
||||
|
||||
bar(x) {
|
||||
assert.equal(x, true);
|
||||
return 3.2;
|
||||
}
|
||||
|
||||
get a() {
|
||||
return this._a;
|
||||
}
|
||||
set a(v) {
|
||||
this._a = v;
|
||||
}
|
||||
};
|
40
tests/wasm/final.rs
Normal file
40
tests/wasm/final.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type Math;
|
||||
#[wasm_bindgen(static_method_of = Math, final)]
|
||||
fn log(f: f32) -> f32;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "tests/wasm/final.js")]
|
||||
extern "C" {
|
||||
type MyType;
|
||||
#[wasm_bindgen(constructor, final)]
|
||||
fn new(x: u32) -> MyType;
|
||||
#[wasm_bindgen(static_method_of = MyType, final)]
|
||||
fn foo(a: &str) -> String;
|
||||
#[wasm_bindgen(method, final)]
|
||||
fn bar(this: &MyType, arg: bool) -> f32;
|
||||
|
||||
#[wasm_bindgen(method, getter, final)]
|
||||
fn a(this: &MyType) -> u32;
|
||||
#[wasm_bindgen(method, setter, final)]
|
||||
fn set_a(this: &MyType, a: u32);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn simple() {
|
||||
assert_eq!(Math::log(1.0), 0.0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn classes() {
|
||||
assert_eq!(MyType::foo("x"), "xy");
|
||||
let x = MyType::new(2);
|
||||
assert_eq!(x.bar(true), 3.2);
|
||||
assert_eq!(x.a(), 1);
|
||||
x.set_a(3);
|
||||
assert_eq!(x.a(), 3);
|
||||
}
|
@ -131,3 +131,9 @@ exports.CatchConstructors = class {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.StaticStructural = class {
|
||||
static static_structural(x) {
|
||||
return x + 3;
|
||||
}
|
||||
};
|
||||
|
@ -32,12 +32,12 @@ extern "C" {
|
||||
fn switch_methods_a();
|
||||
fn switch_methods_b();
|
||||
type SwitchMethods;
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[wasm_bindgen(constructor, final)]
|
||||
fn new() -> SwitchMethods;
|
||||
#[wasm_bindgen(js_namespace = SwitchMethods)]
|
||||
#[wasm_bindgen(js_namespace = SwitchMethods, final)]
|
||||
fn a();
|
||||
fn switch_methods_called() -> bool;
|
||||
#[wasm_bindgen(method)]
|
||||
#[wasm_bindgen(method, final)]
|
||||
fn b(this: &SwitchMethods);
|
||||
|
||||
type Properties;
|
||||
@ -84,6 +84,10 @@ extern "C" {
|
||||
type CatchConstructors;
|
||||
#[wasm_bindgen(constructor, catch)]
|
||||
fn new(x: u32) -> Result<CatchConstructors, JsValue>;
|
||||
|
||||
type StaticStructural;
|
||||
#[wasm_bindgen(static_method_of = StaticStructural, structural)]
|
||||
fn static_structural(a: u32) -> u32;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -136,7 +140,7 @@ fn switch_methods() {
|
||||
SwitchMethods::new().b();
|
||||
assert!(switch_methods_called());
|
||||
|
||||
switch_methods_a();
|
||||
switch_methods_b();
|
||||
|
||||
assert!(!switch_methods_called());
|
||||
SwitchMethods::new().b();
|
||||
@ -213,3 +217,8 @@ fn catch_constructors() {
|
||||
assert!(CatchConstructors::new(0).is_err());
|
||||
assert!(CatchConstructors::new(1).is_ok());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn static_structural() {
|
||||
assert_eq!(StaticStructural::static_structural(30), 33);
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ pub mod comments;
|
||||
pub mod duplicate_deps;
|
||||
pub mod duplicates;
|
||||
pub mod enums;
|
||||
#[path = "final.rs"]
|
||||
pub mod final_;
|
||||
pub mod import_class;
|
||||
pub mod imports;
|
||||
pub mod js_objects;
|
||||
|
Loading…
x
Reference in New Issue
Block a user