From 5b76a6291e5083b24f96b96a4939f18ec9d9d4df Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 10:58:55 -0800 Subject: [PATCH 01/10] Implement `Deref` for all imported JS types This commit implements the first half of [RFC #5] where the `Deref` trait is implemented for all imported types. The target of `Deref` is either the first entry of the list of `extends` attribute or `JsValue`. All examples using `.as_ref()` with various `web-sys` types have been updated to the more ergonomic deref casts now. Additionally the `web-sys` generation of the `extends` array has been fixed slightly to explicitly list implementatoins in the hierarchy order to ensure the correct target for `Deref` is chosen. [RFC #5]: https://github.com/rustwasm/rfcs/blob/master/text/005-structural-and-deref.md --- crates/backend/src/codegen.rs | 22 +++++++++-- crates/web-sys/tests/wasm/main.rs | 11 +++++- crates/webidl/src/first_pass.rs | 17 ++++++--- examples/dom/src/lib.rs | 5 +-- examples/paint/src/lib.rs | 47 ++++++++++------------- examples/raytrace-parallel/src/lib.rs | 6 +-- examples/webaudio/src/lib.rs | 38 +++++++----------- examples/webgl/src/lib.rs | 25 ++++++------ guide/src/SUMMARY.md | 1 + guide/src/web-sys/inheritance.md | 55 +++++++++++++++++++++++++++ 10 files changed, 146 insertions(+), 81 deletions(-) create mode 100644 guide/src/web-sys/inheritance.md diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 3df58c40..b812ce24 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -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 = ::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 = ::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 { diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index 71d88fff..5d72af04 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -7,7 +7,8 @@ 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::{JsValue, JsCast}; +use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -56,3 +57,11 @@ pub mod table_element; pub mod title_element; pub mod xpath_result; pub mod indexeddb; + +#[wasm_bindgen_test] +fn deref_works() { + let x = JsValue::from(3); + let x = x.unchecked_into::(); + let y: &web_sys::XmlHttpRequestEventTarget = &x; + drop(y); +} diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 0fc18c3a..663c726f 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -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 + '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) { + fn fill_superclasses( + &self, + interface: &str, + set: &mut BTreeSet<&'a str>, + list: &mut Vec, + ) { 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); } } } diff --git a/examples/dom/src/lib.rs b/examples/dom/src/lib.rs index acf796f8..a30af3bc 100644 --- a/examples/dom/src/lib.rs +++ b/examples/dom/src/lib.rs @@ -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::::as_ref(&body).append_child(val.as_ref())?; + body.append_child(&val)?; Ok(()) } diff --git a/examples/paint/src/lib.rs b/examples/paint/src/lib.rs index b842bac8..67910de9 100644 --- a/examples/paint/src/lib.rs +++ b/examples/paint/src/lib.rs @@ -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::() - .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::()?; + 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::() - .unwrap(); + .dyn_into::()?; 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); - (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); - (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); - (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(()) } diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index c8d4a3ff..db4ca103 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -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(()) } } diff --git a/examples/webaudio/src/lib.rs b/examples/webaudio/src/lib.rs index e518ce7e..fa283192 100644 --- a/examples/webaudio/src/lib.rs +++ b/examples/webaudio/src/lib.rs @@ -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()?; diff --git a/examples/webgl/src/lib.rs b/examples/webgl/src/lib.rs index 5e338fa2..f65b8d59 100644 --- a/examples/webgl/src/lib.rs +++ b/examples/webgl/src/lib.rs @@ -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::() - .map_err(|_| ()) - .unwrap(); + .dyn_into::()?; let context = canvas - .get_context("webgl") + .get_context("webgl")? .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); + .dyn_into::()?; 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::().unwrap().buffer(); + let memory_buffer = wasm_bindgen::memory().dyn_into::()?.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( diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 1faa7077..47f9ec9b 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -83,6 +83,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) -------------------------------------------------------------------------------- diff --git a/guide/src/web-sys/inheritance.md b/guide/src/web-sys/inheritance.md new file mode 100644 index 00000000..211117f3 --- /dev/null +++ b/guide/src/web-sys/inheritance.md @@ -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 for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef for HtmlAnchorElement +impl AsRef 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 From b013ec6288796b96c91a14c873065ba4da7bffc7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 11:46:52 -0800 Subject: [PATCH 02/10] Use splat instead of `arguments` in tests Previously `arguments` was used to pass around an array of arguments, but this wasn't actually a `js_sys::Array` but rather a somewhat esoteric internal object. When switching over `Array` methods to be `structural` this caused issues because the inherent methods on an `arguments` object were different than that of `js_sys::Array`. --- .../bin/wasm-bindgen-test-runner/index-headless.html | 12 ++++++------ crates/cli/src/bin/wasm-bindgen-test-runner/node.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/index-headless.html b/crates/cli/src/bin/wasm-bindgen-test-runner/index-headless.html index c7eed7bb..d671a205 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/index-headless.html +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/index-headless.html @@ -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(); diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs index 537b0481..d94d8e92 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs @@ -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); }} }}; From 64a624196005eb1e2b321a558cac8a79dba94254 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 11:50:34 -0800 Subject: [PATCH 03/10] Fix tagging static methods as `structural` --- crates/cli-support/src/js/mod.rs | 9 ++++++--- tests/wasm/import_class.js | 6 ++++++ tests/wasm/import_class.rs | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f5349db3..872dc020 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1979,17 +1979,20 @@ impl<'a, 'b> SubContext<'a, 'b> { match &op.kind { decode::OperationKind::Regular => { let nargs = descriptor.unwrap_function().arguments.len(); + let nargs = nargs - if op.is_static { 0 } else { 1 }; let mut s = format!("function("); - for i in 0..nargs - 1 { + for i in 0..nargs { if i > 0 { drop(write!(s, ", ")); } drop(write!(s, "x{}", i)); } - s.push_str(") { \nreturn this."); + s.push_str(") { \nreturn "); + s.push_str(&location); + s.push_str("."); s.push_str(&import.function.name); s.push_str("("); - for i in 0..nargs - 1 { + for i in 0..nargs { if i > 0 { drop(write!(s, ", ")); } diff --git a/tests/wasm/import_class.js b/tests/wasm/import_class.js index fa60847c..ebc29759 100644 --- a/tests/wasm/import_class.js +++ b/tests/wasm/import_class.js @@ -131,3 +131,9 @@ exports.CatchConstructors = class { } } }; + +exports.StaticStructural = class { + static static_structural(x) { + return x + 3; + } +}; diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 60b5181c..906147b1 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -84,6 +84,10 @@ extern "C" { type CatchConstructors; #[wasm_bindgen(constructor, catch)] fn new(x: u32) -> Result; + + type StaticStructural; + #[wasm_bindgen(static_method_of = StaticStructural, structural)] + fn static_structural(a: u32) -> u32; } #[wasm_bindgen] @@ -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); +} From 58c3a99f94d2e5c41faae50016284d7faf7c8b06 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 12:30:40 -0800 Subject: [PATCH 04/10] Fix a test to actually test the right property This was a copy/paste typo! --- tests/wasm/import_class.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 906147b1..8af90e0e 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -140,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(); From a16b4dd9a491d4307fb4a07a1226299ebff2532b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 12:31:39 -0800 Subject: [PATCH 05/10] Optimize shim generation for `structural` items This commit removes shims, where possible, for `structural` items. Instead of generating code that looks like: const target = function() { this.foo(); }; exports.__wbg_thing = function(a) { target.call(getObject(a)); }; we now instead generate: exports.__wbg_thing = function(a) { getObject(a).foo(); }; Note that this only applies to `structural` bindings, all default bindings (as of this commit) are still using imported targets to ensure that their binding can't change after instantiation. This change was [detailed in RFC #5][link] as an important optimization for `structural` bindings to ensure they've got performance parity with today's non-`structural` default bindings. [link]: https://rustwasm.github.io/rfcs/005-structural-and-deref.html#why-is-it-ok-to-make-structural-the-default --- crates/cli-support/src/js/mod.rs | 188 ++++++++++++--------------- crates/cli-support/src/js/rust2js.rs | 111 ++++++++++++---- crates/js-sys/tests/wasm/Object.rs | 2 +- crates/js-sys/tests/wasm/Reflect.rs | 2 +- 4 files changed, 167 insertions(+), 136 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 872dc020..6fe7257c 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -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), + StructuralSetter(Option, String), + StructuralIndexingGetter(Option), + StructuralIndexingSetter(Option), + StructuralIndexingDeleter(Option), +} + const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"]; impl<'a> Context<'a> { @@ -1933,7 +1944,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) @@ -1948,140 +1959,103 @@ impl<'a, 'b> SubContext<'a, 'b> { &mut self, info: &decode::Import<'b>, import: &decode::ImportFunction, - descriptor: &Descriptor, - ) -> Result { + ) -> Result { 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 nargs = nargs - if op.is_static { 0 } else { 1 }; - let mut s = format!("function("); - for i in 0..nargs { - 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 "); - s.push_str(&location); - s.push_str("."); - s.push_str(&import.function.name); - s.push_str("("); - for i in 0..nargs { - 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( diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 668b31e1..769df33c 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -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 @@ -499,7 +499,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> { Ok(()) } - pub fn finish(&self, invoc: &str) -> Result { + pub fn finish(&self, invoc: &ImportTarget) -> Result { 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, 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\ diff --git a/crates/js-sys/tests/wasm/Object.rs b/crates/js-sys/tests/wasm/Object.rs index e89d16f5..cf49b7ac 100644 --- a/crates/js-sys/tests/wasm/Object.rs +++ b/crates/js-sys/tests/wasm/Object.rs @@ -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] diff --git a/crates/js-sys/tests/wasm/Reflect.rs b/crates/js-sys/tests/wasm/Reflect.rs index b2041136..9a7b8fb1 100644 --- a/crates/js-sys/tests/wasm/Reflect.rs +++ b/crates/js-sys/tests/wasm/Reflect.rs @@ -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] From 6093fd29d1fe81ca227b6bab062a69803b744e57 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 13:06:03 -0800 Subject: [PATCH 06/10] Don't convert boolean arguments going to wasm The wasm spec defines boolean conversion when crossing to the wasm type i32 as 1 for `true` and 0 for `false`, so no need for us to do it ourselves! --- crates/cli-support/src/js/js2rust.rs | 2 +- crates/cli-support/src/js/rust2js.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index d90fb5e3..701edeeb 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -401,7 +401,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())); diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 769df33c..6e6cc246 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -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: {:?}", From 4c42aba007a2baf8ad2cf58f6b4cdfb32c686dfa Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 13:18:58 -0800 Subject: [PATCH 07/10] Switch all imports to `structural` by default This commit switches all imports of JS methods to `structural` by default. Proposed in [RFC 5] this should increase the performance of bindings today while also providing future-proofing for possible confusion with the recent addition of the `Deref` trait for all imported types by default as well. A new attribute, `host_binding`, is introduced in this PR as well to recover the old behavior of binding directly to an imported function which will one day be the precise function on the prototype. Eventually `web-sys` will switcsh over entirely to being driven via `host_binding` methods, but for now it's been measured to be not quite as fast so we're not making that switch yet. Note that `host_binding` differs from the proposed name of `final` due to the controversy, and its hoped that `host_binding` is a good middle-ground! [RFC 5]: https://rustwasm.github.io/rfcs/005-structural-and-deref.html --- crates/macro-support/src/parser.rs | 14 +- guide/src/SUMMARY.md | 1 + .../attributes/on-js-imports/host_binding.md | 139 ++++++++++++++++++ .../attributes/on-js-imports/structural.md | 22 ++- tests/wasm/host_binding.js | 25 ++++ tests/wasm/host_binding.rs | 40 +++++ tests/wasm/import_class.rs | 6 +- tests/wasm/main.rs | 1 + 8 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 guide/src/reference/attributes/on-js-imports/host_binding.md create mode 100644 tests/wasm/host_binding.js create mode 100644 tests/wasm/host_binding.rs diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 23dc483f..f244a9cd 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -149,6 +149,14 @@ impl BindgenAttrs { }) } + /// Whether the `host_binding` attribute is present + fn host_binding(&self) -> bool { + self.attrs.iter().any(|a| match *a { + BindgenAttr::HostBinding => true, + _ => false, + }) + } + /// 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, + HostBinding, Readonly, JsName(String, Span), JsClass(String), @@ -262,6 +271,9 @@ impl Parse for BindgenAttr { if attr == "structural" { return Ok(BindgenAttr::Structural); } + if attr == "host_binding" { + return Ok(BindgenAttr::HostBinding); + } if attr == "readonly" { return Ok(BindgenAttr::Readonly); } @@ -549,7 +561,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn js_ret, catch, variadic, - structural: opts.structural(), + structural: opts.structural() || !opts.host_binding(), rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), doc_comment: None, diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 47f9ec9b..2db0c17f 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -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) + - [`host_binding`](./reference/attributes/on-js-imports/host_binding.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) diff --git a/guide/src/reference/attributes/on-js-imports/host_binding.md b/guide/src/reference/attributes/on-js-imports/host_binding.md new file mode 100644 index 00000000..734bf48f --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/host_binding.md @@ -0,0 +1,139 @@ +# `host_binding` + +The `host_binding` attribute is the converse of the [`structural` +attribute](structural.html). It configures how `wasm-bindgen` will generate JS +glue to call the imported function. The naming here is intended convey that this +attribute is intended to implement the semantics of the future [host bindings +proposal][host-bindings] for WebAssembly. + +[host-bindings]: https://github.com/WebAssembly/host-bindings +[reference-types]: https://github.com/WebAssembly/reference-types + +The `host_binding` attribute is intended to be purely related to performance. It +ideally has no user-visible effect, and well-typed `structural` imports (the +default) should be able to transparently switch to `host_binding` eventually. + +The eventual performance aspect is that with the [host bindings +proposal][host-bindings] then `wasm-bindgen` will need to generate far fewer JS +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 `host_binding` attribute** the generated JS looks like this: + +```js +// without `host_binding` +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 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, host_binding)] // 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 shim here even with `host_binding`! 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 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 shim at all necessary to call the +imported function! diff --git a/guide/src/reference/attributes/on-js-imports/structural.md b/guide/src/reference/attributes/on-js-imports/structural.md index bdcfef60..4cc20807 100644 --- a/guide/src/reference/attributes/on-js-imports/structural.md +++ b/guide/src/reference/attributes/on-js-imports/structural.md @@ -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 `host_binding` +> attribute](host_binding.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 diff --git a/tests/wasm/host_binding.js b/tests/wasm/host_binding.js new file mode 100644 index 00000000..15b576d0 --- /dev/null +++ b/tests/wasm/host_binding.js @@ -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; + } +}; diff --git a/tests/wasm/host_binding.rs b/tests/wasm/host_binding.rs new file mode 100644 index 00000000..951cfb86 --- /dev/null +++ b/tests/wasm/host_binding.rs @@ -0,0 +1,40 @@ +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen] +extern "C" { + type Math; + #[wasm_bindgen(static_method_of = Math, host_binding)] + fn log(f: f32) -> f32; +} + +#[wasm_bindgen(module = "tests/wasm/host_binding.js")] +extern "C" { + type MyType; + #[wasm_bindgen(constructor, host_binding)] + fn new(x: u32) -> MyType; + #[wasm_bindgen(static_method_of = MyType, host_binding)] + fn foo(a: &str) -> String; + #[wasm_bindgen(method, host_binding)] + fn bar(this: &MyType, arg: bool) -> f32; + + #[wasm_bindgen(method, getter, host_binding)] + fn a(this: &MyType) -> u32; + #[wasm_bindgen(method, setter, host_binding)] + 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); +} diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 8af90e0e..34a301e5 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -32,12 +32,12 @@ extern "C" { fn switch_methods_a(); fn switch_methods_b(); type SwitchMethods; - #[wasm_bindgen(constructor)] + #[wasm_bindgen(constructor, host_binding)] fn new() -> SwitchMethods; - #[wasm_bindgen(js_namespace = SwitchMethods)] + #[wasm_bindgen(js_namespace = SwitchMethods, host_binding)] fn a(); fn switch_methods_called() -> bool; - #[wasm_bindgen(method)] + #[wasm_bindgen(method, host_binding)] fn b(this: &SwitchMethods); type Properties; diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index 4ea2604d..4c70f75e 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -18,6 +18,7 @@ pub mod comments; pub mod duplicate_deps; pub mod duplicates; pub mod enums; +pub mod host_binding; pub mod import_class; pub mod imports; pub mod js_objects; From 8ba467e905668e6193596720f90733657c5d0811 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 16:47:46 -0800 Subject: [PATCH 08/10] Disallow both `structural` and `host_binding` --- crates/macro-support/src/parser.rs | 21 ++++++++++++------- .../ui-tests/structural-and-host-binding.rs | 11 ++++++++++ .../structural-and-host-binding.stderr | 8 +++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 crates/macro/ui-tests/structural-and-host-binding.rs create mode 100644 crates/macro/ui-tests/structural-and-host-binding.stderr diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index f244a9cd..928ec8da 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -150,11 +150,11 @@ impl BindgenAttrs { } /// Whether the `host_binding` attribute is present - fn host_binding(&self) -> bool { - self.attrs.iter().any(|a| match *a { - BindgenAttr::HostBinding => true, - _ => false, - }) + fn host_binding(&self) -> Option<&Ident> { + self.attrs.iter().filter_map(|a| match a { + BindgenAttr::HostBinding(i) => Some(i), + _ => None, + }).next() } /// Whether the readonly attributes is present @@ -237,7 +237,7 @@ pub enum BindgenAttr { IndexingSetter, IndexingDeleter, Structural, - HostBinding, + HostBinding(Ident), Readonly, JsName(String, Span), JsClass(String), @@ -272,7 +272,7 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::Structural); } if attr == "host_binding" { - return Ok(BindgenAttr::HostBinding); + return Ok(BindgenAttr::HostBinding(attr)) } if attr == "readonly" { return Ok(BindgenAttr::Readonly); @@ -555,13 +555,18 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn ShortHash(data) ) }; + if let Some(ident) = opts.host_binding() { + if opts.structural() { + bail_span!(ident, "cannot specify both `structural` and `host_binding`"); + } + } Ok(ast::ImportKind::Function(ast::ImportFunction { function: wasm, kind, js_ret, catch, variadic, - structural: opts.structural() || !opts.host_binding(), + structural: opts.structural() || opts.host_binding().is_none(), rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), doc_comment: None, diff --git a/crates/macro/ui-tests/structural-and-host-binding.rs b/crates/macro/ui-tests/structural-and-host-binding.rs new file mode 100644 index 00000000..68bfccb4 --- /dev/null +++ b/crates/macro/ui-tests/structural-and-host-binding.rs @@ -0,0 +1,11 @@ +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + type Foo; + + #[wasm_bindgen(method, structural, host_binding)] + fn bar(this: &Foo); +} diff --git a/crates/macro/ui-tests/structural-and-host-binding.stderr b/crates/macro/ui-tests/structural-and-host-binding.stderr new file mode 100644 index 00000000..66e6ef67 --- /dev/null +++ b/crates/macro/ui-tests/structural-and-host-binding.stderr @@ -0,0 +1,8 @@ +error: cannot specify both `structural` and `host_binding` + --> $DIR/structural-and-host-binding.rs:9:40 + | +9 | #[wasm_bindgen(method, structural, host_binding)] + | ^^^^^^^^^^^^ + +error: aborting due to previous error + From 2c9084d0e246af98f0555dd7093b8d9a3bcee5d2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 8 Nov 2018 16:51:14 -0800 Subject: [PATCH 09/10] Update web-sys test to only test compilation --- crates/web-sys/tests/wasm/main.rs | 11 ++-- .../{host_binding.md => final.md} | 55 ++++++++++++------- .../attributes/on-js-imports/structural.md | 4 +- 3 files changed, 43 insertions(+), 27 deletions(-) rename guide/src/reference/attributes/on-js-imports/{host_binding.md => final.md} (68%) diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index 5d72af04..0845e3fb 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -7,7 +7,6 @@ extern crate wasm_bindgen_futures; extern crate wasm_bindgen_test; extern crate web_sys; -use wasm_bindgen::{JsValue, JsCast}; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -60,8 +59,10 @@ pub mod indexeddb; #[wasm_bindgen_test] fn deref_works() { - let x = JsValue::from(3); - let x = x.unchecked_into::(); - let y: &web_sys::XmlHttpRequestEventTarget = &x; - drop(y); + 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; + } } diff --git a/guide/src/reference/attributes/on-js-imports/host_binding.md b/guide/src/reference/attributes/on-js-imports/final.md similarity index 68% rename from guide/src/reference/attributes/on-js-imports/host_binding.md rename to guide/src/reference/attributes/on-js-imports/final.md index 734bf48f..0abfa95b 100644 --- a/guide/src/reference/attributes/on-js-imports/host_binding.md +++ b/guide/src/reference/attributes/on-js-imports/final.md @@ -1,21 +1,23 @@ -# `host_binding` +# `final` -The `host_binding` attribute is the converse of the [`structural` +The `final` attribute is the converse of the [`structural` attribute](structural.html). It configures how `wasm-bindgen` will generate JS -glue to call the imported function. The naming here is intended convey that this -attribute is intended to implement the semantics of the future [host bindings -proposal][host-bindings] for WebAssembly. +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 `host_binding` attribute is intended to be purely related to performance. It -ideally has no user-visible effect, and well-typed `structural` imports (the -default) should be able to transparently switch to `host_binding` eventually. +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 `host_binding` eventually. The eventual performance aspect is that with the [host bindings proposal][host-bindings] then `wasm-bindgen` will need to generate far fewer JS -shims to import than it does today. For example, consider this import today: +functino shims to import than it does today. For example, consider this import +today: ```rust #[wasm_bindgen] @@ -26,20 +28,20 @@ extern { } ``` -**Without the `host_binding` attribute** the generated JS looks like this: +**Without the `final` attribute** the generated JS looks like this: ```js -// without `host_binding` +// 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 shim is required, but it's all relatively +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. +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: @@ -47,7 +49,7 @@ If we instead, however, write this: #[wasm_bindgen] extern { type Foo; - #[wasm_bindgen(method, host_binding)] // note the change here + #[wasm_bindgen(method, final)] // note the change here fn bar(this: &Foo, argument: &str) -> JsValue; } ``` @@ -68,7 +70,7 @@ 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 shim here even with `host_binding`! That's true, +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 @@ -77,8 +79,8 @@ runtime when the function is called. ## Interaction with future proposals -If you're curious to see how our JS shim will be eliminated entirely, let's take -a look at the generated bindings. We're starting off with this: +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; @@ -135,5 +137,18 @@ 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 shim at all necessary to call the -imported function! +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. diff --git a/guide/src/reference/attributes/on-js-imports/structural.md b/guide/src/reference/attributes/on-js-imports/structural.md index 4cc20807..07fbfe01 100644 --- a/guide/src/reference/attributes/on-js-imports/structural.md +++ b/guide/src/reference/attributes/on-js-imports/structural.md @@ -4,8 +4,8 @@ > functions. This attribute is largely ignored today and is only retained for > backwards compatibility and learning purposes. > -> The inverse of this attribute, [the `host_binding` -> attribute](host_binding.html) is more functionally interesting than +> 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 From cb246e38fb684e675835f8648f88e146e002af71 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 9 Nov 2018 07:59:48 -0800 Subject: [PATCH 10/10] Rename `host_binding` to `final` --- crates/macro-support/src/parser.rs | 21 ++++++++++--------- ...ost-binding.rs => structural-and-final.rs} | 2 +- .../ui-tests/structural-and-final.stderr | 8 +++++++ .../structural-and-host-binding.stderr | 8 ------- guide/src/SUMMARY.md | 2 +- .../attributes/on-js-imports/final.md | 2 +- tests/wasm/{host_binding.js => final.js} | 0 tests/wasm/{host_binding.rs => final.rs} | 14 ++++++------- tests/wasm/import_class.rs | 6 +++--- tests/wasm/main.rs | 3 ++- 10 files changed, 34 insertions(+), 32 deletions(-) rename crates/macro/ui-tests/{structural-and-host-binding.rs => structural-and-final.rs} (70%) create mode 100644 crates/macro/ui-tests/structural-and-final.stderr delete mode 100644 crates/macro/ui-tests/structural-and-host-binding.stderr rename tests/wasm/{host_binding.js => final.js} (100%) rename tests/wasm/{host_binding.rs => final.rs} (63%) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 928ec8da..776a7d4a 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -149,10 +149,10 @@ impl BindgenAttrs { }) } - /// Whether the `host_binding` attribute is present - fn host_binding(&self) -> Option<&Ident> { + /// Whether the `final` attribute is present + fn final_(&self) -> Option<&Ident> { self.attrs.iter().filter_map(|a| match a { - BindgenAttr::HostBinding(i) => Some(i), + BindgenAttr::Final(i) => Some(i), _ => None, }).next() } @@ -237,7 +237,7 @@ pub enum BindgenAttr { IndexingSetter, IndexingDeleter, Structural, - HostBinding(Ident), + Final(Ident), Readonly, JsName(String, Span), JsClass(String), @@ -249,7 +249,8 @@ pub enum BindgenAttr { impl Parse for BindgenAttr { fn parse(input: ParseStream) -> SynResult { 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); } @@ -271,8 +272,8 @@ impl Parse for BindgenAttr { if attr == "structural" { return Ok(BindgenAttr::Structural); } - if attr == "host_binding" { - return Ok(BindgenAttr::HostBinding(attr)) + if attr == "final" { + return Ok(BindgenAttr::Final(attr)) } if attr == "readonly" { return Ok(BindgenAttr::Readonly); @@ -555,9 +556,9 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn ShortHash(data) ) }; - if let Some(ident) = opts.host_binding() { + if let Some(ident) = opts.final_() { if opts.structural() { - bail_span!(ident, "cannot specify both `structural` and `host_binding`"); + bail_span!(ident, "cannot specify both `structural` and `final`"); } } Ok(ast::ImportKind::Function(ast::ImportFunction { @@ -566,7 +567,7 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn js_ret, catch, variadic, - structural: opts.structural() || opts.host_binding().is_none(), + structural: opts.structural() || opts.final_().is_none(), rust_name: self.ident.clone(), shim: Ident::new(&shim, Span::call_site()), doc_comment: None, diff --git a/crates/macro/ui-tests/structural-and-host-binding.rs b/crates/macro/ui-tests/structural-and-final.rs similarity index 70% rename from crates/macro/ui-tests/structural-and-host-binding.rs rename to crates/macro/ui-tests/structural-and-final.rs index 68bfccb4..5eb65220 100644 --- a/crates/macro/ui-tests/structural-and-host-binding.rs +++ b/crates/macro/ui-tests/structural-and-final.rs @@ -6,6 +6,6 @@ use wasm_bindgen::prelude::*; extern "C" { type Foo; - #[wasm_bindgen(method, structural, host_binding)] + #[wasm_bindgen(method, structural, final)] fn bar(this: &Foo); } diff --git a/crates/macro/ui-tests/structural-and-final.stderr b/crates/macro/ui-tests/structural-and-final.stderr new file mode 100644 index 00000000..405f65f1 --- /dev/null +++ b/crates/macro/ui-tests/structural-and-final.stderr @@ -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 + diff --git a/crates/macro/ui-tests/structural-and-host-binding.stderr b/crates/macro/ui-tests/structural-and-host-binding.stderr deleted file mode 100644 index 66e6ef67..00000000 --- a/crates/macro/ui-tests/structural-and-host-binding.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error: cannot specify both `structural` and `host_binding` - --> $DIR/structural-and-host-binding.rs:9:40 - | -9 | #[wasm_bindgen(method, structural, host_binding)] - | ^^^^^^^^^^^^ - -error: aborting due to previous error - diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 2db0c17f..952f5b77 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -61,7 +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) - - [`host_binding`](./reference/attributes/on-js-imports/host_binding.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) diff --git a/guide/src/reference/attributes/on-js-imports/final.md b/guide/src/reference/attributes/on-js-imports/final.md index 0abfa95b..8dc19975 100644 --- a/guide/src/reference/attributes/on-js-imports/final.md +++ b/guide/src/reference/attributes/on-js-imports/final.md @@ -12,7 +12,7 @@ prototype chain of an object. 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 `host_binding` eventually. +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 diff --git a/tests/wasm/host_binding.js b/tests/wasm/final.js similarity index 100% rename from tests/wasm/host_binding.js rename to tests/wasm/final.js diff --git a/tests/wasm/host_binding.rs b/tests/wasm/final.rs similarity index 63% rename from tests/wasm/host_binding.rs rename to tests/wasm/final.rs index 951cfb86..63aa661f 100644 --- a/tests/wasm/host_binding.rs +++ b/tests/wasm/final.rs @@ -4,23 +4,23 @@ use wasm_bindgen_test::*; #[wasm_bindgen] extern "C" { type Math; - #[wasm_bindgen(static_method_of = Math, host_binding)] + #[wasm_bindgen(static_method_of = Math, final)] fn log(f: f32) -> f32; } -#[wasm_bindgen(module = "tests/wasm/host_binding.js")] +#[wasm_bindgen(module = "tests/wasm/final.js")] extern "C" { type MyType; - #[wasm_bindgen(constructor, host_binding)] + #[wasm_bindgen(constructor, final)] fn new(x: u32) -> MyType; - #[wasm_bindgen(static_method_of = MyType, host_binding)] + #[wasm_bindgen(static_method_of = MyType, final)] fn foo(a: &str) -> String; - #[wasm_bindgen(method, host_binding)] + #[wasm_bindgen(method, final)] fn bar(this: &MyType, arg: bool) -> f32; - #[wasm_bindgen(method, getter, host_binding)] + #[wasm_bindgen(method, getter, final)] fn a(this: &MyType) -> u32; - #[wasm_bindgen(method, setter, host_binding)] + #[wasm_bindgen(method, setter, final)] fn set_a(this: &MyType, a: u32); } diff --git a/tests/wasm/import_class.rs b/tests/wasm/import_class.rs index 34a301e5..10ce84ca 100644 --- a/tests/wasm/import_class.rs +++ b/tests/wasm/import_class.rs @@ -32,12 +32,12 @@ extern "C" { fn switch_methods_a(); fn switch_methods_b(); type SwitchMethods; - #[wasm_bindgen(constructor, host_binding)] + #[wasm_bindgen(constructor, final)] fn new() -> SwitchMethods; - #[wasm_bindgen(js_namespace = SwitchMethods, host_binding)] + #[wasm_bindgen(js_namespace = SwitchMethods, final)] fn a(); fn switch_methods_called() -> bool; - #[wasm_bindgen(method, host_binding)] + #[wasm_bindgen(method, final)] fn b(this: &SwitchMethods); type Properties; diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index 4c70f75e..e0793a78 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -18,7 +18,8 @@ pub mod comments; pub mod duplicate_deps; pub mod duplicates; pub mod enums; -pub mod host_binding; +#[path = "final.rs"] +pub mod final_; pub mod import_class; pub mod imports; pub mod js_objects;