Skip to content

Commit 11553a1

Browse files
committed
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<JsValue>` and `AsMut<JsValue>`
1 parent f3f11ed commit 11553a1

File tree

9 files changed

+178
-6
lines changed

9 files changed

+178
-6
lines changed

crates/backend/src/ast.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub struct ImportType {
127127
pub name: Ident,
128128
pub attrs: Vec<syn::Attribute>,
129129
pub doc_comment: Option<String>,
130+
pub instanceof_shim: String,
130131
}
131132

132133
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@@ -408,7 +409,10 @@ impl ImportStatic {
408409

409410
impl ImportType {
410411
fn shared(&self) -> shared::ImportType {
411-
shared::ImportType {}
412+
shared::ImportType {
413+
name: self.name.to_string(),
414+
instanceof_shim: self.instanceof_shim.clone(),
415+
}
412416
}
413417
}
414418

crates/backend/src/codegen.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,12 @@ impl ToTokens for ast::ImportType {
521521
};
522522
let const_name = format!("__wbg_generated_const_{}", name);
523523
let const_name = Ident::new(&const_name, Span::call_site());
524+
let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site());
524525
(quote! {
525526
#[allow(bad_style)]
526527
#(#attrs)*
527528
#[doc = #doc_comment]
529+
#[repr(transparent)]
528530
#vis struct #name {
529531
obj: ::wasm_bindgen::JsValue,
530532
}
@@ -533,7 +535,7 @@ impl ToTokens for ast::ImportType {
533535
const #const_name: () = {
534536
use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi, Stack};
535537
use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi};
536-
use wasm_bindgen::convert::RefFromWasmAbi;
538+
use wasm_bindgen::convert::{RefFromWasmAbi, GlobalStack};
537539
use wasm_bindgen::describe::WasmDescribe;
538540
use wasm_bindgen::{JsValue, JsCast};
539541
use wasm_bindgen::__rt::core::mem::ManuallyDrop;
@@ -594,18 +596,63 @@ impl ToTokens for ast::ImportType {
594596
}
595597
}
596598

599+
// TODO: remove this on the next major version
597600
impl From<JsValue> for #name {
598601
fn from(obj: JsValue) -> #name {
599602
#name { obj }
600603
}
601604
}
602605

606+
impl AsRef<JsValue> for #name {
607+
fn as_ref(&self) -> &JsValue { &self.obj }
608+
}
609+
610+
impl AsMut<JsValue> for #name {
611+
fn as_mut(&mut self) -> &mut JsValue { &mut self.obj }
612+
}
613+
603614
impl From<#name> for JsValue {
604615
fn from(obj: #name) -> JsValue {
605616
obj.obj
606617
}
607618
}
608619

620+
impl JsCast for #name {
621+
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
622+
fn instanceof(val: &JsValue) -> bool {
623+
#[link(wasm_import_module = "__wbindgen_placeholder__")]
624+
extern {
625+
fn #instanceof_shim(val: u32) -> u32;
626+
}
627+
unsafe {
628+
let idx = val.into_abi(&mut GlobalStack::new());
629+
#instanceof_shim(idx) != 0
630+
}
631+
}
632+
633+
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
634+
fn instanceof(val: &JsValue) -> bool {
635+
drop(val);
636+
panic!("cannot check instanceof on non-wasm targets");
637+
}
638+
639+
fn unchecked_from_js(val: JsValue) -> Self {
640+
#name { obj: val }
641+
}
642+
643+
fn unchecked_from_js_ref(val: &JsValue) -> &Self {
644+
// Should be safe because `#name` is a transparent
645+
// wrapper around `val`
646+
unsafe { &*(val as *const JsValue as *const #name) }
647+
}
648+
649+
fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self {
650+
// Should be safe because `#name` is a transparent
651+
// wrapper around `val`
652+
unsafe { &mut *(val as *mut JsValue as *mut #name) }
653+
}
654+
}
655+
609656
()
610657
};
611658
}).to_tokens(tokens);

crates/cli-support/src/js/mod.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -1755,7 +1755,14 @@ impl<'a, 'b> SubContext<'a, 'b> {
17551755
format!("failed to generate bindings for JS import `{}`", s.name)
17561756
})?;
17571757
}
1758-
shared::ImportKind::Type(_) => {}
1758+
shared::ImportKind::Type(ref ty) => {
1759+
self.generate_import_type(import, ty).with_context(|_| {
1760+
format!(
1761+
"failed to generate bindings for JS import `{}`",
1762+
ty.name,
1763+
)
1764+
})?;
1765+
}
17591766
shared::ImportKind::Enum(_) => {}
17601767
}
17611768
Ok(())
@@ -1936,6 +1943,27 @@ impl<'a, 'b> SubContext<'a, 'b> {
19361943
Ok(())
19371944
}
19381945

1946+
fn generate_import_type(
1947+
&mut self,
1948+
info: &shared::Import,
1949+
import: &shared::ImportType,
1950+
) -> Result<(), Error> {
1951+
if !self.cx.wasm_import_needed(&import.instanceof_shim) {
1952+
return Ok(());
1953+
}
1954+
let name = self.import_name(info, &import.name)?;
1955+
self.cx.expose_get_object();
1956+
let body = format!("
1957+
function(idx) {{
1958+
return getObject(idx) instanceof {} ? 1 : 0;
1959+
}}
1960+
",
1961+
name,
1962+
);
1963+
self.cx.export(&import.instanceof_shim, &body, None);
1964+
Ok(())
1965+
}
1966+
19391967
fn generate_enum(&mut self, enum_: &shared::Enum) {
19401968
let mut variants = String::new();
19411969

crates/macro-support/src/parser.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -524,11 +524,13 @@ impl ConvertToAst<()> for syn::ForeignItemType {
524524
type Target = ast::ImportKind;
525525

526526
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
527+
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
527528
Ok(ast::ImportKind::Type(ast::ImportType {
528529
vis: self.vis,
529-
name: self.ident,
530530
attrs: self.attrs,
531531
doc_comment: None,
532+
instanceof_shim: shim,
533+
name: self.ident,
532534
}))
533535
}
534536
}

crates/shared/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#[macro_use]
44
extern crate serde_derive;
55

6-
pub const SCHEMA_VERSION: &str = "7";
6+
pub const SCHEMA_VERSION: &str = "8";
77

88
#[derive(Deserialize)]
99
pub struct ProgramOnlySchema {
@@ -81,7 +81,10 @@ pub struct ImportStatic {
8181
}
8282

8383
#[derive(Deserialize, Serialize)]
84-
pub struct ImportType {}
84+
pub struct ImportType {
85+
pub name: String,
86+
pub instanceof_shim: String,
87+
}
8588

8689
#[derive(Deserialize, Serialize)]
8790
pub struct ImportEnum {}

crates/webidl/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::InterfaceDefinition<'src> {
246246
name: rust_ident(camel_case_ident(self.identifier.0).as_str()),
247247
attrs: Vec::new(),
248248
doc_comment,
249+
instanceof_shim: format!("__widl_instanceof_{}", self.name),
249250
}),
250251
});
251252

tests/wasm/jscast.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class JsCast1 {
2+
constructor() {
3+
this.val = 1;
4+
}
5+
myval() { return this.val; }
6+
}
7+
class JsCast2 {
8+
}
9+
class JsCast3 extends JsCast1 {
10+
constructor() {
11+
super();
12+
this.val = 3;
13+
}
14+
}
15+
16+
exports.JsCast1 = JsCast1;
17+
exports.JsCast2 = JsCast2;
18+
exports.JsCast3 = JsCast3;

tests/wasm/jscast.rs

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use wasm_bindgen::JsCast;
2+
use wasm_bindgen::prelude::*;
3+
use wasm_bindgen_test::*;
4+
5+
#[wasm_bindgen(module = "tests/wasm/jscast.js", version = "*")]
6+
extern {
7+
type JsCast1;
8+
#[wasm_bindgen(constructor)]
9+
fn new() -> JsCast1;
10+
#[wasm_bindgen(method)]
11+
fn myval(this: &JsCast1) -> u32;
12+
13+
type JsCast2;
14+
#[wasm_bindgen(constructor)]
15+
fn new() -> JsCast2;
16+
17+
type JsCast3;
18+
#[wasm_bindgen(constructor)]
19+
fn new() -> JsCast3;
20+
}
21+
22+
#[wasm_bindgen_test]
23+
fn instanceof_works() {
24+
let a = JsCast1::new();
25+
let b = JsCast2::new();
26+
let c = JsCast3::new();
27+
28+
assert!(a.is_instance_of::<JsCast1>());
29+
assert!(!a.is_instance_of::<JsCast2>());
30+
assert!(!a.is_instance_of::<JsCast3>());
31+
32+
assert!(!b.is_instance_of::<JsCast1>());
33+
assert!(b.is_instance_of::<JsCast2>());
34+
assert!(!b.is_instance_of::<JsCast3>());
35+
36+
assert!(c.is_instance_of::<JsCast1>());
37+
assert!(!c.is_instance_of::<JsCast2>());
38+
assert!(c.is_instance_of::<JsCast3>());
39+
}
40+
41+
#[wasm_bindgen_test]
42+
fn casting() {
43+
let a = JsCast1::new();
44+
let b = JsCast2::new();
45+
let c = JsCast3::new();
46+
47+
assert!(a.dyn_ref::<JsCast1>().is_some());
48+
assert!(a.dyn_ref::<JsCast2>().is_none());
49+
assert!(a.dyn_ref::<JsCast3>().is_none());
50+
51+
assert!(b.dyn_ref::<JsCast1>().is_none());
52+
assert!(b.dyn_ref::<JsCast2>().is_some());
53+
assert!(b.dyn_ref::<JsCast3>().is_none());
54+
55+
assert!(c.dyn_ref::<JsCast1>().is_some());
56+
assert!(c.dyn_ref::<JsCast2>().is_none());
57+
assert!(c.dyn_ref::<JsCast3>().is_some());
58+
}
59+
60+
#[wasm_bindgen_test]
61+
fn method_calling() {
62+
let a = JsCast1::new();
63+
let b = JsCast3::new();
64+
65+
assert_eq!(a.myval(), 1);
66+
assert_eq!(b.dyn_ref::<JsCast1>().unwrap().myval(), 3);
67+
assert_eq!(b.unchecked_ref::<JsCast1>().myval(), 3);
68+
}

tests/wasm/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod enums;
2121
pub mod import_class;
2222
pub mod imports;
2323
pub mod js_objects;
24+
pub mod jscast;
2425
pub mod math;
2526
pub mod node;
2627
pub mod option;

0 commit comments

Comments
 (0)