From bea07abd0f51980ae7e80c871c4e8129b0f768dd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 4 Aug 2018 09:02:31 -0700 Subject: [PATCH] Add a `JsCast` trait specified in [RFC 2] [RFC 2]: https://github.com/rustwasm/rfcs/pull/2 --- src/cast.rs | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 19 ++++++ 2 files changed, 184 insertions(+) create mode 100644 src/cast.rs diff --git a/src/cast.rs b/src/cast.rs new file mode 100644 index 00000000000..83393de7f65 --- /dev/null +++ b/src/cast.rs @@ -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 + AsMut + Into, +{ + /// Test whether this JS value is an instance of the type `T`. + /// + /// This commit performs a dynamic check (at runtime) using the JS + /// `instanceof` operator. This method returns `self instanceof T`. + fn is_instance_of(&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::()` + /// returns `false`, and otherwise it will return `Ok(T)` manufactured with + /// an unchecked cast (verified safe via the `instanceof` operation). + fn dyn_into(self) -> Result + where + T: JsCast, + { + if self.is_instance_of::() { + 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::()` + /// returns `false`, and otherwise it will return `Some(&T)` manufactured + /// with an unchecked cast (verified safe via the `instanceof` operation). + fn dyn_ref(&self) -> Option<&T> + where + T: JsCast, + { + if self.is_instance_of::() { + 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::()` + /// returns `false`, and otherwise it will return `Some(&mut T)` + /// manufactured with an unchecked cast (verified safe via the `instanceof` + /// operation). + fn dyn_mut(&mut self) -> Option<&mut T> + where + T: JsCast, + { + if self.is_instance_of::() { + 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 + /// shoudl be used with caution. + fn unchecked_into(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 + /// shoudl 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(&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 + /// shoudl 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(&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; +} diff --git a/src/lib.rs b/src/lib.rs index 3fedff5bc79..2f6a2be062c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,9 @@ pub mod prelude { pub mod convert; pub mod describe; +mod cast; +pub use cast::JsCast; + if_std! { extern crate std; use std::prelude::v1::*; @@ -347,6 +350,22 @@ impl From 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 for JsValue { + fn as_mut(&mut self) -> &mut JsValue { self } +} + +impl AsRef for JsValue { + fn as_ref(&self) -> &JsValue { self } +} + macro_rules! numbers { ($($n:ident)*) => ($( impl PartialEq<$n> for JsValue {