diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 96c1e3cc..9a6d8a54 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 0b40e468..af989672 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 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..7e3c9b38 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 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/lib.rs b/crates/webidl/src/lib.rs index bf749c00..94812fd4 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 00000000..d93489ff --- /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 00000000..d98187be --- /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 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;