Merge pull request #1405 from RReverser/instanceof

Add support for customising `instanceof` behaviour
This commit is contained in:
Alex Crichton 2019-04-12 12:50:06 -05:00 committed by GitHub
commit 657b97b6d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 20 deletions

View File

@ -183,6 +183,7 @@ pub struct ImportType {
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 instanceof_shim: String,
pub is_type_of: Option<syn::Expr>,
pub extends: Vec<syn::Path>, pub extends: Vec<syn::Path>,
pub vendor_prefixes: Vec<Ident>, pub vendor_prefixes: Vec<Ident>,
} }

View File

@ -588,6 +588,13 @@ impl ToTokens for ast::ImportType {
} }
}; };
let is_type_of = self.is_type_of.as_ref().map(|is_type_of| quote! {
fn is_type_of(val: &JsValue) -> bool {
let is_type_of: fn(&JsValue) -> bool = #is_type_of;
is_type_of(val)
}
});
(quote! { (quote! {
#[allow(bad_style)] #[allow(bad_style)]
#(#attrs)* #(#attrs)*
@ -720,6 +727,8 @@ impl ToTokens for ast::ImportType {
panic!("cannot check instanceof on non-wasm targets"); panic!("cannot check instanceof on non-wasm targets");
} }
#is_type_of
#[inline] #[inline]
fn unchecked_from_js(val: JsValue) -> Self { fn unchecked_from_js(val: JsValue) -> Self {
#rust_name { obj: val.into() } #rust_name { obj: val.into() }

View File

@ -126,7 +126,7 @@ extern "C" {
// Array // Array
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(extends = Object)] #[wasm_bindgen(extends = Object, is_type_of = Array::is_array)]
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub type Array; pub type Array;
@ -466,7 +466,7 @@ extern "C" {
// Boolean // Boolean
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(extends = Object)] #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_bool().is_some())]
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub type Boolean; pub type Boolean;
@ -801,7 +801,7 @@ extern "C" {
// Function // Function
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(extends = Object)] #[wasm_bindgen(extends = Object, is_type_of = JsValue::is_function)]
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub type Function; pub type Function;
@ -897,11 +897,7 @@ impl Function {
/// If this JS value is not an instance of a function then this returns /// If this JS value is not an instance of a function then this returns
/// `None`. /// `None`.
pub fn try_from(val: &JsValue) -> Option<&Function> { pub fn try_from(val: &JsValue) -> Option<&Function> {
if val.is_function() { val.dyn_ref()
Some(val.unchecked_ref())
} else {
None
}
} }
} }
@ -1141,7 +1137,10 @@ pub fn try_iter(val: &JsValue) -> Result<Option<IntoIter>, JsValue> {
return Ok(None); return Ok(None);
} }
let iter_fn: Function = iter_fn.unchecked_into(); let iter_fn: Function = match iter_fn.dyn_into() {
Ok(iter_fn) => iter_fn,
Err(_) => return Ok(None)
};
let it = iter_fn.call0(val)?; let it = iter_fn.call0(val)?;
if !it.is_object() { if !it.is_object() {
return Ok(None); return Ok(None);
@ -1434,7 +1433,7 @@ extern "C" {
// Number. // Number.
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(extends = Object)] #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_f64().is_some())]
#[derive(Clone)] #[derive(Clone)]
pub type Number; pub type Number;
@ -3127,7 +3126,7 @@ extern "C" {
// JsString // JsString
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(js_name = String, extends = Object)] #[wasm_bindgen(js_name = String, extends = Object, is_type_of = JsValue::is_string)]
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub type JsString; pub type JsString;
@ -3587,11 +3586,7 @@ impl JsString {
/// If this JS value is not an instance of a string then this returns /// If this JS value is not an instance of a string then this returns
/// `None`. /// `None`.
pub fn try_from(val: &JsValue) -> Option<&JsString> { pub fn try_from(val: &JsValue) -> Option<&JsString> {
if val.is_string() { val.dyn_ref()
Some(val.unchecked_ref())
} else {
None
}
} }
/// Returns whether this string is a valid UTF-16 string. /// Returns whether this string is a valid UTF-16 string.
@ -3683,6 +3678,7 @@ impl fmt::Debug for JsString {
// Symbol // Symbol
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
#[wasm_bindgen(is_type_of = JsValue::is_symbol)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub type Symbol; pub type Symbol;

View File

@ -45,6 +45,7 @@ macro_rules! attrgen {
(readonly, Readonly(Span)), (readonly, Readonly(Span)),
(js_name, JsName(Span, String, Span)), (js_name, JsName(Span, String, Span)),
(js_class, JsClass(Span, String, Span)), (js_class, JsClass(Span, String, Span)),
(is_type_of, IsTypeOf(Span, syn::Expr)),
(extends, Extends(Span, syn::Path)), (extends, Extends(Span, syn::Path)),
(vendor_prefix, VendorPrefix(Span, Ident)), (vendor_prefix, VendorPrefix(Span, Ident)),
(variadic, Variadic(Span)), (variadic, Variadic(Span)),
@ -240,6 +241,11 @@ impl Parse for BindgenAttr {
return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
}); });
(@parser $variant:ident(Span, syn::Expr)) => ({
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::$variant(attr_span, input.parse()?));
});
(@parser $variant:ident(Span, String, Span)) => ({ (@parser $variant:ident(Span, String, Span)) => ({
input.parse::<Token![=]>()?; input.parse::<Token![=]>()?;
let (val, span) = match input.parse::<syn::LitStr>() { let (val, span) = match input.parse::<syn::LitStr>() {
@ -515,6 +521,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
.js_name() .js_name()
.map(|s| s.0) .map(|s| s.0)
.map_or_else(|| self.ident.to_string(), |s| s.to_string()); .map_or_else(|| self.ident.to_string(), |s| s.to_string());
let is_type_of = attrs.is_type_of().cloned();
let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident)); let shim = format!("__wbg_instanceof_{}_{}", self.ident, ShortHash(&self.ident));
let mut extends = Vec::new(); let mut extends = Vec::new();
let mut vendor_prefixes = Vec::new(); let mut vendor_prefixes = Vec::new();
@ -537,6 +544,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
attrs: self.attrs, attrs: self.attrs,
doc_comment: None, doc_comment: None,
instanceof_shim: shim, instanceof_shim: shim,
is_type_of,
rust_name: self.ident, rust_name: self.ident,
js_name, js_name,
extends, extends,

View File

@ -514,6 +514,7 @@ impl<'src> FirstPassRecord<'src> {
attrs, attrs,
doc_comment: None, doc_comment: None,
instanceof_shim: format!("__widl_instanceof_{}", name), instanceof_shim: format!("__widl_instanceof_{}", name),
is_type_of: None,
extends: Vec::new(), extends: Vec::new(),
vendor_prefixes: Vec::new(), vendor_prefixes: Vec::new(),
}; };

View File

@ -20,6 +20,9 @@ where
/// ///
/// This method performs a dynamic check (at runtime) using the JS /// This method performs a dynamic check (at runtime) using the JS
/// `instanceof` operator. This method returns `self instanceof T`. /// `instanceof` operator. This method returns `self instanceof T`.
///
/// Note that `instanceof` does not work with primitive values or across
/// different realms (e.g. iframes). Prefer using `has_type` instead.
fn is_instance_of<T>(&self) -> bool fn is_instance_of<T>(&self) -> bool
where where
T: JsCast, T: JsCast,
@ -27,17 +30,28 @@ where
T::instanceof(self.as_ref()) T::instanceof(self.as_ref())
} }
/// Test whether this JS value has a type `T`.
///
/// Unlike `is_instance_of`, the type can override this to a specialised
/// check that works reliably with primitives and across realms.
fn has_type<T>(&self) -> bool
where
T: JsCast,
{
T::is_type_of(self.as_ref())
}
/// Performs a dynamic cast (checked at runtime) of this value into the /// Performs a dynamic cast (checked at runtime) of this value into the
/// target type `T`. /// target type `T`.
/// ///
/// This method will return `Err(self)` if `self.is_instance_of::<T>()` /// This method will return `Err(self)` if `self.has_type::<T>()`
/// returns `false`, and otherwise it will return `Ok(T)` manufactured with /// returns `false`, and otherwise it will return `Ok(T)` manufactured with
/// an unchecked cast (verified correct via the `instanceof` operation). /// an unchecked cast (verified correct via the `instanceof` operation).
fn dyn_into<T>(self) -> Result<T, Self> fn dyn_into<T>(self) -> Result<T, Self>
where where
T: JsCast, T: JsCast,
{ {
if self.is_instance_of::<T>() { if self.has_type::<T>() {
Ok(self.unchecked_into()) Ok(self.unchecked_into())
} else { } else {
Err(self) Err(self)
@ -47,14 +61,14 @@ where
/// Performs a dynamic cast (checked at runtime) of this value into the /// Performs a dynamic cast (checked at runtime) of this value into the
/// target type `T`. /// target type `T`.
/// ///
/// This method will return `None` if `self.is_instance_of::<T>()` /// This method will return `None` if `self.has_type::<T>()`
/// returns `false`, and otherwise it will return `Some(&T)` manufactured /// returns `false`, and otherwise it will return `Some(&T)` manufactured
/// with an unchecked cast (verified correct via the `instanceof` operation). /// with an unchecked cast (verified correct via the `instanceof` operation).
fn dyn_ref<T>(&self) -> Option<&T> fn dyn_ref<T>(&self) -> Option<&T>
where where
T: JsCast, T: JsCast,
{ {
if self.is_instance_of::<T>() { if self.has_type::<T>() {
Some(self.unchecked_ref()) Some(self.unchecked_ref())
} else { } else {
None None
@ -100,6 +114,19 @@ where
/// won't need to call this. /// won't need to call this.
fn instanceof(val: &JsValue) -> bool; fn instanceof(val: &JsValue) -> bool;
/// Performs a dynamic check to see whether the `JsValue` provided
/// is a value of this type.
///
/// Unlike `instanceof`, this can be specialised to use a custom check by
/// adding a `#[wasm_bindgen(is_type_of = callback)]` attribute to the
/// type import declaration.
///
/// Other than that, this is intended to be an internal implementation
/// detail of `has_type` and you likely won't need to call this.
fn is_type_of(val: &JsValue) -> bool {
Self::instanceof(val)
}
/// Performs a zero-cost unchecked conversion from a `JsValue` into an /// Performs a zero-cost unchecked conversion from a `JsValue` into an
/// instance of `Self` /// instance of `Self`
/// ///