From cb880bdbff99ad1038a47bbc8b62f6df4335073c Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 26 Mar 2019 19:29:46 +0000 Subject: [PATCH 1/2] Add customisable `is_type_of` --- crates/backend/src/ast.rs | 1 + crates/backend/src/codegen.rs | 9 +++++++++ crates/js-sys/src/lib.rs | 28 ++++++++++++---------------- crates/macro-support/src/parser.rs | 8 ++++++++ crates/webidl/src/lib.rs | 1 + src/cast.rs | 16 ++++++++++++---- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index e61f5878..aeb56ccb 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -183,6 +183,7 @@ pub struct ImportType { pub attrs: Vec, pub doc_comment: Option, pub instanceof_shim: String, + pub is_type_of: Option, pub extends: Vec, pub vendor_prefixes: Vec, } diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index b1ac809a..6ad6a873 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -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! { #[allow(bad_style)] #(#attrs)* @@ -720,6 +727,8 @@ impl ToTokens for ast::ImportType { panic!("cannot check instanceof on non-wasm targets"); } + #is_type_of + #[inline] fn unchecked_from_js(val: JsValue) -> Self { #rust_name { obj: val.into() } diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index d53e9d00..12f8e270 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -126,7 +126,7 @@ extern "C" { // Array #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] + #[wasm_bindgen(extends = Object, is_type_of = Array::is_array)] #[derive(Clone, Debug, PartialEq, Eq)] pub type Array; @@ -466,7 +466,7 @@ extern "C" { // Boolean #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] + #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_bool().is_some())] #[derive(Clone, PartialEq, Eq)] pub type Boolean; @@ -801,7 +801,7 @@ extern "C" { // Function #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] + #[wasm_bindgen(extends = Object, is_type_of = JsValue::is_function)] #[derive(Clone, Debug, PartialEq, Eq)] pub type Function; @@ -897,11 +897,7 @@ impl Function { /// If this JS value is not an instance of a function then this returns /// `None`. pub fn try_from(val: &JsValue) -> Option<&Function> { - if val.is_function() { - Some(val.unchecked_ref()) - } else { - None - } + val.dyn_ref() } } @@ -1141,7 +1137,10 @@ pub fn try_iter(val: &JsValue) -> Result, JsValue> { 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)?; if !it.is_object() { return Ok(None); @@ -1434,7 +1433,7 @@ extern "C" { // Number. #[wasm_bindgen] extern "C" { - #[wasm_bindgen(extends = Object)] + #[wasm_bindgen(extends = Object, is_type_of = |v| v.as_f64().is_some())] #[derive(Clone)] pub type Number; @@ -3127,7 +3126,7 @@ extern "C" { // JsString #[wasm_bindgen] 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)] pub type JsString; @@ -3587,11 +3586,7 @@ impl JsString { /// If this JS value is not an instance of a string then this returns /// `None`. pub fn try_from(val: &JsValue) -> Option<&JsString> { - if val.is_string() { - Some(val.unchecked_ref()) - } else { - None - } + val.dyn_ref() } /// Returns whether this string is a valid UTF-16 string. @@ -3683,6 +3678,7 @@ impl fmt::Debug for JsString { // Symbol #[wasm_bindgen] extern "C" { + #[wasm_bindgen(is_type_of = JsValue::is_symbol)] #[derive(Clone, Debug)] pub type Symbol; diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index 6ad5172e..1ac67cde 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -45,6 +45,7 @@ macro_rules! attrgen { (readonly, Readonly(Span)), (js_name, JsName(Span, String, Span)), (js_class, JsClass(Span, String, Span)), + (is_type_of, IsTypeOf(Span, syn::Expr)), (extends, Extends(Span, syn::Path)), (vendor_prefix, VendorPrefix(Span, Ident)), (variadic, Variadic(Span)), @@ -240,6 +241,11 @@ impl Parse for BindgenAttr { return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); }); + (@parser $variant:ident(Span, syn::Expr)) => ({ + input.parse::()?; + return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); + }); + (@parser $variant:ident(Span, String, Span)) => ({ input.parse::()?; let (val, span) = match input.parse::() { @@ -515,6 +521,7 @@ impl ConvertToAst for syn::ForeignItemType { .js_name() .map(|s| s.0) .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 mut extends = Vec::new(); let mut vendor_prefixes = Vec::new(); @@ -537,6 +544,7 @@ impl ConvertToAst for syn::ForeignItemType { attrs: self.attrs, doc_comment: None, instanceof_shim: shim, + is_type_of, rust_name: self.ident, js_name, extends, diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index cdd86a58..08a85397 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -514,6 +514,7 @@ impl<'src> FirstPassRecord<'src> { attrs, doc_comment: None, instanceof_shim: format!("__widl_instanceof_{}", name), + is_type_of: None, extends: Vec::new(), vendor_prefixes: Vec::new(), }; diff --git a/src/cast.rs b/src/cast.rs index fa690e3d..fd78a9a9 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -30,14 +30,14 @@ where /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `Err(self)` if `self.is_instance_of::()` + /// This method will return `Err(self)` if `T::is_type_of(self)` /// returns `false`, and otherwise it will return `Ok(T)` manufactured with /// an unchecked cast (verified correct via the `instanceof` operation). fn dyn_into(self) -> Result where T: JsCast, { - if self.is_instance_of::() { + if T::is_type_of(self.as_ref()) { Ok(self.unchecked_into()) } else { Err(self) @@ -47,14 +47,14 @@ where /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `None` if `self.is_instance_of::()` + /// This method will return `None` if `T::is_type_of(self)` /// returns `false`, and otherwise it will return `Some(&T)` manufactured /// with an unchecked cast (verified correct via the `instanceof` operation). fn dyn_ref(&self) -> Option<&T> where T: JsCast, { - if self.is_instance_of::() { + if T::is_type_of(self.as_ref()) { Some(self.unchecked_ref()) } else { None @@ -100,6 +100,14 @@ where /// won't need to call this. 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. + fn is_type_of(val: &JsValue) -> bool { + Self::instanceof(val) + } + /// Performs a zero-cost unchecked conversion from a `JsValue` into an /// instance of `Self` /// From c77b46af7d393ab2577f7c0e59169197d62411c1 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Fri, 12 Apr 2019 17:41:51 +0100 Subject: [PATCH 2/2] Add `has_type` and update `is_instance_of` docs --- src/cast.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/cast.rs b/src/cast.rs index fd78a9a9..6db649cb 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -20,6 +20,9 @@ where /// /// This method performs a dynamic check (at runtime) using the JS /// `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(&self) -> bool where T: JsCast, @@ -27,17 +30,28 @@ where 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(&self) -> bool + where + T: JsCast, + { + T::is_type_of(self.as_ref()) + } + /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `Err(self)` if `T::is_type_of(self)` + /// This method will return `Err(self)` if `self.has_type::()` /// returns `false`, and otherwise it will return `Ok(T)` manufactured with /// an unchecked cast (verified correct via the `instanceof` operation). fn dyn_into(self) -> Result where T: JsCast, { - if T::is_type_of(self.as_ref()) { + if self.has_type::() { Ok(self.unchecked_into()) } else { Err(self) @@ -47,14 +61,14 @@ where /// Performs a dynamic cast (checked at runtime) of this value into the /// target type `T`. /// - /// This method will return `None` if `T::is_type_of(self)` + /// This method will return `None` if `self.has_type::()` /// returns `false`, and otherwise it will return `Some(&T)` manufactured /// with an unchecked cast (verified correct via the `instanceof` operation). fn dyn_ref(&self) -> Option<&T> where T: JsCast, { - if T::is_type_of(self.as_ref()) { + if self.has_type::() { Some(self.unchecked_ref()) } else { None @@ -103,7 +117,12 @@ where /// 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. + /// 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) }