Merge pull request #640 from alexcrichton/jscast

Implement RFC #2 - casting hierarchy between JS values
This commit is contained in:
Alex Crichton 2018-08-07 17:26:37 -05:00 committed by GitHub
commit 5b935526ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 590 additions and 74 deletions

View File

@ -127,6 +127,8 @@ pub struct ImportType {
pub name: Ident,
pub attrs: Vec<syn::Attribute>,
pub doc_comment: Option<String>,
pub instanceof_shim: String,
pub extends: Vec<Ident>,
}
#[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(),
}
}
}

View File

@ -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 {
#[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 WasmDescribe for #name {
fn describe() {
::wasm_bindgen::JsValue::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 = <JsValue as IntoWasmAbi>::Abi;
fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi {
fn into_abi(self, extra: &mut Stack) -> Self::Abi {
self.obj.into_abi(extra)
}
}
impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name {
impl OptionIntoWasmAbi for #name {
fn none() -> Self::Abi { 0 }
}
impl<'a> ::wasm_bindgen::convert::OptionIntoWasmAbi for &'a #name {
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 = <JsValue as FromWasmAbi>::Abi;
unsafe fn from_abi(
js: Self::Abi,
extra: &mut ::wasm_bindgen::convert::Stack,
) -> Self {
unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self {
#name {
obj: ::wasm_bindgen::JsValue::from_abi(js, extra),
obj: JsValue::from_abi(js, extra),
}
}
}
impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name {
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 {
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 = <JsValue as RefFromWasmAbi>::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 = <JsValue as RefFromWasmAbi>::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 {
// TODO: remove this on the next major version
impl From<JsValue> for #name {
fn from(obj: JsValue) -> #name {
#name { obj }
}
}
impl From<#name> for ::wasm_bindgen::JsValue {
fn from(obj: #name) -> ::wasm_bindgen::JsValue {
impl AsRef<JsValue> for #name {
fn as_ref(&self) -> &JsValue { &self.obj }
}
impl AsMut<JsValue> 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);
}
}
}

View File

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

View File

@ -182,6 +182,16 @@ impl BindgenAttrs {
})
.next()
}
/// Return the list of classes that a type extends
fn extends(&self) -> impl Iterator<Item = &Ident> {
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<String>)> for syn::ForeignItemFn
}
}
impl ConvertToAst<()> for syn::ForeignItemType {
impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
type Target = ast::ImportKind;
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> {
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"),
};

View File

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

View File

@ -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<OperationId<'src>, 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<Item = String> + 'me
{
let mut set = BTreeSet::new();
self.fill_superclasses(interface, &mut set);
set.into_iter()
}
fn fill_superclasses(&self, interface: &str, set: &mut BTreeSet<String>) {
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);
}
}
}

View File

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

View File

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

View File

@ -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<Bar> for Foo { ... }
impl AsRef<Foo> for Bar { ... }
impl AsMut<Foo> 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();
```

165
src/cast.rs Normal file
View File

@ -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<JsValue> + AsMut<JsValue> + Into<JsValue>,
{
/// 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<T>(&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::<T>()`
/// returns `false`, and otherwise it will return `Ok(T)` manufactured with
/// an unchecked cast (verified correct via the `instanceof` operation).
fn dyn_into<T>(self) -> Result<T, Self>
where
T: JsCast,
{
if self.is_instance_of::<T>() {
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::<T>()`
/// returns `false`, and otherwise it will return `Some(&T)` manufactured
/// with an unchecked cast (verified correct via the `instanceof` operation).
fn dyn_ref<T>(&self) -> Option<&T>
where
T: JsCast,
{
if self.is_instance_of::<T>() {
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::<T>()`
/// returns `false`, and otherwise it will return `Some(&mut T)`
/// manufactured with an unchecked cast (verified correct via the
/// `instanceof` operation).
fn dyn_mut<T>(&mut self) -> Option<&mut T>
where
T: JsCast,
{
if self.is_instance_of::<T>() {
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<T>(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<T>(&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<T>(&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;
}

View File

@ -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<bool> 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<JsValue> for JsValue {
fn as_mut(&mut self) -> &mut JsValue { self }
}
impl AsRef<JsValue> for JsValue {
fn as_ref(&self) -> &JsValue { self }
}
macro_rules! numbers {
($($n:ident)*) => ($(
impl PartialEq<$n> for JsValue {

28
tests/wasm/jscast.js Normal file
View File

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

88
tests/wasm/jscast.rs Normal file
View File

@ -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::<JsCast1>());
assert!(!a.is_instance_of::<JsCast2>());
assert!(!a.is_instance_of::<JsCast3>());
assert!(!b.is_instance_of::<JsCast1>());
assert!(b.is_instance_of::<JsCast2>());
assert!(!b.is_instance_of::<JsCast3>());
assert!(c.is_instance_of::<JsCast1>());
assert!(!c.is_instance_of::<JsCast2>());
assert!(c.is_instance_of::<JsCast3>());
}
#[wasm_bindgen_test]
fn casting() {
let a = JsCast1::new();
let b = JsCast2::new();
let c = JsCast3::new();
assert!(a.dyn_ref::<JsCast1>().is_some());
assert!(a.dyn_ref::<JsCast2>().is_none());
assert!(a.dyn_ref::<JsCast3>().is_none());
assert!(b.dyn_ref::<JsCast1>().is_none());
assert!(b.dyn_ref::<JsCast2>().is_some());
assert!(b.dyn_ref::<JsCast3>().is_none());
assert!(c.dyn_ref::<JsCast1>().is_some());
assert!(c.dyn_ref::<JsCast2>().is_none());
assert!(c.dyn_ref::<JsCast3>().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::<JsCast1>().unwrap().myval(), 3);
assert_eq!(b.unchecked_ref::<JsCast1>().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::<JsCast4>());
assert!(a.is_instance_of::<JsCast3>());
assert!(a.is_instance_of::<JsCast1>());
let _: &JsCast3 = a.as_ref();
let b: &JsCast1 = a.as_ref();
assert_eq!(b.myval(), 4);
}

View File

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