Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for customising instanceof behaviour #1405

Merged
merged 2 commits into from
Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ pub struct ImportType {
pub attrs: Vec<syn::Attribute>,
pub doc_comment: Option<String>,
pub instanceof_shim: String,
pub is_type_of: Option<syn::Expr>,
pub extends: Vec<syn::Path>,
pub vendor_prefixes: Vec<Ident>,
}
Expand Down
9 changes: 9 additions & 0 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*
Expand Down Expand Up @@ -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() }
Expand Down
28 changes: 12 additions & 16 deletions crates/js-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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()
}
}

Expand Down Expand Up @@ -1141,7 +1137,10 @@ pub fn try_iter(val: &JsValue) -> Result<Option<IntoIter>, 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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -240,6 +241,11 @@ impl Parse for BindgenAttr {
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)) => ({
input.parse::<Token![=]>()?;
let (val, span) = match input.parse::<syn::LitStr>() {
Expand Down Expand Up @@ -515,6 +521,7 @@ impl ConvertToAst<BindgenAttrs> 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();
Expand All @@ -537,6 +544,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
attrs: self.attrs,
doc_comment: None,
instanceof_shim: shim,
is_type_of,
rust_name: self.ident,
js_name,
extends,
Expand Down
1 change: 1 addition & 0 deletions crates/webidl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};
Expand Down
35 changes: 31 additions & 4 deletions src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,38 @@ 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<T>(&self) -> bool
where
T: JsCast,
{
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
/// 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
/// 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>() {
if self.has_type::<T>() {
Ok(self.unchecked_into())
} else {
Err(self)
Expand All @@ -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 `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
/// 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>() {
if self.has_type::<T>() {
Some(self.unchecked_ref())
} else {
None
Expand Down Expand Up @@ -100,6 +114,19 @@ 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 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
/// instance of `Self`
///
Expand Down