diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 96c1e3cc..f13ec469 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -127,6 +127,8 @@ pub struct ImportType { pub name: Ident, pub attrs: Vec, pub doc_comment: Option, + pub instanceof_shim: String, + pub extends: Vec, } #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] @@ -408,7 +410,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 7db3913d..d66f9867 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -519,93 +519,168 @@ 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()); + 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, } - 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, GlobalStack}; + use wasm_bindgen::describe::WasmDescribe; + use wasm_bindgen::{JsValue, JsCast}; + use wasm_bindgen::__rt::core::mem::ManuallyDrop; - impl ::wasm_bindgen::convert::IntoWasmAbi for #name { - type Abi = <::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::IntoWasmAbi>::Abi; - - fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { - self.obj.into_abi(extra) - } - } - - impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name { - fn none() -> Self::Abi { 0 } - } - - impl<'a> ::wasm_bindgen::convert::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; - - unsafe fn from_abi( - js: Self::Abi, - extra: &mut ::wasm_bindgen::convert::Stack, - ) -> Self { - #name { - obj: ::wasm_bindgen::JsValue::from_abi(js, extra), + impl WasmDescribe for #name { + fn describe() { + JsValue::describe(); } } - } - impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name { - fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } - } + impl IntoWasmAbi for #name { + type Abi = ::Abi; - impl<'a> ::wasm_bindgen::convert::IntoWasmAbi for &'a #name { - type Abi = <&'a ::wasm_bindgen::JsValue as - ::wasm_bindgen::convert::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>; - - 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), - }) + impl OptionIntoWasmAbi for #name { + fn none() -> Self::Abi { 0 } } - } - impl From<::wasm_bindgen::JsValue> for #name { - fn from(obj: ::wasm_bindgen::JsValue) -> #name { - #name { obj } + impl<'a> OptionIntoWasmAbi for &'a #name { + fn none() -> Self::Abi { 0 } } - } - impl From<#name> for ::wasm_bindgen::JsValue { - fn from(obj: #name) -> ::wasm_bindgen::JsValue { - obj.obj + impl FromWasmAbi for #name { + type Abi = ::Abi; + + unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { + #name { + obj: JsValue::from_abi(js, extra), + } + } } - } + + impl OptionFromWasmAbi for #name { + fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } + } + + impl<'a> IntoWasmAbi for &'a #name { + type Abi = <&'a JsValue as IntoWasmAbi>::Abi; + + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + (&self.obj).into_abi(extra) + } + } + + impl RefFromWasmAbi for #name { + type Abi = ::Abi; + type Anchor = ManuallyDrop<#name>; + + 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), + }) + } + } + + // 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); + + 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/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 93b818a2..e52e6721 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 2258c88f..84fa0638 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,15 +538,18 @@ 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, - name: self.ident, attrs: self.attrs, doc_comment: None, + instanceof_shim: shim, + name: self.ident, + extends: attrs.extends().cloned().collect(), })) } } @@ -935,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/shared/src/lib.rs b/crates/shared/src/lib.rs index c40ad81c..20d78db7 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/first_pass.rs b/crates/webidl/src/first_pass.rs index 7d65d2b8..4e58443a 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 bf749c00..5410012c 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,6 +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.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 41f0b03b..515fb597 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -19,6 +19,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) - [`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) 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 00000000..7eda0f9f --- /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/src/cast.rs b/src/cast.rs new file mode 100644 index 00000000..928283d3 --- /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 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 + 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 correct 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 correct 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 correct 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 + /// should 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 + /// should 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 + /// should 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 3fedff5b..2f6a2be0 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 { diff --git a/tests/wasm/jscast.js b/tests/wasm/jscast.js new file mode 100644 index 00000000..3cecd991 --- /dev/null +++ b/tests/wasm/jscast.js @@ -0,0 +1,28 @@ +class JsCast1 { + constructor() { + this.val = 1; + } + myval() { return this.val; } +} + +class JsCast2 { +} + +class JsCast3 extends JsCast1 { + constructor() { + super(); + this.val = 3; + } +} + +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 new file mode 100644 index 00000000..f87199d5 --- /dev/null +++ b/tests/wasm/jscast.rs @@ -0,0 +1,88 @@ +use wasm_bindgen::JsCast; +use wasm_bindgen::prelude::*; +use wasm_bindgen_test::*; + +#[wasm_bindgen(module = "tests/wasm/jscast.js")] +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; + + #[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] +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); + 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); +} diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index b182f8e6..6b783f93 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;