Browse Source

Initial implementation of NaN-boxing

Co-authored-by: HalidOdat <halidodat@gmail.com>
nan-boxing
HalidOdat 3 years ago committed by jedel1043
parent
commit
f6bf8f4f60
  1. 49
      Cargo.lock
  2. 2
      boa_cli/src/main.rs
  3. 4
      boa_engine/Cargo.toml
  4. 13
      boa_engine/src/bigint.rs
  5. 3
      boa_engine/src/builtins/array/array_iterator.rs
  6. 102
      boa_engine/src/builtins/array/mod.rs
  7. 4
      boa_engine/src/builtins/array_buffer/mod.rs
  8. 18
      boa_engine/src/builtins/async_generator/mod.rs
  9. 1
      boa_engine/src/builtins/bigint/mod.rs
  10. 2
      boa_engine/src/builtins/boolean/tests.rs
  11. 2
      boa_engine/src/builtins/console/mod.rs
  12. 119
      boa_engine/src/builtins/dataview/mod.rs
  13. 37
      boa_engine/src/builtins/date/mod.rs
  14. 2
      boa_engine/src/builtins/date/tests.rs
  15. 4
      boa_engine/src/builtins/error/aggregate.rs
  16. 2
      boa_engine/src/builtins/error/eval.rs
  17. 2
      boa_engine/src/builtins/error/mod.rs
  18. 2
      boa_engine/src/builtins/error/range.rs
  19. 2
      boa_engine/src/builtins/error/reference.rs
  20. 2
      boa_engine/src/builtins/error/syntax.rs
  21. 2
      boa_engine/src/builtins/error/type.rs
  22. 2
      boa_engine/src/builtins/error/uri.rs
  23. 2
      boa_engine/src/builtins/eval/mod.rs
  24. 1
      boa_engine/src/builtins/function/arguments.rs
  25. 17
      boa_engine/src/builtins/function/mod.rs
  26. 4
      boa_engine/src/builtins/function/tests.rs
  27. 8
      boa_engine/src/builtins/generator/mod.rs
  28. 14
      boa_engine/src/builtins/intl/mod.rs
  29. 20
      boa_engine/src/builtins/intl/tests.rs
  30. 4
      boa_engine/src/builtins/iterable/mod.rs
  31. 14
      boa_engine/src/builtins/json/mod.rs
  32. 3
      boa_engine/src/builtins/map/map_iterator.rs
  33. 51
      boa_engine/src/builtins/map/mod.rs
  34. 11
      boa_engine/src/builtins/mod.rs
  35. 31
      boa_engine/src/builtins/number/mod.rs
  36. 3
      boa_engine/src/builtins/object/for_in_iterator.rs
  37. 82
      boa_engine/src/builtins/object/mod.rs
  38. 78
      boa_engine/src/builtins/promise/mod.rs
  39. 10
      boa_engine/src/builtins/promise/promise_job.rs
  40. 13
      boa_engine/src/builtins/proxy/mod.rs
  41. 26
      boa_engine/src/builtins/reflect/mod.rs
  42. 27
      boa_engine/src/builtins/regexp/mod.rs
  43. 3
      boa_engine/src/builtins/regexp/regexp_string_iterator.rs
  44. 14
      boa_engine/src/builtins/set/mod.rs
  45. 6
      boa_engine/src/builtins/set/set_iterator.rs
  46. 84
      boa_engine/src/builtins/string/mod.rs
  47. 3
      boa_engine/src/builtins/string/string_iterator.rs
  48. 8
      boa_engine/src/builtins/symbol/mod.rs
  49. 97
      boa_engine/src/builtins/typed_array/mod.rs
  50. 10
      boa_engine/src/class.rs
  51. 2
      boa_engine/src/context/mod.rs
  52. 2
      boa_engine/src/environments/compile.rs
  53. 2
      boa_engine/src/environments/runtime.rs
  54. 7
      boa_engine/src/lib.rs
  55. 26
      boa_engine/src/object/internal_methods/proxy.rs
  56. 5
      boa_engine/src/object/jsarray.rs
  57. 2
      boa_engine/src/object/jsarraybuffer.rs
  58. 13
      boa_engine/src/object/jsobject.rs
  59. 20
      boa_engine/src/object/jsset.rs
  60. 2
      boa_engine/src/object/jsset_iterator.rs
  61. 87
      boa_engine/src/object/jstypedarray.rs
  62. 13
      boa_engine/src/object/operations.rs
  63. 19
      boa_engine/src/string.rs
  64. 14
      boa_engine/src/symbol.rs
  65. 3
      boa_engine/src/tests.rs
  66. 76
      boa_engine/src/value/conversions.rs
  67. 38
      boa_engine/src/value/display.rs
  68. 115
      boa_engine/src/value/equality.rs
  69. 22
      boa_engine/src/value/hash.rs
  70. 409
      boa_engine/src/value/mod.rs
  71. 310
      boa_engine/src/value/operations.rs
  72. 39
      boa_engine/src/value/serde_json.rs
  73. 290
      boa_engine/src/value/sys/default.rs
  74. 99
      boa_engine/src/value/sys/mod.rs
  75. 760
      boa_engine/src/value/sys/nan_boxed.rs
  76. 4
      boa_engine/src/value/tests.rs
  77. 20
      boa_engine/src/value/type.rs
  78. 24
      boa_engine/src/vm/code_block.rs
  79. 52
      boa_engine/src/vm/mod.rs
  80. 2
      boa_engine/src/vm/tests.rs
  81. 2
      boa_examples/src/bin/modulehandler.rs
  82. 2
      boa_tester/src/exec/js262.rs

49
Cargo.lock generated

@ -74,6 +74,7 @@ dependencies = [
"boa_interner", "boa_interner",
"boa_profiler", "boa_profiler",
"boa_unicode", "boa_unicode",
"cfg-if",
"chrono", "chrono",
"criterion", "criterion",
"dyn-clone", "dyn-clone",
@ -91,6 +92,7 @@ dependencies = [
"num-bigint", "num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
"num_enum",
"once_cell", "once_cell",
"rand", "rand",
"regress", "regress",
@ -98,6 +100,7 @@ dependencies = [
"ryu-js", "ryu-js",
"serde", "serde",
"serde_json", "serde_json",
"sptr",
"sys-locale", "sys-locale",
"tap", "tap",
"unicode-normalization", "unicode-normalization",
@ -992,6 +995,27 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.14.0" version = "1.14.0"
@ -1136,6 +1160,16 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -1459,6 +1493,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sptr"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0d4365121b91e8522958da77ce29c004333daeaf0711225c4727c8c5f49941"
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -1618,6 +1658,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "unicode-general-category" name = "unicode-general-category"
version = "0.5.1" version = "0.5.1"

2
boa_cli/src/main.rs

@ -93,7 +93,7 @@ struct Opt {
#[clap(long, short = 'a', value_name = "FORMAT", ignore_case = true, arg_enum)] #[clap(long, short = 'a', value_name = "FORMAT", ignore_case = true, arg_enum)]
dump_ast: Option<Option<DumpFormat>>, dump_ast: Option<Option<DumpFormat>>,
/// Dump the AST to stdout with the given format. /// Dump the compiled bytecode and trace the execution stack
#[clap(long = "trace", short = 't')] #[clap(long = "trace", short = 't')]
trace: bool, trace: bool,

4
boa_engine/Cargo.toml

@ -23,6 +23,7 @@ intl = [
"dep:icu_testdata", "dep:icu_testdata",
"dep:sys-locale" "dep:sys-locale"
] ]
nan_boxing = ["dep:sptr", "dep:num_enum"]
# Enable Boa's WHATWG console object implementation. # Enable Boa's WHATWG console object implementation.
console = [] console = []
@ -50,6 +51,9 @@ unicode-normalization = "0.1.21"
dyn-clone = "1.0.9" dyn-clone = "1.0.9"
once_cell = "1.14.0" once_cell = "1.14.0"
tap = "1.0.1" tap = "1.0.1"
cfg-if = "1.0.0"
sptr = { version = "0.3.1", optional = true }
num_enum = { version = "0.5.7", optional = true }
icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true } icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true }
icu_locid = { version = "0.6.0", features = ["serde"], optional = true } icu_locid = { version = "0.6.0", features = ["serde"], optional = true }
icu_datetime = { version = "0.6.0", features = ["serde"], optional = true } icu_datetime = { version = "0.6.0", features = ["serde"], optional = true }

13
boa_engine/src/bigint.rs

@ -1,10 +1,11 @@
//! This module implements the JavaScript bigint primitive rust type. //! This module implements the JavaScript bigint primitive rust type.
use crate::{builtins::Number, Context, JsValue}; use crate::{builtins::Number, value::PointerType, Context, JsValue};
use num_integer::Integer; use num_integer::Integer;
use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero};
use std::{ use std::{
fmt::{self, Display}, fmt::{self, Display},
mem::{self, ManuallyDrop},
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
rc::Rc, rc::Rc,
}; };
@ -22,6 +23,16 @@ pub struct JsBigInt {
inner: Rc<RawBigInt>, inner: Rc<RawBigInt>,
} }
unsafe impl PointerType for JsBigInt {
unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop<Self> {
ManuallyDrop::new(mem::transmute(ptr))
}
unsafe fn into_void_ptr(bigint: ManuallyDrop<Self>) -> *mut () {
mem::transmute(bigint)
}
}
impl JsBigInt { impl JsBigInt {
/// Create a new [`JsBigInt`]. /// Create a new [`JsBigInt`].
#[inline] #[inline]

3
boa_engine/src/builtins/array/array_iterator.rs

@ -68,7 +68,8 @@ impl ArrayIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut array_iterator = this.as_object().map(JsObject::borrow_mut); let array_iterator = this.as_object();
let mut array_iterator = array_iterator.as_deref().map(JsObject::borrow_mut);
let array_iterator = array_iterator let array_iterator = array_iterator
.as_mut() .as_mut()
.and_then(|obj| obj.as_array_iterator_mut()) .and_then(|obj| obj.as_array_iterator_mut())

102
boa_engine/src/builtins/array/mod.rs

@ -29,7 +29,7 @@ use crate::{
}, },
property::{Attribute, PropertyDescriptor, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{IntegerOrInfinity, JsValue}, value::{IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
use std::cmp::{max, min, Ordering}; use std::cmp::{max, min, Ordering};
@ -374,7 +374,7 @@ impl Array {
// 7. If IsConstructor(C) is false, throw a TypeError exception. // 7. If IsConstructor(C) is false, throw a TypeError exception.
if let Some(c) = c.as_constructor() { if let Some(c) = c.as_constructor() {
// 8. Return ? Construct(C, « 𝔽(length) »). // 8. Return ? Construct(C, « 𝔽(length) »).
c.construct(&[JsValue::new(length)], Some(c), context) c.construct(&[JsValue::new(length)], Some(&c), context)
} else { } else {
context.throw_type_error("Symbol.species must be a constructor") context.throw_type_error("Symbol.species must be a constructor")
} }
@ -404,9 +404,9 @@ impl Array {
// 3. Else, // 3. Else,
// a. If IsCallable(mapfn) is false, throw a TypeError exception. // a. If IsCallable(mapfn) is false, throw a TypeError exception.
// b. Let mapping be true. // b. Let mapping be true.
let mapping = match mapfn { let mapping = match mapfn.variant() {
JsValue::Undefined => None, JsVariant::Undefined => None,
JsValue::Object(o) if o.is_callable() => Some(o), JsVariant::Object(o) if o.is_callable() => Some(o),
_ => return context.throw_type_error(format!("{} is not a function", mapfn.type_of())), _ => return context.throw_type_error(format!("{} is not a function", mapfn.type_of())),
}; };
@ -423,7 +423,8 @@ impl Array {
// b. Else, // b. Else,
// i. Let A be ? ArrayCreate(0en). // i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[], None, context)?, Some(constructor) => constructor
.construct(&[], None, context)?,
_ => Self::array_create(0, None, context)?, _ => Self::array_create(0, None, context)?,
}; };
@ -455,9 +456,9 @@ impl Array {
let next_value = next.value(context)?; let next_value = next.value(context)?;
// vi. If mapping is true, then // vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping { let mapped_value = if let Some(ref mapfn) = mapping {
// 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). // 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
let mapped_value = mapfn.call(this_arg, &[next_value, k.into()], context); let mapped_value = mapfn.call(&this_arg, &[next_value, k.into()], context);
// 2. IfAbruptCloseIterator(mappedValue, iteratorRecord). // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord).
if_abrupt_close_iterator!(mapped_value, iterator_record, context) if_abrupt_close_iterator!(mapped_value, iterator_record, context)
@ -496,7 +497,8 @@ impl Array {
// 10. Else, // 10. Else,
// a. Let A be ? ArrayCreate(len). // a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?, Some(constructor) => constructor
.construct(&[len.into()], None, context)?,
_ => Self::array_create(len, None, context)?, _ => Self::array_create(len, None, context)?,
}; };
@ -509,10 +511,10 @@ impl Array {
// b. Let kValue be ? Get(arrayLike, Pk). // b. Let kValue be ? Get(arrayLike, Pk).
let k_value = array_like.get(k, context)?; let k_value = array_like.get(k, context)?;
let mapped_value = if let Some(mapfn) = mapping { let mapped_value = if let Some(ref mapfn) = mapping {
// c. If mapping is true, then // c. If mapping is true, then
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
mapfn.call(this_arg, &[k_value, k.into()], context)? mapfn.call(&this_arg, &[k_value, k.into()], context)?
} else { } else {
// d. Else, let mappedValue be kValue. // d. Else, let mappedValue be kValue.
k_value k_value
@ -572,7 +574,8 @@ impl Array {
// 5. Else, // 5. Else,
// a. Let A be ? ArrayCreate(len). // a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() { let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?, Some(constructor) => constructor
.construct(&[len.into()], None, context)?,
_ => Self::array_create(len as u64, None, context)?, _ => Self::array_create(len as u64, None, context)?,
}; };
@ -810,7 +813,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O). // 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.forEach: invalid callback function") context.construct_type_error("Array.prototype.forEach: invalid callback function")
})?; })?;
// 4. Let k be 0. // 4. Let k be 0.
@ -826,7 +830,7 @@ impl Array {
let k_value = o.get(pk, context)?; let k_value = o.get(pk, context)?;
// ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let this_arg = args.get_or_undefined(1); let this_arg = args.get_or_undefined(1);
callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?; callback.call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?;
} }
// d. Set k to k + 1. // d. Set k to k + 1.
} }
@ -1151,7 +1155,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O). // 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.every: callback is not callable") context.construct_type_error("Array.prototype.every: callback is not callable")
})?; })?;
@ -1169,7 +1174,7 @@ impl Array {
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let test_result = callback let test_result = callback
.call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// iii. If testResult is false, return false. // iii. If testResult is false, return false.
if !test_result { if !test_result {
@ -1203,7 +1208,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O). // 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.map: Callbackfn is not callable") context.construct_type_error("Array.prototype.map: Callbackfn is not callable")
})?; })?;
@ -1224,7 +1230,7 @@ impl Array {
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value = let mapped_value =
callback.call(this_arg, &[k_value, k.into(), this.into()], context)?; callback.call(&this_arg, &[k_value, k.into(), this.into()], context)?;
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?; a.create_data_property_or_throw(k, mapped_value, context)?;
} }
@ -1376,7 +1382,7 @@ impl Array {
let element_k = o.get(k, context)?; let element_k = o.get(k, context)?;
// ii. Let same be IsStrictlyEqual(searchElement, elementK). // ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k). // iii. If same is true, return 𝔽(k).
if JsValue::strict_equals(search_element, &element_k) { if JsValue::strict_equals(&search_element, &element_k) {
return Ok(JsValue::new(k)); return Ok(JsValue::new(k));
} }
} }
@ -1411,7 +1417,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { let predicate = args.get_or_undefined(0);
let predicate = predicate.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.find: predicate is not callable") context.construct_type_error("Array.prototype.find: predicate is not callable")
})?; })?;
@ -1428,7 +1435,7 @@ impl Array {
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call( .call(
this_arg, &this_arg,
&[k_value.clone(), k.into(), o.clone().into()], &[k_value.clone(), k.into(), o.clone().into()],
context, context,
)? )?
@ -1468,7 +1475,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { let predicate = args.get_or_undefined(0);
let predicate = predicate.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.findIndex: predicate is not callable") context.construct_type_error("Array.prototype.findIndex: predicate is not callable")
})?; })?;
@ -1484,7 +1492,7 @@ impl Array {
let k_value = o.get(pk, context)?; let k_value = o.get(pk, context)?;
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// d. If testResult is true, return 𝔽(k). // d. If testResult is true, return 𝔽(k).
if test_result { if test_result {
@ -1519,7 +1527,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { let predicate = args.get_or_undefined(0);
let predicate = predicate.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.findLast: predicate is not callable") context.construct_type_error("Array.prototype.findLast: predicate is not callable")
})?; })?;
@ -1534,7 +1543,7 @@ impl Array {
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call( .call(
this_arg, &this_arg,
&[k_value.clone(), k.into(), this.clone()], &[k_value.clone(), k.into(), this.clone()],
context, context,
)? )?
@ -1571,7 +1580,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0).as_callable().ok_or_else(|| { let predicate = args.get_or_undefined(0);
let predicate = predicate.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.findLastIndex: predicate is not callable") context.construct_type_error("Array.prototype.findLastIndex: predicate is not callable")
})?; })?;
@ -1585,7 +1595,7 @@ impl Array {
let k_value = o.get(k, context)?; let k_value = o.get(k, context)?;
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate let test_result = predicate
.call(this_arg, &[k_value, k.into(), this.clone()], context)? .call(&this_arg, &[k_value, k.into(), this.clone()], context)?
.to_boolean(); .to_boolean();
// d. If testResult is true, return 𝔽(k). // d. If testResult is true, return 𝔽(k).
if test_result { if test_result {
@ -1676,7 +1686,8 @@ impl Array {
let source_len = o.length_of_array_like(context)?; let source_len = o.length_of_array_like(context)?;
// 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| { let mapper_function = args.get_or_undefined(0);
let mapper_function = mapper_function.as_callable().ok_or_else(|| {
context.construct_type_error("flatMap mapper function is not callable") context.construct_type_error("flatMap mapper function is not callable")
})?; })?;
@ -1690,8 +1701,8 @@ impl Array {
source_len, source_len,
0, 0,
1, 1,
Some(mapper_function), Some(&mapper_function),
args.get_or_undefined(1), &args.get_or_undefined(1),
context, context,
)?; )?;
@ -1780,7 +1791,7 @@ impl Array {
// 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth) // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
target_index = Self::flatten_into_array( target_index = Self::flatten_into_array(
target, target,
element, &element,
element_len, element_len,
target_index, target_index,
new_depth, new_depth,
@ -1924,7 +1935,7 @@ impl Array {
// a. Let elementK be ? Get(O, ! ToString(𝔽(k))). // a. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?; let element_k = o.get(k, context)?;
// b. If SameValueZero(searchElement, elementK) is true, return true. // b. If SameValueZero(searchElement, elementK) is true, return true.
if JsValue::same_value_zero(search_element, &element_k) { if JsValue::same_value_zero(&search_element, &element_k) {
return Ok(JsValue::new(true)); return Ok(JsValue::new(true));
} }
// c. Set k to k + 1. // c. Set k to k + 1.
@ -2205,7 +2216,8 @@ impl Array {
let length = o.length_of_array_like(context)?; let length = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.filter: `callback` must be callable") context.construct_type_error("Array.prototype.filter: `callback` must be callable")
})?; })?;
let this_arg = args.get_or_undefined(1); let this_arg = args.get_or_undefined(1);
@ -2228,7 +2240,7 @@ impl Array {
let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())]; let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())];
// ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let selected = callback.call(this_arg, &args, context)?.to_boolean(); let selected = callback.call(&this_arg, &args, context)?.to_boolean();
// iii. If selected is true, then // iii. If selected is true, then
if selected { if selected {
@ -2269,7 +2281,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O). // 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error("Array.prototype.some: callback is not callable") context.construct_type_error("Array.prototype.some: callback is not callable")
})?; })?;
@ -2286,7 +2299,7 @@ impl Array {
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let this_arg = args.get_or_undefined(1); let this_arg = args.get_or_undefined(1);
let test_result = callback let test_result = callback
.call(this_arg, &[k_value, k.into(), o.clone().into()], context)? .call(&this_arg, &[k_value, k.into(), o.clone().into()], context)?
.to_boolean(); .to_boolean();
// iii. If testResult is true, return true. // iii. If testResult is true, return true.
if test_result { if test_result {
@ -2317,9 +2330,10 @@ impl Array {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let comparefn = match args.get_or_undefined(0) { let comparefn = args.get_or_undefined(0);
JsValue::Object(ref obj) if obj.is_callable() => Some(obj), let comparefn = match comparefn.variant() {
JsValue::Undefined => None, JsVariant::Object(obj) if obj.is_callable() => Some(obj),
JsVariant::Undefined => None,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
"The comparison function must be either a function or undefined", "The comparison function must be either a function or undefined",
@ -2346,11 +2360,11 @@ impl Array {
} }
// 4. If comparefn is not undefined, then // 4. If comparefn is not undefined, then
if let Some(cmp) = comparefn { if let Some(ref cmp) = comparefn {
let args = [x.clone(), y.clone()]; let args = [x.clone(), y.clone()];
// a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)). // a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)).
let v = cmp let v = cmp
.call(&JsValue::Undefined, &args, context)? .call(&JsValue::undefined(), &args, context)?
.to_number(context)?; .to_number(context)?;
// b. If v is NaN, return +0𝔽. // b. If v is NaN, return +0𝔽.
// c. Return v. // c. Return v.
@ -2454,7 +2468,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context context
.construct_type_error("Array.prototype.reduce: callback function is not callable") .construct_type_error("Array.prototype.reduce: callback function is not callable")
})?; })?;
@ -2549,7 +2564,8 @@ impl Array {
let len = o.length_of_array_like(context)?; let len = o.length_of_array_like(context)?;
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| { let callback = args.get_or_undefined(0);
let callback = callback.as_callable().ok_or_else(|| {
context.construct_type_error( context.construct_type_error(
"Array.prototype.reduceRight: callback function is not callable", "Array.prototype.reduceRight: callback function is not callable",
) )

4
boa_engine/src/builtins/array_buffer/mod.rs

@ -262,7 +262,7 @@ impl ArrayBuffer {
// 20. If SameValue(new, O) is true, throw a TypeError exception. // 20. If SameValue(new, O) is true, throw a TypeError exception.
if this if this
.as_object() .as_object()
.map(|obj| JsObject::equals(obj, &new)) .map(|obj| JsObject::equals(&obj, &new))
.unwrap_or_default() .unwrap_or_default()
{ {
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer"); return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
@ -334,7 +334,7 @@ impl ArrayBuffer {
obj.borrow_mut().data = ObjectData::array_buffer(Self { obj.borrow_mut().data = ObjectData::array_buffer(Self {
array_buffer_data: Some(block), array_buffer_data: Some(block),
array_buffer_byte_length: byte_length, array_buffer_byte_length: byte_length,
array_buffer_detach_key: JsValue::Undefined, array_buffer_detach_key: JsValue::undefined(),
}); });
// 5. Return obj. // 5. Return obj.

18
boa_engine/src/builtins/async_generator/mod.rs

@ -203,7 +203,7 @@ impl AsyncGenerator {
} }
// 7. Let completion be NormalCompletion(value). // 7. Let completion be NormalCompletion(value).
let completion = (Ok(args.get_or_undefined(0).clone()), false); let completion = (Ok(args.get_or_undefined(0)), false);
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -221,7 +221,7 @@ impl AsyncGenerator {
drop(generator_obj_mut); drop(generator_obj_mut);
Self::resume( Self::resume(
generator_object, &generator_object,
state, state,
&generator_context, &generator_context,
completion, completion,
@ -272,7 +272,7 @@ impl AsyncGenerator {
if_abrupt_reject_promise!(generator, promise_capability, context); if_abrupt_reject_promise!(generator, promise_capability, context);
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
let completion = (Ok(args.get_or_undefined(0).clone()), true); let completion = (Ok(args.get_or_undefined(0)), true);
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -305,7 +305,7 @@ impl AsyncGenerator {
drop(generator_obj_mut); drop(generator_obj_mut);
Self::resume( Self::resume(
generator_object, &generator_object,
state, state,
&generator_context, &generator_context,
completion, completion,
@ -376,7 +376,7 @@ impl AsyncGenerator {
.reject() .reject()
.call( .call(
&JsValue::undefined(), &JsValue::undefined(),
&[args.get_or_undefined(0).clone()], &[args.get_or_undefined(0)],
context, context,
) )
.expect("cannot fail per spec"); .expect("cannot fail per spec");
@ -386,7 +386,7 @@ impl AsyncGenerator {
} }
// 8. Let completion be ThrowCompletion(exception). // 8. Let completion be ThrowCompletion(exception).
let completion = (Err(args.get_or_undefined(0).clone()), false); let completion = (Err(args.get_or_undefined(0)), false);
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -401,7 +401,7 @@ impl AsyncGenerator {
// a. Perform AsyncGeneratorResume(generator, completion). // a. Perform AsyncGeneratorResume(generator, completion).
Self::resume( Self::resume(
generator_object, &generator_object,
state, state,
&generator_context, &generator_context,
completion, completion,
@ -622,7 +622,7 @@ impl AsyncGenerator {
gen.state = AsyncGeneratorState::Completed; gen.state = AsyncGeneratorState::Completed;
// b. Let result be NormalCompletion(value). // b. Let result be NormalCompletion(value).
let result = Ok(args.get_or_undefined(0).clone()); let result = Ok(args.get_or_undefined(0));
// c. Perform AsyncGeneratorCompleteStep(generator, result, true). // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
let next = gen.queue.pop_front().expect("must have one entry"); let next = gen.queue.pop_front().expect("must have one entry");
@ -655,7 +655,7 @@ impl AsyncGenerator {
gen.state = AsyncGeneratorState::Completed; gen.state = AsyncGeneratorState::Completed;
// b. Let result be ThrowCompletion(reason). // b. Let result be ThrowCompletion(reason).
let result = Err(args.get_or_undefined(0).clone()); let result = Err(args.get_or_undefined(0));
// c. Perform AsyncGeneratorCompleteStep(generator, result, true). // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
let next = gen.queue.pop_front().expect("must have one entry"); let next = gen.queue.pop_front().expect("must have one entry");

1
boa_engine/src/builtins/bigint/mod.rs

@ -133,6 +133,7 @@ impl BigInt {
value value
// 1. If Type(value) is BigInt, return value. // 1. If Type(value) is BigInt, return value.
.as_bigint() .as_bigint()
.as_deref()
.cloned() .cloned()
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
// a. Assert: Type(value.[[BigIntData]]) is BigInt. // a. Assert: Type(value.[[BigIntData]]) is BigInt.

2
boa_engine/src/builtins/boolean/tests.rs

@ -60,6 +60,6 @@ fn instances_have_correct_proto_set() {
assert_eq!( assert_eq!(
&*bool_instance.as_object().unwrap().prototype(), &*bool_instance.as_object().unwrap().prototype(),
&bool_prototype.as_object().cloned() &bool_prototype.as_object().as_deref().cloned()
); );
} }

2
boa_engine/src/builtins/console/mod.rs

@ -569,7 +569,7 @@ impl Console {
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
logger( logger(
LogMessage::Info(display_obj(args.get_or_undefined(0), true)), LogMessage::Info(display_obj(&args.get_or_undefined(0), true)),
context.console(), context.console(),
); );
Ok(JsValue::undefined()) Ok(JsValue::undefined())

119
boa_engine/src/builtins/dataview/mod.rs

@ -100,8 +100,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let byte_length = args.get_or_undefined(2); let byte_length = args.get_or_undefined(2);
let buffer_obj = args let buffer_obj = args.get_or_undefined(0);
.get_or_undefined(0) let buffer_obj = buffer_obj
.as_object() .as_object()
.ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?; .ok_or_else(|| context.construct_type_error("buffer must be an ArrayBuffer"))?;
@ -194,7 +194,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[DataView]]). // 2. Perform ? RequireInternalSlot(O, [[DataView]]).
let dataview = this.as_object().map(JsObject::borrow); let dataview = this.as_object();
let dataview = dataview.as_deref().map(JsObject::borrow);
let dataview = dataview let dataview = dataview
.as_ref() .as_ref()
.and_then(|obj| obj.as_data_view()) .and_then(|obj| obj.as_data_view())
@ -223,7 +224,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[DataView]]). // 2. Perform ? RequireInternalSlot(O, [[DataView]]).
let dataview = this.as_object().map(JsObject::borrow); let dataview = this.as_object();
let dataview = dataview.as_deref().map(JsObject::borrow);
let dataview = dataview let dataview = dataview
.as_ref() .as_ref()
.and_then(|obj| obj.as_data_view()) .and_then(|obj| obj.as_data_view())
@ -262,7 +264,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[DataView]]). // 2. Perform ? RequireInternalSlot(O, [[DataView]]).
let dataview = this.as_object().map(JsObject::borrow); let dataview = this.as_object();
let dataview = dataview.as_deref().map(JsObject::borrow);
let dataview = dataview let dataview = dataview
.as_ref() .as_ref()
.and_then(|obj| obj.as_data_view()) .and_then(|obj| obj.as_data_view())
@ -302,7 +305,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 1. Perform ? RequireInternalSlot(view, [[DataView]]).
// 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
let view = view.as_object().map(JsObject::borrow); let view = view.as_object();
let view = view.as_deref().map(JsObject::borrow);
let view = view let view = view
.as_ref() .as_ref()
.and_then(|obj| obj.as_data_view()) .and_then(|obj| obj.as_data_view())
@ -373,8 +377,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::BigInt64, TypedArrayKind::BigInt64,
context, context,
) )
@ -402,8 +406,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::BigUint64, TypedArrayKind::BigUint64,
context, context,
) )
@ -431,8 +435,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Float32, TypedArrayKind::Float32,
context, context,
) )
@ -460,8 +464,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Float64, TypedArrayKind::Float64,
context, context,
) )
@ -489,8 +493,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int8, TypedArrayKind::Int8,
context, context,
) )
@ -518,8 +522,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int16, TypedArrayKind::Int16,
context, context,
) )
@ -547,8 +551,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int32, TypedArrayKind::Int32,
context, context,
) )
@ -576,8 +580,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint8, TypedArrayKind::Uint8,
context, context,
) )
@ -605,8 +609,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint16, TypedArrayKind::Uint16,
context, context,
) )
@ -634,8 +638,8 @@ impl DataView {
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64). // 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
Self::get_view_value( Self::get_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint32, TypedArrayKind::Uint32,
context, context,
) )
@ -661,7 +665,8 @@ impl DataView {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 1. Perform ? RequireInternalSlot(view, [[DataView]]).
// 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
let view = view.as_object().map(JsObject::borrow); let view = view.as_object();
let view = view.as_deref().map(JsObject::borrow);
let view = view let view = view
.as_ref() .as_ref()
.and_then(|obj| obj.as_data_view()) .and_then(|obj| obj.as_data_view())
@ -742,10 +747,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::BigInt64, TypedArrayKind::BigInt64,
value, &value,
context, context,
) )
} }
@ -773,10 +778,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::BigUint64, TypedArrayKind::BigUint64,
value, &value,
context, context,
) )
} }
@ -804,10 +809,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float32, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Float32, TypedArrayKind::Float32,
value, &value,
context, context,
) )
} }
@ -835,10 +840,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Float64, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Float64, TypedArrayKind::Float64,
value, &value,
context, context,
) )
} }
@ -866,10 +871,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int8, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int8, TypedArrayKind::Int8,
value, &value,
context, context,
) )
} }
@ -897,10 +902,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int16, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int16, TypedArrayKind::Int16,
value, &value,
context, context,
) )
} }
@ -928,10 +933,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Int32, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Int32, TypedArrayKind::Int32,
value, &value,
context, context,
) )
} }
@ -959,10 +964,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint8, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint8, TypedArrayKind::Uint8,
value, &value,
context, context,
) )
} }
@ -990,10 +995,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint16, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint16, TypedArrayKind::Uint16,
value, &value,
context, context,
) )
} }
@ -1021,10 +1026,10 @@ impl DataView {
// 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value). // 2. Return ? SetViewValue(v, byteOffset, littleEndian, Uint32, value).
Self::set_view_value( Self::set_view_value(
this, this,
byte_offset, &byte_offset,
is_little_endian, &is_little_endian,
TypedArrayKind::Uint32, TypedArrayKind::Uint32,
value, &value,
context, context,
) )
} }

37
boa_engine/src/builtins/date/mod.rs

@ -9,7 +9,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
}, },
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{JsValue, PreferredType}, value::{JsValue, JsVariant, PreferredType},
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -390,24 +390,25 @@ impl Date {
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
let value = &args[0]; let value = &args[0];
let tv = match this_time_value(value, context) { let tv = if let Ok(dt) = this_time_value(value, context) {
Ok(dt) => dt.0, dt.0
_ => match value.to_primitive(context, PreferredType::Default)? { } else {
JsValue::String(ref str) => match chrono::DateTime::parse_from_rfc3339(str) { let tv = value.to_primitive(context, PreferredType::Default)?;
if let JsVariant::String(ref str) = tv.variant() {
match chrono::DateTime::parse_from_rfc3339(str) {
Ok(dt) => Some(dt.naive_utc()), Ok(dt) => Some(dt.naive_utc()),
_ => None, _ => None,
},
tv => {
let tv = tv.to_number(context)?;
if tv.is_nan() {
None
} else {
let secs = (tv / 1_000f64) as i64;
let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32;
NaiveDateTime::from_timestamp_opt(secs, nano_secs)
}
} }
}, } else {
let tv = tv.to_number(context)?;
if tv.is_nan() {
None
} else {
let secs = (tv / 1_000f64) as i64;
let nano_secs = ((tv % 1_000f64) * 1_000_000f64) as u32;
NaiveDateTime::from_timestamp_opt(secs, nano_secs)
}
}
}; };
let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some()); let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
@ -512,7 +513,7 @@ impl Date {
let hint = args.get_or_undefined(0); let hint = args.get_or_undefined(0);
let try_first = match hint.as_string().map(JsString::as_str) { let try_first = match hint.as_string().as_deref().map(JsString::as_str) {
// 3. If hint is "string" or "default", then // 3. If hint is "string" or "default", then
// a. Let tryFirst be string. // a. Let tryFirst be string.
Some("string" | "default") => PreferredType::String, Some("string" | "default") => PreferredType::String,
@ -1848,7 +1849,7 @@ impl Date {
match DateTime::parse_from_rfc3339(&args[0].to_string(context)?) { match DateTime::parse_from_rfc3339(&args[0].to_string(context)?) {
Ok(v) => Ok(JsValue::new(v.naive_utc().timestamp_millis() as f64)), Ok(v) => Ok(JsValue::new(v.naive_utc().timestamp_millis() as f64)),
_ => Ok(JsValue::new(f64::NAN)), _ => Ok(JsValue::nan()),
} }
} }

2
boa_engine/src/builtins/date/tests.rs

@ -13,7 +13,7 @@ fn forward_dt_utc(context: &mut Context, src: &str) -> Option<NaiveDateTime> {
panic!("expected success") panic!("expected success")
}; };
if let JsValue::Object(ref date_time) = date_time { if let Some(date_time) = date_time.as_object() {
if let Some(date_time) = date_time.borrow().as_date() { if let Some(date_time) = date_time.borrow().as_date() {
date_time.0 date_time.0
} else { } else {

4
boa_engine/src/builtins/error/aggregate.rs

@ -86,11 +86,11 @@ impl AggregateError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(2), context)?; Error::install_error_cause(&o, &args.get_or_undefined(2), context)?;
// 5. Let errorsList be ? IterableToList(errors). // 5. Let errorsList be ? IterableToList(errors).
let errors = args.get_or_undefined(0); let errors = args.get_or_undefined(0);
let errors_list = iterable_to_list(context, errors, None)?; let errors_list = iterable_to_list(context, &errors, None)?;
// 6. Perform ! DefinePropertyOrThrow(O, "errors", // 6. Perform ! DefinePropertyOrThrow(O, "errors",
// PropertyDescriptor { // PropertyDescriptor {
// [[Configurable]]: true, // [[Configurable]]: true,

2
boa_engine/src/builtins/error/eval.rs

@ -83,7 +83,7 @@ impl EvalError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/mod.rs

@ -120,7 +120,7 @@ impl Error {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Self::install_error_cause(&o, args.get_or_undefined(1), context)?; Self::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/range.rs

@ -81,7 +81,7 @@ impl RangeError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/reference.rs

@ -87,7 +87,7 @@ impl ReferenceError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/syntax.rs

@ -86,7 +86,7 @@ impl SyntaxError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/type.rs

@ -87,7 +87,7 @@ impl TypeError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/error/uri.rs

@ -82,7 +82,7 @@ impl UriError {
} }
// 4. Perform ? InstallErrorCause(O, options). // 4. Perform ? InstallErrorCause(O, options).
Error::install_error_cause(&o, args.get_or_undefined(1), context)?; Error::install_error_cause(&o, &args.get_or_undefined(1), context)?;
// 5. Return O. // 5. Return O.
Ok(o.into()) Ok(o.into())

2
boa_engine/src/builtins/eval/mod.rs

@ -50,7 +50,7 @@ impl Eval {
/// [spec]: https://tc39.es/ecma262/#sec-eval-x /// [spec]: https://tc39.es/ecma262/#sec-eval-x
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> { fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> {
// 1. Return ? PerformEval(x, false, false). // 1. Return ? PerformEval(x, false, false).
Self::perform_eval(args.get_or_undefined(0), false, false, context) Self::perform_eval(&args.get_or_undefined(0), false, false, context)
} }
/// `19.2.1.1 PerformEval ( x, strictCaller, direct )` /// `19.2.1.1 PerformEval ( x, strictCaller, direct )`

1
boa_engine/src/builtins/function/arguments.rs

@ -196,7 +196,6 @@ impl Arguments {
// In the case of duplicate parameter names, the last one is bound as the environment binding. // In the case of duplicate parameter names, the last one is bound as the environment binding.
// //
// The following logic implements the steps 17-19 adjusted for our environment structure. // The following logic implements the steps 17-19 adjusted for our environment structure.
let mut bindings = FxHashMap::default(); let mut bindings = FxHashMap::default();
let mut property_index = 0; let mut property_index = 0;
'outer: for formal in formals.parameters.iter() { 'outer: for formal in formals.parameters.iter() {

17
boa_engine/src/builtins/function/mod.rs

@ -682,7 +682,7 @@ impl BuiltInFunctionObject {
// TODO?: 3.a. PrepareForTailCall // TODO?: 3.a. PrepareForTailCall
// b. Return ? Call(func, thisArg). // b. Return ? Call(func, thisArg).
return func.call(this_arg, &[], context); return func.call(&this_arg, &[], context);
} }
// 4. Let argList be ? CreateListFromArrayLike(argArray). // 4. Let argList be ? CreateListFromArrayLike(argArray).
@ -692,7 +692,7 @@ impl BuiltInFunctionObject {
// TODO?: 5. PrepareForTailCall // TODO?: 5. PrepareForTailCall
// 6. Return ? Call(func, thisArg, argList). // 6. Return ? Call(func, thisArg, argList).
func.call(this_arg, &arg_list, context) func.call(&this_arg, &arg_list, context)
} }
/// `Function.prototype.bind ( thisArg, ...args )` /// `Function.prototype.bind ( thisArg, ...args )`
@ -714,7 +714,7 @@ impl BuiltInFunctionObject {
context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method") context.construct_type_error("cannot bind `this` without a `[[Call]]` internal method")
})?; })?;
let this_arg = args.get_or_undefined(0).clone(); let this_arg = args.get_or_undefined(0);
let bound_args = args.get(1..).unwrap_or(&[]).to_vec(); let bound_args = args.get(1..).unwrap_or(&[]).to_vec();
let arg_count = bound_args.len() as i64; let arg_count = bound_args.len() as i64;
@ -769,7 +769,9 @@ impl BuiltInFunctionObject {
// 9. If Type(targetName) is not String, set targetName to the empty String. // 9. If Type(targetName) is not String, set targetName to the empty String.
let target_name = target_name let target_name = target_name
.as_string() .as_string()
.map_or(JsString::new(""), Clone::clone); .as_deref()
.cloned()
.unwrap_or_default();
// 10. Perform SetFunctionName(F, targetName, "bound"). // 10. Perform SetFunctionName(F, targetName, "bound").
set_function_name(&f, &target_name.into(), Some("bound"), context); set_function_name(&f, &target_name.into(), Some("bound"), context);
@ -800,12 +802,13 @@ impl BuiltInFunctionObject {
// TODO?: 3. Perform PrepareForTailCall // TODO?: 3. Perform PrepareForTailCall
// 4. Return ? Call(func, thisArg, args). // 4. Return ? Call(func, thisArg, args).
func.call(this_arg, args.get(1..).unwrap_or(&[]), context) func.call(&this_arg, args.get(1..).unwrap_or(&[]), context)
} }
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let object = this.as_object().map(JsObject::borrow); let object = this.as_object();
let object = object.as_deref().map(JsObject::borrow);
let function = object let function = object
.as_deref() .as_deref()
.and_then(Object::as_function) .and_then(Object::as_function)
@ -855,7 +858,7 @@ impl BuiltInFunctionObject {
fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let F be the this value. // 1. Let F be the this value.
// 2. Return ? OrdinaryHasInstance(F, V). // 2. Return ? OrdinaryHasInstance(F, V).
Ok(JsValue::ordinary_has_instance(this, args.get_or_undefined(0), context)?.into()) Ok(JsValue::ordinary_has_instance(this, &args.get_or_undefined(0), context)?.into())
} }
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]

4
boa_engine/src/builtins/function/tests.rs

@ -129,7 +129,7 @@ fn function_prototype_call() {
"#; "#;
let value = forward_val(&mut context, func).unwrap(); let value = forward_val(&mut context, func).unwrap();
assert!(value.is_string()); assert!(value.is_string());
assert_eq!(value.as_string().unwrap(), "[object Error]"); assert_eq!(*value.as_string().unwrap(), "[object Error]");
} }
#[test] #[test]
@ -246,7 +246,7 @@ fn closure_capture_clone() {
object object
.__get_own_property__(&"key".into(), context)? .__get_own_property__(&"key".into(), context)?
.and_then(|prop| prop.value().cloned()) .and_then(|prop| prop.value().cloned())
.and_then(|val| val.as_string().cloned()) .and_then(|val| val.as_string().as_deref().cloned())
.ok_or_else(|| context.construct_type_error("invalid `key` property"))?, .ok_or_else(|| context.construct_type_error("invalid `key` property"))?,
); );
Ok(hw.into()) Ok(hw.into())

8
boa_engine/src/builtins/generator/mod.rs

@ -148,8 +148,8 @@ impl Generator {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Return ? GeneratorResume(this value, value, empty). // 1. Return ? GeneratorResume(this value, value, empty).
match this.as_object() { match this.as_object() {
Some(obj) if obj.is_generator() => { Some(ref obj) if obj.is_generator() => {
Self::generator_resume(obj, args.get_or_undefined(0), context) Self::generator_resume(obj, &args.get_or_undefined(0), context)
} }
_ => context.throw_type_error("Generator.prototype.next called on non generator"), _ => context.throw_type_error("Generator.prototype.next called on non generator"),
} }
@ -173,7 +173,7 @@ impl Generator {
// 1. Let g be the this value. // 1. Let g be the this value.
// 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
// 3. Return ? GeneratorResumeAbrupt(g, C, empty). // 3. Return ? GeneratorResumeAbrupt(g, C, empty).
Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0)), context)
} }
/// `Generator.prototype.throw ( exception )` /// `Generator.prototype.throw ( exception )`
@ -195,7 +195,7 @@ impl Generator {
// 1. Let g be the this value. // 1. Let g be the this value.
// 2. Let C be ThrowCompletion(exception). // 2. Let C be ThrowCompletion(exception).
// 3. Return ? GeneratorResumeAbrupt(g, C, empty). // 3. Return ? GeneratorResumeAbrupt(g, C, empty).
Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0)), context)
} }
/// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )`

14
boa_engine/src/builtins/intl/mod.rs

@ -77,7 +77,7 @@ impl Intl {
let ll = canonicalize_locale_list(args, context)?; let ll = canonicalize_locale_list(args, context)?;
// 2. Return CreateArrayFromList(ll). // 2. Return CreateArrayFromList(ll).
Ok(JsValue::Object(Array::create_array_from_list( Ok(JsValue::new(Array::create_array_from_list(
ll.into_iter().map(|loc| loc.to_string().into()), ll.into_iter().map(|loc| loc.to_string().into()),
context, context,
))) )))
@ -595,7 +595,7 @@ fn resolve_locale(
// e. Let value be keyLocaleData[0]. // e. Let value be keyLocaleData[0].
// TODO f. Assert: Type(value) is either String or Null. // TODO f. Assert: Type(value) is either String or Null.
let mut value = match key_locale_data.get(0) { let mut value = match key_locale_data.get(0) {
Some(first_elt) => JsValue::String(first_elt.clone()), Some(first_elt) => JsValue::from(first_elt.clone()),
None => JsValue::null(), None => JsValue::null(),
}; };
@ -616,7 +616,7 @@ fn resolve_locale(
// a. If keyLocaleData contains requestedValue, then // a. If keyLocaleData contains requestedValue, then
if key_locale_data.contains(requested_value) { if key_locale_data.contains(requested_value) {
// i. Let value be requestedValue. // i. Let value be requestedValue.
value = JsValue::String(JsString::new(requested_value)); value = JsValue::from(JsString::new(requested_value));
// ii. Let supportedExtensionAddition be the string-concatenation // ii. Let supportedExtensionAddition be the string-concatenation
// of "-", key, "-", and value. // of "-", key, "-", and value.
supported_extension_addition = supported_extension_addition =
@ -625,7 +625,7 @@ fn resolve_locale(
// 4. Else if keyLocaleData contains "true", then // 4. Else if keyLocaleData contains "true", then
} else if key_locale_data.contains(&JsString::new("true")) { } else if key_locale_data.contains(&JsString::new("true")) {
// a. Let value be "true". // a. Let value be "true".
value = JsValue::String(JsString::new("true")); value = JsValue::from(JsString::new("true"));
// b. Let supportedExtensionAddition be the string-concatenation of "-" and key. // b. Let supportedExtensionAddition be the string-concatenation of "-" and key.
supported_extension_addition = JsString::concat_array(&["-", key]); supported_extension_addition = JsString::concat_array(&["-", key]);
} }
@ -659,7 +659,7 @@ fn resolve_locale(
if let Some(options_val_str) = options_value.as_string() { if let Some(options_val_str) = options_value.as_string() {
if options_val_str.is_empty() { if options_val_str.is_empty() {
// a. Let optionsValue be "true". // a. Let optionsValue be "true".
options_value = JsValue::String(JsString::new("true")); options_value = JsValue::from(JsString::new("true"));
} }
} }
} }
@ -747,13 +747,13 @@ pub(crate) fn get_option(
// 7. If values is not undefined and values does not contain an element equal to value, // 7. If values is not undefined and values does not contain an element equal to value,
// throw a RangeError exception. // throw a RangeError exception.
value = match r#type { value = match r#type {
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()), GetOptionType::Boolean => JsValue::from(value.to_boolean()),
GetOptionType::String => { GetOptionType::String => {
let string_value = value.to_string(context)?; let string_value = value.to_string(context)?;
if !values.is_empty() && !values.contains(&string_value) { if !values.is_empty() && !values.contains(&string_value) {
return context.throw_range_error("GetOption: values array does not contain value"); return context.throw_range_error("GetOption: values array does not contain value");
} }
JsValue::String(string_value) JsValue::from(string_value)
} }
}; };

20
boa_engine/src/builtins/intl/tests.rs

@ -284,7 +284,7 @@ fn get_opt() {
let mut context = Context::default(); let mut context = Context::default();
let values = Vec::<JsString>::new(); let values = Vec::<JsString>::new();
let fallback = JsValue::String(JsString::new("fallback")); let fallback = JsValue::from(JsString::new("fallback"));
let options_obj = JsObject::empty(); let options_obj = JsObject::empty();
let option_type = GetOptionType::String; let option_type = GetOptionType::String;
let get_option_result = get_option( let get_option_result = get_option(
@ -299,9 +299,9 @@ fn get_opt() {
assert_eq!(get_option_result, fallback); assert_eq!(get_option_result, fallback);
let values = Vec::<JsString>::new(); let values = Vec::<JsString>::new();
let fallback = JsValue::String(JsString::new("fallback")); let fallback = JsValue::from(JsString::new("fallback"));
let options_obj = JsObject::empty(); let options_obj = JsObject::empty();
let locale_value = JsValue::String(JsString::new("en-US")); let locale_value = JsValue::from(JsString::new("en-US"));
options_obj options_obj
.set("Locale", locale_value.clone(), true, &mut context) .set("Locale", locale_value.clone(), true, &mut context)
.expect("Setting a property should not fail"); .expect("Setting a property should not fail");
@ -317,10 +317,10 @@ fn get_opt() {
.expect("GetOption should not fail on string test"); .expect("GetOption should not fail on string test");
assert_eq!(get_option_result, locale_value); assert_eq!(get_option_result, locale_value);
let fallback = JsValue::String(JsString::new("fallback")); let fallback = JsValue::from(JsString::new("fallback"));
let options_obj = JsObject::empty(); let options_obj = JsObject::empty();
let locale_string = JsString::new("en-US"); let locale_string = JsString::new("en-US");
let locale_value = JsValue::String(locale_string.clone()); let locale_value = JsValue::from(locale_string.clone());
let values = vec![locale_string]; let values = vec![locale_string];
options_obj options_obj
.set("Locale", locale_value.clone(), true, &mut context) .set("Locale", locale_value.clone(), true, &mut context)
@ -356,9 +356,9 @@ fn get_opt() {
.expect("GetOption should not fail on boolean test"); .expect("GetOption should not fail on boolean test");
assert_eq!(get_option_result, boolean_value); assert_eq!(get_option_result, boolean_value);
let fallback = JsValue::String(JsString::new("fallback")); let fallback = JsValue::from(JsString::new("fallback"));
let options_obj = JsObject::empty(); let options_obj = JsObject::empty();
let locale_value = JsValue::String(JsString::new("en-US")); let locale_value = JsValue::from(JsString::new("en-US"));
let other_locale_str = JsString::new("de-DE"); let other_locale_str = JsString::new("de-DE");
let values = vec![other_locale_str]; let values = vec![other_locale_str];
options_obj options_obj
@ -473,7 +473,7 @@ fn to_date_time_opts() {
) )
.expect("toDateTimeOptions should not fail in date test"); .expect("toDateTimeOptions should not fail in date test");
let numeric_jsstring = JsValue::String(JsString::new("numeric")); let numeric_jsstring = JsValue::from(JsString::new("numeric"));
assert_eq!( assert_eq!(
date_time_opts.get("year", &mut context), date_time_opts.get("year", &mut context),
Ok(numeric_jsstring.clone()) Ok(numeric_jsstring.clone())
@ -495,7 +495,7 @@ fn to_date_time_opts() {
) )
.expect("toDateTimeOptions should not fail in time test"); .expect("toDateTimeOptions should not fail in time test");
let numeric_jsstring = JsValue::String(JsString::new("numeric")); let numeric_jsstring = JsValue::from(JsString::new("numeric"));
assert_eq!( assert_eq!(
date_time_opts.get("hour", &mut context), date_time_opts.get("hour", &mut context),
Ok(numeric_jsstring.clone()) Ok(numeric_jsstring.clone())
@ -517,7 +517,7 @@ fn to_date_time_opts() {
) )
.expect("toDateTimeOptions should not fail when testing required = 'any'"); .expect("toDateTimeOptions should not fail when testing required = 'any'");
let numeric_jsstring = JsValue::String(JsString::new("numeric")); let numeric_jsstring = JsValue::from(JsString::new("numeric"));
assert_eq!( assert_eq!(
date_time_opts.get("year", &mut context), date_time_opts.get("year", &mut context),
Ok(numeric_jsstring.clone()) Ok(numeric_jsstring.clone())

4
boa_engine/src/builtins/iterable/mod.rs

@ -153,7 +153,7 @@ impl JsValue {
// 1. Let syncMethod be ? GetMethod(obj, @@iterator). // 1. Let syncMethod be ? GetMethod(obj, @@iterator).
let sync_method = self let sync_method = self
.get_method(WellKnownSymbols::iterator(), context)? .get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from); .map_or(Self::undefined(), Self::from);
// 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod). // 2. Let syncIteratorRecord be ? GetIterator(obj, sync, syncMethod).
let _sync_iterator_record = let _sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method)); self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
@ -163,7 +163,7 @@ impl JsValue {
} else { } else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator). // b. Otherwise, set method to ? GetMethod(obj, @@iterator).
self.get_method(WellKnownSymbols::iterator(), context)? self.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from) .map_or(Self::undefined(), Self::from)
} }
}; };

14
boa_engine/src/builtins/json/mod.rs

@ -95,7 +95,7 @@ impl Json {
let unfiltered = context.eval(script_string.as_bytes())?; let unfiltered = context.eval(script_string.as_bytes())?;
// 11. If IsCallable(reviver) is true, then // 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() { if let Some(ref obj) = args.get_or_undefined(1).as_callable() {
// a. Let root be ! OrdinaryObjectCreate(%Object.prototype%). // a. Let root be ! OrdinaryObjectCreate(%Object.prototype%).
let root = context.construct_object(); let root = context.construct_object();
@ -129,7 +129,7 @@ impl Json {
let val = holder.get(name.clone(), context)?; let val = holder.get(name.clone(), context)?;
// 2. If Type(val) is Object, then // 2. If Type(val) is Object, then
if let Some(obj) = val.as_object() { if let Some(ref obj) = val.as_object() {
// a. Let isArray be ? IsArray(val). // a. Let isArray be ? IsArray(val).
// b. If isArray is true, then // b. If isArray is true, then
if obj.is_array_abstract(context)? { if obj.is_array_abstract(context)? {
@ -280,7 +280,7 @@ impl Json {
} }
} }
let mut space = args.get_or_undefined(2).clone(); let mut space = args.get_or_undefined(2);
// 5. If Type(space) is Object, then // 5. If Type(space) is Object, then
if let Some(space_obj) = space.as_object() { if let Some(space_obj) = space.as_object() {
@ -332,7 +332,7 @@ impl Json {
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value). // 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper wrapper
.create_data_property_or_throw("", args.get_or_undefined(0).clone(), context) .create_data_property_or_throw("", args.get_or_undefined(0), context)
.expect("CreateDataPropertyOrThrow should never fail here"); .expect("CreateDataPropertyOrThrow should never fail here");
// 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }. // 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }.
@ -388,7 +388,7 @@ impl Json {
} }
// 4. If Type(value) is Object, then // 4. If Type(value) is Object, then
if let Some(obj) = value.as_object().cloned() { if let Some(obj) = value.as_object().as_deref().cloned() {
// a. If value has a [[NumberData]] internal slot, then // a. If value has a [[NumberData]] internal slot, then
if obj.is_number() { if obj.is_number() {
// i. Set value to ? ToNumber(value). // i. Set value to ? ToNumber(value).
@ -427,7 +427,7 @@ impl Json {
} }
// 8. If Type(value) is String, return QuoteJSONString(value). // 8. If Type(value) is String, return QuoteJSONString(value).
if let Some(s) = value.as_string() { if let Some(ref s) = value.as_string() {
return Ok(Some(Self::quote_json_string(s))); return Ok(Some(Self::quote_json_string(s)));
} }
@ -452,7 +452,7 @@ impl Json {
} }
// 11. If Type(value) is Object and IsCallable(value) is false, then // 11. If Type(value) is Object and IsCallable(value) is false, then
if let Some(obj) = value.as_object() { if let Some(ref obj) = value.as_object() {
if !obj.is_callable() { if !obj.is_callable() {
// a. Let isArray be ? IsArray(value). // a. Let isArray be ? IsArray(value).
// b. If isArray is true, return ? SerializeJSONArray(state, value). // b. If isArray is true, return ? SerializeJSONArray(state, value).

3
boa_engine/src/builtins/map/map_iterator.rs

@ -72,7 +72,8 @@ impl MapIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut map_iterator = this.as_object().map(JsObject::borrow_mut); let map_iterator = this.as_object();
let mut map_iterator = map_iterator.as_deref().map(JsObject::borrow_mut);
let map_iterator = map_iterator let map_iterator = map_iterator
.as_mut() .as_mut()
.and_then(|obj| obj.as_map_iterator_mut()) .and_then(|obj| obj.as_map_iterator_mut())

51
boa_engine/src/builtins/map/mod.rs

@ -21,6 +21,7 @@ use crate::{
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::JsVariant,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -141,7 +142,7 @@ impl Map {
let adder = map.get("set", context)?; let adder = map.get("set", context)?;
// 6. Return ? AddEntriesFromIterable(map, iterable, adder). // 6. Return ? AddEntriesFromIterable(map, iterable, adder).
add_entries_from_iterable(&map, iterable, &adder, context) add_entries_from_iterable(&map, &iterable, &adder, context)
} }
/// `get Map [ @@species ]` /// `get Map [ @@species ]`
@ -219,11 +220,11 @@ impl Map {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]]. // 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() { if let Some(map) = object.borrow_mut().as_map_mut() {
let key = match key { let key = match key.variant() {
JsValue::Rational(r) => { JsVariant::Float64(r) => {
// 5. If key is -0𝔽, set key to +0𝔽. // 5. If key is -0𝔽, set key to +0𝔽.
if r.is_zero() { if r.is_zero() {
JsValue::Rational(0f64) JsValue::new(0f64)
} else { } else {
key.clone() key.clone()
} }
@ -235,7 +236,7 @@ impl Map {
// i. Set p.[[Value]] to value. // i. Set p.[[Value]] to value.
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
// 7. Append p as the last element of entries. // 7. Append p as the last element of entries.
map.insert(key, value.clone()); map.insert(key, value);
// ii. Return M. // ii. Return M.
// 8. Return M. // 8. Return M.
return Ok(this.clone()); return Ok(this.clone());
@ -303,7 +304,7 @@ impl Map {
// ii. Set p.[[Value]] to empty. // ii. Set p.[[Value]] to empty.
// iii. Return true. // iii. Return true.
// 5. Return false. // 5. Return false.
return Ok(map.remove(key).is_some().into()); return Ok(map.remove(&key).is_some().into());
} }
} }
context.throw_type_error("'this' is not a Map") context.throw_type_error("'this' is not a Map")
@ -324,29 +325,21 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
let key = match key { let key = match key.variant() {
JsValue::Rational(r) => { JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64),
if r.is_zero() { _ => key.clone(),
JS_ZERO
} else {
key
}
}
_ => key,
}; };
// 1. Let M be the this value. // 1. Let M be the this value.
if let JsValue::Object(ref object) = this { if let Some(object) = this.as_object() {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]]. // 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow().as_map_ref() { if let Some(map) = object.borrow().as_map_ref() {
// 4. For each Record { [[Key]], [[Value]] } p of entries, do // 4. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]].
// 5. Return undefined. // 5. Return undefined.
return Ok(map.get(key).cloned().unwrap_or_default()); return Ok(map.get(&key).cloned().unwrap_or_default());
} }
} }
@ -396,29 +389,21 @@ impl Map {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
let key = match key { let key = match key.variant() {
JsValue::Rational(r) => { JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64),
if r.is_zero() { _ => key.clone(),
JS_ZERO
} else {
key
}
}
_ => key,
}; };
// 1. Let M be the this value. // 1. Let M be the this value.
if let JsValue::Object(ref object) = this { if let Some(object) = this.as_object() {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]]. // 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow().as_map_ref() { if let Some(map) = object.borrow().as_map_ref() {
// 4. For each Record { [[Key]], [[Value]] } p of entries, do // 4. For each Record { [[Key]], [[Value]] } p of entries, do
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true.
// 5. Return false. // 5. Return false.
return Ok(map.contains_key(key).into()); return Ok(map.contains_key(&key).into());
} }
} }
@ -489,7 +474,7 @@ impl Map {
// a. If e.[[Key]] is not empty, then // a. If e.[[Key]] is not empty, then
if let Some(arguments) = arguments { if let Some(arguments) = arguments {
// i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »). // i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »).
callback.call(this_arg, &arguments, context)?; callback.call(&this_arg, &arguments, context)?;
} }
index += 1; index += 1;

11
boa_engine/src/builtins/mod.rs

@ -211,16 +211,11 @@ pub trait JsArgs {
/// Call this if you are thinking of calling something similar to /// Call this if you are thinking of calling something similar to
/// `args.get(n).cloned().unwrap_or_default()` or /// `args.get(n).cloned().unwrap_or_default()` or
/// `args.get(n).unwrap_or(&undefined)`. /// `args.get(n).unwrap_or(&undefined)`.
/// fn get_or_undefined(&self, index: usize) -> JsValue;
/// This returns a reference for efficiency, in case
/// you only need to call methods of `JsValue`, so
/// try to minimize calling `clone`.
fn get_or_undefined(&self, index: usize) -> &JsValue;
} }
impl JsArgs for [JsValue] { impl JsArgs for [JsValue] {
fn get_or_undefined(&self, index: usize) -> &JsValue { fn get_or_undefined(&self, index: usize) -> JsValue {
const UNDEFINED: &JsValue = &JsValue::Undefined; self.get(index).cloned().unwrap_or_else(JsValue::undefined)
self.get(index).unwrap_or(UNDEFINED)
} }
} }

31
boa_engine/src/builtins/number/mod.rs

@ -21,7 +21,7 @@ use crate::{
JsObject, ObjectData, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
value::{AbstractRelation, IntegerOrInfinity, JsValue}, value::{AbstractRelation, IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult, Context, JsResult,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -220,7 +220,8 @@ impl Number {
// 1. Let x be ? thisNumberValue(this value). // 1. Let x be ? thisNumberValue(this value).
let this_num = Self::this_number_value(this, context)?; let this_num = Self::this_number_value(this, context)?;
let precision = match args.get(0) { let precision = match args.get(0) {
None | Some(JsValue::Undefined) => None, None => None,
Some(n) if n.is_undefined() => None,
// 2. Let f be ? ToIntegerOrInfinity(fractionDigits). // 2. Let f be ? ToIntegerOrInfinity(fractionDigits).
Some(n) => Some(n.to_integer_or_infinity(context)?), Some(n) => Some(n.to_integer_or_infinity(context)?),
}; };
@ -992,9 +993,9 @@ impl Number {
_ctx: &mut Context, _ctx: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Ok(JsValue::new(if let Some(val) = args.get(0) { Ok(JsValue::new(if let Some(val) = args.get(0) {
match val { match val.variant() {
JsValue::Integer(_) => true, JsVariant::Float64(number) => number.is_finite(),
JsValue::Rational(number) => number.is_finite(), JsVariant::Integer32(_) => true,
_ => false, _ => false,
} }
} else { } else {
@ -1041,13 +1042,7 @@ impl Number {
args: &[JsValue], args: &[JsValue],
_ctx: &mut Context, _ctx: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Ok(JsValue::new( Ok(JsValue::new(args.get(0).map_or(false, JsValue::is_nan)))
if let Some(&JsValue::Rational(number)) = args.get(0) {
number.is_nan()
} else {
false
},
))
} }
/// `Number.isSafeInteger( number )` /// `Number.isSafeInteger( number )`
@ -1070,9 +1065,9 @@ impl Number {
args: &[JsValue], args: &[JsValue],
_ctx: &mut Context, _ctx: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
Ok(JsValue::new(match args.get(0) { Ok(JsValue::new(match args.get(0).map(JsValue::variant) {
Some(JsValue::Integer(_)) => true, Some(JsVariant::Integer32(_)) => true,
Some(JsValue::Rational(number)) if Self::is_float_integer(*number) => { Some(JsVariant::Float64(number)) if Self::is_float_integer(number) => {
number.abs() <= Self::MAX_SAFE_INTEGER number.abs() <= Self::MAX_SAFE_INTEGER
} }
_ => false, _ => false,
@ -1087,9 +1082,9 @@ impl Number {
/// [spec]: https://tc39.es/ecma262/#sec-isinteger /// [spec]: https://tc39.es/ecma262/#sec-isinteger
#[inline] #[inline]
pub(crate) fn is_integer(val: &JsValue) -> bool { pub(crate) fn is_integer(val: &JsValue) -> bool {
match val { match val.variant() {
JsValue::Integer(_) => true, JsVariant::Integer32(_) => true,
JsValue::Rational(number) => Self::is_float_integer(*number), JsVariant::Float64(number) => Self::is_float_integer(number),
_ => false, _ => false,
} }
} }

3
boa_engine/src/builtins/object/for_in_iterator.rs

@ -67,7 +67,8 @@ impl ForInIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut iterator = this.as_object().map(JsObject::borrow_mut); let iterator = this.as_object();
let mut iterator = iterator.as_deref().map(JsObject::borrow_mut);
let iterator = iterator let iterator = iterator
.as_mut() .as_mut()
.and_then(|obj| obj.as_for_in_iterator_mut()) .and_then(|obj| obj.as_for_in_iterator_mut())

82
boa_engine/src/builtins/object/mod.rs

@ -23,7 +23,7 @@ use crate::{
}, },
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::JsValue, value::{JsValue, JsVariant},
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -155,7 +155,7 @@ impl Object {
// 2. Return ? O.[[GetPrototypeOf]](). // 2. Return ? O.[[GetPrototypeOf]]().
let proto = obj.__get_prototype_of__(context)?; let proto = obj.__get_prototype_of__(context)?;
Ok(proto.map_or(JsValue::Null, JsValue::new)) Ok(proto.map_or(JsValue::null(), JsValue::new))
} }
/// `set Object.prototype.__proto__` /// `set Object.prototype.__proto__`
@ -178,16 +178,17 @@ impl Object {
let this = this.require_object_coercible(context)?; let this = this.require_object_coercible(context)?;
// 2. If Type(proto) is neither Object nor Null, return undefined. // 2. If Type(proto) is neither Object nor Null, return undefined.
let proto = match args.get_or_undefined(0) { let proto = match args.get_or_undefined(0).variant() {
JsValue::Object(proto) => Some(proto.clone()), JsVariant::Object(proto) => Some(proto.clone()),
JsValue::Null => None, JsVariant::Null => None,
_ => return Ok(JsValue::undefined()), _ => return Ok(JsValue::undefined()),
}; };
// 3. If Type(O) is not Object, return undefined. // 3. If Type(O) is not Object, return undefined.
let object = match this { let object = if let Some(o) = this.as_object() {
JsValue::Object(object) => object, o
_ => return Ok(JsValue::undefined()), } else {
return Ok(JsValue::undefined());
}; };
// 4. Let status be ? O.[[SetPrototypeOf]](proto). // 4. Let status be ? O.[[SetPrototypeOf]](proto).
@ -389,9 +390,9 @@ impl Object {
let prototype = args.get_or_undefined(0); let prototype = args.get_or_undefined(0);
let properties = args.get_or_undefined(1); let properties = args.get_or_undefined(1);
let obj = match prototype { let obj = match prototype.variant() {
JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data( JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data(
prototype.as_object().cloned(), prototype.as_object().as_deref().cloned(),
ObjectData::ordinary(), ObjectData::ordinary(),
), ),
_ => { _ => {
@ -403,7 +404,7 @@ impl Object {
}; };
if !properties.is_undefined() { if !properties.is_undefined() {
object_define_properties(&obj, properties, context)?; object_define_properties(&obj, &properties, context)?;
return Ok(obj.into()); return Ok(obj.into());
} }
@ -480,7 +481,7 @@ impl Object {
} }
// 5. Return descriptors. // 5. Return descriptors.
Ok(descriptors.into()) Ok(JsValue::new(descriptors))
} }
/// The abstract operation `FromPropertyDescriptor`. /// The abstract operation `FromPropertyDescriptor`.
@ -553,7 +554,7 @@ impl Object {
let x = args.get_or_undefined(0); let x = args.get_or_undefined(0);
let y = args.get_or_undefined(1); let y = args.get_or_undefined(1);
Ok(JsValue::same_value(x, y).into()) Ok(JsValue::same_value(&x, &y).into())
} }
/// Get the `prototype` of an object. /// Get the `prototype` of an object.
@ -574,7 +575,7 @@ impl Object {
// 2. Return ? obj.[[GetPrototypeOf]](). // 2. Return ? obj.[[GetPrototypeOf]]().
Ok(obj Ok(obj
.__get_prototype_of__(ctx)? .__get_prototype_of__(ctx)?
.map_or(JsValue::Null, JsValue::new)) .map_or(JsValue::null(), JsValue::new))
} }
/// Set the `prototype` of an object. /// Set the `prototype` of an object.
@ -582,9 +583,13 @@ impl Object {
/// [More information][spec] /// [More information][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
pub fn set_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> { pub fn set_prototype_of(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if args.len() < 2 { if args.len() < 2 {
return ctx.throw_type_error(format!( return context.throw_type_error(format!(
"Object.setPrototypeOf: At least 2 arguments required, but only {} passed", "Object.setPrototypeOf: At least 2 arguments required, but only {} passed",
args.len() args.len()
)); ));
@ -595,16 +600,19 @@ impl Object {
.get(0) .get(0)
.cloned() .cloned()
.unwrap_or_default() .unwrap_or_default()
.require_object_coercible(ctx)? .require_object_coercible(context)?
.clone(); .clone();
let proto = match args.get_or_undefined(1) { let proto = args.get_or_undefined(1);
JsValue::Object(obj) => Some(obj.clone()), let proto = match proto.variant() {
JsValue::Null => None, JsVariant::Object(obj) => Some(obj.clone()),
JsVariant::Null => None,
// 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
val => { _ => {
return ctx return context.throw_type_error(format!(
.throw_type_error(format!("expected an object or null, got {}", val.type_of())) "expected an object or null, got {}",
proto.type_of()
))
} }
}; };
@ -616,11 +624,11 @@ impl Object {
}; };
// 4. Let status be ? O.[[SetPrototypeOf]](proto). // 4. Let status be ? O.[[SetPrototypeOf]](proto).
let status = obj.__set_prototype_of__(proto, ctx)?; let status = obj.__set_prototype_of__(proto, context)?;
// 5. If status is false, throw a TypeError exception. // 5. If status is false, throw a TypeError exception.
if !status { if !status {
return ctx.throw_type_error("can't set prototype of this object"); return context.throw_type_error("can't set prototype of this object");
} }
// 6. Return O. // 6. Return O.
@ -646,7 +654,7 @@ impl Object {
if !v.is_object() { if !v.is_object() {
return Ok(JsValue::new(false)); return Ok(JsValue::new(false));
} }
let mut v = v.clone(); let mut v = v;
let o = JsValue::new(this.to_object(context)?); let o = JsValue::new(this.to_object(context)?);
loop { loop {
v = Self::get_prototype_of(this, &[v], context)?; v = Self::get_prototype_of(this, &[v], context)?;
@ -666,14 +674,14 @@ impl Object {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let object = args.get_or_undefined(0); let object = args.get_or_undefined(0);
if let JsValue::Object(object) = object { if let Some(object) = object.as_object() {
let key = args let key = args
.get(1) .get(1)
.unwrap_or(&JsValue::Undefined) .unwrap_or(&JsValue::undefined())
.to_property_key(context)?; .to_property_key(context)?;
let desc = args let desc = args
.get(2) .get(2)
.unwrap_or(&JsValue::Undefined) .unwrap_or(&JsValue::undefined())
.to_property_descriptor(context)?; .to_property_descriptor(context)?;
object.define_property_or_throw(key, desc, context)?; object.define_property_or_throw(key, desc, context)?;
@ -700,9 +708,9 @@ impl Object {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let arg = args.get_or_undefined(0); let arg = args.get_or_undefined(0);
if let JsValue::Object(obj) = arg { if let Some(ref obj) = arg.as_object() {
let props = args.get_or_undefined(1); let props = args.get_or_undefined(1);
object_define_properties(obj, props, context)?; object_define_properties(obj, &props, context)?;
Ok(arg.clone()) Ok(arg.clone())
} else { } else {
context.throw_type_error("Expected an object") context.throw_type_error("Expected an object")
@ -778,7 +786,8 @@ impl Object {
let tag = o.get(WellKnownSymbols::to_string_tag(), context)?; let tag = o.get(WellKnownSymbols::to_string_tag(), context)?;
// 16. If Type(tag) is not String, set tag to builtinTag. // 16. If Type(tag) is not String, set tag to builtinTag.
let tag_str = tag.as_string().map_or(builtin_tag, JsString::as_str); let tag_str = tag.as_string();
let tag_str = tag_str.as_deref().map_or(builtin_tag, JsString::as_str);
// 17. Return the string-concatenation of "[object ", tag, and "]". // 17. Return the string-concatenation of "[object ", tag, and "]".
Ok(format!("[object {tag_str}]").into()) Ok(format!("[object {tag_str}]").into())
@ -1152,7 +1161,7 @@ impl Object {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, string). // 1. Return ? GetOwnPropertyKeys(O, string).
let o = args.get_or_undefined(0); let o = args.get_or_undefined(0);
get_own_property_keys(o, PropertyKeyType::String, context) get_own_property_keys(&o, PropertyKeyType::String, context)
} }
/// `Object.getOwnPropertySymbols( object )` /// `Object.getOwnPropertySymbols( object )`
@ -1170,7 +1179,7 @@ impl Object {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, symbol). // 1. Return ? GetOwnPropertyKeys(O, symbol).
let o = args.get_or_undefined(0); let o = args.get_or_undefined(0);
get_own_property_keys(o, PropertyKeyType::Symbol, context) get_own_property_keys(&o, PropertyKeyType::Symbol, context)
} }
/// `Object.hasOwn( object, property )` /// `Object.hasOwn( object, property )`
@ -1202,7 +1211,8 @@ impl Object {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries
pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Perform ? RequireObjectCoercible(iterable). // 1. Perform ? RequireObjectCoercible(iterable).
let iterable = args.get_or_undefined(0).require_object_coercible(context)?; let iterable = args.get_or_undefined(0);
let iterable = iterable.require_object_coercible(context)?;
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
// 3. Assert: obj is an extensible ordinary object with no own properties. // 3. Assert: obj is an extensible ordinary object with no own properties.

78
boa_engine/src/builtins/promise/mod.rs

@ -144,13 +144,13 @@ impl PromiseCapability {
let reject = args.get_or_undefined(1); let reject = args.get_or_undefined(1);
// c. Set promiseCapability.[[Resolve]] to resolve. // c. Set promiseCapability.[[Resolve]] to resolve.
promise_capability.resolve = resolve.clone(); promise_capability.resolve = resolve;
// d. Set promiseCapability.[[Reject]] to reject. // d. Set promiseCapability.[[Reject]] to reject.
promise_capability.reject = reject.clone(); promise_capability.reject = reject;
// e. Return undefined. // e. Return undefined.
Ok(JsValue::Undefined) Ok(JsValue::undefined())
}, },
promise_capability.clone(), promise_capability.clone(),
) )
@ -170,6 +170,7 @@ impl PromiseCapability {
// 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception. // 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
let resolve = resolve let resolve = resolve
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.and_then(JsFunction::from_object) .and_then(JsFunction::from_object)
.ok_or_else(|| { .ok_or_else(|| {
@ -180,6 +181,7 @@ impl PromiseCapability {
// 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
let reject = reject let reject = reject
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.and_then(JsFunction::from_object) .and_then(JsFunction::from_object)
.ok_or_else(|| { .ok_or_else(|| {
@ -318,8 +320,8 @@ impl Promise {
// 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
let completion = context.call( let completion = context.call(
executor, &executor,
&JsValue::Undefined, &JsValue::undefined(),
&[ &[
resolving_functions.resolve, resolving_functions.resolve,
resolving_functions.reject.clone(), resolving_functions.reject.clone(),
@ -329,7 +331,7 @@ impl Promise {
// 10. If completion is an abrupt completion, then // 10. If completion is an abrupt completion, then
if let Err(value) = completion { if let Err(value) = completion {
// a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
context.call(&resolving_functions.reject, &JsValue::Undefined, &[value])?; context.call(&resolving_functions.reject, &JsValue::undefined(), &[value])?;
} }
// 11. Return promise. // 11. Return promise.
@ -359,7 +361,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor"); let c = c.as_object().expect("must be a constructor");
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)). // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
let promise_resolve = Self::get_promise_resolve(c, context); let promise_resolve = Self::get_promise_resolve(&c, context);
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
if_abrupt_reject_promise!(promise_resolve, promise_capability, context); if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
@ -374,7 +376,7 @@ impl Promise {
// 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)).
let mut result = Self::perform_promise_all( let mut result = Self::perform_promise_all(
&mut iterator_record, &mut iterator_record,
c, &c,
&promise_capability, &promise_capability,
&promise_resolve, &promise_resolve,
context, context,
@ -489,7 +491,7 @@ impl Promise {
}; };
// h. Append undefined to values. // h. Append undefined to values.
values.borrow_mut().push(JsValue::Undefined); values.borrow_mut().push(JsValue::undefined());
// i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). // i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise = let next_promise =
@ -523,7 +525,7 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]]. // 7. Let remainingElementsCount be F.[[RemainingElements]].
// 8. Set values[index] to x. // 8. Set values[index] to x.
captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0);
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
captures captures
@ -600,7 +602,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor"); let c = c.as_object().expect("must be a constructor");
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)). // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
let promise_resolve = Self::get_promise_resolve(c, context); let promise_resolve = Self::get_promise_resolve(&c, context);
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
if_abrupt_reject_promise!(promise_resolve, promise_capability, context); if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
@ -615,7 +617,7 @@ impl Promise {
// 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)). // 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)).
let mut result = Self::perform_promise_all_settled( let mut result = Self::perform_promise_all_settled(
&mut iterator_record, &mut iterator_record,
c, &c,
&promise_capability, &promise_capability,
&promise_resolve, &promise_resolve,
context, context,
@ -931,7 +933,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor"); let c = c.as_object().expect("must be a constructor");
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)). // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
let promise_resolve = Self::get_promise_resolve(c, context); let promise_resolve = Self::get_promise_resolve(&c, context);
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
if_abrupt_reject_promise!(promise_resolve, promise_capability, context); if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
@ -946,7 +948,7 @@ impl Promise {
// 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)). // 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)).
let mut result = Self::perform_promise_any( let mut result = Self::perform_promise_any(
&mut iterator_record, &mut iterator_record,
c, &c,
&promise_capability, &promise_capability,
&promise_resolve, &promise_resolve,
context, context,
@ -1112,7 +1114,7 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]]. // 7. Let remainingElementsCount be F.[[RemainingElements]].
// 8. Set errors[index] to x. // 8. Set errors[index] to x.
captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0);
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
captures captures
@ -1232,7 +1234,7 @@ impl Promise {
// 5. If alreadyResolved.[[Value]] is true, return undefined. // 5. If alreadyResolved.[[Value]] is true, return undefined.
if already_resolved.get() { if already_resolved.get() {
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
} }
// 6. Set alreadyResolved.[[Value]] to true. // 6. Set alreadyResolved.[[Value]] to true.
@ -1241,7 +1243,7 @@ impl Promise {
let resolution = args.get_or_undefined(0); let resolution = args.get_or_undefined(0);
// 7. If SameValue(resolution, promise) is true, then // 7. If SameValue(resolution, promise) is true, then
if JsValue::same_value(resolution, &promise.clone().into()) { if JsValue::same_value(&resolution, &promise.clone().into()) {
// a. Let selfResolutionError be a newly created TypeError object. // a. Let selfResolutionError be a newly created TypeError object.
let self_resolution_error = let self_resolution_error =
context.construct_type_error("SameValue(resolution, promise) is true"); context.construct_type_error("SameValue(resolution, promise) is true");
@ -1254,7 +1256,7 @@ impl Promise {
.reject_promise(&self_resolution_error, context); .reject_promise(&self_resolution_error, context);
// c. Return undefined. // c. Return undefined.
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
} }
let then = if let Some(resolution) = resolution.as_object() { let then = if let Some(resolution) = resolution.as_object() {
@ -1267,10 +1269,10 @@ impl Promise {
.borrow_mut() .borrow_mut()
.as_promise_mut() .as_promise_mut()
.expect("Expected promise to be a Promise") .expect("Expected promise to be a Promise")
.fulfill_promise(resolution, context)?; .fulfill_promise(&resolution, context)?;
// b. Return undefined. // b. Return undefined.
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
}; };
let then_action = match then { let then_action = match then {
@ -1284,7 +1286,7 @@ impl Promise {
.reject_promise(&value, context); .reject_promise(&value, context);
// b. Return undefined. // b. Return undefined.
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
} }
// 11. Let thenAction be then.[[Value]]. // 11. Let thenAction be then.[[Value]].
Ok(then) => then, Ok(then) => then,
@ -1299,10 +1301,10 @@ impl Promise {
.borrow_mut() .borrow_mut()
.as_promise_mut() .as_promise_mut()
.expect("Expected promise to be a Promise") .expect("Expected promise to be a Promise")
.fulfill_promise(resolution, context)?; .fulfill_promise(&resolution, context)?;
// b. Return undefined. // b. Return undefined.
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
} }
}; };
@ -1321,7 +1323,7 @@ impl Promise {
context.host_enqueue_promise_job(job); context.host_enqueue_promise_job(job);
// 16. Return undefined. // 16. Return undefined.
Ok(JsValue::Undefined) Ok(JsValue::undefined())
}, },
resolve_captures, resolve_captures,
) )
@ -1356,7 +1358,7 @@ impl Promise {
// 5. If alreadyResolved.[[Value]] is true, return undefined. // 5. If alreadyResolved.[[Value]] is true, return undefined.
if already_resolved.get() { if already_resolved.get() {
return Ok(JsValue::Undefined); return Ok(JsValue::undefined());
} }
// 6. Set alreadyResolved.[[Value]] to true. // 6. Set alreadyResolved.[[Value]] to true.
@ -1368,10 +1370,10 @@ impl Promise {
.borrow_mut() .borrow_mut()
.as_promise_mut() .as_promise_mut()
.expect("Expected promise to be a Promise") .expect("Expected promise to be a Promise")
.reject_promise(args.get_or_undefined(0), context); .reject_promise(&args.get_or_undefined(0), context);
// 8. Return undefined. // 8. Return undefined.
Ok(JsValue::Undefined) Ok(JsValue::undefined())
}, },
reject_captures, reject_captures,
) )
@ -1523,7 +1525,7 @@ impl Promise {
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)). // 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
let promise_resolve = let promise_resolve =
Self::get_promise_resolve(c.as_object().expect("this was not an object"), context); Self::get_promise_resolve(&c.as_object().expect("this was not an object"), context);
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
if_abrupt_reject_promise!(promise_resolve, promise_capability, context); if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
@ -1648,7 +1650,7 @@ impl Promise {
context.call( context.call(
&promise_capability.reject.clone().into(), &promise_capability.reject.clone().into(),
&JsValue::undefined(), &JsValue::undefined(),
&[r.clone()], &[r],
)?; )?;
// 4. Return promiseCapability.[[Promise]]. // 4. Return promiseCapability.[[Promise]].
@ -1671,7 +1673,7 @@ impl Promise {
if let Some(c) = c.as_object() { if let Some(c) = c.as_object() {
// 3. Return ? PromiseResolve(C, x). // 3. Return ? PromiseResolve(C, x).
Self::promise_resolve(c.clone(), x.clone(), context) Self::promise_resolve(c.clone(), x, context)
} else { } else {
// 2. If Type(C) is not Object, throw a TypeError exception. // 2. If Type(C) is not Object, throw a TypeError exception.
context.throw_type_error("Promise.resolve() called on a non-object") context.throw_type_error("Promise.resolve() called on a non-object")
@ -1710,7 +1712,7 @@ impl Promise {
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »). // 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
promise.invoke( promise.invoke(
"then", "then",
&[JsValue::undefined(), on_rejected.clone()], &[JsValue::undefined(), on_rejected],
context, context,
) )
} }
@ -1777,7 +1779,7 @@ impl Promise {
Ok(captures.value.clone()) Ok(captures.value.clone())
}, },
ReturnValueCaptures { ReturnValueCaptures {
value: value.clone(), value,
}, },
); );
@ -1822,7 +1824,7 @@ impl Promise {
Err(captures.reason.clone()) Err(captures.reason.clone())
}, },
ThrowReasonCaptures { ThrowReasonCaptures {
reason: reason.clone(), reason,
}, },
); );
@ -1833,7 +1835,7 @@ impl Promise {
promise.invoke("then", &[thrower.into()], context) promise.invoke("then", &[thrower.into()], context)
}, },
FinallyCaptures { FinallyCaptures {
on_finally: on_finally.clone(), on_finally,
c, c,
}, },
); );
@ -1846,7 +1848,7 @@ impl Promise {
// 6. Else, // 6. Else,
// a. Let thenFinally be onFinally. // a. Let thenFinally be onFinally.
// b. Let catchFinally be onFinally. // b. Let catchFinally be onFinally.
(on_finally.clone(), on_finally.clone()) (on_finally.clone(), on_finally)
}; };
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »). // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
@ -1881,11 +1883,11 @@ impl Promise {
let on_rejected = args.get_or_undefined(1); let on_rejected = args.get_or_undefined(1);
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability). // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
let mut promise_obj = promise_obj.borrow_mut();
promise_obj promise_obj
.borrow_mut()
.as_promise_mut() .as_promise_mut()
.expect("IsPromise(promise) is false") .expect("IsPromise(promise) is false")
.perform_promise_then(on_fulfilled, on_rejected, Some(result_capability), context) .perform_promise_then(&on_fulfilled, &on_rejected, Some(result_capability), context)
.pipe(Ok) .pipe(Ok)
} }
@ -1999,7 +2001,7 @@ impl Promise {
match result_capability { match result_capability {
// 13. If resultCapability is undefined, then // 13. If resultCapability is undefined, then
// a. Return undefined. // a. Return undefined.
None => JsValue::Undefined, None => JsValue::undefined(),
// 14. Else, // 14. Else,
// a. Return resultCapability.[[Promise]]. // a. Return resultCapability.[[Promise]].

10
boa_engine/src/builtins/promise/promise_job.rs

@ -55,7 +55,7 @@ impl PromiseJob {
}, },
// e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
Some(handler) => { Some(handler) => {
handler.call_job_callback(&JsValue::Undefined, &[argument.clone()], context) handler.call_job_callback(&JsValue::undefined(), &[argument.clone()], context)
} }
}; };
@ -69,7 +69,7 @@ impl PromiseJob {
); );
// ii. Return empty. // ii. Return empty.
Ok(JsValue::Undefined) Ok(JsValue::undefined())
} }
Some(promise_capability_record) => { Some(promise_capability_record) => {
// g. Assert: promiseCapability is a PromiseCapability Record. // g. Assert: promiseCapability is a PromiseCapability Record.
@ -83,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then // h. If handlerResult is an abrupt completion, then
Err(value) => { Err(value) => {
// i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) context.call(&reject.clone().into(), &JsValue::undefined(), &[value])
} }
// i. Else, // i. Else,
Ok(value) => { Ok(value) => {
// i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).
context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) context.call(&resolve.clone().into(), &JsValue::undefined(), &[value])
} }
} }
} }
@ -149,7 +149,7 @@ impl PromiseJob {
// i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
return context.call( return context.call(
&resolving_functions.reject, &resolving_functions.reject,
&JsValue::Undefined, &JsValue::undefined(),
&[value], &[value],
); );
} }

13
boa_engine/src/builtins/proxy/mod.rs

@ -81,7 +81,12 @@ impl Proxy {
} }
// 2. Return ? ProxyCreate(target, handler). // 2. Return ? ProxyCreate(target, handler).
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from) Self::create(
&args.get_or_undefined(0),
&args.get_or_undefined(1),
context,
)
.map(JsValue::from)
} }
// `10.5.14 ProxyCreate ( target, handler )` // `10.5.14 ProxyCreate ( target, handler )`
@ -162,7 +167,11 @@ impl Proxy {
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable /// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let p be ? ProxyCreate(target, handler). // 1. Let p be ? ProxyCreate(target, handler).
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?; let p = Self::create(
&args.get_or_undefined(0),
&args.get_or_undefined(1),
context,
)?;
// Revoker creation steps on `Proxy::revoker` // Revoker creation steps on `Proxy::revoker`
let revoker = Self::revoker(p.clone(), context); let revoker = Self::revoker(p.clone(), context);

26
boa_engine/src/builtins/reflect/mod.rs

@ -16,6 +16,7 @@ use crate::{
object::ObjectInitializer, object::ObjectInitializer,
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::JsVariant,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -86,7 +87,7 @@ impl Reflect {
return context.throw_type_error("target must be a function"); return context.throw_type_error("target must be a function");
} }
let args = args_list.create_list_from_array_like(&[], context)?; let args = args_list.create_list_from_array_like(&[], context)?;
target.call(this_arg, &args, context) target.call(&this_arg, &args, context)
} }
/// Calls a target function as a constructor with arguments. /// Calls a target function as a constructor with arguments.
@ -104,20 +105,21 @@ impl Reflect {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If IsConstructor(target) is false, throw a TypeError exception. // 1. If IsConstructor(target) is false, throw a TypeError exception.
let target = args let target = args
.get_or_undefined(0) .get_or_undefined(0);
let target = target
.as_constructor() .as_constructor()
.ok_or_else(|| context.construct_type_error("target must be a constructor"))?; .ok_or_else(|| context.construct_type_error("target must be a constructor"))?;
let new_target = if let Some(new_target) = args.get(2) { let new_target = if let Some(new_target) = args.get(2) {
// 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception. // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
if let Some(new_target) = new_target.as_constructor() { if let Some(new_target) = new_target.as_constructor() {
new_target new_target.clone()
} else { } else {
return context.throw_type_error("newTarget must be a constructor"); return context.throw_type_error("newTarget must be a constructor");
} }
} else { } else {
// 2. If newTarget is not present, set newTarget to target. // 2. If newTarget is not present, set newTarget to target.
target target.clone()
}; };
// 4. Let args be ? CreateListFromArrayLike(argumentsList). // 4. Let args be ? CreateListFromArrayLike(argumentsList).
@ -127,7 +129,7 @@ impl Reflect {
// 5. Return ? Construct(target, args, newTarget). // 5. Return ? Construct(target, args, newTarget).
target target
.construct(&args, Some(new_target), context) .construct(&args, Some(&new_target), context)
.map(JsValue::from) .map(JsValue::from)
} }
@ -151,7 +153,7 @@ impl Reflect {
let key = args.get_or_undefined(1).to_property_key(context)?; let key = args.get_or_undefined(1).to_property_key(context)?;
let prop_desc: JsValue = args let prop_desc: JsValue = args
.get(2) .get(2)
.and_then(|v| v.as_object().cloned()) .and_then(|v| v.as_object().as_deref().cloned())
.ok_or_else(|| context.construct_type_error("property descriptor must be an object"))? .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))?
.into(); .into();
@ -254,7 +256,7 @@ impl Reflect {
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
Ok(target Ok(target
.__get_prototype_of__(context)? .__get_prototype_of__(context)?
.map_or(JsValue::Null, JsValue::new)) .map_or(JsValue::null(), JsValue::new))
} }
/// Returns `true` if the object has the property, `false` otherwise. /// Returns `true` if the object has the property, `false` otherwise.
@ -365,9 +367,7 @@ impl Reflect {
} else { } else {
target.clone().into() target.clone().into()
}; };
Ok(target Ok(target.__set__(key, value, receiver, context)?.into())
.__set__(key, value.clone(), receiver, context)?
.into())
} }
/// Sets the prototype of an object. /// Sets the prototype of an object.
@ -387,9 +387,9 @@ impl Reflect {
.get(0) .get(0)
.and_then(JsValue::as_object) .and_then(JsValue::as_object)
.ok_or_else(|| context.construct_type_error("target must be an object"))?; .ok_or_else(|| context.construct_type_error("target must be an object"))?;
let proto = match args.get_or_undefined(1) { let proto = match args.get_or_undefined(1).variant() {
JsValue::Object(obj) => Some(obj.clone()), JsVariant::Object(obj) => Some(obj.clone()),
JsValue::Null => None, JsVariant::Null => None,
_ => return context.throw_type_error("proto must be an object or null"), _ => return context.throw_type_error("proto must be an object or null"),
}; };
Ok(target.__set_prototype_of__(proto, context)?.into()) Ok(target.__set_prototype_of__(proto, context)?.into())

27
boa_engine/src/builtins/regexp/mod.rs

@ -179,13 +179,13 @@ impl RegExp {
if new_target.is_undefined() { if new_target.is_undefined() {
// a. Let newTarget be the active function object. // a. Let newTarget be the active function object.
// b. If patternIsRegExp is true and flags is undefined, then // b. If patternIsRegExp is true and flags is undefined, then
if let Some(pattern) = pattern_is_regexp { if let Some(ref pattern) = pattern_is_regexp {
if flags.is_undefined() { if flags.is_undefined() {
// i. Let patternConstructor be ? Get(pattern, "constructor"). // i. Let patternConstructor be ? Get(pattern, "constructor").
let pattern_constructor = pattern.get("constructor", context)?; let pattern_constructor = pattern.get("constructor", context)?;
// ii. If SameValue(newTarget, patternConstructor) is true, return pattern. // ii. If SameValue(newTarget, patternConstructor) is true, return pattern.
if JsValue::same_value(new_target, &pattern_constructor) { if JsValue::same_value(new_target, &pattern_constructor) {
return Ok(pattern.clone().into()); return Ok((&**pattern).clone().into());
} }
} }
} }
@ -208,12 +208,12 @@ impl RegExp {
JsValue::new(regexp.original_flags.clone()), JsValue::new(regexp.original_flags.clone()),
) )
} else { } else {
(JsValue::new(regexp.original_source.clone()), flags.clone()) (JsValue::new(regexp.original_source.clone()), flags)
} }
} else { } else {
// a. Let P be pattern. // a. Let P be pattern.
// b. Let F be flags. // b. Let F be flags.
(pattern.clone(), flags.clone()) (pattern.clone(), flags)
}; };
// 7. Let O be ? RegExpAlloc(newTarget). // 7. Let O be ? RegExpAlloc(newTarget).
@ -352,7 +352,7 @@ impl RegExp {
#[inline] #[inline]
fn regexp_has_flag(this: &JsValue, flag: u8, context: &mut Context) -> JsResult<JsValue> { fn regexp_has_flag(this: &JsValue, flag: u8, context: &mut Context) -> JsResult<JsValue> {
if let Some(object) = this.as_object() { if let Some(ref object) = this.as_object() {
if let Some(regexp) = object.borrow().as_regexp() { if let Some(regexp) = object.borrow().as_regexp() {
return Ok(JsValue::new(match flag { return Ok(JsValue::new(match flag {
b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES), b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES),
@ -695,7 +695,7 @@ impl RegExp {
.to_string(context)?; .to_string(context)?;
// 4. Let match be ? RegExpExec(R, string). // 4. Let match be ? RegExpExec(R, string).
let m = Self::abstract_exec(this, arg_str, context)?; let m = Self::abstract_exec(&this, arg_str, context)?;
// 5. If match is not null, return true; else return false. // 5. If match is not null, return true; else return false.
if m.is_some() { if m.is_some() {
@ -735,7 +735,7 @@ impl RegExp {
let arg_str = args.get_or_undefined(0).to_string(context)?; let arg_str = args.get_or_undefined(0).to_string(context)?;
// 4. Return ? RegExpBuiltinExec(R, S). // 4. Return ? RegExpBuiltinExec(R, S).
if let Some(v) = Self::abstract_builtin_exec(obj, &arg_str, context)? { if let Some(v) = Self::abstract_builtin_exec(&obj, &arg_str, context)? {
Ok(v.into()) Ok(v.into())
} else { } else {
Ok(JsValue::null()) Ok(JsValue::null())
@ -770,7 +770,7 @@ impl RegExp {
} }
// c. Return result. // c. Return result.
return Ok(result.as_object().cloned()); return Ok(result.as_object().as_deref().cloned());
} }
// 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]).
@ -1050,7 +1050,7 @@ impl RegExp {
#[allow(clippy::if_not_else)] #[allow(clippy::if_not_else)]
if !global { if !global {
// a. Return ? RegExpExec(rx, S). // a. Return ? RegExpExec(rx, S).
if let Some(v) = Self::abstract_exec(rx, arg_str, context)? { if let Some(v) = Self::abstract_exec(&rx, arg_str, context)? {
Ok(v.into()) Ok(v.into())
} else { } else {
Ok(JsValue::null()) Ok(JsValue::null())
@ -1075,7 +1075,7 @@ impl RegExp {
// f. Repeat, // f. Repeat,
loop { loop {
// i. Let result be ? RegExpExec(rx, S). // i. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(rx, arg_str.clone(), context)?; let result = Self::abstract_exec(&rx, arg_str.clone(), context)?;
// ii. If result is null, then // ii. If result is null, then
// iii. Else, // iii. Else,
@ -1244,9 +1244,10 @@ impl RegExp {
let length_arg_str = arg_str.encode_utf16().count(); let length_arg_str = arg_str.encode_utf16().count();
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let mut replace_value = args.get_or_undefined(1).clone(); let mut replace_value = args.get_or_undefined(1);
let functional_replace = replace_value let functional_replace = replace_value
.as_object() .as_object()
.as_deref()
.map(JsObject::is_callable) .map(JsObject::is_callable)
.unwrap_or_default(); .unwrap_or_default();
@ -1276,7 +1277,7 @@ impl RegExp {
// 11. Repeat, while done is false, // 11. Repeat, while done is false,
loop { loop {
// a. Let result be ? RegExpExec(rx, S). // a. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(rx, arg_str.clone(), context)?; let result = Self::abstract_exec(&rx, arg_str.clone(), context)?;
// b. If result is null, set done to true. // b. If result is null, set done to true.
// c. Else, // c. Else,
@ -1498,7 +1499,7 @@ impl RegExp {
} }
// 6. Let result be ? RegExpExec(rx, S). // 6. Let result be ? RegExpExec(rx, S).
let result = Self::abstract_exec(rx, arg_str, context)?; let result = Self::abstract_exec(&rx, arg_str, context)?;
// 7. Let currentLastIndex be ? Get(rx, "lastIndex"). // 7. Let currentLastIndex be ? Get(rx, "lastIndex").
let current_last_index = rx.get("lastIndex", context)?; let current_last_index = rx.get("lastIndex", context)?;

3
boa_engine/src/builtins/regexp/regexp_string_iterator.rs

@ -81,7 +81,8 @@ impl RegExpStringIterator {
} }
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut iterator = this.as_object().map(JsObject::borrow_mut); let iterator = this.as_object();
let mut iterator = iterator.as_deref().map(JsObject::borrow_mut);
let iterator = iterator let iterator = iterator
.as_mut() .as_mut()
.and_then(|obj| obj.as_regexp_string_iterator_mut()) .and_then(|obj| obj.as_regexp_string_iterator_mut())

14
boa_engine/src/builtins/set/mod.rs

@ -142,7 +142,7 @@ impl Set {
})?; })?;
// 7. Let iteratorRecord be ? GetIterator(iterable). // 7. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.clone().get_iterator(context, None, None)?; let iterator_record = iterable.get_iterator(context, None, None)?;
// 8. Repeat, // 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord). // a. Let next be ? IteratorStep(iteratorRecord).
@ -224,9 +224,9 @@ impl Set {
if let Some(object) = this.as_object() { if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() { if let Some(set) = object.borrow_mut().as_set_mut() {
set.add(if value.as_number().map_or(false, |n| n == -0f64) { set.add(if value.as_number().map_or(false, |n| n == -0f64) {
JsValue::Integer(0) JsValue::new(0)
} else { } else {
value.clone() value
}); });
} else { } else {
return context.throw_type_error("'this' is not a Set"); return context.throw_type_error("'this' is not a Set");
@ -281,7 +281,7 @@ impl Set {
let res = if let Some(object) = this.as_object() { let res = if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() { if let Some(set) = object.borrow_mut().as_set_mut() {
set.delete(value) set.delete(&value)
} else { } else {
return context.throw_type_error("'this' is not a Set"); return context.throw_type_error("'this' is not a Set");
} }
@ -352,7 +352,7 @@ impl Set {
let this_arg = if this_arg.is_undefined() { let this_arg = if this_arg.is_undefined() {
context.global_object().clone().into() context.global_object().clone().into()
} else { } else {
this_arg.clone() this_arg
}; };
let mut index = 0; let mut index = 0;
@ -375,7 +375,7 @@ impl Set {
index += 1; index += 1;
} }
Ok(JsValue::Undefined) Ok(JsValue::undefined())
} }
/// `Map.prototype.has( key )` /// `Map.prototype.has( key )`
@ -399,7 +399,7 @@ impl Set {
.and_then(|obj| { .and_then(|obj| {
obj.borrow() obj.borrow()
.as_set_ref() .as_set_ref()
.map(|set| set.contains(value).into()) .map(|set| set.contains(&value).into())
}) })
.ok_or_else(|| context.construct_type_error("'this' is not a Set")) .ok_or_else(|| context.construct_type_error("'this' is not a Set"))
} }

6
boa_engine/src/builtins/set/set_iterator.rs

@ -67,7 +67,8 @@ impl SetIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut set_iterator = this.as_object().map(JsObject::borrow_mut); let set_iterator = this.as_object();
let mut set_iterator = set_iterator.as_deref().map(JsObject::borrow_mut);
let set_iterator = set_iterator let set_iterator = set_iterator
.as_mut() .as_mut()
@ -86,7 +87,8 @@ impl SetIterator {
)); ));
} }
let entries = m.as_object().map(JsObject::borrow); let entries = m.as_object();
let entries = entries.as_deref().map(JsObject::borrow);
let entries = entries let entries = entries
.as_ref() .as_ref()
.and_then(|obj| obj.as_set_ref()) .and_then(|obj| obj.as_set_ref())

84
boa_engine/src/builtins/string/mod.rs

@ -179,8 +179,12 @@ impl String {
let string = match args.get(0) { let string = match args.get(0) {
// 2. Else, // 2. Else,
// a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value). // a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value).
Some(JsValue::Symbol(ref sym)) if new_target.is_undefined() => { Some(value) if new_target.is_undefined() && value.is_symbol() => {
return Ok(sym.descriptive_string().into()) return Ok(value
.as_symbol()
.expect("Already checked for a symbol")
.descriptive_string()
.into())
} }
// b. Let s be ? ToString(value). // b. Let s be ? ToString(value).
Some(value) => value.to_string(context)?, Some(value) => value.to_string(context)?,
@ -244,6 +248,7 @@ impl String {
fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult<JsString> { fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult<JsString> {
// 1. If Type(value) is String, return value. // 1. If Type(value) is String, return value.
this.as_string() this.as_string()
.as_deref()
.cloned() .cloned()
// 2. If Type(value) is Object and value has a [[StringData]] internal slot, then // 2. If Type(value) is Object and value has a [[StringData]] internal slot, then
// a. Let s be value.[[StringData]]. // a. Let s be value.[[StringData]].
@ -395,7 +400,7 @@ impl String {
// If codeUnits is empty, the empty String is returned. // If codeUnits is empty, the empty String is returned.
let s = std::string::String::from_utf16_lossy(elements.as_slice()); let s = std::string::String::from_utf16_lossy(elements.as_slice());
Ok(JsValue::String(JsString::new(s))) Ok(JsValue::new(JsString::new(s)))
} }
/// `String.prototype.toString ( )` /// `String.prototype.toString ( )`
@ -780,7 +785,7 @@ impl String {
// 3. Let isRegExp be ? IsRegExp(searchString). // 3. Let isRegExp be ? IsRegExp(searchString).
// 4. If isRegExp is true, throw a TypeError exception. // 4. If isRegExp is true, throw a TypeError exception.
if is_reg_exp(search_string, context)? { if is_reg_exp(&search_string, context)? {
context.throw_type_error( context.throw_type_error(
"First argument to String.prototype.startsWith must not be a regular expression", "First argument to String.prototype.startsWith must not be a regular expression",
)?; )?;
@ -793,9 +798,11 @@ impl String {
let len = string.encode_utf16().count() as i64; let len = string.encode_utf16().count() as i64;
// 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position). // 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position).
let pos = match args.get_or_undefined(1) { let pos = args.get_or_undefined(1);
&JsValue::Undefined => IntegerOrInfinity::Integer(0), let pos = if pos.is_undefined() {
position => position.to_integer_or_infinity(context)?, IntegerOrInfinity::Integer(0)
} else {
pos.to_integer_or_infinity(context)?
}; };
// 8. Let start be the result of clamping pos between 0 and len. // 8. Let start be the result of clamping pos between 0 and len.
@ -850,7 +857,7 @@ impl String {
let search_str = match args.get_or_undefined(0) { let search_str = match args.get_or_undefined(0) {
// 3. Let isRegExp be ? IsRegExp(searchString). // 3. Let isRegExp be ? IsRegExp(searchString).
// 4. If isRegExp is true, throw a TypeError exception. // 4. If isRegExp is true, throw a TypeError exception.
search_string if is_reg_exp(search_string, context)? => { search_string if is_reg_exp(&search_string, context)? => {
return context.throw_type_error( return context.throw_type_error(
"First argument to String.prototype.endsWith must not be a regular expression", "First argument to String.prototype.endsWith must not be a regular expression",
); );
@ -919,7 +926,7 @@ impl String {
let search_str = match args.get_or_undefined(0) { let search_str = match args.get_or_undefined(0) {
// 3. Let isRegExp be ? IsRegExp(searchString). // 3. Let isRegExp be ? IsRegExp(searchString).
search_string if is_reg_exp(search_string, context)? => { search_string if is_reg_exp(&search_string, context)? => {
return context.throw_type_error( return context.throw_type_error(
// 4. If isRegExp is true, throw a TypeError exception. // 4. If isRegExp is true, throw a TypeError exception.
"First argument to String.prototype.includes must not be a regular expression", "First argument to String.prototype.includes must not be a regular expression",
@ -979,7 +986,7 @@ impl String {
if let Some(replacer) = replacer { if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return replacer.call( return replacer.call(
search_value, &search_value,
&[this.clone(), replace_value.clone()], &[this.clone(), replace_value.clone()],
context, context,
); );
@ -995,6 +1002,7 @@ impl String {
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value let functional_replace = replace_value
.as_object() .as_object()
.as_deref()
.map(JsObject::is_callable) .map(JsObject::is_callable)
.unwrap_or_default(); .unwrap_or_default();
@ -1023,7 +1031,7 @@ impl String {
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)). // a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
context context
.call( .call(
replace_value, &replace_value,
&JsValue::undefined(), &JsValue::undefined(),
&[search_str.into(), position.into(), this_str.clone().into()], &[search_str.into(), position.into(), this_str.clone().into()],
)? )?
@ -1088,7 +1096,7 @@ impl String {
// 2. If searchValue is neither undefined nor null, then // 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() { if !search_value.is_null_or_undefined() {
// a. Let isRegExp be ? IsRegExp(searchValue). // a. Let isRegExp be ? IsRegExp(searchValue).
if let Some(obj) = search_value.as_object() { if let Some(ref obj) = search_value.as_object() {
// b. If isRegExp is true, then // b. If isRegExp is true, then
if is_reg_exp_object(obj, context)? { if is_reg_exp_object(obj, context)? {
// i. Let flags be ? Get(searchValue, "flags"). // i. Let flags be ? Get(searchValue, "flags").
@ -1112,7 +1120,7 @@ impl String {
// d. If replacer is not undefined, then // d. If replacer is not undefined, then
if let Some(replacer) = replacer { if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »). // i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return replacer.call(search_value, &[o.into(), replace_value.clone()], context); return replacer.call(&search_value, &[o.into(), replace_value.clone()], context);
} }
} }
@ -1125,6 +1133,7 @@ impl String {
// 5. Let functionalReplace be IsCallable(replaceValue). // 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value let functional_replace = replace_value
.as_object() .as_object()
.as_deref()
.map(JsObject::is_callable) .map(JsObject::is_callable)
.unwrap_or_default(); .unwrap_or_default();
@ -1196,7 +1205,7 @@ impl String {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)). // i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
context context
.call( .call(
replace_value, &replace_value,
&JsValue::undefined(), &JsValue::undefined(),
&[ &[
search_string.clone().into(), search_string.clone().into(),
@ -1372,7 +1381,7 @@ impl String {
// b. If matcher is not undefined, then // b. If matcher is not undefined, then
if let Some(matcher) = matcher { if let Some(matcher) = matcher {
// i. Return ? Call(matcher, regexp, « O »). // i. Return ? Call(matcher, regexp, « O »).
return matcher.call(regexp, &[o.clone()], context); return matcher.call(&regexp, &[o.clone()], context);
} }
} }
@ -1380,7 +1389,7 @@ impl String {
let s = o.to_string(context)?; let s = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, undefined). // 4. Let rx be ? RegExpCreate(regexp, undefined).
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; let rx = RegExp::create(&regexp, &JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@match, « S »). // 5. Return ? Invoke(rx, @@match, « S »).
rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context) rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context)
@ -1485,7 +1494,7 @@ impl String {
let fill_string = args.get_or_undefined(1); let fill_string = args.get_or_undefined(1);
// 2. Return ? StringPad(O, maxLength, fillString, end). // 2. Return ? StringPad(O, maxLength, fillString, end).
Self::string_pad(this, max_length, fill_string, Placement::End, context) Self::string_pad(this, &max_length, &fill_string, Placement::End, context)
} }
/// `String.prototype.padStart( targetLength [, padString] )` /// `String.prototype.padStart( targetLength [, padString] )`
@ -1512,7 +1521,7 @@ impl String {
let fill_string = args.get_or_undefined(1); let fill_string = args.get_or_undefined(1);
// 2. Return ? StringPad(O, maxLength, fillString, start). // 2. Return ? StringPad(O, maxLength, fillString, start).
Self::string_pad(this, max_length, fill_string, Placement::Start, context) Self::string_pad(this, &max_length, &fill_string, Placement::Start, context)
} }
/// String.prototype.trim() /// String.prototype.trim()
@ -1676,9 +1685,11 @@ impl String {
let int_start = args.get_or_undefined(0).to_integer_or_infinity(context)?; let int_start = args.get_or_undefined(0).to_integer_or_infinity(context)?;
// 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end). // 5. If end is undefined, let intEnd be len; else let intEnd be ? ToIntegerOrInfinity(end).
let int_end = match args.get_or_undefined(1) { let end = args.get_or_undefined(1);
&JsValue::Undefined => IntegerOrInfinity::Integer(len), let int_end = if end.is_undefined() {
end => end.to_integer_or_infinity(context)?, IntegerOrInfinity::Integer(len)
} else {
end.to_integer_or_infinity(context)?
}; };
// 6. Let finalStart be the result of clamping intStart between 0 and len. // 6. Let finalStart be the result of clamping intStart between 0 and len.
@ -1733,9 +1744,11 @@ impl String {
// 7. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length). // 7. If length is undefined, let intLength be size; otherwise let intLength be ? ToIntegerOrInfinity(length).
// Moved it before to ensure an error throws before returning the empty string on `match int_start` // Moved it before to ensure an error throws before returning the empty string on `match int_start`
let int_length = match args.get_or_undefined(1) { let length = args.get_or_undefined(1);
&JsValue::Undefined => IntegerOrInfinity::Integer(size), let int_length = if length.is_undefined() {
val => val.to_integer_or_infinity(context)?, IntegerOrInfinity::Integer(size)
} else {
length.to_integer_or_infinity(context)?
}; };
let int_start = match int_start { let int_start = match int_start {
@ -1798,7 +1811,7 @@ impl String {
// b. If splitter is not undefined, then // b. If splitter is not undefined, then
if let Some(splitter) = splitter { if let Some(splitter) = splitter {
// i. Return ? Call(splitter, separator, « O, limit »). // i. Return ? Call(splitter, separator, « O, limit »).
return splitter.call(separator, &[this.clone(), limit.clone()], context); return splitter.call(&separator, &[this.clone(), limit.clone()], context);
} }
} }
@ -1983,7 +1996,7 @@ impl String {
let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?; let matcher = regexp.get_method(WellKnownSymbols::match_all(), context)?;
// d. If matcher is not undefined, then // d. If matcher is not undefined, then
if let Some(matcher) = matcher { if let Some(matcher) = matcher {
return matcher.call(regexp, &[o.clone()], context); return matcher.call(&regexp, &[o.clone()], context);
} }
} }
@ -1991,7 +2004,7 @@ impl String {
let s = o.to_string(context)?; let s = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, "g"). // 4. Let rx be ? RegExpCreate(regexp, "g").
let rx = RegExp::create(regexp, &JsValue::new("g"), context)?; let rx = RegExp::create(&regexp, &JsValue::new("g"), context)?;
// 5. Return ? Invoke(rx, @@matchAll, « S »). // 5. Return ? Invoke(rx, @@matchAll, « S »).
rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context) rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context)
@ -2072,7 +2085,7 @@ impl String {
// b. If searcher is not undefined, then // b. If searcher is not undefined, then
if let Some(searcher) = searcher { if let Some(searcher) = searcher {
// i. Return ? Call(searcher, regexp, « O »). // i. Return ? Call(searcher, regexp, « O »).
return searcher.call(regexp, &[o.clone()], context); return searcher.call(&regexp, &[o.clone()], context);
} }
} }
@ -2080,7 +2093,7 @@ impl String {
let string = o.to_string(context)?; let string = o.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, undefined). // 4. Let rx be ? RegExpCreate(regexp, undefined).
let rx = RegExp::create(regexp, &JsValue::Undefined, context)?; let rx = RegExp::create(&regexp, &JsValue::undefined(), context)?;
// 5. Return ? Invoke(rx, @@search, « string »). // 5. Return ? Invoke(rx, @@search, « string »).
rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context) rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context)
@ -2193,7 +2206,7 @@ pub(crate) fn get_substitution(
result.push(second); result.push(second);
result.push(*third); result.push(*third);
} else if let Some(capture) = captures.get(nn - 1) { } else if let Some(capture) = captures.get(nn - 1) {
if let Some(s) = capture.as_string() { if let Some(ref s) = capture.as_string() {
result.push_str(s); result.push_str(s);
} }
} }
@ -2214,7 +2227,7 @@ pub(crate) fn get_substitution(
result.push('$'); result.push('$');
result.push(second); result.push(second);
} else if let Some(capture) = captures.get(n - 1) { } else if let Some(capture) = captures.get(n - 1) {
if let Some(s) = capture.as_string() { if let Some(ref s) = capture.as_string() {
result.push_str(s); result.push_str(s);
} }
} }
@ -2324,12 +2337,13 @@ fn split_match(s_str: &str, q: usize, r_str: &str) -> Option<usize> {
/// [spec]: https://tc39.es/ecma262/#sec-isregexp /// [spec]: https://tc39.es/ecma262/#sec-isregexp
fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult<bool> { fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult<bool> {
// 1. If Type(argument) is not Object, return false. // 1. If Type(argument) is not Object, return false.
let argument = match argument { let argument = if let Some(o) = argument.as_object() {
JsValue::Object(o) => o, o
_ => return Ok(false), } else {
return Ok(false);
}; };
is_reg_exp_object(argument, context) is_reg_exp_object(&argument, context)
} }
fn is_reg_exp_object(argument: &JsObject, context: &mut Context) -> JsResult<bool> { fn is_reg_exp_object(argument: &JsObject, context: &mut Context) -> JsResult<bool> {
// 2. Let matcher be ? Get(argument, @@match). // 2. Let matcher be ? Get(argument, @@match).

3
boa_engine/src/builtins/string/string_iterator.rs

@ -37,7 +37,8 @@ impl StringIterator {
} }
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut string_iterator = this.as_object().map(JsObject::borrow_mut); let string_iterator = this.as_object();
let mut string_iterator = string_iterator.as_deref().map(JsObject::borrow_mut);
let string_iterator = string_iterator let string_iterator = string_iterator
.as_mut() .as_mut()
.and_then(|obj| obj.as_string_iterator_mut()) .and_then(|obj| obj.as_string_iterator_mut())

8
boa_engine/src/builtins/symbol/mod.rs

@ -192,6 +192,8 @@ impl Symbol {
fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> { fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> {
value value
.as_symbol() .as_symbol()
.as_deref()
.cloned()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol()))
.ok_or_else(|| context.construct_type_error("'this' is not a Symbol")) .ok_or_else(|| context.construct_type_error("'this' is not a Symbol"))
} }
@ -236,7 +238,7 @@ impl Symbol {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. Return ? thisSymbolValue(this value). // 1. Return ? thisSymbolValue(this value).
let symbol = Self::this_symbol_value(this, context)?; let symbol = Self::this_symbol_value(this, context)?;
Ok(JsValue::Symbol(symbol)) Ok(JsValue::new(symbol))
} }
/// `get Symbol.prototype.description` /// `get Symbol.prototype.description`
@ -307,14 +309,14 @@ impl Symbol {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let sym = args.get_or_undefined(0); let sym = args.get_or_undefined(0);
// 1. If Type(sym) is not Symbol, throw a TypeError exception. // 1. If Type(sym) is not Symbol, throw a TypeError exception.
if let Some(sym) = sym.as_symbol() { if let Some(ref sym) = sym.as_symbol() {
// 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do
// a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]].
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym. // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
// 4. Return undefined. // 4. Return undefined.
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
let registry = registry.borrow(); let registry = registry.borrow();
registry.get_symbol(&sym) registry.get_symbol(sym)
}); });
Ok(symbol.map(JsValue::from).unwrap_or_default()) Ok(symbol.map(JsValue::from).unwrap_or_default())

97
boa_engine/src/builtins/typed_array/mod.rs

@ -26,7 +26,7 @@ use crate::{
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{IntegerOrInfinity, JsValue}, value::{IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult, JsString, Context, JsResult, JsString,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -161,7 +161,7 @@ macro_rules! typed_array {
// ii. If firstArgument has a [[TypedArrayName]] internal slot, then // ii. If firstArgument has a [[TypedArrayName]] internal slot, then
if first_argument.is_typed_array() { if first_argument.is_typed_array() {
// 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument).
TypedArray::initialize_from_typed_array(&o, first_argument, context)?; TypedArray::initialize_from_typed_array(&o, &first_argument, context)?;
} else if first_argument.is_array_buffer() { } else if first_argument.is_array_buffer() {
// iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then // iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then
@ -175,8 +175,8 @@ macro_rules! typed_array {
TypedArray::initialize_from_array_buffer( TypedArray::initialize_from_array_buffer(
&o, &o,
first_argument.clone(), first_argument.clone(),
byte_offset, &byte_offset,
length, &length,
context, context,
)?; )?;
} else { } else {
@ -407,7 +407,9 @@ impl TypedArray {
let mapping = match args.get(1) { let mapping = match args.get(1) {
// 3. If mapfn is undefined, let mapping be false. // 3. If mapfn is undefined, let mapping be false.
None | Some(JsValue::Undefined) => None, None => None,
Some(v) if v.is_undefined() => None,
// 4. Else, // 4. Else,
Some(v) => match v.as_object() { Some(v) => match v.as_object() {
// b. Let mapping be true. // b. Let mapping be true.
@ -429,11 +431,11 @@ impl TypedArray {
// 6. If usingIterator is not undefined, then // 6. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator { if let Some(using_iterator) = using_iterator {
// a. Let values be ? IterableToList(source, usingIterator). // a. Let values be ? IterableToList(source, usingIterator).
let values = iterable_to_list(context, source, Some(using_iterator.into()))?; let values = iterable_to_list(context, &source, Some(using_iterator.into()))?;
// b. Let len be the number of elements in values. // b. Let len be the number of elements in values.
// c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). // c. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let target_obj = Self::create(constructor, &[values.len().into()], context)?; let target_obj = Self::create(&constructor, &[values.len().into()], context)?;
// d. Let k be 0. // d. Let k be 0.
// e. Repeat, while k < len, // e. Repeat, while k < len,
@ -443,7 +445,7 @@ impl TypedArray {
// iii. If mapping is true, then // iii. If mapping is true, then
let mapped_value = if let Some(map_fn) = &mapping { let mapped_value = if let Some(map_fn) = &mapping {
// 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). // 1. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
map_fn.call(this_arg, &[k_value.clone(), k.into()], context)? map_fn.call(&this_arg, &[k_value.clone(), k.into()], context)?
} }
// iv. Else, let mappedValue be kValue. // iv. Else, let mappedValue be kValue.
else { else {
@ -469,7 +471,7 @@ impl TypedArray {
let len = array_like.length_of_array_like(context)?; let len = array_like.length_of_array_like(context)?;
// 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »). // 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let target_obj = Self::create(constructor, &[len.into()], context)?; let target_obj = Self::create(&constructor, &[len.into()], context)?;
// 11. Let k be 0. // 11. Let k be 0.
// 12. Repeat, while k < len, // 12. Repeat, while k < len,
@ -481,7 +483,7 @@ impl TypedArray {
// c. If mapping is true, then // c. If mapping is true, then
let mapped_value = if let Some(map_fn) = &mapping { let mapped_value = if let Some(map_fn) = &mapping {
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). // i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
map_fn.call(this_arg, &[k_value, k.into()], context)? map_fn.call(&this_arg, &[k_value, k.into()], context)?
} }
// d. Else, let mappedValue be kValue. // d. Else, let mappedValue be kValue.
else { else {
@ -515,7 +517,7 @@ impl TypedArray {
}; };
// 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »). // 4. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »).
let new_obj = Self::create(constructor, &[args.len().into()], context)?; let new_obj = Self::create(&constructor, &[args.len().into()], context)?;
// 5. Let k be 0. // 5. Let k be 0.
// 6. Repeat, while k < len, // 6. Repeat, while k < len,
@ -612,7 +614,7 @@ impl TypedArray {
// 5. Return buffer. // 5. Return buffer.
Ok(typed_array Ok(typed_array
.viewed_array_buffer() .viewed_array_buffer()
.map_or_else(JsValue::undefined, |buffer| buffer.clone().into())) .map_or(JsValue::undefined(), |buffer| buffer.clone().into()))
} }
/// `23.2.3.3 get %TypedArray%.prototype.byteLength` /// `23.2.3.3 get %TypedArray%.prototype.byteLength`
@ -906,7 +908,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -925,7 +928,7 @@ impl TypedArray {
// c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). // c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let test_result = callback_fn let test_result = callback_fn
.call( .call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value, k.into(), this.clone()], &[k_value, k.into(), this.clone()],
context, context,
)? )?
@ -1053,7 +1056,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -1078,7 +1082,7 @@ impl TypedArray {
// c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).# // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).#
let selected = callback_fn let selected = callback_fn
.call( .call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()], &[k_value.clone(), k.into(), this.clone()],
context, context,
)? )?
@ -1095,7 +1099,7 @@ impl TypedArray {
} }
// 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »).
let a = Self::species_create(obj, o.typed_array_name(), &[captured.into()], context)?; let a = Self::species_create(&obj, o.typed_array_name(), &[captured.into()], context)?;
// 10. Let n be 0. // 10. Let n be 0.
// 11. For each element e of kept, do // 11. For each element e of kept, do
@ -1138,7 +1142,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(predicate) is false, throw a TypeError exception. // 4. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = match args.get_or_undefined(0).as_object() { let predicate = args.get_or_undefined(0);
let predicate = match predicate.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -1158,7 +1163,7 @@ impl TypedArray {
// d. If testResult is true, return kValue. // d. If testResult is true, return kValue.
if predicate if predicate
.call( .call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()], &[k_value.clone(), k.into(), this.clone()],
context, context,
)? )?
@ -1200,8 +1205,9 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(predicate) is false, throw a TypeError exception. // 4. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0);
let predicate = let predicate =
match args.get_or_undefined(0).as_object() { match predicate.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => return context.throw_type_error( _ => return context.throw_type_error(
"TypedArray.prototype.findindex called with non-callable predicate function", "TypedArray.prototype.findindex called with non-callable predicate function",
@ -1219,7 +1225,7 @@ impl TypedArray {
// d. If testResult is true, return 𝔽(k). // d. If testResult is true, return 𝔽(k).
if predicate if predicate
.call( .call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()], &[k_value.clone(), k.into(), this.clone()],
context, context,
)? )?
@ -1261,7 +1267,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -1279,7 +1286,7 @@ impl TypedArray {
// c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
callback_fn.call( callback_fn.call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value, k.into(), this.clone()], &[k_value, k.into(), this.clone()],
context, context,
)?; )?;
@ -1354,7 +1361,7 @@ impl TypedArray {
let element_k = obj.get(k, context).expect("Get cannot fail here"); let element_k = obj.get(k, context).expect("Get cannot fail here");
// b. If SameValueZero(searchElement, elementK) is true, return true. // b. If SameValueZero(searchElement, elementK) is true, return true.
if JsValue::same_value_zero(args.get_or_undefined(0), &element_k) { if JsValue::same_value_zero(&args.get_or_undefined(0), &element_k) {
return Ok(true.into()); return Ok(true.into());
} }
@ -1680,7 +1687,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -1690,7 +1698,7 @@ impl TypedArray {
}; };
// 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »).
let a = Self::species_create(obj, o.typed_array_name(), &[len.into()], context)?; let a = Self::species_create(&obj, o.typed_array_name(), &[len.into()], context)?;
// 6. Let k be 0. // 6. Let k be 0.
// 7. Repeat, while k < len, // 7. Repeat, while k < len,
@ -1701,7 +1709,7 @@ impl TypedArray {
// c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value = callback_fn.call( let mapped_value = callback_fn.call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value, k.into(), this.clone()], &[k_value, k.into(), this.clone()],
context, context,
)?; )?;
@ -1742,7 +1750,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -1823,8 +1832,9 @@ impl TypedArray {
let len = o.array_length() as i64; let len = o.array_length() as i64;
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = args.get_or_undefined(0);
let callback_fn = let callback_fn =
match args.get_or_undefined(0).as_object() { match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => return context.throw_type_error( _ => return context.throw_type_error(
"TypedArray.prototype.reduceright called with non-callable callback function", "TypedArray.prototype.reduceright called with non-callable callback function",
@ -1974,16 +1984,16 @@ impl TypedArray {
} }
let source = args.get_or_undefined(0); let source = args.get_or_undefined(0);
match source { match source.variant() {
// 6. If source is an Object that has a [[TypedArrayName]] internal slot, then // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then
JsValue::Object(source) if source.is_typed_array() => { JsVariant::Object(source) if source.is_typed_array() => {
// a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
Self::set_typed_array_from_typed_array(target, target_offset, source, context)?; Self::set_typed_array_from_typed_array(&target, target_offset, &source, context)?;
} }
// 7. Else, // 7. Else,
_ => { _ => {
// a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source). // a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source).
Self::set_typed_array_from_array_like(target, target_offset, source, context)?; Self::set_typed_array_from_array_like(&target, target_offset, &source, context)?;
} }
} }
@ -2380,7 +2390,7 @@ impl TypedArray {
let count = std::cmp::max(r#final - k, 0) as u64; let count = std::cmp::max(r#final - k, 0) as u64;
// 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »).
let a = Self::species_create(obj, o.typed_array_name(), &[count.into()], context)?; let a = Self::species_create(&obj, o.typed_array_name(), &[count.into()], context)?;
let a_borrow = a.borrow(); let a_borrow = a.borrow();
let a_array = a_borrow let a_array = a_borrow
.as_typed_array() .as_typed_array()
@ -2517,7 +2527,8 @@ impl TypedArray {
let len = o.array_length(); let len = o.array_length();
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception. // 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = match args.get_or_undefined(0).as_object() { let callback_fn = args.get_or_undefined(0);
let callback_fn = match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj, Some(obj) if obj.is_callable() => obj,
_ => { _ => {
return context.throw_type_error( return context.throw_type_error(
@ -2537,7 +2548,7 @@ impl TypedArray {
// d. If testResult is true, return true. // d. If testResult is true, return true.
if callback_fn if callback_fn
.call( .call(
args.get_or_undefined(1), &args.get_or_undefined(1),
&[k_value, k.into(), this.clone()], &[k_value, k.into(), this.clone()],
context, context,
)? )?
@ -2563,9 +2574,9 @@ impl TypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let compare_fn = match args.get(0) { let compare_fn = match args.get(0).map(JsValue::variant) {
None | Some(JsValue::Undefined) => None, None | Some(JsVariant::Undefined) => None,
Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj), Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj),
_ => { _ => {
return context return context
.throw_type_error("TypedArray.sort called with non-callable comparefn") .throw_type_error("TypedArray.sort called with non-callable comparefn")
@ -2649,7 +2660,7 @@ impl TypedArray {
return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal)); return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal));
} }
if let (JsValue::BigInt(x), JsValue::BigInt(y)) = (x, y) { if let (Some(x), Some(y)) = (x.as_bigint(), y.as_bigint()) {
// 6. If x < y, return -1𝔽. // 6. If x < y, return -1𝔽.
if x < y { if x < y {
return Ok(Ordering::Less); return Ok(Ordering::Less);
@ -2734,7 +2745,7 @@ impl TypedArray {
let mut sort_err = Ok(()); let mut sort_err = Ok(());
items.sort_by(|x, y| { items.sort_by(|x, y| {
if sort_err.is_ok() { if sort_err.is_ok() {
sort_compare(x, y, compare_fn, context).unwrap_or_else(|err| { sort_compare(x, y, compare_fn.as_deref(), context).unwrap_or_else(|err| {
sort_err = Err(err); sort_err = Err(err);
Ordering::Equal Ordering::Equal
}) })
@ -2838,7 +2849,7 @@ impl TypedArray {
// 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». // 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
// 20. Return ? TypedArraySpeciesCreate(O, argumentsList). // 20. Return ? TypedArraySpeciesCreate(O, argumentsList).
Ok(Self::species_create( Ok(Self::species_create(
obj, &obj,
o.typed_array_name(), o.typed_array_name(),
&[ &[
buffer.clone().into(), buffer.clone().into(),
@ -2901,7 +2912,7 @@ impl TypedArray {
.as_typed_array() .as_typed_array()
.map(|o| o.typed_array_name().name().into()) .map(|o| o.typed_array_name().name().into())
}) })
.unwrap_or(JsValue::Undefined)) .unwrap_or(JsValue::undefined()))
} }
/// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )` /// `23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList )`

10
boa_engine/src/class.rs

@ -111,7 +111,7 @@ impl<T: Class> ClassConstructor for T {
} }
let class_constructor = context.global_object().clone().get(T::NAME, context)?; let class_constructor = context.global_object().clone().get(T::NAME, context)?;
let class_constructor = if let JsValue::Object(ref obj) = class_constructor { let class_constructor = if let Some(obj) = class_constructor.as_object() {
obj obj
} else { } else {
return context.throw_type_error(format!( return context.throw_type_error(format!(
@ -120,8 +120,8 @@ impl<T: Class> ClassConstructor for T {
)); ));
}; };
let class_prototype = let class_prototype =
if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? { if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() {
obj.clone() object.clone()
} else { } else {
return context.throw_type_error(format!( return context.throw_type_error(format!(
"invalid default prototype for native class `{}`", "invalid default prototype for native class `{}`",
@ -131,10 +131,10 @@ impl<T: Class> ClassConstructor for T {
let prototype = this let prototype = this
.as_object() .as_object()
.cloned() .as_deref()
.map(|obj| { .map(|obj| {
obj.get(PROTOTYPE, context) obj.get(PROTOTYPE, context)
.map(|val| val.as_object().cloned()) .map(|val| val.as_object().as_deref().cloned())
}) })
.transpose()? .transpose()?
.flatten() .flatten()

2
boa_engine/src/context/mod.rs

@ -743,7 +743,7 @@ impl Context {
/// Runs all the jobs in the job queue. /// Runs all the jobs in the job queue.
fn run_queued_jobs(&mut self) -> JsResult<()> { fn run_queued_jobs(&mut self) -> JsResult<()> {
while let Some(job) = self.promise_job_queue.pop_front() { while let Some(job) = self.promise_job_queue.pop_front() {
job.call_job_callback(&JsValue::Undefined, &[], self)?; job.call_job_callback(&JsValue::undefined(), &[], self)?;
} }
Ok(()) Ok(())
} }

2
boa_engine/src/environments/compile.rs

@ -300,7 +300,7 @@ impl Context {
self.global_bindings_mut().insert( self.global_bindings_mut().insert(
name_str, name_str,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(JsValue::Undefined) .value(JsValue::undefined())
.writable(true) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(true) .configurable(true)

2
boa_engine/src/environments/runtime.rs

@ -402,7 +402,7 @@ impl DeclarativeEnvironmentStack {
let this = if let Some(this) = this { let this = if let Some(this) = this {
this this
} else { } else {
JsValue::Null JsValue::null()
}; };
self.stack.push(Gc::new(DeclarativeEnvironment { self.stack.push(Gc::new(DeclarativeEnvironment {

7
boa_engine/src/lib.rs

@ -5,6 +5,8 @@
//! - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree). //! - **serde** - Enables serialization and deserialization of the AST (Abstract Syntax Tree).
//! - **console** - Enables `boa`'s [WHATWG `console`][whatwg] object implementation. //! - **console** - Enables `boa`'s [WHATWG `console`][whatwg] object implementation.
//! - **profiler** - Enables profiling with measureme (this is mostly internal). //! - **profiler** - Enables profiling with measureme (this is mostly internal).
//! - **nan_boxing** - Enables `boa`'s nan-boxed [`JsValue`] implementation
//! (Only available on x86_64 platforms, does nothing on incompatible platforms).
//! - **intl** - Enables `boa`'s [ECMA-402 Internationalization API][ecma-402] (`Intl` object) //! - **intl** - Enables `boa`'s [ECMA-402 Internationalization API][ecma-402] (`Intl` object)
//! //!
//! [whatwg]: https://console.spec.whatwg.org //! [whatwg]: https://console.spec.whatwg.org
@ -70,11 +72,11 @@
// Ignore because `write!(string, ...)` instead of `string.push_str(&format!(...))` can fail. // Ignore because `write!(string, ...)` instead of `string.push_str(&format!(...))` can fail.
// We only use it in `ToInternedString` where performance is not an issue. // We only use it in `ToInternedString` where performance is not an issue.
clippy::format_push_string, clippy::format_push_string,
// TODO deny once false positive are fixed (https://github.com/rust-lang/rust-clippy/issues/9076).
clippy::trait_duplication_in_bounds,
rustdoc::missing_doc_code_examples rustdoc::missing_doc_code_examples
)] )]
pub mod value; // In the top to give priority to `JsValue` docs
pub mod bigint; pub mod bigint;
pub mod builtins; pub mod builtins;
pub mod bytecompiler; pub mod bytecompiler;
@ -88,7 +90,6 @@ pub mod realm;
pub mod string; pub mod string;
pub mod symbol; pub mod symbol;
pub mod syntax; pub mod syntax;
pub mod value;
pub mod vm; pub mod vm;
#[cfg(test)] #[cfg(test)]

26
boa_engine/src/object/internal_methods/proxy.rs

@ -2,7 +2,7 @@ use crate::{
builtins::{array, object::Object}, builtins::{array, object::Object},
object::{InternalObjectMethods, JsObject, JsPrototype}, object::{InternalObjectMethods, JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
value::Type, value::{JsVariant, Type},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@ -76,9 +76,9 @@ pub(crate) fn proxy_exotic_get_prototype_of(
let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?;
// 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.
let handler_proto = match &handler_proto { let handler_proto = match handler_proto.variant() {
JsValue::Object(obj) => Some(obj.clone()), JsVariant::Object(obj) => Some(obj.clone()),
JsValue::Null => None, JsVariant::Null => None,
_ => return context.throw_type_error("Proxy trap result is neither object nor null"), _ => return context.throw_type_error("Proxy trap result is neither object nor null"),
}; };
@ -138,7 +138,7 @@ pub(crate) fn proxy_exotic_set_prototype_of(
&handler.into(), &handler.into(),
&[ &[
target.clone().into(), target.clone().into(),
val.clone().map_or(JsValue::Null, Into::into), val.clone().map_or(JsValue::null(), Into::into),
], ],
context, context,
)? )?
@ -679,7 +679,11 @@ pub(crate) fn proxy_exotic_set(
if target_desc.is_accessor_descriptor() { if target_desc.is_accessor_descriptor() {
// i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception.
match target_desc.set() { match target_desc.set() {
None | Some(&JsValue::Undefined) => { None => {
return context
.throw_type_error("Proxy trap set unexpected accessor descriptor");
}
Some(v) if v.is_undefined() => {
return context return context
.throw_type_error("Proxy trap set unexpected accessor descriptor"); .throw_type_error("Proxy trap set unexpected accessor descriptor");
} }
@ -800,8 +804,8 @@ pub(crate) fn proxy_exotic_own_property_keys(
let mut unchecked_result_keys: FxHashSet<PropertyKey> = FxHashSet::default(); let mut unchecked_result_keys: FxHashSet<PropertyKey> = FxHashSet::default();
let mut trap_result = Vec::new(); let mut trap_result = Vec::new();
for value in &trap_result_raw { for value in &trap_result_raw {
match value { match value.variant() {
JsValue::String(s) => { JsVariant::String(s) => {
if !unchecked_result_keys.insert(s.clone().into()) { if !unchecked_result_keys.insert(s.clone().into()) {
return context.throw_type_error( return context.throw_type_error(
"Proxy trap result contains duplicate string property keys", "Proxy trap result contains duplicate string property keys",
@ -809,7 +813,7 @@ pub(crate) fn proxy_exotic_own_property_keys(
} }
trap_result.push(s.clone().into()); trap_result.push(s.clone().into());
} }
JsValue::Symbol(s) => { JsVariant::Symbol(s) => {
if !unchecked_result_keys.insert(s.clone().into()) { if !unchecked_result_keys.insert(s.clone().into()) {
return context.throw_type_error( return context.throw_type_error(
"Proxy trap result contains duplicate symbol property keys", "Proxy trap result contains duplicate symbol property keys",
@ -984,10 +988,10 @@ fn proxy_exotic_construct(
)?; )?;
// 10. If Type(newObj) is not Object, throw a TypeError exception. // 10. If Type(newObj) is not Object, throw a TypeError exception.
let new_obj = new_obj.as_object().cloned().ok_or_else(|| { let new_obj = new_obj.as_object().ok_or_else(|| {
context.construct_type_error("Proxy trap constructor returned non-object value") context.construct_type_error("Proxy trap constructor returned non-object value")
})?; })?;
// 11. Return newObj. // 11. Return newObj.
Ok(new_obj) Ok(new_obj.clone())
} }

5
boa_engine/src/object/jsarray.rs

@ -109,6 +109,7 @@ impl JsArray {
pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> { pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> {
let object = Array::concat(&self.inner.clone().into(), items, context)? let object = Array::concat(&self.inner.clone().into(), items, context)?
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.expect("Array.prototype.filter should always return object"); .expect("Array.prototype.filter should always return object");
@ -124,6 +125,7 @@ impl JsArray {
) )
.map(|x| { .map(|x| {
x.as_string() x.as_string()
.as_deref()
.cloned() .cloned()
.expect("Array.prototype.join always returns string") .expect("Array.prototype.join always returns string")
}) })
@ -231,6 +233,7 @@ impl JsArray {
context, context,
)? )?
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.expect("Array.prototype.filter should always return object"); .expect("Array.prototype.filter should always return object");
@ -250,6 +253,7 @@ impl JsArray {
context, context,
)? )?
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.expect("Array.prototype.map should always return object"); .expect("Array.prototype.map should always return object");
@ -316,6 +320,7 @@ impl JsArray {
context, context,
)? )?
.as_object() .as_object()
.as_deref()
.cloned() .cloned()
.expect("Array.prototype.slice should always return object"); .expect("Array.prototype.slice should always return object");

2
boa_engine/src/object/jsarraybuffer.rs

@ -69,7 +69,7 @@ impl JsArrayBuffer {
obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer { obj.borrow_mut().data = ObjectData::array_buffer(ArrayBuffer {
array_buffer_data: Some(block), array_buffer_data: Some(block),
array_buffer_byte_length: byte_length as u64, array_buffer_byte_length: byte_length as u64,
array_buffer_detach_key: JsValue::Undefined, array_buffer_detach_key: JsValue::undefined(),
}); });
Ok(Self { inner: obj }) Ok(Self { inner: obj })

13
boa_engine/src/object/jsobject.rs

@ -6,7 +6,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap};
use crate::{ use crate::{
object::{ObjectData, ObjectKind}, object::{ObjectData, ObjectKind},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
value::PreferredType, value::{PointerType, PreferredType},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_gc::{self, Finalize, Gc, Trace}; use boa_gc::{self, Finalize, Gc, Trace};
@ -16,6 +16,7 @@ use std::{
collections::HashMap, collections::HashMap,
error::Error, error::Error,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
mem::{self, ManuallyDrop},
result::Result as StdResult, result::Result as StdResult,
}; };
@ -31,6 +32,16 @@ pub struct JsObject {
inner: Gc<boa_gc::Cell<Object>>, inner: Gc<boa_gc::Cell<Object>>,
} }
unsafe impl PointerType for JsObject {
unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop<Self> {
ManuallyDrop::new(mem::transmute(ptr))
}
unsafe fn into_void_ptr(object: ManuallyDrop<Self>) -> *mut () {
mem::transmute(object)
}
}
impl JsObject { impl JsObject {
/// Create a new `JsObject` from an internal `Object`. /// Create a new `JsObject` from an internal `Object`.
#[inline] #[inline]

20
boa_engine/src/object/jsset.rs

@ -5,7 +5,7 @@ use boa_gc::{Finalize, Trace};
use crate::{ use crate::{
builtins::Set, builtins::Set,
object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, object::{JsFunction, JsObject, JsObjectType, JsSetIterator},
Context, JsResult, JsValue, Context, JsResult, JsValue, value::JsVariant,
}; };
// This is an wrapper for `JsSet` // This is an wrapper for `JsSet`
@ -61,7 +61,7 @@ impl JsSet {
/// Same as JavaScript's `set.clear()`. /// Same as JavaScript's `set.clear()`.
#[inline] #[inline]
pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> { pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> {
Set::clear(&self.inner.clone().into(), &[JsValue::Null], context) Set::clear(&self.inner.clone().into(), &[JsValue::null()], context)
} }
/// Removes the element associated to the value. /// Removes the element associated to the value.
@ -74,9 +74,9 @@ impl JsSet {
where where
T: Into<JsValue>, T: Into<JsValue>,
{ {
match Set::delete(&self.inner.clone().into(), &[value.into()], context)? { match Set::delete(&self.inner.clone().into(), &[value.into()], context)?.variant() {
JsValue::Boolean(bool) => Ok(bool), JsVariant::Boolean(bool) => Ok(bool),
_ => Err(JsValue::Undefined), _ => Err(JsValue::undefined()),
} }
} }
@ -89,9 +89,9 @@ impl JsSet {
where where
T: Into<JsValue>, T: Into<JsValue>,
{ {
match Set::has(&self.inner.clone().into(), &[value.into()], context)? { match Set::has(&self.inner.clone().into(), &[value.into()], context)?.variant() {
JsValue::Boolean(bool) => Ok(bool), JsVariant::Boolean(bool) => Ok(bool),
_ => Err(JsValue::Undefined), _ => Err(JsValue::undefined()),
} }
} }
@ -101,7 +101,7 @@ impl JsSet {
/// Same as JavaScript's `set.values()`. /// Same as JavaScript's `set.values()`.
#[inline] #[inline]
pub fn values(&self, context: &mut Context) -> JsResult<JsSetIterator> { pub fn values(&self, context: &mut Context) -> JsResult<JsSetIterator> {
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)?
.get_iterator(context, None, None)?; .get_iterator(context, None, None)?;
JsSetIterator::from_object(iterator_object.iterator().clone(), context) JsSetIterator::from_object(iterator_object.iterator().clone(), context)
@ -114,7 +114,7 @@ impl JsSet {
/// Same as JavaScript's `set.keys()`. /// Same as JavaScript's `set.keys()`.
#[inline] #[inline]
pub fn keys(&self, context: &mut Context) -> JsResult<JsSetIterator> { pub fn keys(&self, context: &mut Context) -> JsResult<JsSetIterator> {
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::null()], context)?
.get_iterator(context, None, None)?; .get_iterator(context, None, None)?;
JsSetIterator::from_object(iterator_object.iterator().clone(), context) JsSetIterator::from_object(iterator_object.iterator().clone(), context)

2
boa_engine/src/object/jsset_iterator.rs

@ -26,7 +26,7 @@ impl JsSetIterator {
} }
/// Advances the `JsSetIterator` and gets the next result in the `JsSet`. /// Advances the `JsSetIterator` and gets the next result in the `JsSet`.
pub fn next(&self, context: &mut Context) -> JsResult<JsValue> { pub fn next(&self, context: &mut Context) -> JsResult<JsValue> {
SetIterator::next(&self.inner.clone().into(), &[JsValue::Null], context) SetIterator::next(&self.inner.clone().into(), &[JsValue::null()], context)
} }
} }

87
boa_engine/src/object/jstypedarray.rs

@ -10,7 +10,7 @@ use std::ops::Deref;
/// JavaScript `TypedArray` rust object. /// JavaScript `TypedArray` rust object.
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone, Trace, Finalize)]
pub struct JsTypedArray { pub struct JsTypedArray {
inner: JsValue, inner: JsObject,
} }
impl JsTypedArray { impl JsTypedArray {
@ -20,9 +20,7 @@ impl JsTypedArray {
#[inline] #[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_typed_array() { if object.borrow().is_typed_array() {
Ok(Self { Ok(Self { inner: object })
inner: object.into(),
})
} else { } else {
context.throw_type_error("object is not a TypedArray") context.throw_type_error("object is not a TypedArray")
} }
@ -33,7 +31,7 @@ impl JsTypedArray {
/// Same a `array.length` in JavaScript. /// Same a `array.length` in JavaScript.
#[inline] #[inline]
pub fn length(&self, context: &mut Context) -> JsResult<usize> { pub fn length(&self, context: &mut Context) -> JsResult<usize> {
Ok(TypedArray::length(&self.inner, &[], context)? Ok(TypedArray::length(&self.clone().into(), &[], context)?
.as_number() .as_number()
.map(|x| x as usize) .map(|x| x as usize)
.expect("length should return a number")) .expect("length should return a number"))
@ -50,12 +48,12 @@ impl JsTypedArray {
where where
T: Into<i64>, T: Into<i64>,
{ {
TypedArray::at(&self.inner, &[index.into().into()], context) TypedArray::at(&self.clone().into(), &[index.into().into()], context)
} }
#[inline] #[inline]
pub fn byte_length(&self, context: &mut Context) -> JsResult<usize> { pub fn byte_length(&self, context: &mut Context) -> JsResult<usize> {
Ok(TypedArray::byte_length(&self.inner, &[], context)? Ok(TypedArray::byte_length(&self.clone().into(), &[], context)?
.as_number() .as_number()
.map(|x| x as usize) .map(|x| x as usize)
.expect("byteLength should return a number")) .expect("byteLength should return a number"))
@ -63,7 +61,7 @@ impl JsTypedArray {
#[inline] #[inline]
pub fn byte_offset(&self, context: &mut Context) -> JsResult<usize> { pub fn byte_offset(&self, context: &mut Context) -> JsResult<usize> {
Ok(TypedArray::byte_offset(&self.inner, &[], context)? Ok(TypedArray::byte_offset(&self.clone().into(), &[], context)?
.as_number() .as_number()
.map(|x| x as usize) .map(|x| x as usize)
.expect("byteLength should return a number")) .expect("byteLength should return a number"))
@ -81,7 +79,7 @@ impl JsTypedArray {
T: Into<JsValue>, T: Into<JsValue>,
{ {
TypedArray::fill( TypedArray::fill(
&self.inner, &self.clone().into(),
&[ &[
value.into(), value.into(),
start.into_or_undefined(), start.into_or_undefined(),
@ -99,7 +97,7 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<bool> { ) -> JsResult<bool> {
let result = TypedArray::every( let result = TypedArray::every(
&self.inner, &self.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()], &[predicate.into(), this_arg.into_or_undefined()],
context, context,
)? )?
@ -117,7 +115,7 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<bool> { ) -> JsResult<bool> {
let result = TypedArray::some( let result = TypedArray::some(
&self.inner, &self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()], &[callback.into(), this_arg.into_or_undefined()],
context, context,
)? )?
@ -129,7 +127,11 @@ impl JsTypedArray {
#[inline] #[inline]
pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> { pub fn sort(&self, compare_fn: Option<JsFunction>, context: &mut Context) -> JsResult<Self> {
TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; TypedArray::sort(
&self.clone().into(),
&[compare_fn.into_or_undefined()],
context,
)?;
Ok(self.clone()) Ok(self.clone())
} }
@ -142,10 +144,14 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<Self> { ) -> JsResult<Self> {
let object = TypedArray::filter( let object = TypedArray::filter(
&self.inner, &self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()], &[callback.into(), this_arg.into_or_undefined()],
context, context,
)?; )?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object }) Ok(Self { inner: object })
} }
@ -158,10 +164,14 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<Self> { ) -> JsResult<Self> {
let object = TypedArray::map( let object = TypedArray::map(
&self.inner, &self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()], &[callback.into(), this_arg.into_or_undefined()],
context, context,
)?; )?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object }) Ok(Self { inner: object })
} }
@ -174,7 +184,7 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
TypedArray::reduce( TypedArray::reduce(
&self.inner, &self.clone().into(),
&[callback.into(), initial_value.into_or_undefined()], &[callback.into(), initial_value.into_or_undefined()],
context, context,
) )
@ -188,7 +198,7 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
TypedArray::reduceright( TypedArray::reduceright(
&self.inner, &self.clone().into(),
&[callback.into(), initial_value.into_or_undefined()], &[callback.into(), initial_value.into_or_undefined()],
context, context,
) )
@ -196,7 +206,7 @@ impl JsTypedArray {
#[inline] #[inline]
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> { pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
TypedArray::reverse(&self.inner, &[], context)?; TypedArray::reverse(&self.clone().into(), &[], context)?;
Ok(self.clone()) Ok(self.clone())
} }
@ -208,10 +218,14 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<Self> { ) -> JsResult<Self> {
let object = TypedArray::slice( let object = TypedArray::slice(
&self.inner, &self.clone().into(),
&[start.into_or_undefined(), end.into_or_undefined()], &[start.into_or_undefined(), end.into_or_undefined()],
context, context,
)?; )?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object }) Ok(Self { inner: object })
} }
@ -224,7 +238,7 @@ impl JsTypedArray {
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
TypedArray::find( TypedArray::find(
&self.inner, &self.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()], &[predicate.into(), this_arg.into_or_undefined()],
context, context,
) )
@ -241,7 +255,7 @@ impl JsTypedArray {
T: Into<JsValue>, T: Into<JsValue>,
{ {
let index = TypedArray::index_of( let index = TypedArray::index_of(
&self.inner, &self.clone().into(),
&[search_element.into(), from_index.into_or_undefined()], &[search_element.into(), from_index.into_or_undefined()],
context, context,
)? )?
@ -267,7 +281,7 @@ impl JsTypedArray {
T: Into<JsValue>, T: Into<JsValue>,
{ {
let index = TypedArray::last_index_of( let index = TypedArray::last_index_of(
&self.inner, &self.clone().into(),
&[search_element.into(), from_index.into_or_undefined()], &[search_element.into(), from_index.into_or_undefined()],
context, context,
)? )?
@ -284,8 +298,14 @@ impl JsTypedArray {
#[inline] #[inline]
pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> { pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> {
TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { TypedArray::join(
&self.clone().into(),
&[separator.into_or_undefined()],
context,
)
.map(|x| {
x.as_string() x.as_string()
.as_deref()
.cloned() .cloned()
.expect("TypedArray.prototype.join always returns string") .expect("TypedArray.prototype.join always returns string")
}) })
@ -295,17 +315,14 @@ impl JsTypedArray {
impl From<JsTypedArray> for JsObject { impl From<JsTypedArray> for JsObject {
#[inline] #[inline]
fn from(o: JsTypedArray) -> Self { fn from(o: JsTypedArray) -> Self {
o.inner o.inner.clone()
.as_object()
.expect("should always be an object")
.clone()
} }
} }
impl From<JsTypedArray> for JsValue { impl From<JsTypedArray> for JsValue {
#[inline] #[inline]
fn from(o: JsTypedArray) -> Self { fn from(o: JsTypedArray) -> Self {
o.inner.clone() o.inner.clone().into()
} }
} }
@ -314,7 +331,7 @@ impl Deref for JsTypedArray {
#[inline] #[inline]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.inner.as_object().expect("should always be an object") &self.inner
} }
} }
@ -392,18 +409,14 @@ macro_rules! JsTypedArrayType {
impl From<$name> for JsObject { impl From<$name> for JsObject {
#[inline] #[inline]
fn from(o: $name) -> Self { fn from(o: $name) -> Self {
o.inner o.inner.inner.clone()
.inner
.as_object()
.expect("should always be an object")
.clone()
} }
} }
impl From<$name> for JsValue { impl From<$name> for JsValue {
#[inline] #[inline]
fn from(o: $name) -> Self { fn from(o: $name) -> Self {
o.inner.inner.clone() o.inner.inner.clone().into()
} }
} }

13
boa_engine/src/object/operations.rs

@ -4,7 +4,7 @@ use crate::{
object::JsObject, object::JsObject,
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::Type, value::{JsVariant, Type},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -594,11 +594,14 @@ impl JsObject {
// 1. Assert: IsPropertyKey(P) is true. // 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P). // 2. Let func be ? GetV(V, P).
match &self.__get__(&key.into(), self.clone().into(), context)? { match self
.__get__(&key.into(), self.clone().into(), context)?
.variant()
{
// 3. If func is either undefined or null, return undefined. // 3. If func is either undefined or null, return undefined.
JsValue::Undefined | JsValue::Null => Ok(None), JsVariant::Undefined | JsVariant::Null => Ok(None),
// 5. Return func. // 5. Return func.
JsValue::Object(obj) if obj.is_callable() => Ok(Some(obj.clone())), JsVariant::Object(object) if object.is_callable() => Ok(Some(object.clone())),
// 4. If IsCallable(func) is false, throw a TypeError exception. // 4. If IsCallable(func) is false, throw a TypeError exception.
_ => { _ => {
context.throw_type_error("value returned for property of object is not a function") context.throw_type_error("value returned for property of object is not a function")
@ -833,7 +836,7 @@ impl JsValue {
}; };
// c. If SameValue(P, O) is true, return true. // c. If SameValue(P, O) is true, return true.
if JsObject::equals(&object, prototype) { if JsObject::equals(&object, &prototype) {
return Ok(true); return Ok(true);
} }
} }

19
boa_engine/src/string.rs

@ -1,4 +1,4 @@
use crate::builtins::string::is_trimmable_whitespace; use crate::{builtins::string::is_trimmable_whitespace, value::PointerType};
use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use boa_gc::{unsafe_empty_trace, Finalize, Trace};
use rustc_hash::{FxHashMap, FxHasher}; use rustc_hash::{FxHashMap, FxHasher};
use std::{ use std::{
@ -8,6 +8,7 @@ use std::{
hash::BuildHasherDefault, hash::BuildHasherDefault,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
marker::PhantomData, marker::PhantomData,
mem::ManuallyDrop,
ops::Deref, ops::Deref,
ptr::{copy_nonoverlapping, NonNull}, ptr::{copy_nonoverlapping, NonNull},
rc::Rc, rc::Rc,
@ -635,12 +636,26 @@ pub struct JsString {
_marker: PhantomData<Rc<str>>, _marker: PhantomData<Rc<str>>,
} }
unsafe impl PointerType for JsString {
unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop<Self> {
let string = Self {
inner: TaggedInner(NonNull::new_unchecked(ptr.cast())),
_marker: PhantomData,
};
ManuallyDrop::new(string)
}
unsafe fn into_void_ptr(string: ManuallyDrop<Self>) -> *mut () {
string.inner.0.as_ptr().cast()
}
}
// Safety: JsString does not contain any objects which needs to be traced, // Safety: JsString does not contain any objects which needs to be traced,
// so this is safe. // so this is safe.
unsafe impl Trace for JsString { unsafe impl Trace for JsString {
unsafe_empty_trace!(); unsafe_empty_trace!();
} }
/// This struct uses a technique called tagged pointer to benefit from the fact that newly /// This struct uses a technique called tagged pointer to benefit from the fact that newly
/// allocated pointers are always word aligned on 64-bits platforms, making it impossible /// allocated pointers are always word aligned on 64-bits platforms, making it impossible
/// to have a LSB equal to 1. More details about this technique on the article of Wikipedia /// to have a LSB equal to 1. More details about this technique on the article of Wikipedia

14
boa_engine/src/symbol.rs

@ -15,12 +15,13 @@
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value //! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
use crate::JsString; use crate::{value::PointerType, JsString};
use boa_gc::{unsafe_empty_trace, Finalize, Trace}; use boa_gc::{unsafe_empty_trace, Finalize, Trace};
use std::{ use std::{
cell::Cell, cell::Cell,
fmt::{self, Display}, fmt::{self, Display},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
mem::{self, ManuallyDrop},
rc::Rc, rc::Rc,
}; };
@ -253,12 +254,23 @@ pub struct JsSymbol {
inner: Rc<Inner>, inner: Rc<Inner>,
} }
unsafe impl PointerType for JsSymbol {
unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop<Self> {
ManuallyDrop::new(mem::transmute(ptr))
}
unsafe fn into_void_ptr(symbol: ManuallyDrop<Self>) -> *mut () {
mem::transmute(symbol)
}
}
// Safety: JsSymbol does not contain any objects which needs to be traced, // Safety: JsSymbol does not contain any objects which needs to be traced,
// so this is safe. // so this is safe.
unsafe impl Trace for JsSymbol { unsafe impl Trace for JsSymbol {
unsafe_empty_trace!(); unsafe_empty_trace!();
} }
impl JsSymbol { impl JsSymbol {
/// Create a new symbol. /// Create a new symbol.
#[inline] #[inline]

3
boa_engine/src/tests.rs

@ -874,6 +874,7 @@ mod in_operator {
.get("prototype", &mut context) .get("prototype", &mut context)
.unwrap() .unwrap()
.as_object() .as_object()
.as_deref()
.cloned()) .cloned())
); );
} }
@ -1057,7 +1058,7 @@ fn to_integer_or_infinity() {
fn to_length() { fn to_length() {
let mut context = Context::default(); let mut context = Context::default();
assert_eq!(JsValue::new(f64::NAN).to_length(&mut context).unwrap(), 0); assert_eq!(JsValue::nan().to_length(&mut context).unwrap(), 0);
assert_eq!( assert_eq!(
JsValue::new(f64::NEG_INFINITY) JsValue::new(f64::NEG_INFINITY)
.to_length(&mut context) .to_length(&mut context)

76
boa_engine/src/value/conversions.rs

@ -1,4 +1,8 @@
use super::{Display, JsBigInt, JsObject, JsString, JsSymbol, JsValue, Profiler}; use boa_profiler::Profiler;
use crate::{object::JsObject, JsBigInt, JsString, JsSymbol};
use super::{Display, JsValue};
impl From<&Self> for JsValue { impl From<&Self> for JsValue {
#[inline] #[inline]
@ -15,7 +19,7 @@ where
fn from(value: T) -> Self { fn from(value: T) -> Self {
let _timer = Profiler::global().start_event("From<String>", "value"); let _timer = Profiler::global().start_event("From<String>", "value");
Self::String(value.into()) Self::string(value.into())
} }
} }
@ -29,16 +33,7 @@ impl From<char> for JsValue {
impl From<JsSymbol> for JsValue { impl From<JsSymbol> for JsValue {
#[inline] #[inline]
fn from(value: JsSymbol) -> Self { fn from(value: JsSymbol) -> Self {
Self::Symbol(value) Self::symbol(value)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TryFromCharError;
impl Display for TryFromCharError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not convert value to a char type")
} }
} }
@ -49,7 +44,7 @@ impl From<f32> for JsValue {
// if value as i32 as f64 == value { // if value as i32 as f64 == value {
// Self::Integer(value as i32) // Self::Integer(value as i32)
// } else { // } else {
Self::Rational(value.into()) Self::float64(value.into())
// } // }
} }
} }
@ -61,7 +56,7 @@ impl From<f64> for JsValue {
// if value as i32 as f64 == value { // if value as i32 as f64 == value {
// Self::Integer(value as i32) // Self::Integer(value as i32)
// } else { // } else {
Self::Rational(value) Self::float64(value)
// } // }
} }
} }
@ -69,28 +64,28 @@ impl From<f64> for JsValue {
impl From<u8> for JsValue { impl From<u8> for JsValue {
#[inline] #[inline]
fn from(value: u8) -> Self { fn from(value: u8) -> Self {
Self::Integer(value.into()) Self::integer32(value.into())
} }
} }
impl From<i8> for JsValue { impl From<i8> for JsValue {
#[inline] #[inline]
fn from(value: i8) -> Self { fn from(value: i8) -> Self {
Self::Integer(value.into()) Self::integer32(value.into())
} }
} }
impl From<u16> for JsValue { impl From<u16> for JsValue {
#[inline] #[inline]
fn from(value: u16) -> Self { fn from(value: u16) -> Self {
Self::Integer(value.into()) Self::integer32(value.into())
} }
} }
impl From<i16> for JsValue { impl From<i16> for JsValue {
#[inline] #[inline]
fn from(value: i16) -> Self { fn from(value: i16) -> Self {
Self::Integer(value.into()) Self::integer32(value.into())
} }
} }
@ -98,9 +93,9 @@ impl From<u32> for JsValue {
#[inline] #[inline]
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
if let Ok(integer) = i32::try_from(value) { if let Ok(integer) = i32::try_from(value) {
Self::Integer(integer) Self::integer32(integer)
} else { } else {
Self::Rational(value.into()) Self::float64(value.into())
} }
} }
} }
@ -108,14 +103,14 @@ impl From<u32> for JsValue {
impl From<i32> for JsValue { impl From<i32> for JsValue {
#[inline] #[inline]
fn from(value: i32) -> Self { fn from(value: i32) -> Self {
Self::Integer(value) Self::integer32(value)
} }
} }
impl From<JsBigInt> for JsValue { impl From<JsBigInt> for JsValue {
#[inline] #[inline]
fn from(value: JsBigInt) -> Self { fn from(value: JsBigInt) -> Self {
Self::BigInt(value) Self::bigint(value)
} }
} }
@ -123,9 +118,9 @@ impl From<usize> for JsValue {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
if let Ok(value) = i32::try_from(value) { if let Ok(value) = i32::try_from(value) {
Self::Integer(value) Self::integer32(value)
} else { } else {
Self::Rational(value as f64) Self::float64(value as f64)
} }
} }
} }
@ -134,9 +129,9 @@ impl From<u64> for JsValue {
#[inline] #[inline]
fn from(value: u64) -> Self { fn from(value: u64) -> Self {
if let Ok(value) = i32::try_from(value) { if let Ok(value) = i32::try_from(value) {
Self::Integer(value) Self::integer32(value)
} else { } else {
Self::Rational(value as f64) Self::float64(value as f64)
} }
} }
} }
@ -145,9 +140,9 @@ impl From<i64> for JsValue {
#[inline] #[inline]
fn from(value: i64) -> Self { fn from(value: i64) -> Self {
if let Ok(value) = i32::try_from(value) { if let Ok(value) = i32::try_from(value) {
Self::Integer(value) Self::integer32(value)
} else { } else {
Self::Rational(value as f64) Self::float64(value as f64)
} }
} }
} }
@ -155,7 +150,7 @@ impl From<i64> for JsValue {
impl From<bool> for JsValue { impl From<bool> for JsValue {
#[inline] #[inline]
fn from(value: bool) -> Self { fn from(value: bool) -> Self {
Self::Boolean(value) Self::boolean(value)
} }
} }
@ -163,7 +158,7 @@ impl From<JsObject> for JsValue {
#[inline] #[inline]
fn from(object: JsObject) -> Self { fn from(object: JsObject) -> Self {
let _timer = Profiler::global().start_event("From<JsObject>", "value"); let _timer = Profiler::global().start_event("From<JsObject>", "value");
Self::Object(object) Self::object(object)
} }
} }
@ -174,6 +169,25 @@ impl From<()> for JsValue {
} }
} }
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TryFromObjectError;
impl Display for TryFromObjectError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not convert value to an Object type")
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TryFromCharError;
impl Display for TryFromCharError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not convert value to a char type")
}
}
pub(crate) trait IntoOrUndefined { pub(crate) trait IntoOrUndefined {
fn into_or_undefined(self) -> JsValue; fn into_or_undefined(self) -> JsValue;
} }
@ -183,6 +197,6 @@ where
T: Into<JsValue>, T: Into<JsValue>,
{ {
fn into_or_undefined(self) -> JsValue { fn into_or_undefined(self) -> JsValue {
self.map_or_else(JsValue::undefined, Into::into) self.map_or(JsValue::undefined(), Into::into)
} }
} }

38
boa_engine/src/value/display.rs

@ -1,6 +1,6 @@
use crate::{object::ObjectKind, property::PropertyDescriptor}; use crate::{object::ObjectKind, property::PropertyDescriptor};
use super::{fmt, Display, HashSet, JsValue, PropertyKey}; use super::{fmt, Display, HashSet, JsValue, JsVariant, PropertyKey};
/// This object is used for displaying a `Value`. /// This object is used for displaying a `Value`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -55,7 +55,7 @@ macro_rules! print_obj_value {
vec![format!( vec![format!(
"{:>width$}: {}", "{:>width$}: {}",
"__proto__", "__proto__",
JsValue::Null.display(), JsValue::null().display(),
width = $indent, width = $indent,
)] )]
} }
@ -96,9 +96,9 @@ macro_rules! print_obj_value {
} }
pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String { pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String {
match x { match x.variant() {
// We don't want to print private (compiler) or prototype properties // We don't want to print private (compiler) or prototype properties
JsValue::Object(ref v) => { JsVariant::Object(v) => {
// Can use the private "type" field of an Object to match on // Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing // which type of Object it represents for special printing
match v.borrow().kind() { match v.borrow().kind() {
@ -196,7 +196,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
_ => display_obj(x, print_internals), _ => display_obj(x, print_internals),
} }
} }
JsValue::Symbol(ref symbol) => symbol.to_string(), JsVariant::Symbol(ref symbol) => symbol.to_string(),
_ => x.display().to_string(), _ => x.display().to_string(),
} }
} }
@ -216,7 +216,7 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
indent: usize, indent: usize,
print_internals: bool, print_internals: bool,
) -> String { ) -> String {
if let JsValue::Object(ref v) = *data { if let Some(v) = data.as_object() {
// The in-memory address of the current object // The in-memory address of the current object
let addr = address_of(v.as_ref()); let addr = address_of(v.as_ref());
@ -254,20 +254,20 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
// in-memory address in this set // in-memory address in this set
let mut encounters = HashSet::new(); let mut encounters = HashSet::new();
if let JsValue::Object(object) = v { if let Some(object) = v.as_object() {
if object.borrow().is_error() { if object.borrow().is_error() {
let name = v let name = v
.get_property("name") .get_property("name")
.as_ref() .as_ref()
.and_then(PropertyDescriptor::value) .and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined) .unwrap_or(&JsValue::undefined())
.display() .display()
.to_string(); .to_string();
let message = v let message = v
.get_property("message") .get_property("message")
.as_ref() .as_ref()
.and_then(PropertyDescriptor::value) .and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined) .unwrap_or(&JsValue::undefined())
.display() .display()
.to_string(); .to_string();
return format!("{name}: {message}"); return format!("{name}: {message}");
@ -279,21 +279,21 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
impl Display for ValueDisplay<'_> { impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.value { match self.value.variant() {
JsValue::Null => write!(f, "null"), JsVariant::Null => write!(f, "null"),
JsValue::Undefined => write!(f, "undefined"), JsVariant::Undefined => write!(f, "undefined"),
JsValue::Boolean(v) => write!(f, "{v}"), JsVariant::Boolean(v) => write!(f, "{v}"),
JsValue::Symbol(ref symbol) => match symbol.description() { JsVariant::Symbol(symbol) => match symbol.description() {
Some(description) => write!(f, "Symbol({description})"), Some(description) => write!(f, "Symbol({description})"),
None => write!(f, "Symbol()"), None => write!(f, "Symbol()"),
}, },
JsValue::String(ref v) => write!(f, "\"{v}\""), JsVariant::String(v) => write!(f, "\"{}\"", *v),
JsValue::Rational(v) => format_rational(*v, f), JsVariant::Float64(v) => format_rational(v, f),
JsValue::Object(_) => { JsVariant::Object(_) => {
write!(f, "{}", log_string_from(self.value, self.internals, true)) write!(f, "{}", log_string_from(self.value, self.internals, true))
} }
JsValue::Integer(v) => write!(f, "{v}"), JsVariant::Integer32(v) => write!(f, "{v}"),
JsValue::BigInt(ref num) => write!(f, "{num}n"), JsVariant::BigInt(num) => write!(f, "{}n", *num),
} }
} }
} }

115
boa_engine/src/value/equality.rs

@ -1,4 +1,4 @@
use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType}; use super::{JsBigInt, JsObject, JsResult, JsValue, JsVariant, PreferredType};
use crate::{builtins::Number, Context}; use crate::{builtins::Number, Context};
impl JsValue { impl JsValue {
@ -12,20 +12,20 @@ impl JsValue {
return false; return false;
} }
match (self, other) { match (self.variant(), other.variant()) {
// 2. If Type(x) is Number or BigInt, then // 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::equal(x, y). // a. Return ! Type(x)::equal(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::equal(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y),
(Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)),
(Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(x), y),
(Self::Integer(x), Self::Integer(y)) => x == y, (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
//Null has to be handled specially because "typeof null" returns object and if we managed //Null has to be handled specially because "typeof null" returns object and if we managed
//this without a special case we would compare self and other as if they were actually //this without a special case we would compare self and other as if they were actually
//objects which unfortunately fails //objects which unfortunately fails
//Specification Link: https://tc39.es/ecma262/#sec-typeof-operator //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator
(Self::Null, Self::Null) => true, (JsVariant::Null, JsVariant::Null) => true,
// 3. Return ! SameValueNonNumeric(x, y). // 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(self, other), (_, _) => Self::same_value_non_numeric(self, other),
@ -44,17 +44,22 @@ impl JsValue {
return Ok(self.strict_equals(other)); return Ok(self.strict_equals(other));
} }
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// 2. If x is null and y is undefined, return true. // 2. If x is null and y is undefined, return true.
// 3. If x is undefined and y is null, return true. // 3. If x is undefined and y is null, return true.
(Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true, (JsVariant::Null, JsVariant::Undefined) | (JsVariant::Undefined, JsVariant::Null) => {
true
}
// 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y). // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).
// 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y. // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.
// //
// https://github.com/rust-lang/rust/issues/54883 // https://github.com/rust-lang/rust/issues/54883
(Self::Integer(_) | Self::Rational(_), Self::String(_) | Self::Boolean(_)) (
| (Self::String(_), Self::Integer(_) | Self::Rational(_)) => { JsVariant::Integer32(_) | JsVariant::Float64(_),
JsVariant::String(_) | JsVariant::Boolean(_),
)
| (JsVariant::String(_), JsVariant::Integer32(_) | JsVariant::Float64(_)) => {
let x = self.to_number(context)?; let x = self.to_number(context)?;
let y = other.to_number(context)?; let y = other.to_number(context)?;
Number::equal(x, y) Number::equal(x, y)
@ -64,32 +69,32 @@ impl JsValue {
// a. Let n be ! StringToBigInt(y). // a. Let n be ! StringToBigInt(y).
// b. If n is NaN, return false. // b. If n is NaN, return false.
// c. Return the result of the comparison x == n. // c. Return the result of the comparison x == n.
(Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) { (JsVariant::BigInt(a), JsVariant::String(ref b)) => match JsBigInt::from_string(b) {
Some(ref b) => a == b, Some(b) => *a == b,
None => false, None => false,
}, },
// 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x. // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
(Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) { (JsVariant::String(ref a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) {
Some(ref a) => a == b, Some(a) => a == *b,
None => false, None => false,
}, },
// 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
(Self::Boolean(x), _) => return other.equals(&Self::new(i32::from(*x)), context), (JsVariant::Boolean(x), _) => return other.equals(&Self::new(i32::from(x)), context),
// 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
(_, Self::Boolean(y)) => return self.equals(&Self::new(i32::from(*y)), context), (_, JsVariant::Boolean(y)) => return self.equals(&Self::new(i32::from(y)), context),
// 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result
// of the comparison x == ? ToPrimitive(y). // of the comparison x == ? ToPrimitive(y).
( (
Self::Object(_), JsVariant::Object(_),
Self::String(_) JsVariant::String(_)
| Self::Rational(_) | JsVariant::Float64(_)
| Self::Integer(_) | JsVariant::Integer32(_)
| Self::BigInt(_) | JsVariant::BigInt(_)
| Self::Symbol(_), | JsVariant::Symbol(_),
) => { ) => {
let primitive = self.to_primitive(context, PreferredType::Default)?; let primitive = self.to_primitive(context, PreferredType::Default)?;
return Ok(primitive return Ok(primitive
@ -100,12 +105,12 @@ impl JsValue {
// 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result
// of the comparison ? ToPrimitive(x) == y. // of the comparison ? ToPrimitive(x) == y.
( (
Self::String(_) JsVariant::String(_)
| Self::Rational(_) | JsVariant::Float64(_)
| Self::Integer(_) | JsVariant::Integer32(_)
| Self::BigInt(_) | JsVariant::BigInt(_)
| Self::Symbol(_), | JsVariant::Symbol(_),
Self::Object(_), JsVariant::Object(_),
) => { ) => {
let primitive = other.to_primitive(context, PreferredType::Default)?; let primitive = other.to_primitive(context, PreferredType::Default)?;
return Ok(primitive return Ok(primitive
@ -116,10 +121,10 @@ impl JsValue {
// 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then
// a. If x or y are any of NaN, +∞, or -∞, return false. // a. If x or y are any of NaN, +∞, or -∞, return false.
// b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false. // b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.
(Self::BigInt(ref a), Self::Rational(ref b)) => a == b, (JsVariant::BigInt(a), JsVariant::Float64(b)) => *a == b,
(Self::Rational(ref a), Self::BigInt(ref b)) => a == b, (JsVariant::Float64(a), JsVariant::BigInt(b)) => a == *b,
(Self::BigInt(ref a), Self::Integer(ref b)) => a == b, (JsVariant::BigInt(a), JsVariant::Integer32(b)) => *a == b,
(Self::Integer(ref a), Self::BigInt(ref b)) => a == b, (JsVariant::Integer32(a), JsVariant::BigInt(b)) => a == *b,
// 13. Return false. // 13. Return false.
_ => false, _ => false,
@ -139,14 +144,14 @@ impl JsValue {
return false; return false;
} }
match (x, y) { match (x.variant(), y.variant()) {
// 2. If Type(x) is Number or BigInt, then // 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValue(x, y). // a. Return ! Type(x)::SameValue(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y),
(Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)),
(Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y),
(Self::Integer(x), Self::Integer(y)) => x == y, (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
// 3. Return ! SameValueNonNumeric(x, y). // 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(x, y), (_, _) => Self::same_value_non_numeric(x, y),
@ -167,19 +172,19 @@ impl JsValue {
return false; return false;
} }
match (x, y) { match (x.variant(), y.variant()) {
// 2. If Type(x) is Number or BigInt, then // 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValueZero(x, y). // a. Return ! Type(x)::SameValueZero(x, y).
(JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value_zero(x, y), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value_zero(x, y),
(JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value_zero(*x, *y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y),
(JsValue::Rational(x), JsValue::Integer(y)) => { (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Number::same_value_zero(*x, f64::from(*y)) Number::same_value_zero(x, f64::from(y))
} }
(JsValue::Integer(x), JsValue::Rational(y)) => { (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
Number::same_value_zero(f64::from(*x), *y) Number::same_value_zero(f64::from(x), y)
} }
(JsValue::Integer(x), JsValue::Integer(y)) => x == y, (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
// 3. Return ! SameValueNonNumeric(x, y). // 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(x, y), (_, _) => Self::same_value_non_numeric(x, y),
@ -188,12 +193,14 @@ impl JsValue {
fn same_value_non_numeric(x: &Self, y: &Self) -> bool { fn same_value_non_numeric(x: &Self, y: &Self) -> bool {
debug_assert!(x.get_type() == y.get_type()); debug_assert!(x.get_type() == y.get_type());
match (x, y) { match (x.variant(), y.variant()) {
(Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true, (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => {
(Self::String(ref x), Self::String(ref y)) => x == y, true
(Self::Boolean(x), Self::Boolean(y)) => x == y, }
(Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y), (JsVariant::String(x), JsVariant::String(y)) => x == y,
(Self::Symbol(ref x), Self::Symbol(ref y)) => x == y, (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y,
(JsVariant::Object(ref x), JsVariant::Object(ref y)) => JsObject::equals(x, y),
(JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y,
_ => false, _ => false,
} }
} }

22
boa_engine/src/value/hash.rs

@ -1,4 +1,4 @@
use super::JsValue; use super::{JsValue, JsVariant};
use crate::builtins::Number; use crate::builtins::Number;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -37,16 +37,16 @@ impl Hash for RationalHashable {
impl Hash for JsValue { impl Hash for JsValue {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
match self { match self.variant() {
Self::Undefined => UndefinedHashable.hash(state), JsVariant::Undefined => UndefinedHashable.hash(state),
Self::Null => NullHashable.hash(state), JsVariant::Null => NullHashable.hash(state),
Self::String(ref string) => string.hash(state), JsVariant::String(string) => string.hash(state),
Self::Boolean(boolean) => boolean.hash(state), JsVariant::Boolean(boolean) => boolean.hash(state),
Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state), JsVariant::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state),
Self::BigInt(ref bigint) => bigint.hash(state), JsVariant::BigInt(bigint) => bigint.hash(state),
Self::Rational(rational) => RationalHashable(*rational).hash(state), JsVariant::Float64(rational) => RationalHashable(rational).hash(state),
Self::Symbol(ref symbol) => Hash::hash(symbol, state), JsVariant::Symbol(symbol) => Hash::hash(&*symbol, state),
Self::Object(ref object) => std::ptr::hash(object.as_ref(), state), JsVariant::Object(object) => std::ptr::hash(object.as_ref(), state),
} }
} }
} }

409
boa_engine/src/value/mod.rs

@ -1,6 +1,27 @@
//! This module implements the JavaScript Value. #![warn(unsafe_op_in_unsafe_fn)]
//! This module implements the JavaScript [`JsValue`] type.
//! //!
//! Javascript values, utility methods and conversion between Javascript values and Rust values. //! [`JsValue`] implements several utility methods and conversions between Javascript
//! values and Rust values.
//!
//! # Notes
//!
//! We recommend using [`JsValue::from`], [`JsValue::new`] or the [`Into::into`]
//! trait if you need to convert from float types ([`f64`], [`f32`]) to [`JsValue`],
//! since there are some checks implemented that convert float values to signed
//! integer values when there's no loss of precision involved.
//!
//! Alternatively, you can use the [`JsValue::float64`] method if you do need a pure
//! [`f64`] value. The constructor skips all conversions from [`f64`]s to
//! [`i32`]s, even if the value can be represented by an [`i32`] without loss of
//! precision.
//!
//! # Alternative implementations
//!
//! `boa` right now implements two versions of [`JsValue`]:
//! - The default implementation using a simple `enum`.
//! - A NaN-boxed implementation for x86-64 platforms that can be enabled using the `nan_boxing` feature.
//! The feature flag is ignored on incompatible platforms.
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -12,10 +33,9 @@ use crate::{
}, },
object::{JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols}, symbol::WellKnownSymbols,
Context, JsBigInt, JsResult, JsString, Context, JsBigInt, JsResult, JsString,
}; };
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use num_bigint::BigInt; use num_bigint::BigInt;
use num_integer::Integer; use num_integer::Integer;
@ -28,6 +48,11 @@ use std::{
str::FromStr, str::FromStr,
}; };
mod sys;
#[doc(inline)]
pub use sys::{JsValue, JsVariant, Ref};
mod conversions; mod conversions;
pub(crate) mod display; pub(crate) mod display;
mod equality; mod equality;
@ -44,6 +69,7 @@ pub use hash::*;
pub use integer::IntegerOrInfinity; pub use integer::IntegerOrInfinity;
pub use operations::*; pub use operations::*;
pub use r#type::Type; pub use r#type::Type;
pub(crate) use sys::PointerType;
static TWO_E_64: Lazy<BigInt> = Lazy::new(|| { static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
const TWO_E_64: u128 = 2u128.pow(64); const TWO_E_64: u128 = 2u128.pow(64);
@ -55,37 +81,6 @@ static TWO_E_63: Lazy<BigInt> = Lazy::new(|| {
BigInt::from(TWO_E_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-8 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, {
if let Self::Object(o) = this {
mark(o);
}
}}
}
impl JsValue { impl JsValue {
/// Create a new [`JsValue`]. /// Create a new [`JsValue`].
#[inline] #[inline]
@ -96,48 +91,28 @@ impl JsValue {
value.into() value.into()
} }
/// Creates a new `undefined` value. /// Returns true if the value is null or undefined.
#[inline]
pub fn undefined() -> Self {
Self::Undefined
}
/// Creates a new `null` value.
#[inline] #[inline]
pub fn null() -> Self { pub fn is_null_or_undefined(&self) -> bool {
Self::Null self.is_null() || self.is_undefined()
} }
/// Creates a new number with `NaN` value. /// Creates a new number with `NaN` value.
#[inline] #[inline]
pub fn nan() -> Self { pub fn nan() -> Self {
Self::Rational(f64::NAN) Self::new(f64::NAN)
} }
/// Creates a new number with `Infinity` value. /// Creates a new number with `Infinity` value.
#[inline] #[inline]
pub fn positive_infinity() -> Self { pub fn positive_infinity() -> Self {
Self::Rational(f64::INFINITY) Self::new(f64::INFINITY)
} }
/// Creates a new number with `-Infinity` value. /// Creates a new number with `-Infinity` value.
#[inline] #[inline]
pub fn negative_infinity() -> Self { pub fn negative_infinity() -> Self {
Self::Rational(f64::NEG_INFINITY) Self::new(f64::NEG_INFINITY)
}
/// Returns true if the value is an object
#[inline]
pub fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
#[inline]
pub 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. /// It determines if the value is a callable function with a `[[Call]]` internal method.
@ -148,143 +123,68 @@ impl JsValue {
/// [spec]: https://tc39.es/ecma262/#sec-iscallable /// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline] #[inline]
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_callable()) self.as_object()
.as_deref()
.map_or(false, JsObject::is_callable)
} }
#[inline] #[inline]
pub fn as_callable(&self) -> Option<&JsObject> { pub fn as_callable(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_callable()) self.as_object().filter(|obj| obj.is_callable())
} }
/// Returns true if the value is a constructor object. /// Returns true if the value is a constructor object.
#[inline] #[inline]
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_constructor()) self.as_object()
.as_deref()
.map_or(false, JsObject::is_constructor)
} }
#[inline] #[inline]
pub fn as_constructor(&self) -> Option<&JsObject> { pub fn as_constructor(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_constructor()) self.as_object().filter(|obj| obj.is_constructor())
} }
/// Returns true if the value is a promise object. /// Returns true if the value is a promise object.
#[inline] #[inline]
pub fn is_promise(&self) -> bool { pub fn is_promise(&self) -> bool {
matches!(self, Self::Object(obj) if obj.is_promise()) self.as_object()
.as_deref()
.map_or(false, JsObject::is_promise)
} }
#[inline] #[inline]
pub fn as_promise(&self) -> Option<&JsObject> { pub fn as_promise(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_promise()) self.as_object().filter(|obj| obj.is_promise())
} }
/// Returns true if the value is a symbol. /// Returns true if the value is an integer, even if it's represented by an [`f64`].
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
pub fn as_symbol(&self) -> Option<JsSymbol> {
match self {
Self::Symbol(symbol) => Some(symbol.clone()),
_ => None,
}
}
/// Returns true if the value is undefined.
#[inline]
pub fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
/// Returns true if the value is null.
#[inline]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
/// Returns true if the value is null or undefined.
#[inline]
pub 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]
pub fn is_double(&self) -> bool {
matches!(self, Self::Rational(_))
}
/// Returns true if the value is integer.
#[inline] #[inline]
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
pub fn is_integer(&self) -> bool { pub fn is_integer(&self) -> bool {
// If it can fit in a i32 and the truncated version is if self.is_integer32() {
// equal to the original then it is an integer. return true;
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,
} }
self.as_float64()
// If it can fit in a i32 and the trucated version is
// equal to the original then it is an integer.
.map(|num| num == f64::from(num as i32))
.unwrap_or_default()
} }
/// Returns true if the value is a number. /// Returns true if the value is a number.
#[inline] #[inline]
pub fn is_number(&self) -> bool { pub fn is_number(&self) -> bool {
matches!(self, Self::Rational(_) | Self::Integer(_)) self.is_integer32() || self.is_float64()
} }
#[inline] #[inline]
pub fn as_number(&self) -> Option<f64> { pub fn as_number(&self) -> Option<f64> {
match *self { match self.variant() {
Self::Integer(integer) => Some(integer.into()), JsVariant::Integer32(integer) => Some(integer.into()),
Self::Rational(rational) => Some(rational), JsVariant::Float64(rational) => Some(rational),
_ => None,
}
}
/// Returns true if the value is a string.
#[inline]
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
/// Returns the string if the values is a string, otherwise `None`.
#[inline]
pub fn as_string(&self) -> Option<&JsString> {
match self {
Self::String(ref string) => Some(string),
_ => None,
}
}
/// Returns true if the value is a boolean.
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(boolean) => Some(*boolean),
_ => None,
}
}
/// Returns true if the value is a bigint.
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self, Self::BigInt(_))
}
/// Returns an optional reference to a `BigInt` if the value is a `BigInt` primitive.
#[inline]
pub fn as_bigint(&self) -> Option<&JsBigInt> {
match self {
Self::BigInt(bigint) => Some(bigint),
_ => None, _ => None,
} }
} }
@ -296,13 +196,13 @@ impl JsValue {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-toboolean /// [spec]: https://tc39.es/ecma262/#sec-toboolean
pub fn to_boolean(&self) -> bool { pub fn to_boolean(&self) -> bool {
match *self { match self.variant() {
Self::Symbol(_) | Self::Object(_) => true, JsVariant::Symbol(_) | JsVariant::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true, JsVariant::String(s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true, JsVariant::Float64(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true, JsVariant::Integer32(n) if n != 0 => true,
Self::BigInt(ref n) if !n.is_zero() => true, JsVariant::BigInt(n) if !n.is_zero() => true,
Self::Boolean(v) => v, JsVariant::Boolean(v) => v,
_ => false, _ => false,
} }
} }
@ -316,28 +216,27 @@ impl JsValue {
{ {
let key = key.into(); let key = key.into();
let _timer = Profiler::global().start_event("Value::get_property", "value"); let _timer = Profiler::global().start_event("Value::get_property", "value");
match self { if let Some(object) = self.as_object() {
Self::Object(ref object) => { // TODO: had to skip `__get_own_properties__` since we don't have context here
// TODO: had to skip `__get_own_properties__` since we don't have context here let property = object.borrow().properties().get(&key);
let property = object.borrow().properties().get(&key); if property.is_some() {
if property.is_some() { return property;
return property;
}
object
.prototype()
.as_ref()
.map_or(Self::Null, |obj| obj.clone().into())
.get_property(key)
} }
_ => None,
object
.prototype()
.as_ref()
.map_or(Self::null(), |obj| obj.clone().into())
.get_property(key)
} else {
None
} }
} }
/// Set the kind of an object. /// Set the kind of an object.
#[inline] #[inline]
pub fn set_data(&self, data: ObjectData) { pub fn set_data(&self, data: ObjectData) {
if let Self::Object(ref obj) = *self { if let Some(obj) = self.as_object() {
obj.borrow_mut().data = data; obj.borrow_mut().data = data;
} }
} }
@ -352,9 +251,9 @@ impl JsValue {
) -> JsResult<Self> { ) -> JsResult<Self> {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check) // 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then // 2. If Type(input) is Object, then
if self.is_object() { if let Some(object) = self.as_object() {
// a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
let exotic_to_prim = self.get_method(WellKnownSymbols::to_primitive(), context)?; let exotic_to_prim = object.get_method(WellKnownSymbols::to_primitive(), context)?;
// b. If exoticToPrim is not undefined, then // b. If exoticToPrim is not undefined, then
if let Some(exotic_to_prim) = exotic_to_prim { if let Some(exotic_to_prim) = exotic_to_prim {
@ -388,9 +287,7 @@ impl JsValue {
}; };
// d. Return ? OrdinaryToPrimitive(input, preferredType). // d. Return ? OrdinaryToPrimitive(input, preferredType).
self.as_object() object.ordinary_to_primitive(context, preferred_type)
.expect("self was not an object")
.ordinary_to_primitive(context, preferred_type)
} else { } else {
// 3. Return input. // 3. Return input.
Ok(self.clone()) Ok(self.clone())
@ -404,10 +301,13 @@ impl JsValue {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-tobigint /// [spec]: https://tc39.es/ecma262/#sec-tobigint
pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> { pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
match self { match self.variant() {
Self::Null => context.throw_type_error("cannot convert null to a BigInt"), JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"),
Self::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"), JsVariant::Undefined => {
Self::String(ref string) => { context.throw_type_error("cannot convert undefined to a BigInt")
}
JsVariant::String(string) => {
let string = &*string;
if let Some(value) = JsBigInt::from_string(string) { if let Some(value) = JsBigInt::from_string(string) {
Ok(value) Ok(value)
} else { } else {
@ -416,17 +316,17 @@ impl JsValue {
)) ))
} }
} }
Self::Boolean(true) => Ok(JsBigInt::one()), JsVariant::Boolean(true) => Ok(JsBigInt::one()),
Self::Boolean(false) => Ok(JsBigInt::zero()), JsVariant::Boolean(false) => Ok(JsBigInt::zero()),
Self::Integer(_) | Self::Rational(_) => { JsVariant::Integer32(_) | JsVariant::Float64(_) => {
context.throw_type_error("cannot convert Number to a BigInt") context.throw_type_error("cannot convert Number to a BigInt")
} }
Self::BigInt(b) => Ok(b.clone()), JsVariant::BigInt(b) => Ok(b.clone()),
Self::Object(_) => { JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?; let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_bigint(context) primitive.to_bigint(context)
} }
Self::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"), JsVariant::Symbol(_) => context.throw_type_error("cannot convert Symbol to a BigInt"),
} }
} }
@ -456,16 +356,16 @@ impl JsValue {
/// ///
/// This function is equivalent to `String(value)` in JavaScript. /// This function is equivalent to `String(value)` in JavaScript.
pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> { pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
match self { match self.variant() {
Self::Null => Ok("null".into()), JsVariant::Null => Ok("null".into()),
Self::Undefined => Ok("undefined".into()), JsVariant::Undefined => Ok("undefined".into()),
Self::Boolean(boolean) => Ok(boolean.to_string().into()), JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()),
Self::Rational(rational) => Ok(Number::to_native_string(*rational).into()), JsVariant::Float64(rational) => Ok(Number::to_native_string(rational).into()),
Self::Integer(integer) => Ok(integer.to_string().into()), JsVariant::Integer32(integer) => Ok(integer.to_string().into()),
Self::String(string) => Ok(string.clone()), JsVariant::String(string) => Ok(string.clone()),
Self::Symbol(_) => context.throw_type_error("can't convert symbol to string"), JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"),
Self::BigInt(ref bigint) => Ok(bigint.to_string().into()), JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()),
Self::Object(_) => { JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::String)?; let primitive = self.to_primitive(context, PreferredType::String)?;
primitive.to_string(context) primitive.to_string(context)
} }
@ -478,32 +378,32 @@ impl JsValue {
/// ///
/// See: <https://tc39.es/ecma262/#sec-toobject> /// See: <https://tc39.es/ecma262/#sec-toobject>
pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> { pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
match self { match self.variant() {
Self::Undefined | Self::Null => { JsVariant::Undefined | JsVariant::Null => {
context.throw_type_error("cannot convert 'null' or 'undefined' to object") context.throw_type_error("cannot convert 'null' or 'undefined' to object")
} }
Self::Boolean(boolean) => { JsVariant::Boolean(boolean) => {
let prototype = context.intrinsics().constructors().boolean().prototype(); let prototype = context.intrinsics().constructors().boolean().prototype();
Ok(JsObject::from_proto_and_data( Ok(JsObject::from_proto_and_data(
prototype, prototype,
ObjectData::boolean(*boolean), ObjectData::boolean(boolean),
)) ))
} }
Self::Integer(integer) => { JsVariant::Integer32(integer) => {
let prototype = context.intrinsics().constructors().number().prototype(); let prototype = context.intrinsics().constructors().number().prototype();
Ok(JsObject::from_proto_and_data( Ok(JsObject::from_proto_and_data(
prototype, prototype,
ObjectData::number(f64::from(*integer)), ObjectData::number(f64::from(integer)),
)) ))
} }
Self::Rational(rational) => { JsVariant::Float64(rational) => {
let prototype = context.intrinsics().constructors().number().prototype(); let prototype = context.intrinsics().constructors().number().prototype();
Ok(JsObject::from_proto_and_data( Ok(JsObject::from_proto_and_data(
prototype, prototype,
ObjectData::number(*rational), ObjectData::number(rational),
)) ))
} }
Self::String(ref string) => { JsVariant::String(string) => {
let prototype = context.intrinsics().constructors().string().prototype(); let prototype = context.intrinsics().constructors().string().prototype();
let object = let object =
@ -519,14 +419,14 @@ impl JsValue {
); );
Ok(object) Ok(object)
} }
Self::Symbol(ref symbol) => { JsVariant::Symbol(symbol) => {
let prototype = context.intrinsics().constructors().symbol().prototype(); let prototype = context.intrinsics().constructors().symbol().prototype();
Ok(JsObject::from_proto_and_data( Ok(JsObject::from_proto_and_data(
prototype, prototype,
ObjectData::symbol(symbol.clone()), ObjectData::symbol(symbol.clone()),
)) ))
} }
Self::BigInt(ref bigint) => { JsVariant::BigInt(bigint) => {
let prototype = context let prototype = context
.intrinsics() .intrinsics()
.constructors() .constructors()
@ -537,7 +437,7 @@ impl JsValue {
ObjectData::big_int(bigint.clone()), ObjectData::big_int(bigint.clone()),
)) ))
} }
Self::Object(jsobject) => Ok(jsobject.clone()), JsVariant::Object(jsobject) => Ok(jsobject.clone()),
} }
} }
@ -545,19 +445,22 @@ impl JsValue {
/// ///
/// See <https://tc39.es/ecma262/#sec-topropertykey> /// See <https://tc39.es/ecma262/#sec-topropertykey>
pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> { pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
Ok(match self { Ok(match self.variant() {
// Fast path: // Fast path:
Self::String(string) => string.clone().into(), JsVariant::String(string) => string.clone().into(),
Self::Symbol(symbol) => symbol.clone().into(), JsVariant::Symbol(symbol) => symbol.clone().into(),
Self::Integer(integer) => (*integer).into(), JsVariant::Integer32(integer) => integer.into(),
// Slow path: // Slow path:
Self::Object(_) => match self.to_primitive(context, PreferredType::String)? { JsVariant::Object(_) => {
Self::String(ref string) => string.clone().into(), let primitive = self.to_primitive(context, PreferredType::String)?;
Self::Symbol(ref symbol) => symbol.clone().into(), match primitive.variant() {
Self::Integer(integer) => integer.into(), JsVariant::String(string) => string.clone().into(),
primitive => primitive.to_string(context)?.into(), JsVariant::Symbol(symbol) => symbol.clone().into(),
}, JsVariant::Integer32(integer) => integer.into(),
primitive => primitive.to_string(context)?.into(), _ => primitive.to_string(context)?.into(),
}
},
_ => self.to_string(context)?.into(),
}) })
} }
@ -579,7 +482,7 @@ impl JsValue {
/// See: <https://tc39.es/ecma262/#sec-touint32> /// See: <https://tc39.es/ecma262/#sec-touint32>
pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> { 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. // This is the fast path, if the value is Integer we can just return it.
if let JsValue::Integer(number) = *self { if let Some(number) = self.as_integer32() {
return Ok(number as u32); return Ok(number as u32);
} }
let number = self.to_number(context)?; let number = self.to_number(context)?;
@ -592,7 +495,7 @@ impl JsValue {
/// See: <https://tc39.es/ecma262/#sec-toint32> /// See: <https://tc39.es/ecma262/#sec-toint32>
pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> { 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. // This is the fast path, if the value is Integer we can just return it.
if let JsValue::Integer(number) = *self { if let Some(number) = self.as_integer32() {
return Ok(number); return Ok(number);
} }
let number = self.to_number(context)?; let number = self.to_number(context)?;
@ -873,16 +776,16 @@ impl JsValue {
/// ///
/// See: <https://tc39.es/ecma262/#sec-tonumber> /// See: <https://tc39.es/ecma262/#sec-tonumber>
pub fn to_number(&self, context: &mut Context) -> JsResult<f64> { pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
match *self { match self.variant() {
Self::Null => Ok(0.0), JsVariant::Null => Ok(0.0),
Self::Undefined => Ok(f64::NAN), JsVariant::Undefined => Ok(f64::NAN),
Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
Self::String(ref string) => Ok(string.string_to_number()), JsVariant::String(string) => Ok(string.string_to_number()),
Self::Rational(number) => Ok(number), JsVariant::Float64(number) => Ok(number),
Self::Integer(integer) => Ok(f64::from(integer)), JsVariant::Integer32(integer) => Ok(f64::from(integer)),
Self::Symbol(_) => context.throw_type_error("argument must not be a symbol"), JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"),
Self::BigInt(_) => context.throw_type_error("argument must not be a bigint"), JsVariant::BigInt(_) => context.throw_type_error("argument must not be a bigint"),
Self::Object(_) => { JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?; let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_number(context) primitive.to_number(context)
} }
@ -942,15 +845,15 @@ impl JsValue {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator /// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
pub fn type_of(&self) -> JsString { pub fn type_of(&self) -> JsString {
match *self { match self.variant() {
Self::Rational(_) | Self::Integer(_) => "number", JsVariant::Float64(_) | JsVariant::Integer32(_) => "number",
Self::String(_) => "string", JsVariant::String(_) => "string",
Self::Boolean(_) => "boolean", JsVariant::Boolean(_) => "boolean",
Self::Symbol(_) => "symbol", JsVariant::Symbol(_) => "symbol",
Self::Null => "object", JsVariant::Null => "object",
Self::Undefined => "undefined", JsVariant::Undefined => "undefined",
Self::BigInt(_) => "bigint", JsVariant::BigInt(_) => "bigint",
Self::Object(ref object) => { JsVariant::Object(object) => {
if object.is_callable() { if object.is_callable() {
"function" "function"
} else { } else {
@ -986,7 +889,7 @@ impl JsValue {
impl Default for JsValue { impl Default for JsValue {
fn default() -> Self { fn default() -> Self {
Self::Undefined Self::undefined()
} }
} }

310
boa_engine/src/value/operations.rs

@ -1,5 +1,5 @@
use super::{ use super::{
Context, FromStr, JsBigInt, JsResult, JsString, JsValue, Numeric, PreferredType, Context, FromStr, JsBigInt, JsResult, JsString, JsValue, JsVariant, Numeric, PreferredType,
WellKnownSymbols, WellKnownSymbols,
}; };
use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number};
@ -7,60 +7,63 @@ use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number};
impl JsValue { impl JsValue {
#[inline] #[inline]
pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
// Numeric add // Numeric add
(Self::Integer(x), Self::Integer(y)) => x (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_add(*y) .checked_add(y)
.map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new), .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x + y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)), (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)),
// String concat // String concat
(Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)), (JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)),
(Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)), (JsVariant::String(x), _) => {
(x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)), Self::new(JsString::concat(&*x, other.to_string(context)?))
}
(_, JsVariant::String(y)) => Self::new(JsString::concat(self.to_string(context)?, &*y)),
// Slow path: // Slow path:
(_, _) => match ( (_, _) => {
self.to_primitive(context, PreferredType::Default)?, let x = self.to_primitive(context, PreferredType::Default)?;
other.to_primitive(context, PreferredType::Default)?, let y = other.to_primitive(context, PreferredType::Default)?;
) { match (x.variant(), y.variant()) {
(Self::String(ref x), ref y) => { (JsVariant::String(x), _) => {
Self::from(JsString::concat(x, y.to_string(context)?)) Self::from(JsString::concat(&*x, y.to_string(context)?))
}
(ref x, Self::String(ref y)) => {
Self::from(JsString::concat(x.to_string(context)?, y))
}
(x, y) => match (x.to_numeric(context)?, y.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::add(x, y))
} }
(_, _) => { (_, JsVariant::String(y)) => {
return context.throw_type_error( Self::from(JsString::concat(x.to_string(context)?, &*y))
"cannot mix BigInt and other types, use explicit conversions",
)
} }
}, (_, _) => match (x.to_numeric(context)?, y.to_numeric(context)?) {
}, (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::new(JsBigInt::add(x, y))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
)
}
},
}
}
}) })
} }
#[inline] #[inline]
pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => x (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_sub(*y) .checked_sub(y)
.map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new), .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x - y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x - f64::from(y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -77,16 +80,16 @@ impl JsValue {
#[inline] #[inline]
pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => x (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_mul(*y) .checked_mul(y)
.map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new), .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x * y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x * f64::from(y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -103,17 +106,17 @@ impl JsValue {
#[inline] #[inline]
pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => x (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_div(*y) .checked_div(y)
.filter(|div| *y * div == *x) .filter(|div| y * div == x)
.map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new), .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x / y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => { (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
if y.is_zero() { if y.is_zero() {
return context.throw_range_error("BigInt division by zero"); return context.throw_range_error("BigInt division by zero");
} }
@ -140,33 +143,32 @@ impl JsValue {
#[inline] #[inline]
pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => { (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
if *y == 0 { if y == 0 {
Self::nan() Self::from(f64::NAN)
} else { } else {
match x % *y { match x % y {
rem if rem == 0 && *x < 0 => Self::new(-0.0), rem if rem == 0 && x < 0 => Self::new(-0.0),
rem => Self::new(rem), rem => Self::new(rem),
} }
} }
} }
(Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)),
(Self::Integer(x), Self::Rational(y)) => { (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
let x = f64::from(*x); let x = f64::from(x);
Self::new((x % y).copysign(x)) Self::new((x % y).copysign(x))
} }
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
(Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)), Self::new((x % f64::from(y)).copysign(x))
}
(Self::BigInt(ref x), Self::BigInt(ref y)) => { (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
if y.is_zero() { if y.is_zero() {
return context.throw_range_error("BigInt division by zero"); return context.throw_range_error("BigInt division by zero");
} }
Self::new(JsBigInt::rem(x, y)) Self::new(JsBigInt::rem(x, y))
} }
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b), (Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b),
@ -187,17 +189,19 @@ impl JsValue {
#[inline] #[inline]
pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => u32::try_from(*y) (JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y)
.ok() .ok()
.and_then(|y| x.checked_pow(y)) .and_then(|y| x.checked_pow(y))
.map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new), .map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x.powf(*y)), (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x.powf(y)),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x).powf(y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x.powi(y)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b, context)?), (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => {
Self::new(JsBigInt::pow(a, b, context)?)
}
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -216,16 +220,18 @@ impl JsValue {
#[inline] #[inline]
pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x & y), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y),
(Self::Rational(x), Self::Rational(y)) => { (JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(*x) & f64_to_int32(*y)) Self::new(f64_to_int32(x) & f64_to_int32(y))
} }
(Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) & y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitand(x, y)), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
Self::new(JsBigInt::bitand(x, y))
}
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -246,16 +252,18 @@ impl JsValue {
#[inline] #[inline]
pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x | y), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y),
(Self::Rational(x), Self::Rational(y)) => { (JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(*x) | f64_to_int32(*y)) Self::new(f64_to_int32(x) | f64_to_int32(y))
} }
(Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) | y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitor(x, y)), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
Self::new(JsBigInt::bitor(x, y))
}
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -276,16 +284,18 @@ impl JsValue {
#[inline] #[inline]
pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y),
(Self::Rational(x), Self::Rational(y)) => { (JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(*x) ^ f64_to_int32(*y)) Self::new(f64_to_int32(x) ^ f64_to_int32(y))
} }
(Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) ^ y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::bitxor(x, y)), (JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
Self::new(JsBigInt::bitxor(x, y))
}
// Slow path: // Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) { (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -306,18 +316,22 @@ impl JsValue {
#[inline] #[inline]
pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
(Self::Rational(x), Self::Rational(y)) => { Self::new(x.wrapping_shl(y as u32))
Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y)))
} }
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))), (JsVariant::Float64(x), JsVariant::Float64(y)) => {
(Self::Rational(x), Self::Integer(y)) => { Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
Self::new(f64_to_int32(*x).wrapping_shl(*y as u32)) }
(JsVariant::Integer32(x), JsVariant::Float64(y)) => {
Self::new(x.wrapping_shl(f64_to_uint32(y)))
}
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Self::new(f64_to_int32(x).wrapping_shl(y as u32))
} }
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => {
Self::new(JsBigInt::shift_left(a, b, context)?) Self::new(JsBigInt::shift_left(a, b, context)?)
} }
@ -340,18 +354,22 @@ impl JsValue {
#[inline] #[inline]
pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
(Self::Rational(x), Self::Rational(y)) => { Self::new(x.wrapping_shr(y as u32))
Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) }
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
} }
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shr(f64_to_uint32(*y))), (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
(Self::Rational(x), Self::Integer(y)) => { Self::new(x.wrapping_shr(f64_to_uint32(y)))
Self::new(f64_to_int32(*x).wrapping_shr(*y as u32)) }
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Self::new(f64_to_int32(x).wrapping_shr(y as u32))
} }
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (JsVariant::BigInt(ref a), JsVariant::BigInt(ref b)) => {
Self::new(JsBigInt::shift_right(a, b, context)?) Self::new(JsBigInt::shift_right(a, b, context)?)
} }
@ -374,17 +392,19 @@ impl JsValue {
#[inline] #[inline]
pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> { pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path: // Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
(Self::Rational(x), Self::Rational(y)) => { Self::new((x as u32).wrapping_shr(y as u32))
Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) }
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
} }
(Self::Integer(x), Self::Rational(y)) => { (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y))) Self::new((x as u32).wrapping_shr(f64_to_uint32(y)))
} }
(Self::Rational(x), Self::Integer(y)) => { (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32)) Self::new(f64_to_uint32(x).wrapping_shr(y as u32))
} }
// Slow path: // Slow path:
@ -443,22 +463,22 @@ impl JsValue {
#[inline] #[inline]
pub fn neg(&self, context: &mut Context) -> JsResult<Self> { pub fn neg(&self, context: &mut Context) -> JsResult<Self> {
Ok(match *self { Ok(match self.variant() {
Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN), JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN),
Self::Object(_) => Self::new(match self.to_numeric_number(context) { JsVariant::Object(_) => Self::new(match self.to_numeric_number(context) {
Ok(num) => -num, Ok(num) => -num,
Err(_) => f64::NAN, Err(_) => f64::NAN,
}), }),
Self::String(ref str) => Self::new(match f64::from_str(str) { JsVariant::String(ref str) => Self::new(match f64::from_str(str) {
Ok(num) => -num, Ok(num) => -num,
Err(_) => f64::NAN, Err(_) => f64::NAN,
}), }),
Self::Rational(num) => Self::new(-num), JsVariant::Float64(num) => Self::new(-num),
Self::Integer(num) if num == 0 => Self::new(-f64::from(0)), JsVariant::Integer32(num) if num == 0 => Self::new(-f64::from(0)),
Self::Integer(num) => Self::new(-num), JsVariant::Integer32(num) => Self::new(-num),
Self::Boolean(true) => Self::new(1), JsVariant::Boolean(true) => Self::new(1),
Self::Boolean(false) | Self::Null => Self::new(0), JsVariant::Boolean(false) | JsVariant::Null => Self::new(0),
Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)), JsVariant::BigInt(ref x) => Self::new(JsBigInt::neg(x)),
}) })
} }
@ -490,13 +510,13 @@ impl JsValue {
left_first: bool, left_first: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<AbstractRelation> { ) -> JsResult<AbstractRelation> {
Ok(match (self, other) { Ok(match (self.variant(), other.variant()) {
// Fast path (for some common operations): // Fast path (for some common operations):
(Self::Integer(x), Self::Integer(y)) => (x < y).into(), (JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(),
(Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y), (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y),
(Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)), (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)),
(Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y), (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(), (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(),
// Slow path: // Slow path:
(_, _) => { (_, _) => {
@ -511,8 +531,8 @@ impl JsValue {
(px, py) (px, py)
}; };
match (px, py) { match (px.variant(), py.variant()) {
(Self::String(ref x), Self::String(ref y)) => { (JsVariant::String(x), JsVariant::String(y)) => {
if x.starts_with(y.as_str()) { if x.starts_with(y.as_str()) {
return Ok(AbstractRelation::False); return Ok(AbstractRelation::False);
} }
@ -526,21 +546,21 @@ impl JsValue {
} }
unreachable!() unreachable!()
} }
(Self::BigInt(ref x), Self::String(ref y)) => { (JsVariant::BigInt(x), JsVariant::String(ref y)) => {
if let Some(y) = JsBigInt::from_string(y) { if let Some(y) = JsBigInt::from_string(y) {
(*x < y).into() (*x < y).into()
} else { } else {
AbstractRelation::Undefined AbstractRelation::Undefined
} }
} }
(Self::String(ref x), Self::BigInt(ref y)) => { (JsVariant::String(ref x), JsVariant::BigInt(y)) => {
if let Some(x) = JsBigInt::from_string(x) { if let Some(x) = JsBigInt::from_string(x) {
(x < *y).into() (x < *y).into()
} else { } else {
AbstractRelation::Undefined AbstractRelation::Undefined
} }
} }
(px, py) => match (px.to_numeric(context)?, py.to_numeric(context)?) { (_, _) => match (px.to_numeric(context)?, py.to_numeric(context)?) {
(Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y), (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(Numeric::BigInt(ref x), Numeric::Number(y)) => { (Numeric::BigInt(ref x), Numeric::Number(y)) => {

39
boa_engine/src/value/serde_json.rs

@ -1,6 +1,6 @@
//! This module implements the conversions from and into [`serde_json::Value`]. //! This module implements the conversions from and into [`serde_json::Value`].
use super::JsValue; use super::{JsValue, JsVariant};
use crate::{ use crate::{
builtins::Array, builtins::Array,
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
@ -41,13 +41,13 @@ impl JsValue {
const MIN_INT: i64 = i32::MIN as i64; const MIN_INT: i64 = i32::MIN as i64;
match json { match json {
Value::Null => Ok(Self::Null), Value::Null => Ok(Self::null()),
Value::Bool(b) => Ok(Self::Boolean(*b)), Value::Bool(b) => Ok(Self::from(*b)),
Value::Number(num) => num Value::Number(num) => num
.as_i64() .as_i64()
.filter(|n| (MIN_INT..=MAX_INT).contains(n)) .filter(|n| (MIN_INT..=MAX_INT).contains(n))
.map(|i| Self::Integer(i as i32)) .map(|i| Self::from(i as i32))
.or_else(|| num.as_f64().map(Self::Rational)) .or_else(|| num.as_f64().map(Self::from))
.ok_or_else(|| { .ok_or_else(|| {
context.construct_type_error(format!( context.construct_type_error(format!(
"could not convert JSON number {num} to JsValue" "could not convert JSON number {num} to JsValue"
@ -104,15 +104,15 @@ impl JsValue {
/// # assert_eq!(json, back_to_json); /// # assert_eq!(json, back_to_json);
/// ``` /// ```
pub fn to_json(&self, context: &mut Context) -> JsResult<Value> { pub fn to_json(&self, context: &mut Context) -> JsResult<Value> {
match self { match self.variant() {
Self::Null => Ok(Value::Null), JsVariant::Null => Ok(Value::Null),
Self::Undefined => todo!("undefined to JSON"), JsVariant::Undefined => todo!("undefined to JSON"),
&Self::Boolean(b) => Ok(b.into()), JsVariant::Boolean(b) => Ok(b.into()),
Self::String(string) => Ok(string.as_str().into()), JsVariant::String(string) => Ok(string.as_str().into()),
&Self::Rational(rat) => Ok(rat.into()), JsVariant::Float64(rat) => Ok(rat.into()),
&Self::Integer(int) => Ok(int.into()), JsVariant::Integer32(int) => Ok(int.into()),
Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"), JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"),
Self::Object(obj) => { JsVariant::Object(obj) => {
if obj.is_array() { if obj.is_array() {
let len = obj.length_of_array_like(context)?; let len = obj.length_of_array_like(context)?;
let mut arr = Vec::with_capacity(len as usize); let mut arr = Vec::with_capacity(len as usize);
@ -120,9 +120,12 @@ impl JsValue {
let obj = obj.borrow(); let obj = obj.borrow();
for k in 0..len as u32 { for k in 0..len as u32 {
let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| { let val = obj
desc.value().cloned().unwrap_or(Self::Null) .properties()
}); .get(&k.into())
.map_or(Self::null(), |desc| {
desc.value().cloned().unwrap_or(Self::null())
});
arr.push(val.to_json(context)?); arr.push(val.to_json(context)?);
} }
@ -149,7 +152,7 @@ impl JsValue {
Ok(Value::Object(map)) Ok(Value::Object(map))
} }
} }
Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"), JsVariant::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"),
} }
} }
} }

290
boa_engine/src/value/sys/default.rs

@ -0,0 +1,290 @@
use boa_gc::{custom_trace, Finalize, Trace};
use super::JsVariant;
use crate::{object::JsObject, JsBigInt, JsString, JsSymbol};
#[derive(Finalize, Debug, Clone)]
enum Value {
Null,
Undefined,
Boolean(bool),
Integer32(i32),
Float64(f64),
String(JsString),
BigInt(JsBigInt),
Symbol(JsSymbol),
Object(JsObject),
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe impl Trace for Value {
custom_trace! {this, {
if let Self::Object(o) = this {
mark(o);
}
}}
}
/// A Javascript value
///
/// Check the [`value`][`super::super`] module for more information.
#[derive(Trace, Finalize, Debug, Clone)]
pub struct JsValue(Value);
impl JsValue {
/// `null` - A null value, for when a value doesn't exist.
#[inline]
pub const fn null() -> Self {
Self(Value::Null)
}
/// `undefined` - An undefined value, for when a field or index doesn't exist
#[inline]
pub const fn undefined() -> Self {
Self(Value::Undefined)
}
/// `boolean` - A `true` / `false` value.
#[inline]
pub fn boolean(boolean: bool) -> Self {
Self(Value::Boolean(boolean))
}
/// `integer32` - A 32-bit integer value, such as `42`.
#[inline]
pub fn integer32(integer: i32) -> Self {
Self(Value::Integer32(integer))
}
/// `float64` - A 64-bit floating point number value, such as `3.1415`
#[inline]
pub fn float64(float64: f64) -> Self {
Self(Value::Float64(float64))
}
/// `String` - A [`JsString`] value, such as `"Hello, world"`.
#[inline]
pub fn string(string: JsString) -> Self {
Self(Value::String(string))
}
/// `BigInt` - A [`JsBigInt`] value, an arbitrarily large signed integer.
#[inline]
pub fn bigint(bigint: JsBigInt) -> Self {
Self(Value::BigInt(bigint))
}
/// `Symbol` - A [`JsSymbol`] value.
#[inline]
pub fn symbol(symbol: JsSymbol) -> Self {
Self(Value::Symbol(symbol))
}
/// `Object` - A [`JsObject`], such as `Math`, represented by a binary tree of string keys to Javascript values.
#[inline]
pub fn object(object: JsObject) -> Self {
Self(Value::Object(object))
}
/// Returns the internal [`bool`] if the value is a boolean, or
/// [`None`] otherwise.
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self.0 {
Value::Boolean(boolean) => Some(boolean),
_ => None,
}
}
/// Returns the internal [`i32`] if the value is a 32-bit signed integer number, or
/// [`None`] otherwise.
#[inline]
pub fn as_integer32(&self) -> Option<i32> {
match self.0 {
Value::Integer32(integer) => Some(integer),
_ => None,
}
}
/// Returns the internal [`f64`] if the value is a 64-bit floating-point number, or
/// [`None`] otherwise.
#[inline]
pub fn as_float64(&self) -> Option<f64> {
match self.0 {
Value::Float64(rational) => Some(rational),
_ => None,
}
}
/// Returns a reference to the internal [`JsString`] if the value is a string, or
/// [`None`] otherwise.
#[inline]
pub fn as_string(&self) -> Option<Ref<'_, JsString>> {
match self.0 {
Value::String(ref inner) => Some(Ref { inner }),
_ => None,
}
}
/// Returns a reference to the internal [`JsBigInt`] if the value is a big int, or
/// [`None`] otherwise.
#[inline]
pub fn as_bigint(&self) -> Option<Ref<'_, JsBigInt>> {
match self.0 {
Value::BigInt(ref inner) => Some(Ref { inner }),
_ => None,
}
}
/// Returns a reference to the internal [`JsSymbol`] if the value is a symbol, or
/// [`None`] otherwise.
#[inline]
pub fn as_symbol(&self) -> Option<Ref<'_, JsSymbol>> {
match self.0 {
Value::Symbol(ref inner) => Some(Ref { inner }),
_ => None,
}
}
/// Returns a reference to the internal [`JsObject`] if the value is an object, or
/// [`None`] otherwise.
#[inline]
pub fn as_object(&self) -> Option<Ref<'_, JsObject>> {
match self.0 {
Value::Object(ref inner) => Some(Ref { inner }),
_ => None,
}
}
/// Returns true if the value is null.
#[inline]
pub fn is_null(&self) -> bool {
matches!(self.0, Value::Null)
}
/// Returns true if the value is undefined.
#[inline]
pub fn is_undefined(&self) -> bool {
matches!(self.0, Value::Undefined)
}
/// Returns true if the value is a boolean.
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self.0, Value::Boolean(_))
}
/// Returns true if the value is a 32-bit signed integer number.
#[inline]
pub fn is_integer32(&self) -> bool {
matches!(self.0, Value::Integer32(_))
}
/// Returns true if the value is a 64-bit floating-point number.
#[inline]
pub fn is_float64(&self) -> bool {
matches!(self.0, Value::Float64(_))
}
/// Returns true if the value is a 64-bit floating-point `NaN` number.
#[inline]
pub fn is_nan(&self) -> bool {
matches!(self.0, Value::Float64(r) if r.is_nan())
}
/// Returns true if the value is a string.
#[inline]
pub fn is_string(&self) -> bool {
matches!(self.0, Value::String(_))
}
/// Returns true if the value is a bigint.
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self.0, Value::BigInt(_))
}
/// Returns true if the value is a symbol.
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self.0, Value::Symbol(_))
}
/// Returns true if the value is an object
#[inline]
pub fn is_object(&self) -> bool {
matches!(self.0, Value::Object(_))
}
/// Returns a [`JsVariant`] enum representing the current variant of the value.
///
/// # Note
///
/// More exotic implementations of [`JsValue`] cannot use direct references to
/// heap based types, so [`JsVariant`] instead returns [`Ref`]s on those cases.
pub fn variant(&self) -> JsVariant<'_> {
match self.0 {
Value::Null => JsVariant::Null,
Value::Undefined => JsVariant::Undefined,
Value::Integer32(i) => JsVariant::Integer32(i),
Value::Float64(d) => JsVariant::Float64(d),
Value::Boolean(b) => JsVariant::Boolean(b),
Value::Object(ref inner) => JsVariant::Object(Ref { inner }),
Value::String(ref inner) => JsVariant::String(Ref { inner }),
Value::Symbol(ref inner) => JsVariant::Symbol(Ref { inner }),
Value::BigInt(ref inner) => JsVariant::BigInt(Ref { inner }),
}
}
}
/// Represents a reference to a boxed pointer type inside a [`JsValue`]
///
/// This is exclusively used to return references to [`JsString`], [`JsObject`],
/// [`JsSymbol`] and [`JsBigInt`], since some [`JsValue`] implementations makes
/// returning proper references difficult.
/// It is mainly returned by the [`JsValue::variant`] method and the
/// `as_` methods for checked casts to pointer types.
///
/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion
/// to a proper [`reference`] by using the `ref` keyword or the
/// [`Option::as_deref`][`std::option::Option::as_deref`] method.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ref<'a, T> {
inner: &'a T,
}
// Lift `Ref` over `AsRef`, since implementing `AsRef<T>` would override the
// `as_ref` implementations of `T`.
impl<U, T> AsRef<U> for Ref<'_, T>
where
T: AsRef<U>,
{
#[inline]
fn as_ref(&self) -> &U {
<T as AsRef<U>>::as_ref(self.inner)
}
}
impl<T> std::ops::Deref for Ref<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<T: PartialEq> PartialEq<T> for Ref<'_, T> {
#[inline]
fn eq(&self, other: &T) -> bool {
self.inner == other
}
}
impl<T> std::borrow::Borrow<T> for Ref<'_, T> {
#[inline]
fn borrow(&self) -> &T {
self.inner
}
}

99
boa_engine/src/value/sys/mod.rs

@ -0,0 +1,99 @@
//! This module contains various implementations of the [`JsValue`] type, and
//! type redefinitions to select the requested [`JsValue`] implementation at
//! compile time, using features.
use crate::{object::JsObject, JsBigInt, JsString, JsSymbol};
use std::mem::ManuallyDrop;
// Minimum required definition for a correct `JsValue` implementation:
// pub const fn null() -> Self;
// pub const fn undefined() -> Self;
// pub fn boolean(bool) -> Self;
// pub fn integer(i32) -> Self;
// pub fn rational(f64) -> Self;
// pub const fn nan() -> Self;
// pub fn string(JsString) -> Self;
// pub fn bigint(JsBigInt) -> Self;
// pub fn symbol(JsSymbol) -> Self;
// pub fn object(JsObject) -> Self;
// pub fn as_boolean(&self) -> Option<bool>;
// pub fn as_integer(&self) -> Option<i32>;
// pub fn as_rational(&self) -> Option<f64>;
// pub fn as_string(&self) -> Option<Ref<'_, JsString>>;
// pub fn as_bigint(&self) -> Option<Ref<'_, JsBigInt>>;
// pub fn as_symbol(&self) -> Option<Ref<'_, JsSymbol>>;
// pub fn as_object(&self) -> Option<Ref<'_, JsObject>>;
// pub fn is_null(&self) -> bool;
// pub fn is_undefined(&self) -> bool;
// pub fn is_boolean(&self) -> bool;
// pub fn is_integer(&self) -> bool;
// pub fn is_rational(&self) -> bool;
// pub fn is_nan(&self) -> bool;
// pub fn is_string(&self) -> bool;
// pub fn is_bigint(&self) -> bool;
// pub fn is_symbol(&self) -> bool;
// pub fn is_object(&self) -> bool;
// pub fn variant(&self) -> JsVariant<'_>;
// Ref<'a, T> type
cfg_if::cfg_if! {
if #[cfg(all(feature = "nan_boxing", not(doc)))] {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] {
#[path = "nan_boxed.rs"]
mod r#impl;
} else {
compile_error!("This platform doesn't support NaN-boxing.");
}
}
} else {
#[path = "default.rs"]
mod r#impl;
}
}
pub use r#impl::*;
/// Return value of the [`JsValue::variant`] method.
///
/// Represents either a primitive value ([`bool`], [`f64`], [`i32`]) or a reference
/// to a heap allocated value ([`JsString`], [`JsSymbol`]).
///
/// References to heap allocated values are represented by [`Ref`], since
/// more exotic implementations of [`JsValue`] such as nan-boxed ones cannot
/// effectively return references.
#[derive(Debug)]
pub enum JsVariant<'a> {
Null,
Undefined,
Boolean(bool),
Integer32(i32),
Float64(f64),
String(Ref<'a, JsString>),
BigInt(Ref<'a, JsBigInt>),
Symbol(Ref<'a, JsSymbol>),
Object(Ref<'a, JsObject>),
}
/// This abstracts over every pointer type and the required conversions
/// for some of the [`JsValue`] implementations.
///
/// # Safety
///
/// Non-exhaustive list of situations that could cause undefined behaviour:
/// - Returning an invalid `*mut ()`.
/// - Returning a `ManuallyDrop<Self>` that doesn't correspond with the provided
/// `ptr`.
/// - Dropping `ty` before returning its pointer.
pub(crate) unsafe trait PointerType {
unsafe fn from_void_ptr(ptr: *mut ()) -> ManuallyDrop<Self>;
unsafe fn into_void_ptr(ty: ManuallyDrop<Self>) -> *mut ();
}

760
boa_engine/src/value/sys/nan_boxed.rs

@ -0,0 +1,760 @@
#![allow(unstable_name_collisions)]
use num_enum::TryFromPrimitive;
use std::cell::Cell;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use boa_gc::{Finalize, Trace};
// TODO: Remove if/when https://github.com/rust-lang/rust/issues/95228 gets stabilized
use sptr::Strict;
use super::JsVariant;
use crate::{object::JsObject, value::PointerType, JsBigInt, JsString, JsSymbol};
// Our `cfg` options must ensure `usize == u64`.
// Using `usize`s only makes it more convenient to use in this module
const SIGN_BIT: usize = 0x8000_0000_0000_0000;
const EXPONENT: usize = 0x7FF0_0000_0000_0000;
// const MANTISA: usize = 0x000F_FFFF_FFFF_FFFF;
const SIGNAL_BIT: usize = 0x0008_0000_0000_0000;
const QNAN: usize = EXPONENT | SIGNAL_BIT; // 0x7FF8000000000000
const CANONICALIZED_NAN: usize = QNAN;
// const PAYLOAD: usize = 0x0000_7FFF_FFFF_FFFF;
// const TYPE: usize = !PAYLOAD;
const TAG_MASK: usize = 0xFFFF_0000_0000_0000;
const DOUBLE_TYPE: usize = QNAN;
const INTEGER_TYPE: usize = QNAN | (0b001 << 48);
const BOOLEAN_TYPE: usize = QNAN | (0b010 << 48);
const UNDEFINED_TYPE: usize = QNAN | (0b011 << 48);
const NULL_TYPE: usize = QNAN | (0b100 << 48);
#[allow(unused)]
const RESERVED1_TYPE: usize = QNAN | (0b101 << 48);
#[allow(unused)]
const RESERVED2_TYPE: usize = QNAN | (0b110 << 48);
#[allow(unused)]
const RESERVED3_TYPE: usize = QNAN | (0b111 << 48);
const POINTER_TYPE: usize = SIGN_BIT | QNAN;
const OBJECT_TYPE: usize = POINTER_TYPE | (0b001 << 48);
const STRING_TYPE: usize = POINTER_TYPE | (0b010 << 48);
const SYMBOL_TYPE: usize = POINTER_TYPE | (0b011 << 48);
const BIGINT_TYPE: usize = POINTER_TYPE | (0b100 << 48);
#[allow(unused)]
const RESERVED4_TYPE: usize = POINTER_TYPE | (0b101 << 48);
#[allow(unused)]
const RESERVED5_TYPE: usize = POINTER_TYPE | (0b110 << 48);
#[allow(unused)]
const RESERVED6_TYPE: usize = POINTER_TYPE | (0b111 << 48);
const MASK_INT_PAYLOAD: usize = 0x00000000FFFFFFFF;
const MASK_POINTER_PAYLOAD: usize = 0x0000FFFFFFFFFFFF;
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u16)]
enum ValueTag {
Float64 = (DOUBLE_TYPE >> 48) as _,
Integer32 = (INTEGER_TYPE >> 48) as _,
Boolean = (BOOLEAN_TYPE >> 48) as _,
Undefined = (UNDEFINED_TYPE >> 48) as _,
Null = (NULL_TYPE >> 48) as _,
Object = (OBJECT_TYPE >> 48) as _,
String = (STRING_TYPE >> 48) as _,
Symbol = (SYMBOL_TYPE >> 48) as _,
BigInt = (BIGINT_TYPE >> 48) as _,
// Reserved1 = RESERVED1_TYPE,
// Reserved2 = RESERVED2_TYPE,
// Reserved3 = RESERVED3_TYPE,
// Reserved4 = RESERVED4_TYPE,
// Reserved5 = RESERVED5_TYPE,
// Reserved6 = RESERVED6_TYPE,
}
/// A Javascript value
///
/// Check the [`value`][`super::super`] module for more information.
#[derive(Debug)]
#[repr(transparent)]
pub struct JsValue {
value: Cell<*mut ()>,
}
impl JsValue {
/// `null` - A null value, for when a value doesn't exist.
#[inline]
pub const fn null() -> Self {
Self {
value: Cell::new(sptr::invalid_mut(NULL_TYPE)),
}
}
/// `undefined` - An undefined value, for when a field or index doesn't exist
#[inline]
pub const fn undefined() -> Self {
Self {
value: Cell::new(sptr::invalid_mut(UNDEFINED_TYPE)),
}
}
/// `boolean` - A `true` / `false` value.
#[inline]
pub fn boolean(boolean: bool) -> Self {
let value = Self {
value: Cell::new(sptr::invalid_mut(usize::from(boolean) | BOOLEAN_TYPE)),
};
debug_assert!(value.is_boolean());
debug_assert_eq!(value.tag(), ValueTag::Boolean);
value
}
/// `integer32` - A 32-bit integer value, such as `42`.
#[inline]
pub fn integer32(integer32: i32) -> Self {
let value = Self {
value: Cell::new(sptr::invalid_mut(integer32 as u32 as usize | INTEGER_TYPE)),
};
debug_assert!(value.is_integer32());
debug_assert_eq!(value.tag(), ValueTag::Integer32);
value
}
/// `float64` - A 64-bit floating point number value, such as `3.1415`
#[inline]
pub fn float64(float64: f64) -> Self {
if float64.is_nan() {
return Self {
value: Cell::new(sptr::invalid_mut(CANONICALIZED_NAN)),
};
}
let value = Self {
value: Cell::new(sptr::invalid_mut(float64.to_bits() as usize)),
};
debug_assert!(value.is_float64());
debug_assert_eq!(value.tag(), ValueTag::Float64);
value
}
/// `String` - A [`JsString`] value, such as `"Hello, world"`.
#[inline]
pub fn string(string: JsString) -> Self {
let string = ManuallyDrop::new(string);
let pointer = unsafe { JsString::into_void_ptr(string) };
debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr());
let value = Self {
value: Cell::new(pointer.map_addr(|addr| addr | STRING_TYPE)),
};
debug_assert!(value.is_string());
debug_assert_eq!(value.tag(), ValueTag::String);
value
}
/// `BigInt` - A [`JsBigInt`] value, an arbitrarily large signed integer.
#[inline]
pub fn bigint(bigint: JsBigInt) -> Self {
let bigint = ManuallyDrop::new(bigint);
let pointer = unsafe { JsBigInt::into_void_ptr(bigint) };
debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr());
let value = Self {
value: Cell::new(pointer.map_addr(|addr| addr | BIGINT_TYPE)),
};
debug_assert!(value.is_bigint());
debug_assert_eq!(value.tag(), ValueTag::BigInt);
value
}
/// `Symbol` - A [`JsSymbol`] value.
#[inline]
pub fn symbol(symbol: JsSymbol) -> Self {
let symbol = ManuallyDrop::new(symbol);
let pointer = unsafe { JsSymbol::into_void_ptr(symbol) };
debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr());
let value = Self {
value: Cell::new(pointer.map_addr(|addr| addr | SYMBOL_TYPE)),
};
debug_assert!(value.is_symbol());
debug_assert_eq!(value.tag(), ValueTag::Symbol);
value
}
/// `Object` - A [`JsObject`], such as `Math`, represented by a binary tree of string keys to Javascript values.
#[inline]
pub fn object(object: JsObject) -> Self {
let object = ManuallyDrop::new(object);
let pointer = unsafe { JsObject::into_void_ptr(object) };
debug_assert_eq!(pointer.addr() & MASK_POINTER_PAYLOAD, pointer.addr());
debug_assert_eq!(OBJECT_TYPE & MASK_POINTER_PAYLOAD, 0);
let value = Self {
value: Cell::new(pointer.map_addr(|addr| addr | OBJECT_TYPE)),
};
debug_assert!(value.is_object());
debug_assert_eq!(value.tag(), ValueTag::Object);
value
}
/// Returns the internal [`bool`] if the value is a boolean, or
/// [`None`] otherwise.
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
if self.is_boolean() {
return Some(self.as_boolean_uncheched());
}
None
}
/// Returns the internal [`i32`] if the value is a 32-bit signed integer number, or
/// [`None`] otherwise.
pub fn as_integer32(&self) -> Option<i32> {
if self.is_integer32() {
return Some(self.as_integer32_uncheched());
}
None
}
/// Returns the internal [`f64`] if the value is a 64-bit floating-point number, or
/// [`None`] otherwise.
pub fn as_float64(&self) -> Option<f64> {
if self.is_float64() {
return Some(self.as_float64_unchecked());
}
None
}
/// Returns a reference to the internal [`JsString`] if the value is a string, or
/// [`None`] otherwise.
#[inline]
pub fn as_string(&self) -> Option<Ref<'_, JsString>> {
if self.is_string() {
return unsafe { Some(self.as_string_unchecked()) };
}
None
}
/// Returns a reference to the internal [`JsBigInt`] if the value is a big int, or
/// [`None`] otherwise.
pub fn as_bigint(&self) -> Option<Ref<'_, JsBigInt>> {
if self.is_bigint() {
return unsafe { Some(self.as_bigint_unchecked()) };
}
None
}
/// Returns a reference to the internal [`JsSymbol`] if the value is a symbol, or
/// [`None`] otherwise.
pub fn as_symbol(&self) -> Option<Ref<'_, JsSymbol>> {
if self.is_symbol() {
return unsafe { Some(self.as_symbol_unchecked()) };
}
None
}
/// Returns a reference to the internal [`JsObject`] if the value is an object, or
/// [`None`] otherwise.
pub fn as_object(&self) -> Option<Ref<'_, JsObject>> {
if self.is_object() {
return unsafe { Some(self.as_object_unchecked()) };
}
None
}
/// Returns true if the value is null.
#[inline]
pub fn is_null(&self) -> bool {
self.value.get().addr() == NULL_TYPE
}
/// Returns true if the value is undefined.
#[inline]
pub fn is_undefined(&self) -> bool {
self.value.get().addr() == UNDEFINED_TYPE
}
/// Returns true if the value is a boolean.
#[inline]
pub fn is_boolean(&self) -> bool {
self.value.get().addr() & TAG_MASK == BOOLEAN_TYPE
}
/// Returns true if the value is a 32-bit signed integer number.
pub fn is_integer32(&self) -> bool {
self.value.get().addr() & TAG_MASK == INTEGER_TYPE
}
/// Returns true if the value is a 64-bit floating-point number.
pub fn is_float64(&self) -> bool {
(self.value.get().addr() & !SIGN_BIT) <= QNAN
}
/// Returns true if the value is a 64-bit floating-point `NaN` number.
pub fn is_nan(&self) -> bool {
self.value.get().addr() == CANONICALIZED_NAN
}
/// Returns true if the value is a string.
#[inline]
pub fn is_string(&self) -> bool {
self.value.get().addr() & TAG_MASK == STRING_TYPE
}
/// Returns true if the value is a bigint.
#[inline]
pub fn is_bigint(&self) -> bool {
self.value.get().addr() & TAG_MASK == BIGINT_TYPE
}
/// Returns true if the value is a symbol.
pub fn is_symbol(&self) -> bool {
self.value.get().addr() & TAG_MASK == SYMBOL_TYPE
}
/// Returns true if the value is an object
#[inline]
pub fn is_object(&self) -> bool {
self.value.get().addr() & TAG_MASK == OBJECT_TYPE
}
/// Returns a [`JsVariant`] enum representing the current variant of the value.
///
/// # Note
///
/// More exotic implementations of [`JsValue`] cannot use direct references to
/// heap based types, so [`JsVariant`] instead returns [`Ref`]s on those cases.
pub fn variant(&self) -> JsVariant<'_> {
unsafe {
match self.tag() {
ValueTag::Null => JsVariant::Null,
ValueTag::Undefined => JsVariant::Undefined,
ValueTag::Integer32 => JsVariant::Integer32(self.as_integer32_uncheched()),
ValueTag::Float64 => JsVariant::Float64(self.as_float64_unchecked()),
ValueTag::Boolean => JsVariant::Boolean(self.as_boolean_uncheched()),
ValueTag::Object => JsVariant::Object(self.as_object_unchecked()),
ValueTag::String => JsVariant::String(self.as_string_unchecked()),
ValueTag::Symbol => JsVariant::Symbol(self.as_symbol_unchecked()),
ValueTag::BigInt => JsVariant::BigInt(self.as_bigint_unchecked()),
}
}
}
fn as_pointer(&self) -> *mut () {
self.value
.get()
.map_addr(|addr| addr & MASK_POINTER_PAYLOAD)
}
fn tag(&self) -> ValueTag {
if self.is_float64() {
return ValueTag::Float64;
}
let tag = ((self.value.get().addr() & TAG_MASK) >> 48) as u16;
ValueTag::try_from(tag).expect("Implementation must never construct an invalid tag")
}
fn as_boolean_uncheched(&self) -> bool {
(self.value.get().addr() & 0xFF) != 0
}
fn as_integer32_uncheched(&self) -> i32 {
(self.value.get().addr() & MASK_INT_PAYLOAD) as u32 as i32
}
fn as_float64_unchecked(&self) -> f64 {
f64::from_bits(self.value.get().addr() as u64)
}
/// Returns a reference to the boxed [`JsString`] without checking
/// if the tag of `self` is valid.
///
/// # Safety
///
/// Calling this method with a [`JsValue`] that doesn't box
/// a [`JsString`] is undefined behaviour.
unsafe fn as_string_unchecked(&self) -> Ref<'_, JsString> {
unsafe { Ref::new(JsString::from_void_ptr(self.as_pointer())) }
}
/// Returns a reference to the boxed [`JsBigInt`] without checking
/// if the tag of `self` is valid.
///
/// # Safety
///
/// Calling this method with a [`JsValue`] that doesn't box
/// a [`JsBigInt`] is undefined behaviour.
#[inline]
unsafe fn as_bigint_unchecked(&self) -> Ref<'_, JsBigInt> {
// SAFETY: The safety contract must be upheld by the caller
unsafe { Ref::new(JsBigInt::from_void_ptr(self.as_pointer())) }
}
/// Returns a reference to the boxed [`JsSymbol`] without checking
/// if the tag of `self` is valid.
///
/// # Safety
///
/// Calling this method with a [`JsValue`] that doesn't box
/// a [`JsSymbol`] is undefined behaviour.
unsafe fn as_symbol_unchecked(&self) -> Ref<'_, JsSymbol> {
unsafe { Ref::new(JsSymbol::from_void_ptr(self.as_pointer())) }
}
/// Returns a reference to the boxed [`JsObject`] without checking
/// if the tag of `self` is valid.
///
/// # Safety
///
/// Calling this method with a [`JsValue`] that doesn't box
/// a [`JsObject`] is undefined behaviour.
unsafe fn as_object_unchecked(&self) -> Ref<'_, JsObject> {
unsafe { Ref::new(JsObject::from_void_ptr(self.as_pointer())) }
}
}
impl Drop for JsValue {
#[inline]
fn drop(&mut self) {
unsafe {
match self.tag() {
ValueTag::Object => {
ManuallyDrop::into_inner(JsObject::from_void_ptr(self.as_pointer()));
}
ValueTag::String => {
ManuallyDrop::into_inner(JsString::from_void_ptr(self.as_pointer()));
}
ValueTag::Symbol => {
ManuallyDrop::into_inner(JsSymbol::from_void_ptr(self.as_pointer()));
}
ValueTag::BigInt => {
ManuallyDrop::into_inner(JsBigInt::from_void_ptr(self.as_pointer()));
}
_ => {}
}
}
}
}
impl Clone for JsValue {
#[inline]
fn clone(&self) -> Self {
unsafe {
match self.tag() {
ValueTag::Object => Self::new(self.as_object_unchecked().clone()),
ValueTag::String => Self::new(self.as_string_unchecked().clone()),
ValueTag::Symbol => Self::new(self.as_symbol_unchecked().clone()),
ValueTag::BigInt => Self::new(self.as_bigint_unchecked().clone()),
_ => Self {
value: Cell::new(self.value.get()),
},
}
}
}
}
impl Finalize for JsValue {}
unsafe impl Trace for JsValue {
unsafe fn trace(&self) {
if let Some(o) = self.as_object() {
// SAFETY: `self.as_object()` must always return a valid `JsObject
unsafe {
o.trace();
}
}
}
unsafe fn root(&self) {
if self.tag() == ValueTag::Object {
// SAFETY: Implementors of `PointerType` must guarantee the
// safety of both `from_void_ptr` and `into_void_ptr`
unsafe {
let o = JsObject::from_void_ptr(self.as_pointer());
o.root();
self.value
.set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE));
}
}
}
unsafe fn unroot(&self) {
if self.tag() == ValueTag::Object {
// SAFETY: Implementors of `PointerType` must guarantee the
// safety of both `from_void_ptr` and `into_void_ptr`
unsafe {
let o = JsObject::from_void_ptr(self.as_pointer());
o.unroot();
self.value
.set(JsObject::into_void_ptr(o).map_addr(|addr| addr | OBJECT_TYPE));
}
}
}
#[inline]
fn finalize_glue(&self) {
if let Some(o) = self.as_object() {
o.finalize_glue();
}
}
}
/// Represents a reference to a boxed pointer type inside a [`JsValue`]
///
/// This is exclusively used to return references to [`JsString`], [`JsObject`],
/// [`JsSymbol`] and [`JsBigInt`], since some [`JsValue`] implementations makes
/// returning proper references difficult.
/// It is mainly returned by the [`JsValue::variant`] method and the
/// `as_` methods for checked casts to pointer types.
///
/// [`Ref`] implements [`Deref`][`std::ops::Deref`], which facilitates conversion
/// to a proper [`reference`] by using the `ref` keyword or the
/// [`Option::as_deref`][`std::option::Option::as_deref`] method.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ref<'a, T> {
inner: ManuallyDrop<T>,
_marker: PhantomData<&'a T>,
}
impl<T> Ref<'_, T> {
#[inline]
fn new(inner: ManuallyDrop<T>) -> Self {
Self {
inner,
_marker: PhantomData,
}
}
}
// Lift `Ref` over `AsRef`, since implementing `AsRef<T>` would override the
// `as_ref` implementations of `T`.
impl<U, T> AsRef<U> for Ref<'_, T>
where
T: AsRef<U>,
{
#[inline]
fn as_ref(&self) -> &U {
<T as AsRef<U>>::as_ref(&*self)
}
}
impl<T> std::ops::Deref for Ref<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl<T: PartialEq> PartialEq<T> for Ref<'_, T> {
#[inline]
fn eq(&self, other: &T) -> bool {
&**self == other
}
}
impl<T> std::borrow::Borrow<T> for Ref<'_, T> {
#[inline]
fn borrow(&self) -> &T {
&**self
}
}
#[cfg(test)]
mod tests_nan_box {
use crate::object::ObjectData;
use super::*;
#[test]
fn bigint() {
let value = JsValue::new(JsBigInt::new(12345));
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(!value.is_boolean());
assert!(!value.is_object());
assert!(!value.is_string());
assert!(!value.is_symbol());
assert!(value.is_bigint());
let bigint = value.as_bigint().unwrap();
assert_eq!(&bigint, &JsBigInt::new(12345));
}
#[test]
fn symbol() {
let value = JsValue::new(JsSymbol::new(Some("description...".into())));
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(!value.is_boolean());
assert!(!value.is_object());
assert!(!value.is_string());
assert!(value.is_symbol());
let symbol = value.as_symbol().unwrap();
assert_eq!(symbol.description(), Some("description...".into()));
}
#[test]
fn string() {
let value = JsValue::new("I am a string :)");
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(!value.is_boolean());
assert!(!value.is_object());
assert!(value.is_string());
let string = value.as_string().unwrap();
assert_eq!(JsString::refcount(&string), Some(1));
assert_eq!(*string, "I am a string :)");
}
#[test]
fn object() {
//let context = Context::default();
let o1 = JsObject::from_proto_and_data(None, ObjectData::ordinary());
// let value = JsValue::new(context.construct_object());
let value = JsValue::new(o1.clone());
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(!value.is_boolean());
assert!(value.is_object());
let o2 = value.as_object().unwrap();
assert!(JsObject::equals(&o1, &o2));
}
#[test]
fn boolean() {
let value = JsValue::new(true);
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(value.is_boolean());
assert_eq!(value.as_boolean(), Some(true));
let value = JsValue::new(false);
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(!value.is_float64());
assert!(value.is_boolean());
assert_eq!(value.as_boolean(), Some(false));
}
#[test]
fn float64() {
let value = JsValue::new(1.3);
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(!value.is_integer32());
assert!(value.is_float64());
assert_eq!(value.as_float64(), Some(1.3));
let value = JsValue::new(f64::MAX);
assert!(value.is_float64());
assert_eq!(value.as_float64(), Some(f64::MAX));
let value = JsValue::new(f64::MIN);
assert!(value.is_float64());
assert_eq!(value.as_float64(), Some(f64::MIN));
let value = JsValue::nan();
assert!(value.is_float64());
assert!(value.as_float64().unwrap().is_nan());
let value = JsValue::new(12345);
assert!(!value.is_float64());
assert_eq!(value.as_float64(), None);
let value = JsValue::undefined();
assert!(!value.is_float64());
assert_eq!(value.as_float64(), None);
let value = JsValue::null();
assert!(!value.is_float64());
assert_eq!(value.as_float64(), None);
}
#[test]
fn undefined() {
let value = JsValue::undefined();
println!("{:?}", value);
println!("{:?}", UNDEFINED_TYPE);
assert!(value.is_undefined());
}
#[test]
fn null() {
let value = JsValue::null();
assert!(value.is_null());
assert!(value.is_null_or_undefined());
assert!(!value.is_undefined());
}
#[test]
fn integer32() {
let value = JsValue::new(-0xcafe);
assert!(!value.is_null());
assert!(!value.is_null_or_undefined());
assert!(!value.is_undefined());
assert!(value.is_integer32());
assert_eq!(value.as_integer32(), Some(-0xcafe));
let value = JsValue::null();
assert_eq!(value.as_integer32(), None);
}
}

4
boa_engine/src/value/tests.rs

@ -694,7 +694,7 @@ mod cyclic_conversions {
let value = forward_val(&mut context, src).unwrap(); let value = forward_val(&mut context, src).unwrap();
let result = value.as_string().unwrap(); let result = value.as_string().unwrap();
assert_eq!(result, "[[],[]]",); assert_eq!(*result, "[[],[]]",);
} }
// These tests don't throw errors. Instead we mirror Chrome / Firefox behavior for these // These tests don't throw errors. Instead we mirror Chrome / Firefox behavior for these
@ -711,7 +711,7 @@ mod cyclic_conversions {
let value = forward_val(&mut context, src).unwrap(); let value = forward_val(&mut context, src).unwrap();
let result = value.as_string().unwrap(); let result = value.as_string().unwrap();
assert_eq!(result, ""); assert_eq!(*result, "");
} }
#[test] #[test]

20
boa_engine/src/value/type.rs

@ -1,4 +1,4 @@
use super::JsValue; use super::{JsValue, JsVariant};
/// Possible types of values as defined at <https://tc39.es/ecma262/#sec-typeof-operator>. /// Possible types of values as defined at <https://tc39.es/ecma262/#sec-typeof-operator>.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -21,15 +21,15 @@ impl JsValue {
/// ///
/// Check [`JsValue::type_of`] if you need to call the `typeof` operator. /// Check [`JsValue::type_of`] if you need to call the `typeof` operator.
pub fn get_type(&self) -> Type { pub fn get_type(&self) -> Type {
match *self { match self.variant() {
Self::Rational(_) | Self::Integer(_) => Type::Number, JsVariant::Float64(_) | JsVariant::Integer32(_) => Type::Number,
Self::String(_) => Type::String, JsVariant::String(_) => Type::String,
Self::Boolean(_) => Type::Boolean, JsVariant::Boolean(_) => Type::Boolean,
Self::Symbol(_) => Type::Symbol, JsVariant::Symbol(_) => Type::Symbol,
Self::Null => Type::Null, JsVariant::Null => Type::Null,
Self::Undefined => Type::Undefined, JsVariant::Undefined => Type::Undefined,
Self::BigInt(_) => Type::BigInt, JsVariant::BigInt(_) => Type::BigInt,
Self::Object(_) => Type::Object, JsVariant::Object(_) => Type::Object,
} }
} }
} }

24
boa_engine/src/vm/code_block.rs

@ -20,7 +20,7 @@ use crate::{
syntax::ast::node::FormalParameterList, syntax::ast::node::FormalParameterList,
vm::call_frame::GeneratorResumeKind, vm::call_frame::GeneratorResumeKind,
vm::{call_frame::FinallyReturn, CallFrame, Opcode}, vm::{call_frame::FinallyReturn, CallFrame, Opcode},
Context, JsResult, JsValue, Context, JsResult, JsValue, value::JsVariant,
}; };
use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::{Interner, Sym, ToInternedString}; use boa_interner::{Interner, Sym, ToInternedString};
@ -749,7 +749,7 @@ impl JsObject {
let args = if code.params.parameters.len() > args.len() { let args = if code.params.parameters.len() > args.len() {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.extend(vec![ v.extend(vec![
JsValue::Undefined; JsValue::undefined();
code.params.parameters.len() - args.len() code.params.parameters.len() - args.len()
]); ]);
v v
@ -872,7 +872,7 @@ impl JsObject {
let args = if code.params.parameters.len() > args.len() { let args = if code.params.parameters.len() > args.len() {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.extend(vec![ v.extend(vec![
JsValue::Undefined; JsValue::undefined();
code.params.parameters.len() - args.len() code.params.parameters.len() - args.len()
]); ]);
v v
@ -989,7 +989,7 @@ impl JsObject {
let mut args = if code.params.parameters.len() > args.len() { let mut args = if code.params.parameters.len() > args.len() {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.extend(vec![ v.extend(vec![
JsValue::Undefined; JsValue::undefined();
code.params.parameters.len() - args.len() code.params.parameters.len() - args.len()
]); ]);
v v
@ -1124,7 +1124,7 @@ impl JsObject {
let mut args = if code.params.parameters.len() > args.len() { let mut args = if code.params.parameters.len() > args.len() {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.extend(vec![ v.extend(vec![
JsValue::Undefined; JsValue::undefined();
code.params.parameters.len() - args.len() code.params.parameters.len() - args.len()
]); ]);
v v
@ -1238,10 +1238,10 @@ impl JsObject {
let constructor = *constructor; let constructor = *constructor;
drop(object); drop(object);
match function(this_target, args, context)? { match function(this_target, args, context)?.variant() {
JsValue::Object(ref o) => Ok(o.clone()), JsVariant::Object(o) => Ok(o.clone()),
val => { val => {
if constructor.expect("hmm").is_base() || val.is_undefined() { if constructor.expect("hmm").is_base() || matches!(val, JsVariant::Undefined) {
create_this(context) create_this(context)
} else { } else {
context.throw_type_error( context.throw_type_error(
@ -1262,10 +1262,10 @@ impl JsObject {
let constructor = *constructor; let constructor = *constructor;
drop(object); drop(object);
match (function)(this_target, args, captures, context)? { match (function)(this_target, args, captures, context)?.variant() {
JsValue::Object(ref o) => Ok(o.clone()), JsVariant::Object(o) => Ok(o.clone()),
val => { val => {
if constructor.expect("hmma").is_base() || val.is_undefined() { if constructor.expect("hmma").is_base() || matches!(val, JsVariant::Undefined) {
create_this(context) create_this(context)
} else { } else {
context.throw_type_error( context.throw_type_error(
@ -1369,7 +1369,7 @@ impl JsObject {
let args = if code.params.parameters.len() > args.len() { let args = if code.params.parameters.len() > args.len() {
let mut v = args.to_vec(); let mut v = args.to_vec();
v.extend(vec![ v.extend(vec![
JsValue::Undefined; JsValue::undefined();
code.params.parameters.len() - args.len() code.params.parameters.len() - args.len()
]); ]);
v v

52
boa_engine/src/vm/mod.rs

@ -12,7 +12,7 @@ use crate::{
environments::EnvironmentSlots, environments::EnvironmentSlots,
object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement}, object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement},
property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey}, property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey},
value::Numeric, value::{JsVariant, Numeric},
vm::{ vm::{
call_frame::CatchAddresses, call_frame::CatchAddresses,
code_block::{initialize_instance_elements, Readable}, code_block::{initialize_instance_elements, Readable},
@ -229,17 +229,17 @@ impl Context {
self.vm.push(class); self.vm.push(class);
self.vm.push(proto); self.vm.push(proto);
} else if superclass.is_null() { } else if superclass.is_null() {
self.vm.push(JsValue::Null); self.vm.push(JsValue::null());
} else { } else {
return self.throw_type_error("superclass must be a constructor"); return self.throw_type_error("superclass must be a constructor");
} }
} }
Opcode::SetClassPrototype => { Opcode::SetClassPrototype => {
let prototype_value = self.vm.pop(); let prototype_value = self.vm.pop();
let prototype = match &prototype_value { let prototype = match prototype_value.variant() {
JsValue::Object(proto) => Some(proto.clone()), JsVariant::Object(proto) => Some(proto.clone()),
JsValue::Null => None, JsVariant::Null => None,
JsValue::Undefined => { JsVariant::Undefined => {
Some(self.intrinsics().constructors().object().prototype.clone()) Some(self.intrinsics().constructors().object().prototype.clone())
} }
_ => unreachable!(), _ => unreachable!(),
@ -484,7 +484,7 @@ impl Context {
.into(); .into();
self.global_bindings_mut().entry(key).or_insert( self.global_bindings_mut().entry(key).or_insert(
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(JsValue::Undefined) .value(JsValue::undefined())
.writable(true) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(true) .configurable(true)
@ -494,7 +494,7 @@ impl Context {
self.realm.environments.put_value_if_uninitialized( self.realm.environments.put_value_if_uninitialized(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
JsValue::Undefined, JsValue::undefined(),
); );
} }
} }
@ -526,7 +526,7 @@ impl Context {
self.realm.environments.put_value( self.realm.environments.put_value(
binding_locator.environment_index(), binding_locator.environment_index(),
binding_locator.binding_index(), binding_locator.binding_index(),
JsValue::Undefined, JsValue::undefined(),
); );
} }
Opcode::DefInitLet | Opcode::DefInitConst | Opcode::DefInitArg => { Opcode::DefInitLet | Opcode::DefInitConst | Opcode::DefInitArg => {
@ -1530,10 +1530,10 @@ impl Context {
if let Some(proto) = home.__get_prototype_of__(self)? { if let Some(proto) = home.__get_prototype_of__(self)? {
self.vm.push(JsValue::from(proto)); self.vm.push(JsValue::from(proto));
} else { } else {
self.vm.push(JsValue::Null); self.vm.push(JsValue::null());
} }
} else { } else {
self.vm.push(JsValue::Null); self.vm.push(JsValue::null());
}; };
} }
Opcode::SuperCall => { Opcode::SuperCall => {
@ -1739,8 +1739,8 @@ impl Context {
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let this = self.vm.pop();
let object = match func { let object = match func.variant() {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsVariant::Object(object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
@ -1755,7 +1755,7 @@ impl Context {
crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?;
self.vm.push(result); self.vm.push(result);
} else { } else {
self.vm.push(JsValue::Undefined); self.vm.push(JsValue::undefined());
} }
} else { } else {
let result = object.__call__(&this, &arguments, self)?; let result = object.__call__(&this, &arguments, self)?;
@ -1782,8 +1782,8 @@ impl Context {
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let this = self.vm.pop();
let object = match func { let object = match func.variant() {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsVariant::Object(object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
@ -1798,7 +1798,7 @@ impl Context {
crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?;
self.vm.push(result); self.vm.push(result);
} else { } else {
self.vm.push(JsValue::Undefined); self.vm.push(JsValue::undefined());
} }
} else { } else {
let result = object.__call__(&this, &arguments, self)?; let result = object.__call__(&this, &arguments, self)?;
@ -1819,8 +1819,8 @@ impl Context {
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let this = self.vm.pop();
let object = match func { let object = match func.variant() {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsVariant::Object(object) if object.is_callable() => object,
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
@ -1848,8 +1848,8 @@ impl Context {
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let this = self.vm.pop();
let object = match func { let object = match func.variant() {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsVariant::Object(object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
@ -1872,7 +1872,7 @@ impl Context {
let result = func let result = func
.as_constructor() .as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor")) .ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&arguments, cons, self))?; .and_then(|cons| cons.__construct__(&arguments, &cons, self))?;
self.vm.push(result); self.vm.push(result);
} }
@ -1897,7 +1897,7 @@ impl Context {
let result = func let result = func
.as_constructor() .as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor")) .ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&arguments, cons, self))?; .and_then(|cons| cons.__construct__(&arguments, &cons, self))?;
self.vm.push(result); self.vm.push(result);
} }
@ -2054,7 +2054,7 @@ impl Context {
let iterator = iterator.as_object().expect("iterator was not an object"); let iterator = iterator.as_object().expect("iterator was not an object");
if !done { if !done {
let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done);
iterator_record.close(Ok(JsValue::Null), self)?; iterator_record.close(Ok(JsValue::null()), self)?;
} }
} }
Opcode::IteratorToArray => { Opcode::IteratorToArray => {
@ -2309,7 +2309,7 @@ impl Context {
self.vm.frame_mut().pc = done_address as usize; self.vm.frame_mut().pc = done_address as usize;
let iterator_record = let iterator_record =
IteratorRecord::new(iterator.clone(), next_method, done); IteratorRecord::new(iterator.clone(), next_method, done);
iterator_record.close(Ok(JsValue::Undefined), self)?; iterator_record.close(Ok(JsValue::undefined()), self)?;
let error = let error =
self.construct_type_error("iterator does not have a throw method"); self.construct_type_error("iterator does not have a throw method");
return Err(error); return Err(error);
@ -2582,7 +2582,7 @@ impl Context {
} }
Ok(ShouldExit::False) => {} Ok(ShouldExit::False) => {}
Ok(ShouldExit::Yield) => { Ok(ShouldExit::Yield) => {
let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); let result = self.vm.stack.pop().unwrap_or(JsValue::undefined());
return Ok((result, ReturnType::Yield)); return Ok((result, ReturnType::Yield));
} }
Err(e) => { Err(e) => {

2
boa_engine/src/vm/tests.rs

@ -62,7 +62,7 @@ fn multiple_catches() {
assert_eq!( assert_eq!(
Context::default().eval(source.as_bytes()), Context::default().eval(source.as_bytes()),
Ok(JsValue::Undefined) Ok(JsValue::undefined())
); );
} }

2
boa_examples/src/bin/modulehandler.rs

@ -46,7 +46,7 @@ fn require(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue
let buffer = read_to_string(libfile); let buffer = read_to_string(libfile);
if let Err(..) = buffer { if let Err(..) = buffer {
println!("Error: {}", buffer.unwrap_err()); println!("Error: {}", buffer.unwrap_err());
Ok(JsValue::Rational(-1.0)) Ok(JsValue::from(-1.0))
} else { } else {
// Load and parse the module source // Load and parse the module source
ctx.eval(&buffer.unwrap()).unwrap(); ctx.eval(&buffer.unwrap()).unwrap();

2
boa_tester/src/exec/js262.rs

@ -64,7 +64,7 @@ fn detach_array_buffer(
let key = args.get_or_undefined(1); let key = args.get_or_undefined(1);
// 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception.
if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) { if !JsValue::same_value(&array_buffer.array_buffer_detach_key, &key) {
return context.throw_type_error("Cannot detach array buffer with different key"); return context.throw_type_error("Cannot detach array buffer with different key");
} }

Loading…
Cancel
Save