From bea07abd0f51980ae7e80c871c4e8129b0f768dd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Aug 2018 09:02:31 -0700 Subject: [PATCH 1/5] Add a `JsCast` trait specified in [RFC 2] [RFC 2]: https://github.com/rustwasm/rfcs/pull/2 --- src/cast.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 19 ++++++ 2 files changed, 184 insertions(+) create mode 100644 src/cast.rs diff --git a/src/cast.rs b/src/cast.rs new file mode 100644 index 00000000000..83393de7f65 --- /dev/null +++ b/src/cast.rs @@ -0,0 +1,165 @@ +use JsValue; + +/// A trait for checked and unchecked casting between JS types. +/// +/// Specified [in an RFC][rfc] this trait is intended to provide support for +/// casting JS values between differnet types of one another. In JS there aren't +/// many static types but we've ascribed JS values with static types in Rust, +/// yet they often need to be switched to other types temporarily! This trait +/// provides both checked and unchecked casting into various kinds of values. +/// +/// This trait is automatically implemented for any type imported in a +/// `#[wasm_bindgen]` `extern` block. +/// +/// [rfc]: https://github.com/rustwasm/rfcs/pull/2 +pub trait JsCast +where + Self: AsRef + AsMut + Into, +{ + /// Test whether this JS value is an instance of the type `T`. + /// + /// This commit performs a dynamic check (at runtime) using the JS + /// `instanceof` operator. This method returns `self instanceof T`. + fn is_instance_of(&self) -> bool + where + T: JsCast, + { + T::instanceof(self.as_ref()) + } + + /// Performs a dynamic cast (checked at runtime) of this value into the + /// target type `T`. + /// + /// This method will return `Err(self)` is `self.is_instance_of::()` + /// returns `false`, and otherwise it will return `Ok(T)` manufactured with + /// an unchecked cast (verified safe via the `instanceof` operation). + fn dyn_into(self) -> Result + where + T: JsCast, + { + if self.is_instance_of::() { + Ok(self.unchecked_into()) + } else { + Err(self) + } + } + + /// Performs a dynamic cast (checked at runtime) of this value into the + /// target type `T`. + /// + /// This method will return `None` is `self.is_instance_of::()` + /// returns `false`, and otherwise it will return `Some(&T)` manufactured + /// with an unchecked cast (verified safe via the `instanceof` operation). + fn dyn_ref(&self) -> Option<&T> + where + T: JsCast, + { + if self.is_instance_of::() { + Some(self.unchecked_ref()) + } else { + None + } + } + + /// Performs a dynamic cast (checked at runtime) of this value into the + /// target type `T`. + /// + /// This method will return `None` is `self.is_instance_of::()` + /// returns `false`, and otherwise it will return `Some(&mut T)` + /// manufactured with an unchecked cast (verified safe via the `instanceof` + /// operation). + fn dyn_mut(&mut self) -> Option<&mut T> + where + T: JsCast, + { + if self.is_instance_of::() { + Some(self.unchecked_mut()) + } else { + None + } + } + + /// Performs a zero-cost unchecked cast into the specified type. + /// + /// This method will convert the `self` value to the type `T`, where both + /// `self` and `T` are simple wrappers around `JsValue`. This method **does + /// not check whether `self` is an instance of `T`**. If used incorrectly + /// then this method may cause runtime exceptions in both Rust and JS, this + /// shoudl be used with caution. + fn unchecked_into(self) -> T + where + T: JsCast, + { + T::unchecked_from_js(self.into()) + } + + /// Performs a zero-cost unchecked cast into a reference to the specified + /// type. + /// + /// This method will convert the `self` value to the type `T`, where both + /// `self` and `T` are simple wrappers around `JsValue`. This method **does + /// not check whether `self` is an instance of `T`**. If used incorrectly + /// then this method may cause runtime exceptions in both Rust and JS, this + /// shoudl be used with caution. + /// + /// This method, unlike `unchecked_into`, does not consume ownership of + /// `self` and instead works over a shared reference. + fn unchecked_ref(&self) -> &T + where + T: JsCast, + { + T::unchecked_from_js_ref(self.as_ref()) + } + + /// Performs a zero-cost unchecked cast into a mutable reference to the + /// specified type. + /// + /// This method will convert the `self` value to the type `T`, where both + /// `self` and `T` are simple wrappers around `JsValue`. This method **does + /// not check whether `self` is an instance of `T`**. If used incorrectly + /// then this method may cause runtime exceptions in both Rust and JS, this + /// shoudl be used with caution. + /// + /// This method, unlike `unchecked_into`, does not consume ownership of + /// `self` and instead works over a utable reference. + fn unchecked_mut(&mut self) -> &mut T + where + T: JsCast, + { + T::unchecked_from_js_mut(self.as_mut()) + } + + /// Performs a dynamic `instanceof` check to see whether the `JsValue` + /// provided is an instance of this type. + /// + /// This is intended to be an internal implementation detail, you likely + /// won't need to call this. + fn instanceof(val: &JsValue) -> bool; + + /// Performs a zero-cost unchecked conversion from a `JsValue` into an + /// instance of `Self` + /// + /// This is intended to be an internal implementation detail, you likely + /// won't need to call this. + fn unchecked_from_js(val: JsValue) -> Self; + + /// Performs a zero-cost unchecked conversion from a `&JsValue` into an + /// instance of `&Self`. + /// + /// Note the safety of this method, which basically means that `Self` must + /// be a newtype wrapper around `JsValue`. + /// + /// This is intended to be an internal implementation detail, you likely + /// won't need to call this. + fn unchecked_from_js_ref(val: &JsValue) -> &Self; + + /// Performs a zero-cost unchecked conversion from a `&mut JsValue` into an + /// instance of `&mut Self`. + /// + /// Note the safety of this method, which basically means that `Self` must + /// be a newtype wrapper around `JsValue`. + /// + /// This is intended to be an internal implementation detail, you likely + /// won't need to call this. + fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self; +} diff --git a/src/lib.rs b/src/lib.rs index 3fedff5bc79..2f6a2be062c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,9 @@ pub mod prelude { pub mod convert; pub mod describe; +mod cast; +pub use cast::JsCast; + if_std! { extern crate std; use std::prelude::v1::*; @@ -347,6 +350,22 @@ impl From for JsValue { } } +impl JsCast for JsValue { + // everything is a `JsValue`! + fn instanceof(_val: &JsValue) -> bool { true } + fn unchecked_from_js(val: JsValue) -> Self { val } + fn unchecked_from_js_ref(val: &JsValue) -> &Self { val } + fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self { val } +} + +impl AsMut for JsValue { + fn as_mut(&mut self) -> &mut JsValue { self } +} + +impl AsRef for JsValue { + fn as_ref(&self) -> &JsValue { self } +} + macro_rules! numbers { ($($n:ident)*) => ($( impl PartialEq<$n> for JsValue { From f3f11ed8eb571d472ca7bb93ec763ba8f9a3a920 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Aug 2018 09:10:23 -0700 Subject: [PATCH 2/5] Clean up generated code for imported types Group all the generated impls in a `const` block so we can use `use` without clashing with the outside scope. --- crates/backend/src/codegen.rs | 117 +++++++++++++++++----------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 7db3913d521..0b40e468bc4 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -519,6 +519,8 @@ impl ToTokens for ast::ImportType { None => "", Some(comment) => comment, }; + let const_name = format!("__wbg_generated_const_{}", name); + let const_name = Ident::new(&const_name, Span::call_site()); (quote! { #[allow(bad_style)] #(#attrs)* @@ -527,84 +529,85 @@ impl ToTokens for ast::ImportType { obj: ::wasm_bindgen::JsValue, } - impl ::wasm_bindgen::describe::WasmDescribe for #name { - fn describe() { - ::wasm_bindgen::JsValue::describe(); + #[allow(bad_style)] + const #const_name: () = { + use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi, Stack}; + use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; + use wasm_bindgen::convert::RefFromWasmAbi; + use wasm_bindgen::describe::WasmDescribe; + use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::__rt::core::mem::ManuallyDrop; + + impl WasmDescribe for #name { + fn describe() { + JsValue::describe(); + } } - } - impl ::wasm_bindgen::convert::IntoWasmAbi for #name { - type Abi = <::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::IntoWasmAbi>::Abi; + impl IntoWasmAbi for #name { + type Abi = ::Abi; - fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { - self.obj.into_abi(extra) + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.obj.into_abi(extra) + } } - } - impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name { - fn none() -> Self::Abi { 0 } - } + impl OptionIntoWasmAbi for #name { + fn none() -> Self::Abi { 0 } + } - impl<'a> ::wasm_bindgen::convert::OptionIntoWasmAbi for &'a #name { - fn none() -> Self::Abi { 0 } - } + impl<'a> OptionIntoWasmAbi for &'a #name { + fn none() -> Self::Abi { 0 } + } - impl ::wasm_bindgen::convert::FromWasmAbi for #name { - type Abi = <::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::FromWasmAbi>::Abi; + impl FromWasmAbi for #name { + type Abi = ::Abi; - unsafe fn from_abi( - js: Self::Abi, - extra: &mut ::wasm_bindgen::convert::Stack, - ) -> Self { - #name { - obj: ::wasm_bindgen::JsValue::from_abi(js, extra), + unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { + #name { + obj: JsValue::from_abi(js, extra), + } } } - } - impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name { - fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } - } + impl OptionFromWasmAbi for #name { + fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } + } - impl<'a> ::wasm_bindgen::convert::IntoWasmAbi for &'a #name { - type Abi = <&'a ::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::IntoWasmAbi>::Abi; + impl<'a> IntoWasmAbi for &'a #name { + type Abi = <&'a JsValue as IntoWasmAbi>::Abi; - fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { - (&self.obj).into_abi(extra) + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + (&self.obj).into_abi(extra) + } } - } - impl ::wasm_bindgen::convert::RefFromWasmAbi for #name { - type Abi = <::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::RefFromWasmAbi>::Abi; - type Anchor = ::wasm_bindgen::__rt::core::mem::ManuallyDrop<#name>; + impl RefFromWasmAbi for #name { + type Abi = ::Abi; + type Anchor = ManuallyDrop<#name>; - unsafe fn ref_from_abi( - js: Self::Abi, - extra: &mut ::wasm_bindgen::convert::Stack, - ) -> Self::Anchor { - let tmp = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::RefFromWasmAbi> - ::ref_from_abi(js, extra); - ::wasm_bindgen::__rt::core::mem::ManuallyDrop::new(#name { - obj: ::wasm_bindgen::__rt::core::mem::ManuallyDrop::into_inner(tmp), - }) + unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { + let tmp = ::ref_from_abi(js, extra); + ManuallyDrop::new(#name { + obj: ManuallyDrop::into_inner(tmp), + }) + } } - } - impl From<::wasm_bindgen::JsValue> for #name { - fn from(obj: ::wasm_bindgen::JsValue) -> #name { - #name { obj } + impl From for #name { + fn from(obj: JsValue) -> #name { + #name { obj } + } } - } - impl From<#name> for ::wasm_bindgen::JsValue { - fn from(obj: #name) -> ::wasm_bindgen::JsValue { - obj.obj + impl From<#name> for JsValue { + fn from(obj: #name) -> JsValue { + obj.obj + } } - } + + () + }; }).to_tokens(tokens); } } From 11553a1af26ee48c1ef0938f51719a0d956ccc27 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Aug 2018 09:41:59 -0700 Subject: [PATCH 3/5] Implement `JsCast` for all imported types This commit implements the `JsCast` trait automatically for all imported types in `#[wasm_bindgen] extern { ... }` blocks. The main change here was to generate an `instanceof` shim for all imported types in case it's needed. All imported types now also implement `AsRef` and `AsMut` --- crates/backend/src/ast.rs | 6 ++- crates/backend/src/codegen.rs | 49 ++++++++++++++++++++- crates/cli-support/src/js/mod.rs | 30 ++++++++++++- crates/macro-support/src/parser.rs | 4 +- crates/shared/src/lib.rs | 7 ++- crates/webidl/src/lib.rs | 1 + tests/wasm/jscast.js | 18 ++++++++ tests/wasm/jscast.rs | 68 ++++++++++++++++++++++++++++++ tests/wasm/main.rs | 1 + 9 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 tests/wasm/jscast.js create mode 100644 tests/wasm/jscast.rs diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 96c1e3ccf35..9a6d8a545f0 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -127,6 +127,7 @@ pub struct ImportType { pub name: Ident, pub attrs: Vec, pub doc_comment: Option, + pub instanceof_shim: String, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -408,7 +409,10 @@ impl ImportStatic { impl ImportType { fn shared(&self) -> shared::ImportType { - shared::ImportType {} + shared::ImportType { + name: self.name.to_string(), + instanceof_shim: self.instanceof_shim.clone(), + } } } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 0b40e468bc4..af9896725ed 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -521,10 +521,12 @@ impl ToTokens for ast::ImportType { }; let const_name = format!("__wbg_generated_const_{}", name); let const_name = Ident::new(&const_name, Span::call_site()); + let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site()); (quote! { #[allow(bad_style)] #(#attrs)* #[doc = #doc_comment] + #[repr(transparent)] #vis struct #name { obj: ::wasm_bindgen::JsValue, } @@ -533,7 +535,7 @@ impl ToTokens for ast::ImportType { const #const_name: () = { use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi, Stack}; use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; - use wasm_bindgen::convert::RefFromWasmAbi; + use wasm_bindgen::convert::{RefFromWasmAbi, GlobalStack}; use wasm_bindgen::describe::WasmDescribe; use wasm_bindgen::{JsValue, JsCast}; use wasm_bindgen::__rt::core::mem::ManuallyDrop; @@ -594,18 +596,63 @@ impl ToTokens for ast::ImportType { } } + // TODO: remove this on the next major version impl From for #name { fn from(obj: JsValue) -> #name { #name { obj } } } + impl AsRef for #name { + fn as_ref(&self) -> &JsValue { &self.obj } + } + + impl AsMut for #name { + fn as_mut(&mut self) -> &mut JsValue { &mut self.obj } + } + impl From<#name> for JsValue { fn from(obj: #name) -> JsValue { obj.obj } } + impl JsCast for #name { + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + fn instanceof(val: &JsValue) -> bool { + #[link(wasm_import_module = "__wbindgen_placeholder__")] + extern { + fn #instanceof_shim(val: u32) -> u32; + } + unsafe { + let idx = val.into_abi(&mut GlobalStack::new()); + #instanceof_shim(idx) != 0 + } + } + + #[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] + fn instanceof(val: &JsValue) -> bool { + drop(val); + panic!("cannot check instanceof on non-wasm targets"); + } + + fn unchecked_from_js(val: JsValue) -> Self { + #name { obj: val } + } + + fn unchecked_from_js_ref(val: &JsValue) -> &Self { + // Should be safe because `#name` is a transparent + // wrapper around `val` + unsafe { &*(val as *const JsValue as *const #name) } + } + + fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self { + // Should be safe because `#name` is a transparent + // wrapper around `val` + unsafe { &mut *(val as *mut JsValue as *mut #name) } + } + } + () }; }).to_tokens(tokens); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 93b818a2d37..e52e6721ec6 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1755,7 +1755,14 @@ impl<'a, 'b> SubContext<'a, 'b> { format!("failed to generate bindings for JS import `{}`", s.name) })?; } - shared::ImportKind::Type(_) => {} + shared::ImportKind::Type(ref ty) => { + self.generate_import_type(import, ty).with_context(|_| { + format!( + "failed to generate bindings for JS import `{}`", + ty.name, + ) + })?; + } shared::ImportKind::Enum(_) => {} } Ok(()) @@ -1936,6 +1943,27 @@ impl<'a, 'b> SubContext<'a, 'b> { Ok(()) } + fn generate_import_type( + &mut self, + info: &shared::Import, + import: &shared::ImportType, + ) -> Result<(), Error> { + if !self.cx.wasm_import_needed(&import.instanceof_shim) { + return Ok(()); + } + let name = self.import_name(info, &import.name)?; + self.cx.expose_get_object(); + let body = format!(" + function(idx) {{ + return getObject(idx) instanceof {} ? 1 : 0; + }} + ", + name, + ); + self.cx.export(&import.instanceof_shim, &body, None); + Ok(()) + } + fn generate_enum(&mut self, enum_: &shared::Enum) { let mut variants = String::new(); diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 2258c88f6d6..7e3c9b38919 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -524,11 +524,13 @@ impl ConvertToAst<()> for syn::ForeignItemType { type Target = ast::ImportKind; fn convert(self, (): ()) -> Result { + let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); Ok(ast::ImportKind::Type(ast::ImportType { vis: self.vis, - name: self.ident, attrs: self.attrs, doc_comment: None, + instanceof_shim: shim, + name: self.ident, })) } } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index c40ad81ca45..20d78db7169 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -3,7 +3,7 @@ #[macro_use] extern crate serde_derive; -pub const SCHEMA_VERSION: &str = "7"; +pub const SCHEMA_VERSION: &str = "8"; #[derive(Deserialize)] pub struct ProgramOnlySchema { @@ -81,7 +81,10 @@ pub struct ImportStatic { } #[derive(Deserialize, Serialize)] -pub struct ImportType {} +pub struct ImportType { + pub name: String, + pub instanceof_shim: String, +} #[derive(Deserialize, Serialize)] pub struct ImportEnum {} diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index bf749c00190..94812fd43b7 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -246,6 +246,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::InterfaceDefinition<'src> { name: rust_ident(camel_case_ident(self.identifier.0).as_str()), attrs: Vec::new(), doc_comment, + instanceof_shim: format!("__widl_instanceof_{}", self.name), }), }); diff --git a/tests/wasm/jscast.js b/tests/wasm/jscast.js new file mode 100644 index 00000000000..d93489ffb53 --- /dev/null +++ b/tests/wasm/jscast.js @@ -0,0 +1,18 @@ +class JsCast1 { + constructor() { + this.val = 1; + } + myval() { return this.val; } +} +class JsCast2 { +} +class JsCast3 extends JsCast1 { + constructor() { + super(); + this.val = 3; + } +} + +exports.JsCast1 = JsCast1; +exports.JsCast2 = JsCast2; +exports.JsCast3 = JsCast3; diff --git a/tests/wasm/jscast.rs b/tests/wasm/jscast.rs new file mode 100644 index 00000000000..d98187bec54 --- /dev/null +++ b/tests/wasm/jscast.rs @@ -0,0 +1,68 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/jscast.js", version = "*")] +extern { + type JsCast1; + #[wasm_bindgen(constructor)] + fn new() -> JsCast1; + #[wasm_bindgen(method)] + fn myval(this: &JsCast1) -> u32; + + type JsCast2; + #[wasm_bindgen(constructor)] + fn new() -> JsCast2; + + type JsCast3; + #[wasm_bindgen(constructor)] + fn new() -> JsCast3; +} + +#[wasm_bindgen_test] +fn instanceof_works() { + let a = JsCast1::new(); + let b = JsCast2::new(); + let c = JsCast3::new(); + + assert!(a.is_instance_of::()); + assert!(!a.is_instance_of::()); + assert!(!a.is_instance_of::()); + + assert!(!b.is_instance_of::()); + assert!(b.is_instance_of::()); + assert!(!b.is_instance_of::()); + + assert!(c.is_instance_of::()); + assert!(!c.is_instance_of::()); + assert!(c.is_instance_of::()); +} + +#[wasm_bindgen_test] +fn casting() { + let a = JsCast1::new(); + let b = JsCast2::new(); + let c = JsCast3::new(); + + assert!(a.dyn_ref::().is_some()); + assert!(a.dyn_ref::().is_none()); + assert!(a.dyn_ref::().is_none()); + + assert!(b.dyn_ref::().is_none()); + assert!(b.dyn_ref::().is_some()); + assert!(b.dyn_ref::().is_none()); + + assert!(c.dyn_ref::().is_some()); + assert!(c.dyn_ref::().is_none()); + assert!(c.dyn_ref::().is_some()); +} + +#[wasm_bindgen_test] +fn method_calling() { + let a = JsCast1::new(); + let b = JsCast3::new(); + + assert_eq!(a.myval(), 1); + assert_eq!(b.dyn_ref::().unwrap().myval(), 3); + assert_eq!(b.unchecked_ref::().myval(), 3); +} diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index b182f8e69cd..6b783f93f95 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -21,6 +21,7 @@ pub mod enums; pub mod import_class; pub mod imports; pub mod js_objects; +pub mod jscast; pub mod math; pub mod node; pub mod option; From 37db88ebfa66adf09bfe4dc3f65d8bd1687b9fd1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Aug 2018 10:00:30 -0700 Subject: [PATCH 4/5] Implement `#[wasm_bindgen(extends = ...)]` This commit implements the `extends` attribute for `#[wasm_bindgen]` to statically draw the inheritance hierarchy in the generated bindings, generating appropriate `AsRef`, `AsMut`, and `From` implementations. --- crates/backend/src/ast.rs | 1 + crates/backend/src/codegen.rs | 25 +++ crates/macro-support/src/parser.rs | 25 ++- crates/webidl/src/first_pass.rs | 28 +++ crates/webidl/src/lib.rs | 6 +- guide/src/SUMMARY.md | 1 + guide/src/design/import-customization.md | 170 ++++++++++++++++++ .../attributes/on-js-imports/extends.md | 49 +++++ tests/wasm/jscast.js | 10 ++ tests/wasm/jscast.rs | 20 +++ 10 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 guide/src/design/import-customization.md create mode 100644 guide/src/reference/attributes/on-js-imports/extends.md diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 9a6d8a545f0..f13ec4693c7 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -128,6 +128,7 @@ pub struct ImportType { pub attrs: Vec, pub doc_comment: Option, pub instanceof_shim: String, + pub extends: Vec, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index af9896725ed..d66f98677aa 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -656,6 +656,31 @@ impl ToTokens for ast::ImportType { () }; }).to_tokens(tokens); + + for superclass in self.extends.iter() { + (quote! { + impl From<#name> for #superclass { + fn from(obj: #name) -> #superclass { + use wasm_bindgen::JsCast; + #superclass::unchecked_from_js(obj.into()) + } + } + + impl AsRef<#superclass> for #name { + fn as_ref(&self) -> &#superclass { + use wasm_bindgen::JsCast; + #superclass::unchecked_from_js_ref(self.as_ref()) + } + } + + impl AsMut<#superclass> for #name { + fn as_mut(&mut self) -> &mut #superclass { + use wasm_bindgen::JsCast; + #superclass::unchecked_from_js_mut(self.as_mut()) + } + } + }).to_tokens(tokens); + } } } diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 7e3c9b38919..84fa0638964 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -182,6 +182,16 @@ impl BindgenAttrs { }) .next() } + + /// Return the list of classes that a type extends + fn extends(&self) -> impl Iterator { + self.attrs + .iter() + .filter_map(|a| match a { + BindgenAttr::Extends(s) => Some(s), + _ => None, + }) + } } impl syn::synom::Synom for BindgenAttrs { @@ -217,6 +227,7 @@ pub enum BindgenAttr { Readonly, JsName(String), JsClass(String), + Extends(Ident), } impl syn::synom::Synom for BindgenAttr { @@ -295,6 +306,13 @@ impl syn::synom::Synom for BindgenAttr { s: syn!(syn::LitStr) >> (s.value()) )=> { BindgenAttr::JsClass } + | + do_parse!( + call!(term, "extends") >> + punct!(=) >> + ns: call!(term2ident) >> + (ns) + )=> { BindgenAttr::Extends } )); } @@ -520,10 +538,10 @@ impl<'a> ConvertToAst<(BindgenAttrs, &'a Option)> for syn::ForeignItemFn } } -impl ConvertToAst<()> for syn::ForeignItemType { +impl ConvertToAst for syn::ForeignItemType { type Target = ast::ImportKind; - fn convert(self, (): ()) -> Result { + fn convert(self, attrs: BindgenAttrs) -> Result { let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); Ok(ast::ImportKind::Type(ast::ImportType { vis: self.vis, @@ -531,6 +549,7 @@ impl ConvertToAst<()> for syn::ForeignItemType { doc_comment: None, instanceof_shim: shim, name: self.ident, + extends: attrs.extends().cloned().collect(), })) } } @@ -937,7 +956,7 @@ impl<'a> MacroParse<&'a BindgenAttrs> for syn::ForeignItem { let js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned(); let kind = match self { syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?, - syn::ForeignItem::Type(t) => t.convert(())?, + syn::ForeignItem::Type(t) => t.convert(item_opts)?, syn::ForeignItem::Static(s) => s.convert(item_opts)?, _ => panic!("only foreign functions/types allowed for now"), }; diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 7d65d2b8e1f..4e58443a970 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -17,6 +17,7 @@ use weedle; use super::Result; use util; +use util::camel_case_ident; /// Collection of constructs that may use partial. #[derive(Default)] @@ -36,6 +37,7 @@ pub(crate) struct InterfaceData<'src> { pub(crate) partial: bool, pub(crate) global: bool, pub(crate) operations: BTreeMap, OperationData<'src>>, + pub(crate) superclass: Option<&'src str>, } #[derive(PartialEq, Eq, PartialOrd, Ord)] @@ -146,6 +148,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> { .entry(self.identifier.0) .or_insert_with(Default::default); interface.partial = false; + interface.superclass = self.inheritance.map(|s| s.identifier.0); } if util::is_chrome_only(&self.attributes) { @@ -176,6 +179,7 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> { partial: true, operations: Default::default(), global: false, + superclass: None, }, ); @@ -307,3 +311,27 @@ impl<'src> FirstPass<'src, ()> for weedle::TypedefDefinition<'src> { Ok(()) } } + +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() + } + + fn fill_superclasses(&self, interface: &str, set: &mut BTreeSet) { + let data = match self.interfaces.get(interface) { + Some(data) => data, + None => return, + }; + let superclass = match &data.superclass { + Some(class) => class, + None => return, + }; + if set.insert(camel_case_ident(superclass)) { + self.fill_superclasses(superclass, set); + } + } +} diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 94812fd43b7..5410012c16d 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -39,6 +39,7 @@ use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::ResultExt; use heck::{ShoutySnakeCase}; +use proc_macro2::{Ident, Span}; use weedle::argument::Argument; use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList}; @@ -246,7 +247,10 @@ impl<'src> WebidlParse<'src, ()> for weedle::InterfaceDefinition<'src> { name: rust_ident(camel_case_ident(self.identifier.0).as_str()), attrs: Vec::new(), doc_comment, - instanceof_shim: format!("__widl_instanceof_{}", self.name), + instanceof_shim: format!("__widl_instanceof_{}", self.identifier.0), + extends: first_pass.all_superclasses(self.identifier.0) + .map(|name| Ident::new(&name, Span::call_site())) + .collect(), }), }); diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 57246bd41ac..4ab2b0cb0b6 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -20,6 +20,7 @@ - [On JavaScript Imports](./reference/attributes/on-js-imports/index.md) - [`catch`](./reference/attributes/on-js-imports/catch.md) - [`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) - [`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/design/import-customization.md b/guide/src/design/import-customization.md new file mode 100644 index 00000000000..4f1b6d9aa40 --- /dev/null +++ b/guide/src/design/import-customization.md @@ -0,0 +1,170 @@ +# Customizing import behavior + +The `#[wasm_bindgen]` macro supports a good amount of configuration for +controlling precisely how imports are imported and what they map to in JS. This +section is intended to hopefully be an exhaustive reference of the +possibilities! + +* `catch` - this attribute allows catching a JS exception. This can be attached + to any imported function and the function must return a `Result` where the + `Err` payload is a `JsValue`, like so: + + ```rust + #[wasm_bindgen] + extern { + #[wasm_bindgen(catch)] + fn foo() -> Result<(), JsValue>; + } + ``` + + If the imported function throws an exception then `Err` will be returned with + the exception that was raised, and otherwise `Ok` is returned with the result + of the function. + + By default `wasm-bindgen` will take no action when wasm calls a JS function + which ends up throwing an exception. The wasm spec right now doesn't support + stack unwinding and as a result Rust code **will not execute destructors**. + This can unfortunately cause memory leaks in Rust right now, but as soon as + wasm implements catching exceptions we'll be sure to add support as well! + +* `constructor` - this is used to indicate that the function being bound should + actually translate to a `new` constructor in JS. The final argument must be a + type that's imported from JS, and it's what'll get used in JS: + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(constructor)] + fn new() -> Foo; + } + ``` + + This will attach the `new` function to the `Foo` type (implied by + `constructor`) and in JS when this function is called it will be equivalent to + `new Foo()`. + +* `method` - this is the gateway to adding methods to imported objects or + otherwise accessing properties on objects via methods and such. This should be + done for doing the equivalent of expressions like `foo.bar()` in JS. + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(method)] + fn work(this: &Foo); + } + ``` + + The first argument of a `method` annotation must be a borrowed reference (not + mutable, shared) to the type that the method is attached to. In this case + we'll be able to call this method like `foo.work()` in JS (where `foo` has + type `Foo`). + + In JS this invocation will correspond to accessing `Foo.prototype.work` and + then calling that when the import is called. Note that `method` by default + implies going through `prototype` to get a function pointer. + +* `js_namespace` - this attribute indicates that the JS type is accessed through + a particular namespace. For example the `WebAssembly.Module` APIs are all + accessed through the `WebAssembly` namespace. The `js_namespace` can be + applied to any import and whenever the generated JS attempts to reference a + name (like a class or function name) it'll be accessed through this namespace. + + ```rust + #[wasm_bindgen] + extern { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + } + ``` + + This is an example of how to bind `console.log(x)` in Rust. The `log` function + will be available in the Rust module and will be invoked as `console.log` in + JS. + +* `getter` and `setter` - these two attributes can be combined with `method` to + indicate that this is a getter or setter method. A `getter`-tagged function by + default accesses the JS property with the same name as the getter function. A + `setter`'s function name is currently required to start with "set\_" and the + property it accesses is the suffix after "set\_". + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(method, getter)] + fn property(this: &Foo) -> u32; + #[wasm_bindgen(method, setter)] + fn set_property(this: &Foo, val: u32); + } + ``` + + Here we're importing the `Foo` type and defining the ability to access each + object's `property` property. The first function here is a getter and will be + available in Rust as `foo.property()`, and the latter is the setter which is + accessible as `foo.set_property(2)`. Note that both functions have a `this` + argument as they're tagged with `method`. + + Finally, you can also pass an argument to the `getter` and `setter` + properties to configure what property is accessed. When the property is + explicitly specified then there is no restriction on the method name. For + example the below is equivalent to the above: + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(method, getter = property)] + fn assorted_method_name(this: &Foo) -> u32; + #[wasm_bindgen(method, setter = "property")] + fn some_other_method_name(this: &Foo, val: u32); + } + ``` + + Properties in JS are accessed through `Object.getOwnPropertyDescriptor`. Note + that this typically only works for class-like-defined properties which aren't + just attached properties on any old object. For accessing any old property on + an object we can use... + +* `structural` - this is a flag to `method` annotations which indicates that the + method being accessed (or property with getters/setters) should be accessed in + a structural fashion. For example methods are *not* accessed through + `prototype` and properties are accessed on the object directly rather than + through `Object.getOwnPropertyDescriptor`. + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(method, structural)] + fn bar(this: &Foo); + #[wasm_bindgen(method, getter, structural)] + fn baz(this: &Foo) -> u32; + } + ``` + + The type here, `Foo`, is not required to exist in JS (it's not referenced). + Instead wasm-bindgen will generate shims that will access the passed in JS + value's `bar` property to or the `baz` property (depending on the function). + +* `js_name = foo` - this can be used to bind to a different function in JS than + the identifier that's defined in Rust. For example you can also define + multiple signatures for a polymorphic function in JS as well: + + ```rust + #[wasm_bindgen] + extern { + type Foo; + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_string(s: &str); + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_u32(n: u32); + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_many(a: u32, b: JsValue); + } + ``` + + All of these functions will call `console.log` in JS, but each identifier + will have only one signature in Rust. diff --git a/guide/src/reference/attributes/on-js-imports/extends.md b/guide/src/reference/attributes/on-js-imports/extends.md new file mode 100644 index 00000000000..7eda0f9f16c --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/extends.md @@ -0,0 +1,49 @@ +# `extends = Class` + +The `extends` attribute can be used to say that an imported type extends (in the +JS class hierarchy sense) another type. This will generate `AsRef`, `AsMut`, and +`From` impls for converting a type into another given that we statically know +the inheritance hierarchy: + +```rust +#[wasm_bindgen] +extern { + type Foo; + + #[wasm_bindgen(extends = Foo)] + type Bar; +} + +let x: &Bar = ...; +let y: &Foo = x.as_ref(); // zero cost cast +``` + +The trait implementations generated for the above block are: + +```rust +impl From for Foo { ... } +impl AsRef for Bar { ... } +impl AsMut for Bar { ... } +``` + + +The `extends = ...` attribute can be specified multiple times for longer +inheritance chains, and `AsRef` and such impls will be generated for each of +the types. + +```rust +#[wasm_bindgen] +extern { + type Foo; + + #[wasm_bindgen(extends = Foo)] + type Bar; + + #[wasm_bindgen(extends = Foo, extends = Bar)] + type Baz; +} + +let x: &Baz = ...; +let y1: &Bar = x.as_ref(); +let y2: &Foo = x.as_ref(); +``` diff --git a/tests/wasm/jscast.js b/tests/wasm/jscast.js index d93489ffb53..3cecd9915e0 100644 --- a/tests/wasm/jscast.js +++ b/tests/wasm/jscast.js @@ -4,8 +4,10 @@ class JsCast1 { } myval() { return this.val; } } + class JsCast2 { } + class JsCast3 extends JsCast1 { constructor() { super(); @@ -13,6 +15,14 @@ class JsCast3 extends JsCast1 { } } +class JsCast4 extends JsCast3 { + constructor() { + super(); + this.val = 4; + } +} + exports.JsCast1 = JsCast1; exports.JsCast2 = JsCast2; exports.JsCast3 = JsCast3; +exports.JsCast4 = JsCast4; diff --git a/tests/wasm/jscast.rs b/tests/wasm/jscast.rs index d98187bec54..acdbf56104a 100644 --- a/tests/wasm/jscast.rs +++ b/tests/wasm/jscast.rs @@ -14,9 +14,15 @@ extern { #[wasm_bindgen(constructor)] fn new() -> JsCast2; + #[wasm_bindgen(extends = JsCast1)] type JsCast3; #[wasm_bindgen(constructor)] fn new() -> JsCast3; + + #[wasm_bindgen(extends = JsCast1, extends = JsCast3)] + type JsCast4; + #[wasm_bindgen(constructor)] + fn new() -> JsCast4; } #[wasm_bindgen_test] @@ -65,4 +71,18 @@ fn method_calling() { assert_eq!(a.myval(), 1); assert_eq!(b.dyn_ref::().unwrap().myval(), 3); assert_eq!(b.unchecked_ref::().myval(), 3); + let c: &JsCast1 = b.as_ref(); + assert_eq!(c.myval(), 3); +} + +#[wasm_bindgen_test] +fn multiple_layers_of_inheritance() { + let a = JsCast4::new(); + assert!(a.is_instance_of::()); + assert!(a.is_instance_of::()); + assert!(a.is_instance_of::()); + + let _: &JsCast3 = a.as_ref(); + let b: &JsCast1 = a.as_ref(); + assert_eq!(b.myval(), 4); } From bd15db40a0f30ba058b34d5fc2565fcf73f4b682 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 6 Aug 2018 14:29:23 -0700 Subject: [PATCH 5/5] Rebase fallout and review comments --- guide/src/design/import-customization.md | 170 ----------------------- src/cast.rs | 16 +-- tests/wasm/jscast.rs | 2 +- 3 files changed, 9 insertions(+), 179 deletions(-) delete mode 100644 guide/src/design/import-customization.md diff --git a/guide/src/design/import-customization.md b/guide/src/design/import-customization.md deleted file mode 100644 index 4f1b6d9aa40..00000000000 --- a/guide/src/design/import-customization.md +++ /dev/null @@ -1,170 +0,0 @@ -# Customizing import behavior - -The `#[wasm_bindgen]` macro supports a good amount of configuration for -controlling precisely how imports are imported and what they map to in JS. This -section is intended to hopefully be an exhaustive reference of the -possibilities! - -* `catch` - this attribute allows catching a JS exception. This can be attached - to any imported function and the function must return a `Result` where the - `Err` payload is a `JsValue`, like so: - - ```rust - #[wasm_bindgen] - extern { - #[wasm_bindgen(catch)] - fn foo() -> Result<(), JsValue>; - } - ``` - - If the imported function throws an exception then `Err` will be returned with - the exception that was raised, and otherwise `Ok` is returned with the result - of the function. - - By default `wasm-bindgen` will take no action when wasm calls a JS function - which ends up throwing an exception. The wasm spec right now doesn't support - stack unwinding and as a result Rust code **will not execute destructors**. - This can unfortunately cause memory leaks in Rust right now, but as soon as - wasm implements catching exceptions we'll be sure to add support as well! - -* `constructor` - this is used to indicate that the function being bound should - actually translate to a `new` constructor in JS. The final argument must be a - type that's imported from JS, and it's what'll get used in JS: - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(constructor)] - fn new() -> Foo; - } - ``` - - This will attach the `new` function to the `Foo` type (implied by - `constructor`) and in JS when this function is called it will be equivalent to - `new Foo()`. - -* `method` - this is the gateway to adding methods to imported objects or - otherwise accessing properties on objects via methods and such. This should be - done for doing the equivalent of expressions like `foo.bar()` in JS. - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(method)] - fn work(this: &Foo); - } - ``` - - The first argument of a `method` annotation must be a borrowed reference (not - mutable, shared) to the type that the method is attached to. In this case - we'll be able to call this method like `foo.work()` in JS (where `foo` has - type `Foo`). - - In JS this invocation will correspond to accessing `Foo.prototype.work` and - then calling that when the import is called. Note that `method` by default - implies going through `prototype` to get a function pointer. - -* `js_namespace` - this attribute indicates that the JS type is accessed through - a particular namespace. For example the `WebAssembly.Module` APIs are all - accessed through the `WebAssembly` namespace. The `js_namespace` can be - applied to any import and whenever the generated JS attempts to reference a - name (like a class or function name) it'll be accessed through this namespace. - - ```rust - #[wasm_bindgen] - extern { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); - } - ``` - - This is an example of how to bind `console.log(x)` in Rust. The `log` function - will be available in the Rust module and will be invoked as `console.log` in - JS. - -* `getter` and `setter` - these two attributes can be combined with `method` to - indicate that this is a getter or setter method. A `getter`-tagged function by - default accesses the JS property with the same name as the getter function. A - `setter`'s function name is currently required to start with "set\_" and the - property it accesses is the suffix after "set\_". - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(method, getter)] - fn property(this: &Foo) -> u32; - #[wasm_bindgen(method, setter)] - fn set_property(this: &Foo, val: u32); - } - ``` - - Here we're importing the `Foo` type and defining the ability to access each - object's `property` property. The first function here is a getter and will be - available in Rust as `foo.property()`, and the latter is the setter which is - accessible as `foo.set_property(2)`. Note that both functions have a `this` - argument as they're tagged with `method`. - - Finally, you can also pass an argument to the `getter` and `setter` - properties to configure what property is accessed. When the property is - explicitly specified then there is no restriction on the method name. For - example the below is equivalent to the above: - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(method, getter = property)] - fn assorted_method_name(this: &Foo) -> u32; - #[wasm_bindgen(method, setter = "property")] - fn some_other_method_name(this: &Foo, val: u32); - } - ``` - - Properties in JS are accessed through `Object.getOwnPropertyDescriptor`. Note - that this typically only works for class-like-defined properties which aren't - just attached properties on any old object. For accessing any old property on - an object we can use... - -* `structural` - this is a flag to `method` annotations which indicates that the - method being accessed (or property with getters/setters) should be accessed in - a structural fashion. For example methods are *not* accessed through - `prototype` and properties are accessed on the object directly rather than - through `Object.getOwnPropertyDescriptor`. - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(method, structural)] - fn bar(this: &Foo); - #[wasm_bindgen(method, getter, structural)] - fn baz(this: &Foo) -> u32; - } - ``` - - The type here, `Foo`, is not required to exist in JS (it's not referenced). - Instead wasm-bindgen will generate shims that will access the passed in JS - value's `bar` property to or the `baz` property (depending on the function). - -* `js_name = foo` - this can be used to bind to a different function in JS than - the identifier that's defined in Rust. For example you can also define - multiple signatures for a polymorphic function in JS as well: - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn log_string(s: &str); - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn log_u32(n: u32); - #[wasm_bindgen(js_namespace = console, js_name = log)] - fn log_many(a: u32, b: JsValue); - } - ``` - - All of these functions will call `console.log` in JS, but each identifier - will have only one signature in Rust. diff --git a/src/cast.rs b/src/cast.rs index 83393de7f65..928283d3b54 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -18,7 +18,7 @@ where { /// Test whether this JS value is an instance of the type `T`. /// - /// This commit performs a dynamic check (at runtime) using the JS + /// This method performs a dynamic check (at runtime) using the JS /// `instanceof` operator. This method returns `self instanceof T`. fn is_instance_of(&self) -> bool where @@ -32,7 +32,7 @@ where /// /// This method will return `Err(self)` is `self.is_instance_of::()` /// returns `false`, and otherwise it will return `Ok(T)` manufactured with - /// an unchecked cast (verified safe via the `instanceof` operation). + /// an unchecked cast (verified correct via the `instanceof` operation). fn dyn_into(self) -> Result where T: JsCast, @@ -49,7 +49,7 @@ where /// /// This method will return `None` is `self.is_instance_of::()` /// returns `false`, and otherwise it will return `Some(&T)` manufactured - /// with an unchecked cast (verified safe via the `instanceof` operation). + /// with an unchecked cast (verified correct via the `instanceof` operation). fn dyn_ref(&self) -> Option<&T> where T: JsCast, @@ -66,8 +66,8 @@ where /// /// This method will return `None` is `self.is_instance_of::()` /// returns `false`, and otherwise it will return `Some(&mut T)` - /// manufactured with an unchecked cast (verified safe via the `instanceof` - /// operation). + /// manufactured with an unchecked cast (verified correct via the + /// `instanceof` operation). fn dyn_mut(&mut self) -> Option<&mut T> where T: JsCast, @@ -85,7 +85,7 @@ where /// `self` and `T` are simple wrappers around `JsValue`. This method **does /// not check whether `self` is an instance of `T`**. If used incorrectly /// then this method may cause runtime exceptions in both Rust and JS, this - /// shoudl be used with caution. + /// should be used with caution. fn unchecked_into(self) -> T where T: JsCast, @@ -100,7 +100,7 @@ where /// `self` and `T` are simple wrappers around `JsValue`. This method **does /// not check whether `self` is an instance of `T`**. If used incorrectly /// then this method may cause runtime exceptions in both Rust and JS, this - /// shoudl be used with caution. + /// should be used with caution. /// /// This method, unlike `unchecked_into`, does not consume ownership of /// `self` and instead works over a shared reference. @@ -118,7 +118,7 @@ where /// `self` and `T` are simple wrappers around `JsValue`. This method **does /// not check whether `self` is an instance of `T`**. If used incorrectly /// then this method may cause runtime exceptions in both Rust and JS, this - /// shoudl be used with caution. + /// should be used with caution. /// /// This method, unlike `unchecked_into`, does not consume ownership of /// `self` and instead works over a utable reference. diff --git a/tests/wasm/jscast.rs b/tests/wasm/jscast.rs index acdbf56104a..f87199d5329 100644 --- a/tests/wasm/jscast.rs +++ b/tests/wasm/jscast.rs @@ -2,7 +2,7 @@ use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; -#[wasm_bindgen(module = "tests/wasm/jscast.js", version = "*")] +#[wasm_bindgen(module = "tests/wasm/jscast.js")] extern { type JsCast1; #[wasm_bindgen(constructor)]