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:
Alex Crichton 2018-11-12 10:59:46 -06:00 committed by GitHub
commit dc4e78550a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 618 additions and 246 deletions

View File

@ -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 {

View File

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

View File

@ -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(

View File

@ -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\

View File

@ -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>

View File

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

View File

@ -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]

View File

@ -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]

View File

@ -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,

View 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);
}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(

View File

@ -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)
--------------------------------------------------------------------------------

View 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.

View File

@ -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

View 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
View 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
View 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);
}

View File

@ -131,3 +131,9 @@ exports.CatchConstructors = class {
}
}
};
exports.StaticStructural = class {
static static_structural(x) {
return x + 3;
}
};

View File

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

View File

@ -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;