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>`
This commit is contained in:
Alex Crichton
2018-08-04 09:41:59 -07:00
parent f3f11ed8eb
commit 11553a1af2
9 changed files with 178 additions and 6 deletions

View File

@ -127,6 +127,7 @@ pub struct ImportType {
pub name: Ident, pub name: Ident,
pub attrs: Vec<syn::Attribute>, pub attrs: Vec<syn::Attribute>,
pub doc_comment: Option<String>, pub doc_comment: Option<String>,
pub instanceof_shim: String,
} }
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))] #[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -408,7 +409,10 @@ impl ImportStatic {
impl ImportType { impl ImportType {
fn shared(&self) -> shared::ImportType { fn shared(&self) -> shared::ImportType {
shared::ImportType {} shared::ImportType {
name: self.name.to_string(),
instanceof_shim: self.instanceof_shim.clone(),
}
} }
} }

View File

@ -521,10 +521,12 @@ impl ToTokens for ast::ImportType {
}; };
let const_name = format!("__wbg_generated_const_{}", name); let const_name = format!("__wbg_generated_const_{}", name);
let const_name = Ident::new(&const_name, Span::call_site()); let const_name = Ident::new(&const_name, Span::call_site());
let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site());
(quote! { (quote! {
#[allow(bad_style)] #[allow(bad_style)]
#(#attrs)* #(#attrs)*
#[doc = #doc_comment] #[doc = #doc_comment]
#[repr(transparent)]
#vis struct #name { #vis struct #name {
obj: ::wasm_bindgen::JsValue, obj: ::wasm_bindgen::JsValue,
} }
@ -533,7 +535,7 @@ impl ToTokens for ast::ImportType {
const #const_name: () = { const #const_name: () = {
use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi, Stack}; use wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi, Stack};
use wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; 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::describe::WasmDescribe;
use wasm_bindgen::{JsValue, JsCast}; use wasm_bindgen::{JsValue, JsCast};
use wasm_bindgen::__rt::core::mem::ManuallyDrop; 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<JsValue> for #name { impl From<JsValue> for #name {
fn from(obj: JsValue) -> #name { fn from(obj: JsValue) -> #name {
#name { obj } #name { obj }
} }
} }
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 { impl From<#name> for JsValue {
fn from(obj: #name) -> JsValue { fn from(obj: #name) -> JsValue {
obj.obj 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); }).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) 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(_) => {} shared::ImportKind::Enum(_) => {}
} }
Ok(()) Ok(())
@ -1936,6 +1943,27 @@ impl<'a, 'b> SubContext<'a, 'b> {
Ok(()) 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) { fn generate_enum(&mut self, enum_: &shared::Enum) {
let mut variants = String::new(); let mut variants = String::new();

View File

@ -524,11 +524,13 @@ impl ConvertToAst<()> for syn::ForeignItemType {
type Target = ast::ImportKind; type Target = ast::ImportKind;
fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> { fn convert(self, (): ()) -> Result<Self::Target, Diagnostic> {
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
Ok(ast::ImportKind::Type(ast::ImportType { Ok(ast::ImportKind::Type(ast::ImportType {
vis: self.vis, vis: self.vis,
name: self.ident,
attrs: self.attrs, attrs: self.attrs,
doc_comment: None, doc_comment: None,
instanceof_shim: shim,
name: self.ident,
})) }))
} }
} }

View File

@ -3,7 +3,7 @@
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub const SCHEMA_VERSION: &str = "7"; pub const SCHEMA_VERSION: &str = "8";
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ProgramOnlySchema { pub struct ProgramOnlySchema {
@ -81,7 +81,10 @@ pub struct ImportStatic {
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ImportType {} pub struct ImportType {
pub name: String,
pub instanceof_shim: String,
}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ImportEnum {} pub struct ImportEnum {}

View File

@ -246,6 +246,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::InterfaceDefinition<'src> {
name: rust_ident(camel_case_ident(self.identifier.0).as_str()), name: rust_ident(camel_case_ident(self.identifier.0).as_str()),
attrs: Vec::new(), attrs: Vec::new(),
doc_comment, doc_comment,
instanceof_shim: format!("__widl_instanceof_{}", self.name),
}), }),
}); });

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

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

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

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

View File

@ -21,6 +21,7 @@ pub mod enums;
pub mod import_class; pub mod import_class;
pub mod imports; pub mod imports;
pub mod js_objects; pub mod js_objects;
pub mod jscast;
pub mod math; pub mod math;
pub mod node; pub mod node;
pub mod option; pub mod option;