mirror of https://github.com/boa-dev/boa.git
Browse Source
* Add TryFromJs for more types * Add a new type Coerce<> to coerce values This can be used to transform values in to coerced versions by using the type system and TryFromJs. * Fix build error * Rename Coerce<> to Convert<> * replace missed coerce to Convert * cargo fmt and clippypull/3812/head
Hans Larsen
8 months ago
committed by
GitHub
4 changed files with 182 additions and 24 deletions
@ -0,0 +1,130 @@ |
|||||||
|
//! Types and functions for applying JavaScript Convert rules to [`JsValue`] when
|
||||||
|
//! converting. See <https://262.ecma-international.org/5.1/#sec-9> (Section 9) for
|
||||||
|
//! conversion rules of JavaScript types.
|
||||||
|
//!
|
||||||
|
//! Some conversions are not specified in the spec (e.g. integer conversions),
|
||||||
|
//! and we apply rules that make sense (e.g. converting to Number and rounding
|
||||||
|
//! if necessary).
|
||||||
|
|
||||||
|
use boa_engine::JsNativeError; |
||||||
|
|
||||||
|
use crate::value::TryFromJs; |
||||||
|
use crate::{Context, JsResult, JsString, JsValue}; |
||||||
|
|
||||||
|
/// A wrapper type that allows converting a `JsValue` to a specific type.
|
||||||
|
/// This is useful when you want to convert a `JsValue` to a Rust type.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// Convert a string to number.
|
||||||
|
/// ```
|
||||||
|
/// # use boa_engine::{Context, js_string, JsValue};
|
||||||
|
/// # use boa_engine::value::{Convert, TryFromJs};
|
||||||
|
/// # let mut context = Context::default();
|
||||||
|
/// let value = JsValue::from(js_string!("42"));
|
||||||
|
/// let Convert(converted): Convert<i32> = Convert::try_from_js(&value, &mut context).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(converted, 42);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Convert a number to a bool.
|
||||||
|
/// ```
|
||||||
|
/// # use boa_engine::{Context, js_string, JsValue};
|
||||||
|
/// # use boa_engine::value::{Convert, TryFromJs};
|
||||||
|
/// # let mut context = Context::default();
|
||||||
|
/// let Convert(conv0): Convert<bool> = Convert::try_from_js(&JsValue::Integer(0), &mut context).unwrap();
|
||||||
|
/// let Convert(conv5): Convert<bool> = Convert::try_from_js(&JsValue::Integer(5), &mut context).unwrap();
|
||||||
|
/// let Convert(conv_nan): Convert<bool> = Convert::try_from_js(&JsValue::Rational(f64::NAN), &mut context).unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(conv0, false);
|
||||||
|
/// assert_eq!(conv5, true);
|
||||||
|
/// assert_eq!(conv_nan, false);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
||||||
|
pub struct Convert<T: TryFromJs>(pub T); |
||||||
|
|
||||||
|
impl<T: TryFromJs> From<T> for Convert<T> { |
||||||
|
fn from(value: T) -> Self { |
||||||
|
Self(value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
macro_rules! decl_convert_to_int { |
||||||
|
($($ty:ty),*) => { |
||||||
|
$( |
||||||
|
impl TryFromJs for Convert<$ty> { |
||||||
|
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { |
||||||
|
value.to_numeric_number(context).and_then(|num| { |
||||||
|
if num.is_finite() { |
||||||
|
if num >= f64::from(<$ty>::MAX) { |
||||||
|
Err(JsNativeError::typ() |
||||||
|
.with_message("cannot convert value to integer, it is too large") |
||||||
|
.into()) |
||||||
|
} else if num <= f64::from(<$ty>::MIN) { |
||||||
|
Err(JsNativeError::typ() |
||||||
|
.with_message("cannot convert value to integer, it is too small") |
||||||
|
.into()) |
||||||
|
// Only round if it differs from the next integer by an epsilon
|
||||||
|
} else if num.abs().fract() >= (1.0 - f64::EPSILON) { |
||||||
|
Ok(Convert(num.round() as $ty)) |
||||||
|
} else { |
||||||
|
Ok(Convert(num as $ty)) |
||||||
|
} |
||||||
|
} else if num.is_nan() { |
||||||
|
Err(JsNativeError::typ() |
||||||
|
.with_message("cannot convert NaN to integer") |
||||||
|
.into()) |
||||||
|
} else if num.is_infinite() { |
||||||
|
Err(JsNativeError::typ() |
||||||
|
.with_message("cannot convert Infinity to integer") |
||||||
|
.into()) |
||||||
|
} else { |
||||||
|
Err(JsNativeError::typ() |
||||||
|
.with_message("cannot convert non-finite number to integer") |
||||||
|
.into()) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
)* |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
decl_convert_to_int!(i8, i16, i32, u8, u16, u32); |
||||||
|
|
||||||
|
macro_rules! decl_convert_to_float { |
||||||
|
($($ty:ty),*) => { |
||||||
|
$( |
||||||
|
impl TryFromJs for Convert<$ty> { |
||||||
|
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { |
||||||
|
value.to_numeric_number(context).and_then(|num| Ok(Convert(<$ty>::try_from(num).map_err(|_| { |
||||||
|
JsNativeError::typ() |
||||||
|
.with_message("cannot convert value to float") |
||||||
|
})?))) |
||||||
|
} |
||||||
|
} |
||||||
|
)* |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
decl_convert_to_float!(f64); |
||||||
|
|
||||||
|
impl TryFromJs for Convert<String> { |
||||||
|
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { |
||||||
|
value |
||||||
|
.to_string(context) |
||||||
|
.and_then(|s| s.to_std_string().map_err(|_| JsNativeError::typ().into())) |
||||||
|
.map(Convert) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFromJs for Convert<JsString> { |
||||||
|
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { |
||||||
|
value.to_string(context).map(Convert) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFromJs for Convert<bool> { |
||||||
|
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> { |
||||||
|
Ok(Self(value.to_boolean())) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue