Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1141 lines
36 KiB

//! Boa's ECMAScript Value implementation.
//!
//! Javascript values, utility methods and conversion between Javascript values and Rust values.
use std::{
collections::HashSet,
fmt::{self, Display},
ops::Sub,
};
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{ToPrimitive, Zero};
use once_cell::sync::Lazy;
use boa_gc::{custom_trace, Finalize, Trace};
#[doc(inline)]
pub use boa_macros::TryFromJs;
use boa_profiler::Profiler;
#[doc(inline)]
pub use conversions::convert::Convert;
use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
Number, Promise,
},
error::JsNativeError,
js_string,
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
symbol::JsSymbol,
Context, JsBigInt, JsResult, JsString,
};
pub(crate) use self::conversions::IntoOrUndefined;
#[doc(inline)]
pub use self::{
conversions::try_from_js::TryFromJs, display::ValueDisplay, integer::IntegerOrInfinity,
operations::*, r#type::Type,
};
mod conversions;
pub(crate) mod display;
mod equality;
mod hash;
mod integer;
mod operations;
mod r#type;
#[cfg(test)]
mod tests;
static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_64: u128 = 2u128.pow(64);
BigInt::from(TWO_E_64)
});
static TWO_E_63: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_63: u128 = 2u128.pow(63);
BigInt::from(TWO_E_63)
});
/// A Javascript value
#[derive(Finalize, Debug, Clone)]
pub enum JsValue {
/// `null` - A null value, for when a value doesn't exist.
Null,
/// `undefined` - An undefined value, for when a field or index doesn't exist.
Undefined,
/// `boolean` - A `true` / `false` value, for if a certain criteria is met.
Boolean(bool),
/// `String` - A UTF-16 string, such as `"Hello, world"`.
String(JsString),
/// `Number` - A 64-bit floating point number, such as `3.1415`
Rational(f64),
/// `Number` - A 32-bit integer, such as `42`.
Integer(i32),
/// `BigInt` - holds any arbitrary large signed integer.
BigInt(JsBigInt),
/// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values.
Object(JsObject),
/// `Symbol` - A Symbol Primitive type.
Symbol(JsSymbol),
}
unsafe impl Trace for JsValue {
custom_trace! {this, mark, {
if let Self::Object(o) = this {
mark(o);
}
}}
}
impl JsValue {
/// Create a new [`JsValue`].
pub fn new<T>(value: T) -> Self
where
T: Into<Self>,
{
value.into()
}
/// Creates a new `undefined` value.
#[inline]
#[must_use]
pub const fn undefined() -> Self {
Self::Undefined
}
/// Creates a new `null` value.
#[inline]
#[must_use]
pub const fn null() -> Self {
Self::Null
}
/// Creates a new number with `NaN` value.
#[inline]
#[must_use]
pub const fn nan() -> Self {
Self::Rational(f64::NAN)
}
/// Creates a new number with `Infinity` value.
#[inline]
#[must_use]
pub const fn positive_infinity() -> Self {
Self::Rational(f64::INFINITY)
}
/// Creates a new number with `-Infinity` value.
#[inline]
#[must_use]
pub const fn negative_infinity() -> Self {
Self::Rational(f64::NEG_INFINITY)
}
/// Returns true if the value is an object.
#[inline]
#[must_use]
pub const fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
/// Returns the object if the value is object, otherwise `None`.
#[inline]
#[must_use]
pub const fn as_object(&self) -> Option<&JsObject> {
match *self {
Self::Object(ref o) => Some(o),
_ => None,
}
}
/// It determines if the value is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
#[must_use]
pub fn is_callable(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_callable())
}
/// Returns the callable value if the value is callable, otherwise `None`.
#[inline]
#[must_use]
pub fn as_callable(&self) -> Option<&JsObject> {
self.as_object().filter(|obj| obj.is_callable())
}
/// Returns true if the value is a constructor object.
#[inline]
#[must_use]
pub fn is_constructor(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_constructor())
}
/// Returns the constructor if the value is a constructor, otherwise `None`.
#[inline]
#[must_use]
pub fn as_constructor(&self) -> Option<&JsObject> {
self.as_object().filter(|obj| obj.is_constructor())
}
/// Returns true if the value is a promise object.
#[inline]
#[must_use]
pub fn is_promise(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is::<Promise>())
}
/// Returns the promise if the value is a promise, otherwise `None`.
#[inline]
#[must_use]
pub fn as_promise(&self) -> Option<&JsObject> {
self.as_object().filter(|obj| obj.is::<Promise>())
}
/// Returns true if the value is a symbol.
#[inline]
#[must_use]
pub const fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
/// Returns the symbol if the value is a symbol, otherwise `None`.
#[inline]
#[must_use]
pub fn as_symbol(&self) -> Option<JsSymbol> {
match self {
Self::Symbol(symbol) => Some(symbol.clone()),
_ => None,
}
}
/// Returns true if the value is undefined.
#[inline]
#[must_use]
pub const fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
/// Returns true if the value is null.
#[inline]
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
/// Returns true if the value is null or undefined.
#[inline]
#[must_use]
pub const fn is_null_or_undefined(&self) -> bool {
matches!(self, Self::Null | Self::Undefined)
}
/// Returns true if the value is a 64-bit floating-point number.
#[inline]
#[must_use]
pub const fn is_double(&self) -> bool {
matches!(self, Self::Rational(_))
}
/// Determines if argument is a finite integral Number value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isintegralnumber
#[must_use]
#[allow(clippy::float_cmp)]
pub fn is_integral_number(&self) -> bool {
// If it can fit in a i32 and the truncated version is
// equal to the original then it is an integer.
let is_rational_integer = |n: f64| n == f64::from(n as i32);
match *self {
Self::Integer(_) => true,
Self::Rational(n) if is_rational_integer(n) => true,
_ => false,
}
}
/// Returns true if the value can be reprented as an integer.
///
/// Similar to [`JsValue::is_integral_number()`] except that it returns `false` for `-0`.
#[must_use]
#[allow(clippy::float_cmp)]
pub fn is_integer(&self) -> bool {
// If it can fit in a i32 and the truncated version is
// equal to the original then it is an integer.
let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();
match *self {
Self::Integer(_) => true,
Self::Rational(n) if is_rational_integer(n) => true,
_ => false,
}
}
/// Returns true if the value is a number.
#[inline]
#[must_use]
pub const fn is_number(&self) -> bool {
matches!(self, Self::Rational(_) | Self::Integer(_))
}
/// Returns the number if the value is a number, otherwise `None`.
#[inline]
#[must_use]
pub fn as_number(&self) -> Option<f64> {
match *self {
Self::Integer(integer) => Some(integer.into()),
Self::Rational(rational) => Some(rational),
_ => None,
}
}
/// Returns true if the value is a string.
#[inline]
#[must_use]
pub const fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
/// Returns the string if the value is a string, otherwise `None`.
#[inline]
#[must_use]
pub const fn as_string(&self) -> Option<&JsString> {
match self {
Self::String(ref string) => Some(string),
_ => None,
}
}
/// Returns true if the value is a boolean.
#[inline]
#[must_use]
pub const fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
/// Returns the boolean if the value is a boolean, otherwise `None`.
#[inline]
#[must_use]
pub const fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(boolean) => Some(*boolean),
_ => None,
}
}
/// Returns true if the value is a bigint.
#[inline]
#[must_use]
pub const fn is_bigint(&self) -> bool {
matches!(self, Self::BigInt(_))
}
/// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive.
#[inline]
#[must_use]
pub const fn as_bigint(&self) -> Option<&JsBigInt> {
match self {
Self::BigInt(bigint) => Some(bigint),
_ => None,
}
}
/// Converts the value to a `bool` type.
///
/// More information:
/// - [ECMAScript][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-toboolean
#[must_use]
pub fn to_boolean(&self) -> bool {
match *self {
Self::Symbol(_) | Self::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::BigInt(ref n) if !n.is_zero() => true,
Self::Boolean(v) => v,
_ => false,
}
}
/// The abstract operation `ToPrimitive` takes an input argument and an optional argument
/// `PreferredType`.
///
/// <https://tc39.es/ecma262/#sec-toprimitive>
pub fn to_primitive(
&self,
context: &mut Context,
preferred_type: PreferredType,
) -> JsResult<Self> {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then
if let Some(input) = self.as_object() {
// a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
let exotic_to_prim = input.get_method(JsSymbol::to_primitive(), context)?;
// b. If exoticToPrim is not undefined, then
if let Some(exotic_to_prim) = exotic_to_prim {
// i. If preferredType is not present, let hint be "default".
// ii. Else if preferredType is string, let hint be "string".
// iii. Else,
// 1. Assert: preferredType is number.
// 2. Let hint be "number".
let hint = match preferred_type {
PreferredType::Default => js_string!("default"),
PreferredType::String => js_string!("string"),
PreferredType::Number => js_string!("number"),
}
.into();
// iv. Let result be ? Call(exoticToPrim, input, « hint »).
let result = exotic_to_prim.call(self, &[hint], context)?;
// v. If Type(result) is not Object, return result.
// vi. Throw a TypeError exception.
return if result.is_object() {
Err(JsNativeError::typ()
.with_message("Symbol.toPrimitive cannot return an object")
.into())
} else {
Ok(result)
};
}
// c. If preferredType is not present, let preferredType be number.
let preferred_type = match preferred_type {
PreferredType::Default | PreferredType::Number => PreferredType::Number,
PreferredType::String => PreferredType::String,
};
// d. Return ? OrdinaryToPrimitive(input, preferredType).
return input.ordinary_to_primitive(context, preferred_type);
}
// 3. Return input.
Ok(self.clone())
}
/// `7.1.13 ToBigInt ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tobigint
pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
match self {
Self::Null => Err(JsNativeError::typ()
.with_message("cannot convert null to a BigInt")
.into()),
Self::Undefined => Err(JsNativeError::typ()
.with_message("cannot convert undefined to a BigInt")
.into()),
Self::String(ref string) => string.to_big_int().map_or_else(
|| {
Err(JsNativeError::syntax()
.with_message(format!(
"cannot convert string '{}' to bigint primitive",
string.to_std_string_escaped()
))
.into())
},
Ok,
),
Self::Boolean(true) => Ok(JsBigInt::one()),
Self::Boolean(false) => Ok(JsBigInt::zero()),
Self::Integer(_) | Self::Rational(_) => Err(JsNativeError::typ()
.with_message("cannot convert Number to a BigInt")
.into()),
Self::BigInt(b) => Ok(b.clone()),
Self::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_bigint(context)
}
Self::Symbol(_) => Err(JsNativeError::typ()
.with_message("cannot convert Symbol to a BigInt")
.into()),
}
}
/// Returns an object that implements `Display`.
///
/// By default the internals are not shown, but they can be toggled
/// with [`ValueDisplay::internals`] method.
///
/// # Examples
///
/// ```
/// use boa_engine::JsValue;
///
/// let value = JsValue::new(3);
///
/// println!("{}", value.display());
/// ```
#[must_use]
#[inline]
pub const fn display(&self) -> ValueDisplay<'_> {
ValueDisplay {
value: self,
internals: false,
}
}
/// Converts the value to a string.
///
/// This function is equivalent to `String(value)` in JavaScript.
pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
match self {
Self::Null => Ok("null".into()),
Self::Undefined => Ok("undefined".into()),
Self::Boolean(boolean) => Ok(boolean.to_string().into()),
Self::Rational(rational) => Ok(Number::to_js_string(*rational)),
Self::Integer(integer) => Ok(integer.to_string().into()),
Self::String(string) => Ok(string.clone()),
Self::Symbol(_) => Err(JsNativeError::typ()
.with_message("can't convert symbol to string")
.into()),
Self::BigInt(ref bigint) => Ok(bigint.to_string().into()),
Self::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::String)?;
primitive.to_string(context)
}
}
}
/// Converts the value to an Object.
///
/// This function is equivalent to `Object(value)` in JavaScript.
///
/// See: <https://tc39.es/ecma262/#sec-toobject>
pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
// TODO: add fast paths with object template
match self {
Self::Undefined | Self::Null => Err(JsNativeError::typ()
.with_message("cannot convert 'null' or 'undefined' to object")
.into()),
Self::Boolean(boolean) => Ok(context
.intrinsics()
.templates()
.boolean()
.create(*boolean, Vec::default())),
Self::Integer(integer) => Ok(context
.intrinsics()
.templates()
.number()
.create(f64::from(*integer), Vec::default())),
Self::Rational(rational) => Ok(context
.intrinsics()
.templates()
.number()
.create(*rational, Vec::default())),
Self::String(ref string) => Ok(context
.intrinsics()
.templates()
.string()
.create(string.clone(), vec![string.len().into()])),
Self::Symbol(ref symbol) => Ok(context
.intrinsics()
.templates()
.symbol()
.create(symbol.clone(), Vec::default())),
Self::BigInt(ref bigint) => Ok(context
.intrinsics()
.templates()
.bigint()
.create(bigint.clone(), Vec::default())),
Self::Object(jsobject) => Ok(jsobject.clone()),
}
}
/// Converts the value to a `PropertyKey`, that can be used as a key for properties.
///
/// See <https://tc39.es/ecma262/#sec-topropertykey>
pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
Ok(match self {
// Fast path:
Self::String(string) => string.clone().into(),
Self::Symbol(symbol) => symbol.clone().into(),
Self::Integer(integer) => (*integer).into(),
// Slow path:
Self::Object(_) => match self.to_primitive(context, PreferredType::String)? {
Self::String(ref string) => string.clone().into(),
Self::Symbol(ref symbol) => symbol.clone().into(),
Self::Integer(integer) => integer.into(),
primitive => primitive.to_string(context)?.into(),
},
primitive => primitive.to_string(context)?.into(),
})
}
/// It returns value converted to a numeric value of type `Number` or `BigInt`.
///
/// See: <https://tc39.es/ecma262/#sec-tonumeric>
pub fn to_numeric(&self, context: &mut Context) -> JsResult<Numeric> {
// 1. Let primValue be ? ToPrimitive(value, number).
let primitive = self.to_primitive(context, PreferredType::Number)?;
// 2. If primValue is a BigInt, return primValue.
if let Some(bigint) = primitive.as_bigint() {
return Ok(bigint.clone().into());
}
// 3. Return ? ToNumber(primValue).
Ok(primitive.to_number(context)?.into())
}
/// Converts a value to an integral 32 bit unsigned integer.
///
/// This function is equivalent to `value | 0` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-touint32>
pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
// This is the fast path, if the value is Integer we can just return it.
if let Self::Integer(number) = *self {
if let Ok(number) = u32::try_from(number) {
return Ok(number);
}
}
let number = self.to_number(context)?;
Ok(f64_to_uint32(number))
}
/// Converts a value to an integral 32 bit signed integer.
///
/// See: <https://tc39.es/ecma262/#sec-toint32>
pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
// This is the fast path, if the value is Integer we can just return it.
if let Self::Integer(number) = *self {
return Ok(number);
}
let number = self.to_number(context)?;
Ok(f64_to_int32(number))
}
/// `7.1.10 ToInt8 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-toint8
pub fn to_int8(&self, context: &mut Context) -> JsResult<i8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.abs().floor().copysign(number) as i64;
// 4. Let int8bit be int modulo 2^8.
let int_8_bit = int % 2i64.pow(8);
// 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit).
if int_8_bit >= 2i64.pow(7) {
Ok((int_8_bit - 2i64.pow(8)) as i8)
} else {
Ok(int_8_bit as i8)
}
}
/// `7.1.11 ToUint8 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint8
pub fn to_uint8(&self, context: &mut Context) -> JsResult<u8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.abs().floor().copysign(number) as i64;
// 4. Let int8bit be int modulo 2^8.
let int_8_bit = int % 2i64.pow(8);
// 5. Return 𝔽(int8bit).
Ok(int_8_bit as u8)
}
/// `7.1.12 ToUint8Clamp ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint8clamp
pub fn to_uint8_clamp(&self, context: &mut Context) -> JsResult<u8> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, return +0𝔽.
if number.is_nan() {
return Ok(0);
}
// 3. If ℝ(number) ≤ 0, return +0𝔽.
if number <= 0.0 {
return Ok(0);
}
// 4. If ℝ(number) ≥ 255, return 255𝔽.
if number >= 255.0 {
return Ok(255);
}
// 5. Let f be floor(ℝ(number)).
let f = number.floor();
// 6. If f + 0.5 < ℝ(number), return 𝔽(f + 1).
if f + 0.5 < number {
return Ok(f as u8 + 1);
}
// 7. If ℝ(number) < f + 0.5, return 𝔽(f).
if number < f + 0.5 {
return Ok(f as u8);
}
// 8. If f is odd, return 𝔽(f + 1).
if f as u8 % 2 != 0 {
return Ok(f as u8 + 1);
}
// 9. Return 𝔽(f).
Ok(f as u8)
}
/// `7.1.8 ToInt16 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-toint16
pub fn to_int16(&self, context: &mut Context) -> JsResult<i16> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.abs().floor().copysign(number) as i64;
// 4. Let int16bit be int modulo 2^16.
let int_16_bit = int % 2i64.pow(16);
// 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit).
if int_16_bit >= 2i64.pow(15) {
Ok((int_16_bit - 2i64.pow(16)) as i16)
} else {
Ok(int_16_bit as i16)
}
}
/// `7.1.9 ToUint16 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-touint16
pub fn to_uint16(&self, context: &mut Context) -> JsResult<u16> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// 2. If number is NaN, +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return +0𝔽.
if number.is_nan() || number.is_zero() || number.is_infinite() {
return Ok(0);
}
// 3. Let int be the mathematical value whose sign is the sign of number and whose magnitude is floor(abs(ℝ(number))).
let int = number.abs().floor().copysign(number) as i64;
// 4. Let int16bit be int modulo 2^16.
let int_16_bit = int % 2i64.pow(16);
// 5. Return 𝔽(int16bit).
Ok(int_16_bit as u16)
}
/// `7.1.15 ToBigInt64 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tobigint64
pub fn to_big_int64(&self, context: &mut Context) -> JsResult<i64> {
// 1. Let n be ? ToBigInt(argument).
let n = self.to_bigint(context)?;
// 2. Let int64bit be ℝ(n) modulo 2^64.
let int64_bit = n.as_inner().mod_floor(&TWO_E_64);
// 3. If int64bit ≥ 2^63, return ℤ(int64bit - 2^64); otherwise return ℤ(int64bit).
let value = if int64_bit >= *TWO_E_63 {
int64_bit.sub(&*TWO_E_64)
} else {
int64_bit
};
Ok(value
.to_i64()
.expect("should be within the range of `i64` by the mod operation"))
}
/// `7.1.16 ToBigUint64 ( argument )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tobiguint64
pub fn to_big_uint64(&self, context: &mut Context) -> JsResult<u64> {
// 1. Let n be ? ToBigInt(argument).
let n = self.to_bigint(context)?;
// 2. Let int64bit be ℝ(n) modulo 2^64.
// 3. Return ℤ(int64bit).
Ok(n.as_inner()
.mod_floor(&TWO_E_64)
.to_u64()
.expect("should be within the range of `u64` by the mod operation"))
}
/// Converts a value to a non-negative integer if it is a valid integer index value.
///
/// See: <https://tc39.es/ecma262/#sec-toindex>
pub fn to_index(&self, context: &mut Context) -> JsResult<u64> {
// 1. If value is undefined, then
if self.is_undefined() {
// a. Return 0.
return Ok(0);
}
// 2. Else,
// a. Let integer be ? ToIntegerOrInfinity(value).
let integer = self.to_integer_or_infinity(context)?;
// b. Let clamped be ! ToLength(𝔽(integer)).
let clamped = integer.clamp_finite(0, Number::MAX_SAFE_INTEGER as i64);
// c. If ! SameValue(𝔽(integer), clamped) is false, throw a RangeError exception.
if integer != clamped {
return Err(JsNativeError::range()
.with_message("Index must be between 0 and 2^53 - 1")
.into());
}
// d. Assert: 0 ≤ integer ≤ 2^53 - 1.
debug_assert!(0 <= clamped && clamped <= Number::MAX_SAFE_INTEGER as i64);
// e. Return integer.
Ok(clamped as u64)
}
/// Converts argument to an integer suitable for use as the length of an array-like object.
///
/// See: <https://tc39.es/ecma262/#sec-tolength>
pub fn to_length(&self, context: &mut Context) -> JsResult<u64> {
// 1. Let len be ? ToInteger(argument).
// 2. If len ≤ +0, return +0.
// 3. Return min(len, 2^53 - 1).
Ok(self
.to_integer_or_infinity(context)?
.clamp_finite(0, Number::MAX_SAFE_INTEGER as i64) as u64)
}
/// Abstract operation `ToIntegerOrInfinity ( argument )`
///
/// This method converts a `Value` to an integer representing its `Number` value with
/// fractional part truncated, or to +∞ or -∞ when that `Number` value is infinite.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-tointegerorinfinity
pub fn to_integer_or_infinity(&self, context: &mut Context) -> JsResult<IntegerOrInfinity> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(context)?;
// Continues on `IntegerOrInfinity::from::<f64>`
Ok(IntegerOrInfinity::from(number))
}
/// Converts a value to a double precision floating point.
///
/// This function is equivalent to the unary `+` operator (`+value`) in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-tonumber>
pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
match *self {
Self::Null => Ok(0.0),
Self::Undefined => Ok(f64::NAN),
Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
Self::String(ref string) => Ok(string.to_number()),
Self::Rational(number) => Ok(number),
Self::Integer(integer) => Ok(f64::from(integer)),
Self::Symbol(_) => Err(JsNativeError::typ()
.with_message("argument must not be a symbol")
.into()),
Self::BigInt(_) => Err(JsNativeError::typ()
.with_message("argument must not be a bigint")
.into()),
Self::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_number(context)
}
}
}
/// This is a more specialized version of `to_numeric`, including `BigInt`.
///
/// This function is equivalent to `Number(value)` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-tonumeric>
pub fn to_numeric_number(&self, context: &mut Context) -> JsResult<f64> {
let primitive = self.to_primitive(context, PreferredType::Number)?;
if let Some(bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
primitive.to_number(context)
}
/// Check if the `Value` can be converted to an `Object`
///
/// The abstract operation `RequireObjectCoercible` takes argument argument.
/// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`.
/// It is defined by [Table 15][table]
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [table]: https://tc39.es/ecma262/#table-14
/// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
#[inline]
pub fn require_object_coercible(&self) -> JsResult<&Self> {
if self.is_null_or_undefined() {
Err(JsNativeError::typ()
.with_message("cannot convert null or undefined to Object")
.into())
} else {
Ok(self)
}
}
/// The abstract operation `ToPropertyDescriptor`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-topropertydescriptor
#[inline]
pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
// 1. If Type(Obj) is not Object, throw a TypeError exception.
self.as_object()
.ok_or_else(|| {
JsNativeError::typ()
.with_message("Cannot construct a property descriptor from a non-object")
.into()
})
.and_then(|obj| obj.to_property_descriptor(context))
}
/// `typeof` operator. Returns a string representing the type of the
/// given ECMA Value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
#[must_use]
pub fn type_of(&self) -> &'static str {
match *self {
Self::Rational(_) | Self::Integer(_) => "number",
Self::String(_) => "string",
Self::Boolean(_) => "boolean",
Self::Symbol(_) => "symbol",
Self::Null => "object",
Self::Undefined => "undefined",
Self::BigInt(_) => "bigint",
Self::Object(ref object) => {
if object.is_callable() {
"function"
} else {
"object"
}
}
}
}
/// Same as [`JsValue::type_of`], but returning a [`JsString`] instead.
#[must_use]
pub fn js_type_of(&self) -> JsString {
match *self {
Self::Rational(_) | Self::Integer(_) => js_string!("number"),
Self::String(_) => js_string!("string"),
Self::Boolean(_) => js_string!("boolean"),
Self::Symbol(_) => js_string!("symbol"),
Self::Null => js_string!("object"),
Self::Undefined => js_string!("undefined"),
Self::BigInt(_) => js_string!("bigint"),
Self::Object(ref object) => {
if object.is_callable() {
js_string!("function")
} else {
js_string!("object")
}
}
}
}
/// Abstract operation `IsArray ( argument )`
///
/// Check if a value is an array.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isarray
pub(crate) fn is_array(&self) -> JsResult<bool> {
// Note: The spec specifies this function for JsValue.
// The main part of the function is implemented for JsObject.
// 1. If Type(argument) is not Object, return false.
self.as_object()
.map_or(Ok(false), JsObject::is_array_abstract)
}
}
impl Default for JsValue {
fn default() -> Self {
Self::Undefined
}
}
/// The preferred type to convert an object to a primitive `Value`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PreferredType {
/// Prefer to convert to a `String` primitive.
String,
/// Prefer to convert to a `Number` primitive.
Number,
/// Do not prefer a type to convert to.
Default,
}
/// Numeric value which can be of two types `Number`, `BigInt`.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Numeric {
/// Double precision floating point number.
Number(f64),
/// BigInt an integer of arbitrary size.
BigInt(JsBigInt),
}
impl From<f64> for Numeric {
#[inline]
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<f32> for Numeric {
#[inline]
fn from(value: f32) -> Self {
Self::Number(value.into())
}
}
impl From<i64> for Numeric {
#[inline]
fn from(value: i64) -> Self {
Self::BigInt(value.into())
}
}
impl From<i32> for Numeric {
#[inline]
fn from(value: i32) -> Self {
Self::Number(value.into())
}
}
impl From<i16> for Numeric {
#[inline]
fn from(value: i16) -> Self {
Self::Number(value.into())
}
}
impl From<i8> for Numeric {
#[inline]
fn from(value: i8) -> Self {
Self::Number(value.into())
}
}
impl From<u64> for Numeric {
#[inline]
fn from(value: u64) -> Self {
Self::BigInt(value.into())
}
}
impl From<u32> for Numeric {
#[inline]
fn from(value: u32) -> Self {
Self::Number(value.into())
}
}
impl From<u16> for Numeric {
#[inline]
fn from(value: u16) -> Self {
Self::Number(value.into())
}
}
impl From<u8> for Numeric {
#[inline]
fn from(value: u8) -> Self {
Self::Number(value.into())
}
}
impl From<JsBigInt> for Numeric {
#[inline]
fn from(value: JsBigInt) -> Self {
Self::BigInt(value)
}
}
impl From<Numeric> for JsValue {
fn from(value: Numeric) -> Self {
match value {
Numeric::Number(number) => Self::new(number),
Numeric::BigInt(bigint) => Self::new(bigint),
}
}
}