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
9 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