mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-29 13:06:06 +03:00
Merge pull request #640 from alexcrichton/jscast
Implement RFC #2 - casting hierarchy between JS values
This commit is contained in:
commit
5b935526ff
@ -127,6 +127,8 @@ 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,
|
||||||
|
pub extends: Vec<Ident>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||||
@ -408,7 +410,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(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,93 +519,168 @@ impl ToTokens for ast::ImportType {
|
|||||||
None => "",
|
None => "",
|
||||||
Some(comment) => comment,
|
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! {
|
(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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::wasm_bindgen::describe::WasmDescribe for #name {
|
#[allow(bad_style)]
|
||||||
fn describe() {
|
const #const_name: () = {
|
||||||
::wasm_bindgen::JsValue::describe();
|
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 {
|
impl WasmDescribe for #name {
|
||||||
type Abi = <::wasm_bindgen::JsValue as
|
fn describe() {
|
||||||
::wasm_bindgen::convert::IntoWasmAbi>::Abi;
|
JsValue::describe();
|
||||||
|
|
||||||
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 ::wasm_bindgen::convert::OptionFromWasmAbi for #name {
|
impl IntoWasmAbi for #name {
|
||||||
fn is_none(abi: &Self::Abi) -> bool { *abi == 0 }
|
type Abi = <JsValue as IntoWasmAbi>::Abi;
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ::wasm_bindgen::convert::IntoWasmAbi for &'a #name {
|
fn into_abi(self, extra: &mut Stack) -> Self::Abi {
|
||||||
type Abi = <&'a ::wasm_bindgen::JsValue as
|
self.obj.into_abi(extra)
|
||||||
::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::RefFromWasmAbi for #name {
|
impl OptionIntoWasmAbi for #name {
|
||||||
type Abi = <::wasm_bindgen::JsValue as
|
fn none() -> Self::Abi { 0 }
|
||||||
::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 From<::wasm_bindgen::JsValue> for #name {
|
impl<'a> OptionIntoWasmAbi for &'a #name {
|
||||||
fn from(obj: ::wasm_bindgen::JsValue) -> #name {
|
fn none() -> Self::Abi { 0 }
|
||||||
#name { obj }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<#name> for ::wasm_bindgen::JsValue {
|
impl FromWasmAbi for #name {
|
||||||
fn from(obj: #name) -> ::wasm_bindgen::JsValue {
|
type Abi = <JsValue as FromWasmAbi>::Abi;
|
||||||
obj.obj
|
|
||||||
|
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 = <JsValue as RefFromWasmAbi>::Abi;
|
||||||
|
type Anchor = ManuallyDrop<#name>;
|
||||||
|
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove this on the next major version
|
||||||
|
impl From<JsValue> for #name {
|
||||||
|
fn from(obj: JsValue) -> #name {
|
||||||
|
#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 {
|
||||||
|
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);
|
}).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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -182,6 +182,16 @@ impl BindgenAttrs {
|
|||||||
})
|
})
|
||||||
.next()
|
.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 {
|
impl syn::synom::Synom for BindgenAttrs {
|
||||||
@ -217,6 +227,7 @@ pub enum BindgenAttr {
|
|||||||
Readonly,
|
Readonly,
|
||||||
JsName(String),
|
JsName(String),
|
||||||
JsClass(String),
|
JsClass(String),
|
||||||
|
Extends(Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl syn::synom::Synom for BindgenAttr {
|
impl syn::synom::Synom for BindgenAttr {
|
||||||
@ -295,6 +306,13 @@ impl syn::synom::Synom for BindgenAttr {
|
|||||||
s: syn!(syn::LitStr) >>
|
s: syn!(syn::LitStr) >>
|
||||||
(s.value())
|
(s.value())
|
||||||
)=> { BindgenAttr::JsClass }
|
)=> { 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;
|
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 {
|
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,
|
||||||
|
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 js_namespace = item_opts.js_namespace().or(opts.js_namespace()).cloned();
|
||||||
let kind = match self {
|
let kind = match self {
|
||||||
syn::ForeignItem::Fn(f) => f.convert((item_opts, &module))?,
|
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)?,
|
syn::ForeignItem::Static(s) => s.convert(item_opts)?,
|
||||||
_ => panic!("only foreign functions/types allowed for now"),
|
_ => panic!("only foreign functions/types allowed for now"),
|
||||||
};
|
};
|
||||||
|
@ -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 {}
|
||||||
|
@ -17,6 +17,7 @@ use weedle;
|
|||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
use util;
|
use util;
|
||||||
|
use util::camel_case_ident;
|
||||||
|
|
||||||
/// Collection of constructs that may use partial.
|
/// Collection of constructs that may use partial.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -36,6 +37,7 @@ pub(crate) struct InterfaceData<'src> {
|
|||||||
pub(crate) partial: bool,
|
pub(crate) partial: bool,
|
||||||
pub(crate) global: bool,
|
pub(crate) global: bool,
|
||||||
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
|
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
|
||||||
|
pub(crate) superclass: Option<&'src str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -146,6 +148,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceDefinition<'src> {
|
|||||||
.entry(self.identifier.0)
|
.entry(self.identifier.0)
|
||||||
.or_insert_with(Default::default);
|
.or_insert_with(Default::default);
|
||||||
interface.partial = false;
|
interface.partial = false;
|
||||||
|
interface.superclass = self.inheritance.map(|s| s.identifier.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if util::is_chrome_only(&self.attributes) {
|
if util::is_chrome_only(&self.attributes) {
|
||||||
@ -176,6 +179,7 @@ impl<'src> FirstPass<'src, ()> for weedle::PartialInterfaceDefinition<'src> {
|
|||||||
partial: true,
|
partial: true,
|
||||||
operations: Default::default(),
|
operations: Default::default(),
|
||||||
global: false,
|
global: false,
|
||||||
|
superclass: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -307,3 +311,27 @@ impl<'src> FirstPass<'src, ()> for weedle::TypedefDefinition<'src> {
|
|||||||
Ok(())
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,6 +39,7 @@ use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
|||||||
use backend::util::{ident_ty, rust_ident, wrap_import_function};
|
use backend::util::{ident_ty, rust_ident, wrap_import_function};
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use heck::{ShoutySnakeCase};
|
use heck::{ShoutySnakeCase};
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
use weedle::argument::Argument;
|
use weedle::argument::Argument;
|
||||||
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
|
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()),
|
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.identifier.0),
|
||||||
|
extends: first_pass.all_superclasses(self.identifier.0)
|
||||||
|
.map(|name| Ident::new(&name, Span::call_site()))
|
||||||
|
.collect(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
- [On JavaScript Imports](./reference/attributes/on-js-imports/index.md)
|
- [On JavaScript Imports](./reference/attributes/on-js-imports/index.md)
|
||||||
- [`catch`](./reference/attributes/on-js-imports/catch.md)
|
- [`catch`](./reference/attributes/on-js-imports/catch.md)
|
||||||
- [`constructor`](./reference/attributes/on-js-imports/constructor.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)
|
- [`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)
|
- [`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)
|
- [`js_class = "Blah"`](./reference/attributes/on-js-imports/js_class.md)
|
||||||
|
49
guide/src/reference/attributes/on-js-imports/extends.md
Normal file
49
guide/src/reference/attributes/on-js-imports/extends.md
Normal 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
165
src/cast.rs
Normal 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;
|
||||||
|
}
|
19
src/lib.rs
19
src/lib.rs
@ -46,6 +46,9 @@ pub mod prelude {
|
|||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod describe;
|
pub mod describe;
|
||||||
|
|
||||||
|
mod cast;
|
||||||
|
pub use cast::JsCast;
|
||||||
|
|
||||||
if_std! {
|
if_std! {
|
||||||
extern crate std;
|
extern crate std;
|
||||||
use std::prelude::v1::*;
|
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 {
|
macro_rules! numbers {
|
||||||
($($n:ident)*) => ($(
|
($($n:ident)*) => ($(
|
||||||
impl PartialEq<$n> for JsValue {
|
impl PartialEq<$n> for JsValue {
|
||||||
|
28
tests/wasm/jscast.js
Normal file
28
tests/wasm/jscast.js
Normal 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
88
tests/wasm/jscast.rs
Normal 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);
|
||||||
|
}
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user