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_profiler",
"boa_unicode",
"cfg-if",
"chrono",
"criterion",
"dyn-clone",
@ -91,6 +92,7 @@ dependencies = [
"num-bigint",
"num-integer",
"num-traits",
"num_enum",
"once_cell",
"rand",
"regress",
@ -98,6 +100,7 @@ dependencies = [
"ryu-js",
"serde",
"serde_json",
"sptr",
"sys-locale",
"tap",
"unicode-normalization",
@ -992,6 +995,27 @@ dependencies = [
"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]]
name = "once_cell"
version = "1.14.0"
@ -1136,6 +1160,16 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-error"
version = "1.0.4"
@ -1459,6 +1493,12 @@ dependencies = [
"serde",
]
[[package]]
name = "sptr"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0d4365121b91e8522958da77ce29c004333daeaf0711225c4727c8c5f49941"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -1618,6 +1658,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-general-category"
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)]
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')]
trace: bool,

4
boa_engine/Cargo.toml

@ -23,6 +23,7 @@ intl = [
"dep:icu_testdata",
"dep:sys-locale"
]
nan_boxing = ["dep:sptr", "dep:num_enum"]
# Enable Boa's WHATWG console object implementation.
console = []
@ -50,6 +51,9 @@ unicode-normalization = "0.1.21"
dyn-clone = "1.0.9"
once_cell = "1.14.0"
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_locid = { 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.
use crate::{builtins::Number, Context, JsValue};
use crate::{builtins::Number, value::PointerType, Context, JsValue};
use num_integer::Integer;
use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero};
use std::{
fmt::{self, Display},
mem::{self, ManuallyDrop},
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
rc::Rc,
};
@ -22,6 +23,16 @@ pub struct JsBigInt {
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 {
/// Create a new [`JsBigInt`].
#[inline]

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

@ -68,7 +68,8 @@ impl ArrayIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
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
.as_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},
symbol::WellKnownSymbols,
value::{IntegerOrInfinity, JsValue},
value::{IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult, JsString,
};
use std::cmp::{max, min, Ordering};
@ -374,7 +374,7 @@ impl Array {
// 7. If IsConstructor(C) is false, throw a TypeError exception.
if let Some(c) = c.as_constructor() {
// 8. Return ? Construct(C, « 𝔽(length) »).
c.construct(&[JsValue::new(length)], Some(c), context)
c.construct(&[JsValue::new(length)], Some(&c), context)
} else {
context.throw_type_error("Symbol.species must be a constructor")
}
@ -404,9 +404,9 @@ impl Array {
// 3. Else,
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
// b. Let mapping be true.
let mapping = match mapfn {
JsValue::Undefined => None,
JsValue::Object(o) if o.is_callable() => Some(o),
let mapping = match mapfn.variant() {
JsVariant::Undefined => None,
JsVariant::Object(o) if o.is_callable() => Some(o),
_ => return context.throw_type_error(format!("{} is not a function", mapfn.type_of())),
};
@ -423,7 +423,8 @@ impl Array {
// b. Else,
// i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[], None, context)?,
Some(constructor) => constructor
.construct(&[], None, context)?,
_ => Self::array_create(0, None, context)?,
};
@ -455,9 +456,9 @@ impl Array {
let next_value = next.value(context)?;
// 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) »).
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).
if_abrupt_close_iterator!(mapped_value, iterator_record, context)
@ -496,7 +497,8 @@ impl Array {
// 10. Else,
// a. Let A be ? ArrayCreate(len).
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)?,
};
@ -509,10 +511,10 @@ impl Array {
// b. Let kValue be ? Get(arrayLike, Pk).
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
// 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 {
// d. Else, let mappedValue be kValue.
k_value
@ -572,7 +574,8 @@ impl Array {
// 5. Else,
// a. Let A be ? ArrayCreate(len).
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)?,
};
@ -810,7 +813,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 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")
})?;
// 4. Let k be 0.
@ -826,7 +830,7 @@ impl Array {
let k_value = o.get(pk, context)?;
// ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
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.
}
@ -1151,7 +1155,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1169,7 +1174,7 @@ impl Array {
let k_value = o.get(k, context)?;
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
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();
// iii. If testResult is false, return false.
if !test_result {
@ -1203,7 +1208,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1224,7 +1230,7 @@ impl Array {
let k_value = o.get(k, context)?;
// ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
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).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
@ -1376,7 +1382,7 @@ impl Array {
let element_k = o.get(k, context)?;
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// 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));
}
}
@ -1411,7 +1417,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1428,7 +1435,7 @@ impl Array {
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate
.call(
this_arg,
&this_arg,
&[k_value.clone(), k.into(), o.clone().into()],
context,
)?
@ -1468,7 +1475,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1484,7 +1492,7 @@ impl Array {
let k_value = o.get(pk, context)?;
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
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();
// d. If testResult is true, return 𝔽(k).
if test_result {
@ -1519,7 +1527,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1534,7 +1543,7 @@ impl Array {
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
let test_result = predicate
.call(
this_arg,
&this_arg,
&[k_value.clone(), k.into(), this.clone()],
context,
)?
@ -1571,7 +1580,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1585,7 +1595,7 @@ impl Array {
let k_value = o.get(k, context)?;
// c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
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();
// d. If testResult is true, return 𝔽(k).
if test_result {
@ -1676,7 +1686,8 @@ impl Array {
let source_len = o.length_of_array_like(context)?;
// 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")
})?;
@ -1690,8 +1701,8 @@ impl Array {
source_len,
0,
1,
Some(mapper_function),
args.get_or_undefined(1),
Some(&mapper_function),
&args.get_or_undefined(1),
context,
)?;
@ -1780,7 +1791,7 @@ impl Array {
// 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
target_index = Self::flatten_into_array(
target,
element,
&element,
element_len,
target_index,
new_depth,
@ -1924,7 +1935,7 @@ impl Array {
// a. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?;
// 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));
}
// c. Set k to k + 1.
@ -2205,7 +2216,8 @@ impl Array {
let length = o.length_of_array_like(context)?;
// 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")
})?;
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())];
// 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
if selected {
@ -2269,7 +2281,8 @@ impl Array {
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 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")
})?;
@ -2286,7 +2299,7 @@ impl Array {
// ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let this_arg = args.get_or_undefined(1);
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();
// iii. If testResult is true, return true.
if test_result {
@ -2317,9 +2330,10 @@ impl Array {
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let comparefn = match args.get_or_undefined(0) {
JsValue::Object(ref obj) if obj.is_callable() => Some(obj),
JsValue::Undefined => None,
let comparefn = args.get_or_undefined(0);
let comparefn = match comparefn.variant() {
JsVariant::Object(obj) if obj.is_callable() => Some(obj),
JsVariant::Undefined => None,
_ => {
return context.throw_type_error(
"The comparison function must be either a function or undefined",
@ -2346,11 +2360,11 @@ impl Array {
}
// 4. If comparefn is not undefined, then
if let Some(cmp) = comparefn {
if let Some(ref cmp) = comparefn {
let args = [x.clone(), y.clone()];
// a. Let v be ? ToNumber(? Call(comparefn, undefined, « x, y »)).
let v = cmp
.call(&JsValue::Undefined, &args, context)?
.call(&JsValue::undefined(), &args, context)?
.to_number(context)?;
// b. If v is NaN, return +0𝔽.
// c. Return v.
@ -2454,7 +2468,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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.reduce: callback function is not callable")
})?;
@ -2549,7 +2564,8 @@ impl Array {
let len = o.length_of_array_like(context)?;
// 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.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.
if this
.as_object()
.map(|obj| JsObject::equals(obj, &new))
.map(|obj| JsObject::equals(&obj, &new))
.unwrap_or_default()
{
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 {
array_buffer_data: Some(block),
array_buffer_byte_length: byte_length,
array_buffer_detach_key: JsValue::Undefined,
array_buffer_detach_key: JsValue::undefined(),
});
// 5. Return obj.

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

@ -203,7 +203,7 @@ impl AsyncGenerator {
}
// 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).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -221,7 +221,7 @@ impl AsyncGenerator {
drop(generator_obj_mut);
Self::resume(
generator_object,
&generator_object,
state,
&generator_context,
completion,
@ -272,7 +272,7 @@ impl AsyncGenerator {
if_abrupt_reject_promise!(generator, promise_capability, context);
// 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).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -305,7 +305,7 @@ impl AsyncGenerator {
drop(generator_obj_mut);
Self::resume(
generator_object,
&generator_object,
state,
&generator_context,
completion,
@ -376,7 +376,7 @@ impl AsyncGenerator {
.reject()
.call(
&JsValue::undefined(),
&[args.get_or_undefined(0).clone()],
&[args.get_or_undefined(0)],
context,
)
.expect("cannot fail per spec");
@ -386,7 +386,7 @@ impl AsyncGenerator {
}
// 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).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -401,7 +401,7 @@ impl AsyncGenerator {
// a. Perform AsyncGeneratorResume(generator, completion).
Self::resume(
generator_object,
&generator_object,
state,
&generator_context,
completion,
@ -622,7 +622,7 @@ impl AsyncGenerator {
gen.state = AsyncGeneratorState::Completed;
// 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).
let next = gen.queue.pop_front().expect("must have one entry");
@ -655,7 +655,7 @@ impl AsyncGenerator {
gen.state = AsyncGeneratorState::Completed;
// 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).
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
// 1. If Type(value) is BigInt, return value.
.as_bigint()
.as_deref()
.cloned()
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
// 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!(
&*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)]
pub(crate) fn dir(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
logger(
LogMessage::Info(display_obj(args.get_or_undefined(0), true)),
LogMessage::Info(display_obj(&args.get_or_undefined(0), true)),
context.console(),
);
Ok(JsValue::undefined())

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

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

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

@ -9,7 +9,7 @@ use crate::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
symbol::WellKnownSymbols,
value::{JsValue, PreferredType},
value::{JsValue, JsVariant, PreferredType},
Context, JsResult, JsString,
};
use boa_profiler::Profiler;
@ -390,24 +390,25 @@ impl Date {
context: &mut Context,
) -> JsResult<JsObject> {
let value = &args[0];
let tv = match this_time_value(value, context) {
Ok(dt) => dt.0,
_ => match value.to_primitive(context, PreferredType::Default)? {
JsValue::String(ref str) => match chrono::DateTime::parse_from_rfc3339(str) {
let tv = if let Ok(dt) = this_time_value(value, context) {
dt.0
} else {
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()),
_ => 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());
@ -512,7 +513,7 @@ impl Date {
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
// a. Let tryFirst be string.
Some("string" | "default") => PreferredType::String,
@ -1848,7 +1849,7 @@ impl Date {
match DateTime::parse_from_rfc3339(&args[0].to_string(context)?) {
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")
};
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() {
date_time.0
} else {

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

@ -86,11 +86,11 @@ impl AggregateError {
}
// 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).
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",
// PropertyDescriptor {
// [[Configurable]]: true,

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

@ -83,7 +83,7 @@ impl EvalError {
}
// 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.
Ok(o.into())

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

@ -120,7 +120,7 @@ impl Error {
}
// 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.
Ok(o.into())

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

@ -81,7 +81,7 @@ impl RangeError {
}
// 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.
Ok(o.into())

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

@ -87,7 +87,7 @@ impl ReferenceError {
}
// 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.
Ok(o.into())

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

@ -86,7 +86,7 @@ impl SyntaxError {
}
// 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.
Ok(o.into())

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

@ -87,7 +87,7 @@ impl TypeError {
}
// 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.
Ok(o.into())

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

@ -82,7 +82,7 @@ impl UriError {
}
// 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.
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
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> Result<JsValue, JsValue> {
// 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 )`

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.
//
// The following logic implements the steps 17-19 adjusted for our environment structure.
let mut bindings = FxHashMap::default();
let mut property_index = 0;
'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
// b. Return ? Call(func, thisArg).
return func.call(this_arg, &[], context);
return func.call(&this_arg, &[], context);
}
// 4. Let argList be ? CreateListFromArrayLike(argArray).
@ -692,7 +692,7 @@ impl BuiltInFunctionObject {
// TODO?: 5. PrepareForTailCall
// 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 )`
@ -714,7 +714,7 @@ impl BuiltInFunctionObject {
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 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.
let target_name = target_name
.as_string()
.map_or(JsString::new(""), Clone::clone);
.as_deref()
.cloned()
.unwrap_or_default();
// 10. Perform SetFunctionName(F, targetName, "bound").
set_function_name(&f, &target_name.into(), Some("bound"), context);
@ -800,12 +802,13 @@ impl BuiltInFunctionObject {
// TODO?: 3. Perform PrepareForTailCall
// 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)]
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
.as_deref()
.and_then(Object::as_function)
@ -855,7 +858,7 @@ impl BuiltInFunctionObject {
fn has_instance(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let F be the this value.
// 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)]

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

@ -129,7 +129,7 @@ fn function_prototype_call() {
"#;
let value = forward_val(&mut context, func).unwrap();
assert!(value.is_string());
assert_eq!(value.as_string().unwrap(), "[object Error]");
assert_eq!(*value.as_string().unwrap(), "[object Error]");
}
#[test]
@ -246,7 +246,7 @@ fn closure_capture_clone() {
object
.__get_own_property__(&"key".into(), context)?
.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(hw.into())

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

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

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

@ -77,7 +77,7 @@ impl Intl {
let ll = canonicalize_locale_list(args, context)?;
// 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()),
context,
)))
@ -595,7 +595,7 @@ fn resolve_locale(
// e. Let value be keyLocaleData[0].
// TODO f. Assert: Type(value) is either String or Null.
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(),
};
@ -616,7 +616,7 @@ fn resolve_locale(
// a. If keyLocaleData contains requestedValue, then
if key_locale_data.contains(requested_value) {
// 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
// of "-", key, "-", and value.
supported_extension_addition =
@ -625,7 +625,7 @@ fn resolve_locale(
// 4. Else if keyLocaleData contains "true", then
} else if key_locale_data.contains(&JsString::new("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.
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 options_val_str.is_empty() {
// 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,
// throw a RangeError exception.
value = match r#type {
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()),
GetOptionType::Boolean => JsValue::from(value.to_boolean()),
GetOptionType::String => {
let string_value = value.to_string(context)?;
if !values.is_empty() && !values.contains(&string_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 values = Vec::<JsString>::new();
let fallback = JsValue::String(JsString::new("fallback"));
let fallback = JsValue::from(JsString::new("fallback"));
let options_obj = JsObject::empty();
let option_type = GetOptionType::String;
let get_option_result = get_option(
@ -299,9 +299,9 @@ fn get_opt() {
assert_eq!(get_option_result, fallback);
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 locale_value = JsValue::String(JsString::new("en-US"));
let locale_value = JsValue::from(JsString::new("en-US"));
options_obj
.set("Locale", locale_value.clone(), true, &mut context)
.expect("Setting a property should not fail");
@ -317,10 +317,10 @@ fn get_opt() {
.expect("GetOption should not fail on string test");
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 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];
options_obj
.set("Locale", locale_value.clone(), true, &mut context)
@ -356,9 +356,9 @@ fn get_opt() {
.expect("GetOption should not fail on boolean test");
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 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 values = vec![other_locale_str];
options_obj
@ -473,7 +473,7 @@ fn to_date_time_opts() {
)
.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!(
date_time_opts.get("year", &mut context),
Ok(numeric_jsstring.clone())
@ -495,7 +495,7 @@ fn to_date_time_opts() {
)
.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!(
date_time_opts.get("hour", &mut context),
Ok(numeric_jsstring.clone())
@ -517,7 +517,7 @@ fn to_date_time_opts() {
)
.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!(
date_time_opts.get("year", &mut context),
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).
let sync_method = self
.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).
let _sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method));
@ -163,7 +163,7 @@ impl JsValue {
} else {
// b. Otherwise, set method to ? GetMethod(obj, @@iterator).
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())?;
// 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%).
let root = context.construct_object();
@ -129,7 +129,7 @@ impl Json {
let val = holder.get(name.clone(), context)?;
// 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).
// b. If isArray is true, then
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
if let Some(space_obj) = space.as_object() {
@ -332,7 +332,7 @@ impl Json {
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
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");
// 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
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
if obj.is_number() {
// i. Set value to ? ToNumber(value).
@ -427,7 +427,7 @@ impl Json {
}
// 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)));
}
@ -452,7 +452,7 @@ impl Json {
}
// 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() {
// a. Let isArray be ? IsArray(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
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
.as_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},
symbol::WellKnownSymbols,
value::JsVariant,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
@ -141,7 +142,7 @@ impl Map {
let adder = map.get("set", context)?;
// 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 ]`
@ -219,11 +220,11 @@ impl Map {
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow_mut().as_map_mut() {
let key = match key {
JsValue::Rational(r) => {
let key = match key.variant() {
JsVariant::Float64(r) => {
// 5. If key is -0𝔽, set key to +0𝔽.
if r.is_zero() {
JsValue::Rational(0f64)
JsValue::new(0f64)
} else {
key.clone()
}
@ -235,7 +236,7 @@ impl Map {
// i. Set p.[[Value]] to value.
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
// 7. Append p as the last element of entries.
map.insert(key, value.clone());
map.insert(key, value);
// ii. Return M.
// 8. Return M.
return Ok(this.clone());
@ -303,7 +304,7 @@ impl Map {
// ii. Set p.[[Value]] to empty.
// iii. Return true.
// 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")
@ -324,29 +325,21 @@ impl Map {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
let key = args.get_or_undefined(0);
let key = match key {
JsValue::Rational(r) => {
if r.is_zero() {
JS_ZERO
} else {
key
}
}
_ => key,
let key = match key.variant() {
JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64),
_ => key.clone(),
};
// 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]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow().as_map_ref() {
// 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]].
// 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],
context: &mut Context,
) -> JsResult<JsValue> {
const JS_ZERO: &JsValue = &JsValue::Rational(0f64);
let key = args.get_or_undefined(0);
let key = match key {
JsValue::Rational(r) => {
if r.is_zero() {
JS_ZERO
} else {
key
}
}
_ => key,
let key = match key.variant() {
JsVariant::Float64(r) if r.is_zero() => JsValue::new(0f64),
_ => key.clone(),
};
// 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]]).
// 3. Let entries be the List that is M.[[MapData]].
if let Some(map) = object.borrow().as_map_ref() {
// 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.
// 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
if let Some(arguments) = arguments {
// i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »).
callback.call(this_arg, &arguments, context)?;
callback.call(&this_arg, &arguments, context)?;
}
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
/// `args.get(n).cloned().unwrap_or_default()` or
/// `args.get(n).unwrap_or(&undefined)`.
///
/// 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;
fn get_or_undefined(&self, index: usize) -> JsValue;
}
impl JsArgs for [JsValue] {
fn get_or_undefined(&self, index: usize) -> &JsValue {
const UNDEFINED: &JsValue = &JsValue::Undefined;
self.get(index).unwrap_or(UNDEFINED)
fn get_or_undefined(&self, index: usize) -> JsValue {
self.get(index).cloned().unwrap_or_else(JsValue::undefined)
}
}

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

@ -21,7 +21,7 @@ use crate::{
JsObject, ObjectData,
},
property::Attribute,
value::{AbstractRelation, IntegerOrInfinity, JsValue},
value::{AbstractRelation, IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult,
};
use boa_profiler::Profiler;
@ -220,7 +220,8 @@ impl Number {
// 1. Let x be ? thisNumberValue(this value).
let this_num = Self::this_number_value(this, context)?;
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).
Some(n) => Some(n.to_integer_or_infinity(context)?),
};
@ -992,9 +993,9 @@ impl Number {
_ctx: &mut Context,
) -> JsResult<JsValue> {
Ok(JsValue::new(if let Some(val) = args.get(0) {
match val {
JsValue::Integer(_) => true,
JsValue::Rational(number) => number.is_finite(),
match val.variant() {
JsVariant::Float64(number) => number.is_finite(),
JsVariant::Integer32(_) => true,
_ => false,
}
} else {
@ -1041,13 +1042,7 @@ impl Number {
args: &[JsValue],
_ctx: &mut Context,
) -> JsResult<JsValue> {
Ok(JsValue::new(
if let Some(&JsValue::Rational(number)) = args.get(0) {
number.is_nan()
} else {
false
},
))
Ok(JsValue::new(args.get(0).map_or(false, JsValue::is_nan)))
}
/// `Number.isSafeInteger( number )`
@ -1070,9 +1065,9 @@ impl Number {
args: &[JsValue],
_ctx: &mut Context,
) -> JsResult<JsValue> {
Ok(JsValue::new(match args.get(0) {
Some(JsValue::Integer(_)) => true,
Some(JsValue::Rational(number)) if Self::is_float_integer(*number) => {
Ok(JsValue::new(match args.get(0).map(JsValue::variant) {
Some(JsVariant::Integer32(_)) => true,
Some(JsVariant::Float64(number)) if Self::is_float_integer(number) => {
number.abs() <= Self::MAX_SAFE_INTEGER
}
_ => false,
@ -1087,9 +1082,9 @@ impl Number {
/// [spec]: https://tc39.es/ecma262/#sec-isinteger
#[inline]
pub(crate) fn is_integer(val: &JsValue) -> bool {
match val {
JsValue::Integer(_) => true,
JsValue::Rational(number) => Self::is_float_integer(*number),
match val.variant() {
JsVariant::Integer32(_) => true,
JsVariant::Float64(number) => Self::is_float_integer(number),
_ => 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
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
.as_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},
symbol::WellKnownSymbols,
value::JsValue,
value::{JsValue, JsVariant},
Context, JsResult, JsString,
};
use boa_profiler::Profiler;
@ -155,7 +155,7 @@ impl Object {
// 2. Return ? O.[[GetPrototypeOf]]().
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__`
@ -178,16 +178,17 @@ impl Object {
let this = this.require_object_coercible(context)?;
// 2. If Type(proto) is neither Object nor Null, return undefined.
let proto = match args.get_or_undefined(0) {
JsValue::Object(proto) => Some(proto.clone()),
JsValue::Null => None,
let proto = match args.get_or_undefined(0).variant() {
JsVariant::Object(proto) => Some(proto.clone()),
JsVariant::Null => None,
_ => return Ok(JsValue::undefined()),
};
// 3. If Type(O) is not Object, return undefined.
let object = match this {
JsValue::Object(object) => object,
_ => return Ok(JsValue::undefined()),
let object = if let Some(o) = this.as_object() {
o
} else {
return Ok(JsValue::undefined());
};
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
@ -389,9 +390,9 @@ impl Object {
let prototype = args.get_or_undefined(0);
let properties = args.get_or_undefined(1);
let obj = match prototype {
JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data(
prototype.as_object().cloned(),
let obj = match prototype.variant() {
JsVariant::Object(_) | JsVariant::Null => JsObject::from_proto_and_data(
prototype.as_object().as_deref().cloned(),
ObjectData::ordinary(),
),
_ => {
@ -403,7 +404,7 @@ impl Object {
};
if !properties.is_undefined() {
object_define_properties(&obj, properties, context)?;
object_define_properties(&obj, &properties, context)?;
return Ok(obj.into());
}
@ -480,7 +481,7 @@ impl Object {
}
// 5. Return descriptors.
Ok(descriptors.into())
Ok(JsValue::new(descriptors))
}
/// The abstract operation `FromPropertyDescriptor`.
@ -553,7 +554,7 @@ impl Object {
let x = args.get_or_undefined(0);
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.
@ -574,7 +575,7 @@ impl Object {
// 2. Return ? obj.[[GetPrototypeOf]]().
Ok(obj
.__get_prototype_of__(ctx)?
.map_or(JsValue::Null, JsValue::new))
.map_or(JsValue::null(), JsValue::new))
}
/// Set the `prototype` of an object.
@ -582,9 +583,13 @@ impl Object {
/// [More information][spec]
///
/// [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 {
return ctx.throw_type_error(format!(
return context.throw_type_error(format!(
"Object.setPrototypeOf: At least 2 arguments required, but only {} passed",
args.len()
));
@ -595,16 +600,19 @@ impl Object {
.get(0)
.cloned()
.unwrap_or_default()
.require_object_coercible(ctx)?
.require_object_coercible(context)?
.clone();
let proto = match args.get_or_undefined(1) {
JsValue::Object(obj) => Some(obj.clone()),
JsValue::Null => None,
let proto = args.get_or_undefined(1);
let proto = match proto.variant() {
JsVariant::Object(obj) => Some(obj.clone()),
JsVariant::Null => None,
// 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
val => {
return ctx
.throw_type_error(format!("expected an object or null, got {}", val.type_of()))
_ => {
return context.throw_type_error(format!(
"expected an object or null, got {}",
proto.type_of()
))
}
};
@ -616,11 +624,11 @@ impl Object {
};
// 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.
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.
@ -646,7 +654,7 @@ impl Object {
if !v.is_object() {
return Ok(JsValue::new(false));
}
let mut v = v.clone();
let mut v = v;
let o = JsValue::new(this.to_object(context)?);
loop {
v = Self::get_prototype_of(this, &[v], context)?;
@ -666,14 +674,14 @@ impl Object {
context: &mut Context,
) -> JsResult<JsValue> {
let object = args.get_or_undefined(0);
if let JsValue::Object(object) = object {
if let Some(object) = object.as_object() {
let key = args
.get(1)
.unwrap_or(&JsValue::Undefined)
.unwrap_or(&JsValue::undefined())
.to_property_key(context)?;
let desc = args
.get(2)
.unwrap_or(&JsValue::Undefined)
.unwrap_or(&JsValue::undefined())
.to_property_descriptor(context)?;
object.define_property_or_throw(key, desc, context)?;
@ -700,9 +708,9 @@ impl Object {
context: &mut Context,
) -> JsResult<JsValue> {
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);
object_define_properties(obj, props, context)?;
object_define_properties(obj, &props, context)?;
Ok(arg.clone())
} else {
context.throw_type_error("Expected an object")
@ -778,7 +786,8 @@ impl Object {
let tag = o.get(WellKnownSymbols::to_string_tag(), context)?;
// 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 "]".
Ok(format!("[object {tag_str}]").into())
@ -1152,7 +1161,7 @@ impl Object {
) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, string).
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 )`
@ -1170,7 +1179,7 @@ impl Object {
) -> JsResult<JsValue> {
// 1. Return ? GetOwnPropertyKeys(O, symbol).
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 )`
@ -1202,7 +1211,8 @@ impl Object {
/// [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> {
// 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%).
// 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);
// c. Set promiseCapability.[[Resolve]] to resolve.
promise_capability.resolve = resolve.clone();
promise_capability.resolve = resolve;
// d. Set promiseCapability.[[Reject]] to reject.
promise_capability.reject = reject.clone();
promise_capability.reject = reject;
// e. Return undefined.
Ok(JsValue::Undefined)
Ok(JsValue::undefined())
},
promise_capability.clone(),
)
@ -170,6 +170,7 @@ impl PromiseCapability {
// 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception.
let resolve = resolve
.as_object()
.as_deref()
.cloned()
.and_then(JsFunction::from_object)
.ok_or_else(|| {
@ -180,6 +181,7 @@ impl PromiseCapability {
// 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception.
let reject = reject
.as_object()
.as_deref()
.cloned()
.and_then(JsFunction::from_object)
.ok_or_else(|| {
@ -318,8 +320,8 @@ impl Promise {
// 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ).
let completion = context.call(
executor,
&JsValue::Undefined,
&executor,
&JsValue::undefined(),
&[
resolving_functions.resolve,
resolving_functions.reject.clone(),
@ -329,7 +331,7 @@ impl Promise {
// 10. If completion is an abrupt completion, then
if let Err(value) = completion {
// 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.
@ -359,7 +361,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor");
// 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).
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)).
let mut result = Self::perform_promise_all(
&mut iterator_record,
c,
&c,
&promise_capability,
&promise_resolve,
context,
@ -489,7 +491,7 @@ impl Promise {
};
// 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 »).
let next_promise =
@ -523,7 +525,7 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]].
// 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.
captures
@ -600,7 +602,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor");
// 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).
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)).
let mut result = Self::perform_promise_all_settled(
&mut iterator_record,
c,
&c,
&promise_capability,
&promise_resolve,
context,
@ -931,7 +933,7 @@ impl Promise {
let c = c.as_object().expect("must be a constructor");
// 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).
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)).
let mut result = Self::perform_promise_any(
&mut iterator_record,
c,
&c,
&promise_capability,
&promise_resolve,
context,
@ -1112,7 +1114,7 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]].
// 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.
captures
@ -1232,7 +1234,7 @@ impl Promise {
// 5. If alreadyResolved.[[Value]] is true, return undefined.
if already_resolved.get() {
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
}
// 6. Set alreadyResolved.[[Value]] to true.
@ -1241,7 +1243,7 @@ impl Promise {
let resolution = args.get_or_undefined(0);
// 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.
let self_resolution_error =
context.construct_type_error("SameValue(resolution, promise) is true");
@ -1254,7 +1256,7 @@ impl Promise {
.reject_promise(&self_resolution_error, context);
// c. Return undefined.
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
}
let then = if let Some(resolution) = resolution.as_object() {
@ -1267,10 +1269,10 @@ impl Promise {
.borrow_mut()
.as_promise_mut()
.expect("Expected promise to be a Promise")
.fulfill_promise(resolution, context)?;
.fulfill_promise(&resolution, context)?;
// b. Return undefined.
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
};
let then_action = match then {
@ -1284,7 +1286,7 @@ impl Promise {
.reject_promise(&value, context);
// b. Return undefined.
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
}
// 11. Let thenAction be then.[[Value]].
Ok(then) => then,
@ -1299,10 +1301,10 @@ impl Promise {
.borrow_mut()
.as_promise_mut()
.expect("Expected promise to be a Promise")
.fulfill_promise(resolution, context)?;
.fulfill_promise(&resolution, context)?;
// b. Return undefined.
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
}
};
@ -1321,7 +1323,7 @@ impl Promise {
context.host_enqueue_promise_job(job);
// 16. Return undefined.
Ok(JsValue::Undefined)
Ok(JsValue::undefined())
},
resolve_captures,
)
@ -1356,7 +1358,7 @@ impl Promise {
// 5. If alreadyResolved.[[Value]] is true, return undefined.
if already_resolved.get() {
return Ok(JsValue::Undefined);
return Ok(JsValue::undefined());
}
// 6. Set alreadyResolved.[[Value]] to true.
@ -1368,10 +1370,10 @@ impl Promise {
.borrow_mut()
.as_promise_mut()
.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.
Ok(JsValue::Undefined)
Ok(JsValue::undefined())
},
reject_captures,
)
@ -1523,7 +1525,7 @@ impl Promise {
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
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).
if_abrupt_reject_promise!(promise_resolve, promise_capability, context);
@ -1648,7 +1650,7 @@ impl Promise {
context.call(
&promise_capability.reject.clone().into(),
&JsValue::undefined(),
&[r.clone()],
&[r],
)?;
// 4. Return promiseCapability.[[Promise]].
@ -1671,7 +1673,7 @@ impl Promise {
if let Some(c) = c.as_object() {
// 3. Return ? PromiseResolve(C, x).
Self::promise_resolve(c.clone(), x.clone(), context)
Self::promise_resolve(c.clone(), x, context)
} else {
// 2. If Type(C) is not Object, throw a TypeError exception.
context.throw_type_error("Promise.resolve() called on a non-object")
@ -1710,7 +1712,7 @@ impl Promise {
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
promise.invoke(
"then",
&[JsValue::undefined(), on_rejected.clone()],
&[JsValue::undefined(), on_rejected],
context,
)
}
@ -1777,7 +1779,7 @@ impl Promise {
Ok(captures.value.clone())
},
ReturnValueCaptures {
value: value.clone(),
value,
},
);
@ -1822,7 +1824,7 @@ impl Promise {
Err(captures.reason.clone())
},
ThrowReasonCaptures {
reason: reason.clone(),
reason,
},
);
@ -1833,7 +1835,7 @@ impl Promise {
promise.invoke("then", &[thrower.into()], context)
},
FinallyCaptures {
on_finally: on_finally.clone(),
on_finally,
c,
},
);
@ -1846,7 +1848,7 @@ impl Promise {
// 6. Else,
// a. Let thenFinally 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 »).
@ -1881,11 +1883,11 @@ impl Promise {
let on_rejected = args.get_or_undefined(1);
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
let mut promise_obj = promise_obj.borrow_mut();
promise_obj
.borrow_mut()
.as_promise_mut()
.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)
}
@ -1999,7 +2001,7 @@ impl Promise {
match result_capability {
// 13. If resultCapability is undefined, then
// a. Return undefined.
None => JsValue::Undefined,
None => JsValue::undefined(),
// 14. Else,
// 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 »)).
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.
Ok(JsValue::Undefined)
Ok(JsValue::undefined())
}
Some(promise_capability_record) => {
// g. Assert: promiseCapability is a PromiseCapability Record.
@ -83,13 +83,13 @@ impl PromiseJob {
// h. If handlerResult is an abrupt completion, then
Err(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,
Ok(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]] »).
return context.call(
&resolving_functions.reject,
&JsValue::Undefined,
&JsValue::undefined(),
&[value],
);
}

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

@ -81,7 +81,12 @@ impl Proxy {
}
// 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 )`
@ -162,7 +167,11 @@ impl Proxy {
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 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`
let revoker = Self::revoker(p.clone(), context);

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

@ -16,6 +16,7 @@ use crate::{
object::ObjectInitializer,
property::Attribute,
symbol::WellKnownSymbols,
value::JsVariant,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
@ -86,7 +87,7 @@ impl Reflect {
return context.throw_type_error("target must be a function");
}
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.
@ -104,20 +105,21 @@ impl Reflect {
) -> JsResult<JsValue> {
// 1. If IsConstructor(target) is false, throw a TypeError exception.
let target = args
.get_or_undefined(0)
.get_or_undefined(0);
let target = target
.as_constructor()
.ok_or_else(|| context.construct_type_error("target must be a constructor"))?;
let new_target = if let Some(new_target) = args.get(2) {
// 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
if let Some(new_target) = new_target.as_constructor() {
new_target
new_target.clone()
} else {
return context.throw_type_error("newTarget must be a constructor");
}
} else {
// 2. If newTarget is not present, set newTarget to target.
target
target.clone()
};
// 4. Let args be ? CreateListFromArrayLike(argumentsList).
@ -127,7 +129,7 @@ impl Reflect {
// 5. Return ? Construct(target, args, newTarget).
target
.construct(&args, Some(new_target), context)
.construct(&args, Some(&new_target), context)
.map(JsValue::from)
}
@ -151,7 +153,7 @@ impl Reflect {
let key = args.get_or_undefined(1).to_property_key(context)?;
let prop_desc: JsValue = args
.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"))?
.into();
@ -254,7 +256,7 @@ impl Reflect {
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
Ok(target
.__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.
@ -365,9 +367,7 @@ impl Reflect {
} else {
target.clone().into()
};
Ok(target
.__set__(key, value.clone(), receiver, context)?
.into())
Ok(target.__set__(key, value, receiver, context)?.into())
}
/// Sets the prototype of an object.
@ -387,9 +387,9 @@ impl Reflect {
.get(0)
.and_then(JsValue::as_object)
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
let proto = match args.get_or_undefined(1) {
JsValue::Object(obj) => Some(obj.clone()),
JsValue::Null => None,
let proto = match args.get_or_undefined(1).variant() {
JsVariant::Object(obj) => Some(obj.clone()),
JsVariant::Null => None,
_ => return context.throw_type_error("proto must be an object or null"),
};
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() {
// a. Let newTarget be the active function object.
// 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() {
// i. Let patternConstructor be ? Get(pattern, "constructor").
let pattern_constructor = pattern.get("constructor", context)?;
// ii. If SameValue(newTarget, patternConstructor) is true, return pattern.
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()),
)
} else {
(JsValue::new(regexp.original_source.clone()), flags.clone())
(JsValue::new(regexp.original_source.clone()), flags)
}
} else {
// a. Let P be pattern.
// b. Let F be flags.
(pattern.clone(), flags.clone())
(pattern.clone(), flags)
};
// 7. Let O be ? RegExpAlloc(newTarget).
@ -352,7 +352,7 @@ impl RegExp {
#[inline]
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() {
return Ok(JsValue::new(match flag {
b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES),
@ -695,7 +695,7 @@ impl RegExp {
.to_string(context)?;
// 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.
if m.is_some() {
@ -735,7 +735,7 @@ impl RegExp {
let arg_str = args.get_or_undefined(0).to_string(context)?;
// 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())
} else {
Ok(JsValue::null())
@ -770,7 +770,7 @@ impl RegExp {
}
// c. Return result.
return Ok(result.as_object().cloned());
return Ok(result.as_object().as_deref().cloned());
}
// 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]).
@ -1050,7 +1050,7 @@ impl RegExp {
#[allow(clippy::if_not_else)]
if !global {
// 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())
} else {
Ok(JsValue::null())
@ -1075,7 +1075,7 @@ impl RegExp {
// f. Repeat,
loop {
// 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
// iii. Else,
@ -1244,9 +1244,10 @@ impl RegExp {
let length_arg_str = arg_str.encode_utf16().count();
// 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
.as_object()
.as_deref()
.map(JsObject::is_callable)
.unwrap_or_default();
@ -1276,7 +1277,7 @@ impl RegExp {
// 11. Repeat, while done is false,
loop {
// 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.
// c. Else,
@ -1498,7 +1499,7 @@ impl RegExp {
}
// 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").
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> {
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
.as_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).
let iterator_record = iterable.clone().get_iterator(context, None, None)?;
let iterator_record = iterable.get_iterator(context, None, None)?;
// 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
@ -224,9 +224,9 @@ impl Set {
if let Some(object) = this.as_object() {
if let Some(set) = object.borrow_mut().as_set_mut() {
set.add(if value.as_number().map_or(false, |n| n == -0f64) {
JsValue::Integer(0)
JsValue::new(0)
} else {
value.clone()
value
});
} else {
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() {
if let Some(set) = object.borrow_mut().as_set_mut() {
set.delete(value)
set.delete(&value)
} else {
return context.throw_type_error("'this' is not a Set");
}
@ -352,7 +352,7 @@ impl Set {
let this_arg = if this_arg.is_undefined() {
context.global_object().clone().into()
} else {
this_arg.clone()
this_arg
};
let mut index = 0;
@ -375,7 +375,7 @@ impl Set {
index += 1;
}
Ok(JsValue::Undefined)
Ok(JsValue::undefined())
}
/// `Map.prototype.has( key )`
@ -399,7 +399,7 @@ impl Set {
.and_then(|obj| {
obj.borrow()
.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"))
}

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

@ -67,7 +67,8 @@ impl SetIterator {
///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
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
.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
.as_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) {
// 2. Else,
// a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value).
Some(JsValue::Symbol(ref sym)) if new_target.is_undefined() => {
return Ok(sym.descriptive_string().into())
Some(value) if new_target.is_undefined() && value.is_symbol() => {
return Ok(value
.as_symbol()
.expect("Already checked for a symbol")
.descriptive_string()
.into())
}
// b. Let s be ? ToString(value).
Some(value) => value.to_string(context)?,
@ -244,6 +248,7 @@ impl String {
fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult<JsString> {
// 1. If Type(value) is String, return value.
this.as_string()
.as_deref()
.cloned()
// 2. If Type(value) is Object and value has a [[StringData]] internal slot, then
// a. Let s be value.[[StringData]].
@ -395,7 +400,7 @@ impl String {
// If codeUnits is empty, the empty String is returned.
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 ( )`
@ -780,7 +785,7 @@ impl String {
// 3. Let isRegExp be ? IsRegExp(searchString).
// 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(
"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;
// 7. If position is undefined, let pos be 0; else let pos be ? ToIntegerOrInfinity(position).
let pos = match args.get_or_undefined(1) {
&JsValue::Undefined => IntegerOrInfinity::Integer(0),
position => position.to_integer_or_infinity(context)?,
let pos = args.get_or_undefined(1);
let pos = if pos.is_undefined() {
IntegerOrInfinity::Integer(0)
} else {
pos.to_integer_or_infinity(context)?
};
// 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) {
// 3. Let isRegExp be ? IsRegExp(searchString).
// 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(
"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) {
// 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(
// 4. If isRegExp is true, throw a TypeError exception.
"First argument to String.prototype.includes must not be a regular expression",
@ -979,7 +986,7 @@ impl String {
if let Some(replacer) = replacer {
// i. Return ? Call(replacer, searchValue, « O, replaceValue »).
return replacer.call(
search_value,
&search_value,
&[this.clone(), replace_value.clone()],
context,
);
@ -995,6 +1002,7 @@ impl String {
// 5. Let functionalReplace be IsCallable(replaceValue).
let functional_replace = replace_value
.as_object()
.as_deref()
.map(JsObject::is_callable)
.unwrap_or_default();
@ -1023,7 +1031,7 @@ impl String {
// a. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »)).
context
.call(
replace_value,
&replace_value,
&JsValue::undefined(),
&[search_str.into(), position.into(), this_str.clone().into()],
)?
@ -1088,7 +1096,7 @@ impl String {
// 2. If searchValue is neither undefined nor null, then
if !search_value.is_null_or_undefined() {
// 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
if is_reg_exp_object(obj, context)? {
// i. Let flags be ? Get(searchValue, "flags").
@ -1112,7 +1120,7 @@ impl String {
// d. If replacer is not undefined, then
if let Some(replacer) = replacer {
// 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).
let functional_replace = replace_value
.as_object()
.as_deref()
.map(JsObject::is_callable)
.unwrap_or_default();
@ -1196,7 +1205,7 @@ impl String {
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(p), string »)).
context
.call(
replace_value,
&replace_value,
&JsValue::undefined(),
&[
search_string.clone().into(),
@ -1372,7 +1381,7 @@ impl String {
// b. If matcher is not undefined, then
if let Some(matcher) = matcher {
// 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)?;
// 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 »).
rx.invoke(WellKnownSymbols::r#match(), &[JsValue::new(s)], context)
@ -1485,7 +1494,7 @@ impl String {
let fill_string = args.get_or_undefined(1);
// 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] )`
@ -1512,7 +1521,7 @@ impl String {
let fill_string = args.get_or_undefined(1);
// 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()
@ -1676,9 +1685,11 @@ impl String {
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).
let int_end = match args.get_or_undefined(1) {
&JsValue::Undefined => IntegerOrInfinity::Integer(len),
end => end.to_integer_or_infinity(context)?,
let end = args.get_or_undefined(1);
let int_end = if end.is_undefined() {
IntegerOrInfinity::Integer(len)
} else {
end.to_integer_or_infinity(context)?
};
// 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).
// 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) {
&JsValue::Undefined => IntegerOrInfinity::Integer(size),
val => val.to_integer_or_infinity(context)?,
let length = args.get_or_undefined(1);
let int_length = if length.is_undefined() {
IntegerOrInfinity::Integer(size)
} else {
length.to_integer_or_infinity(context)?
};
let int_start = match int_start {
@ -1798,7 +1811,7 @@ impl String {
// b. If splitter is not undefined, then
if let Some(splitter) = splitter {
// 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)?;
// d. If matcher is not undefined, then
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)?;
// 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 »).
rx.invoke(WellKnownSymbols::match_all(), &[JsValue::new(s)], context)
@ -2072,7 +2085,7 @@ impl String {
// b. If searcher is not undefined, then
if let Some(searcher) = searcher {
// 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)?;
// 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 »).
rx.invoke(WellKnownSymbols::search(), &[JsValue::new(string)], context)
@ -2193,7 +2206,7 @@ pub(crate) fn get_substitution(
result.push(second);
result.push(*third);
} 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);
}
}
@ -2214,7 +2227,7 @@ pub(crate) fn get_substitution(
result.push('$');
result.push(second);
} 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);
}
}
@ -2324,12 +2337,13 @@ fn split_match(s_str: &str, q: usize, r_str: &str) -> Option<usize> {
/// [spec]: https://tc39.es/ecma262/#sec-isregexp
fn is_reg_exp(argument: &JsValue, context: &mut Context) -> JsResult<bool> {
// 1. If Type(argument) is not Object, return false.
let argument = match argument {
JsValue::Object(o) => o,
_ => return Ok(false),
let argument = if let Some(o) = argument.as_object() {
o
} 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> {
// 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> {
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
.as_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> {
value
.as_symbol()
.as_deref()
.cloned()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol()))
.ok_or_else(|| context.construct_type_error("'this' is not a Symbol"))
}
@ -236,7 +238,7 @@ impl Symbol {
) -> JsResult<JsValue> {
// 1. Return ? thisSymbolValue(this value).
let symbol = Self::this_symbol_value(this, context)?;
Ok(JsValue::Symbol(symbol))
Ok(JsValue::new(symbol))
}
/// `get Symbol.prototype.description`
@ -307,14 +309,14 @@ impl Symbol {
) -> JsResult<JsValue> {
let sym = args.get_or_undefined(0);
// 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
// a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]].
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym.
// 4. Return undefined.
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
let registry = registry.borrow();
registry.get_symbol(&sym)
registry.get_symbol(sym)
});
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},
symbol::WellKnownSymbols,
value::{IntegerOrInfinity, JsValue},
value::{IntegerOrInfinity, JsValue, JsVariant},
Context, JsResult, JsString,
};
use boa_profiler::Profiler;
@ -161,7 +161,7 @@ macro_rules! typed_array {
// ii. If firstArgument has a [[TypedArrayName]] internal slot, then
if first_argument.is_typed_array() {
// 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() {
// iii. Else if firstArgument has an [[ArrayBufferData]] internal slot, then
@ -175,8 +175,8 @@ macro_rules! typed_array {
TypedArray::initialize_from_array_buffer(
&o,
first_argument.clone(),
byte_offset,
length,
&byte_offset,
&length,
context,
)?;
} else {
@ -407,7 +407,9 @@ impl TypedArray {
let mapping = match args.get(1) {
// 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,
Some(v) => match v.as_object() {
// b. Let mapping be true.
@ -429,11 +431,11 @@ impl TypedArray {
// 6. If usingIterator is not undefined, then
if let Some(using_iterator) = using_iterator {
// 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.
// 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.
// e. Repeat, while k < len,
@ -443,7 +445,7 @@ impl TypedArray {
// iii. If mapping is true, then
let mapped_value = if let Some(map_fn) = &mapping {
// 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.
else {
@ -469,7 +471,7 @@ impl TypedArray {
let len = array_like.length_of_array_like(context)?;
// 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.
// 12. Repeat, while k < len,
@ -481,7 +483,7 @@ impl TypedArray {
// c. If mapping is true, then
let mapped_value = if let Some(map_fn) = &mapping {
// 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.
else {
@ -515,7 +517,7 @@ impl TypedArray {
};
// 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.
// 6. Repeat, while k < len,
@ -612,7 +614,7 @@ impl TypedArray {
// 5. Return buffer.
Ok(typed_array
.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`
@ -906,7 +908,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -925,7 +928,7 @@ impl TypedArray {
// c. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
let test_result = callback_fn
.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value, k.into(), this.clone()],
context,
)?
@ -1053,7 +1056,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -1078,7 +1082,7 @@ impl TypedArray {
// c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).#
let selected = callback_fn
.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()],
context,
)?
@ -1095,7 +1099,7 @@ impl TypedArray {
}
// 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.
// 11. For each element e of kept, do
@ -1138,7 +1142,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -1158,7 +1163,7 @@ impl TypedArray {
// d. If testResult is true, return kValue.
if predicate
.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()],
context,
)?
@ -1200,8 +1205,9 @@ impl TypedArray {
let len = o.array_length();
// 4. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = args.get_or_undefined(0);
let predicate =
match args.get_or_undefined(0).as_object() {
match predicate.as_object() {
Some(obj) if obj.is_callable() => obj,
_ => return context.throw_type_error(
"TypedArray.prototype.findindex called with non-callable predicate function",
@ -1219,7 +1225,7 @@ impl TypedArray {
// d. If testResult is true, return 𝔽(k).
if predicate
.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value.clone(), k.into(), this.clone()],
context,
)?
@ -1261,7 +1267,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -1279,7 +1286,7 @@ impl TypedArray {
// c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
callback_fn.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value, k.into(), this.clone()],
context,
)?;
@ -1354,7 +1361,7 @@ impl TypedArray {
let element_k = obj.get(k, context).expect("Get cannot fail here");
// 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());
}
@ -1680,7 +1687,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -1690,7 +1698,7 @@ impl TypedArray {
};
// 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.
// 7. Repeat, while k < len,
@ -1701,7 +1709,7 @@ impl TypedArray {
// c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
let mapped_value = callback_fn.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value, k.into(), this.clone()],
context,
)?;
@ -1742,7 +1750,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -1823,8 +1832,9 @@ impl TypedArray {
let len = o.array_length() as i64;
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback_fn = args.get_or_undefined(0);
let callback_fn =
match args.get_or_undefined(0).as_object() {
match callback_fn.as_object() {
Some(obj) if obj.is_callable() => obj,
_ => return context.throw_type_error(
"TypedArray.prototype.reduceright called with non-callable callback function",
@ -1974,16 +1984,16 @@ impl TypedArray {
}
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
JsValue::Object(source) if source.is_typed_array() => {
JsVariant::Object(source) if source.is_typed_array() => {
// 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,
_ => {
// 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;
// 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_array = a_borrow
.as_typed_array()
@ -2517,7 +2527,8 @@ impl TypedArray {
let len = o.array_length();
// 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,
_ => {
return context.throw_type_error(
@ -2537,7 +2548,7 @@ impl TypedArray {
// d. If testResult is true, return true.
if callback_fn
.call(
args.get_or_undefined(1),
&args.get_or_undefined(1),
&[k_value, k.into(), this.clone()],
context,
)?
@ -2563,9 +2574,9 @@ impl TypedArray {
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
let compare_fn = match args.get(0) {
None | Some(JsValue::Undefined) => None,
Some(JsValue::Object(obj)) if obj.is_callable() => Some(obj),
let compare_fn = match args.get(0).map(JsValue::variant) {
None | Some(JsVariant::Undefined) => None,
Some(JsVariant::Object(obj)) if obj.is_callable() => Some(obj),
_ => {
return context
.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));
}
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𝔽.
if x < y {
return Ok(Ordering::Less);
@ -2734,7 +2745,7 @@ impl TypedArray {
let mut sort_err = Ok(());
items.sort_by(|x, y| {
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);
Ordering::Equal
})
@ -2838,7 +2849,7 @@ impl TypedArray {
// 19. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
// 20. Return ? TypedArraySpeciesCreate(O, argumentsList).
Ok(Self::species_create(
obj,
&obj,
o.typed_array_name(),
&[
buffer.clone().into(),
@ -2901,7 +2912,7 @@ impl TypedArray {
.as_typed_array()
.map(|o| o.typed_array_name().name().into())
})
.unwrap_or(JsValue::Undefined))
.unwrap_or(JsValue::undefined()))
}
/// `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 = if let JsValue::Object(ref obj) = class_constructor {
let class_constructor = if let Some(obj) = class_constructor.as_object() {
obj
} else {
return context.throw_type_error(format!(
@ -120,8 +120,8 @@ impl<T: Class> ClassConstructor for T {
));
};
let class_prototype =
if let JsValue::Object(ref obj) = class_constructor.get(PROTOTYPE, context)? {
obj.clone()
if let Some(object) = class_constructor.get(PROTOTYPE, context)?.as_object() {
object.clone()
} else {
return context.throw_type_error(format!(
"invalid default prototype for native class `{}`",
@ -131,10 +131,10 @@ impl<T: Class> ClassConstructor for T {
let prototype = this
.as_object()
.cloned()
.as_deref()
.map(|obj| {
obj.get(PROTOTYPE, context)
.map(|val| val.as_object().cloned())
.map(|val| val.as_object().as_deref().cloned())
})
.transpose()?
.flatten()

2
boa_engine/src/context/mod.rs

@ -743,7 +743,7 @@ impl Context {
/// Runs all the jobs in the job queue.
fn run_queued_jobs(&mut self) -> JsResult<()> {
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(())
}

2
boa_engine/src/environments/compile.rs

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

2
boa_engine/src/environments/runtime.rs

@ -402,7 +402,7 @@ impl DeclarativeEnvironmentStack {
let this = if let Some(this) = this {
this
} else {
JsValue::Null
JsValue::null()
};
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).
//! - **console** - Enables `boa`'s [WHATWG `console`][whatwg] object implementation.
//! - **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)
//!
//! [whatwg]: https://console.spec.whatwg.org
@ -70,11 +72,11 @@
// Ignore because `write!(string, ...)` instead of `string.push_str(&format!(...))` can fail.
// We only use it in `ToInternedString` where performance is not an issue.
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
)]
pub mod value; // In the top to give priority to `JsValue` docs
pub mod bigint;
pub mod builtins;
pub mod bytecompiler;
@ -88,7 +90,6 @@ pub mod realm;
pub mod string;
pub mod symbol;
pub mod syntax;
pub mod value;
pub mod vm;
#[cfg(test)]

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

@ -2,7 +2,7 @@ use crate::{
builtins::{array, object::Object},
object::{InternalObjectMethods, JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey},
value::Type,
value::{JsVariant, Type},
Context, JsResult, JsValue,
};
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)?;
// 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.
let handler_proto = match &handler_proto {
JsValue::Object(obj) => Some(obj.clone()),
JsValue::Null => None,
let handler_proto = match handler_proto.variant() {
JsVariant::Object(obj) => Some(obj.clone()),
JsVariant::Null => None,
_ => 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(),
&[
target.clone().into(),
val.clone().map_or(JsValue::Null, Into::into),
val.clone().map_or(JsValue::null(), Into::into),
],
context,
)?
@ -679,7 +679,11 @@ pub(crate) fn proxy_exotic_set(
if target_desc.is_accessor_descriptor() {
// i. If targetDesc.[[Set]] is undefined, throw a TypeError exception.
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
.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 trap_result = Vec::new();
for value in &trap_result_raw {
match value {
JsValue::String(s) => {
match value.variant() {
JsVariant::String(s) => {
if !unchecked_result_keys.insert(s.clone().into()) {
return context.throw_type_error(
"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());
}
JsValue::Symbol(s) => {
JsVariant::Symbol(s) => {
if !unchecked_result_keys.insert(s.clone().into()) {
return context.throw_type_error(
"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.
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")
})?;
// 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> {
let object = Array::concat(&self.inner.clone().into(), items, context)?
.as_object()
.as_deref()
.cloned()
.expect("Array.prototype.filter should always return object");
@ -124,6 +125,7 @@ impl JsArray {
)
.map(|x| {
x.as_string()
.as_deref()
.cloned()
.expect("Array.prototype.join always returns string")
})
@ -231,6 +233,7 @@ impl JsArray {
context,
)?
.as_object()
.as_deref()
.cloned()
.expect("Array.prototype.filter should always return object");
@ -250,6 +253,7 @@ impl JsArray {
context,
)?
.as_object()
.as_deref()
.cloned()
.expect("Array.prototype.map should always return object");
@ -316,6 +320,7 @@ impl JsArray {
context,
)?
.as_object()
.as_deref()
.cloned()
.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 {
array_buffer_data: Some(block),
array_buffer_byte_length: byte_length as u64,
array_buffer_detach_key: JsValue::Undefined,
array_buffer_detach_key: JsValue::undefined(),
});
Ok(Self { inner: obj })

13
boa_engine/src/object/jsobject.rs

@ -6,7 +6,7 @@ use super::{JsPrototype, NativeObject, Object, PropertyMap};
use crate::{
object::{ObjectData, ObjectKind},
property::{PropertyDescriptor, PropertyKey},
value::PreferredType,
value::{PointerType, PreferredType},
Context, JsResult, JsValue,
};
use boa_gc::{self, Finalize, Gc, Trace};
@ -16,6 +16,7 @@ use std::{
collections::HashMap,
error::Error,
fmt::{self, Debug, Display},
mem::{self, ManuallyDrop},
result::Result as StdResult,
};
@ -31,6 +32,16 @@ pub struct JsObject {
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 {
/// Create a new `JsObject` from an internal `Object`.
#[inline]

20
boa_engine/src/object/jsset.rs

@ -5,7 +5,7 @@ use boa_gc::{Finalize, Trace};
use crate::{
builtins::Set,
object::{JsFunction, JsObject, JsObjectType, JsSetIterator},
Context, JsResult, JsValue,
Context, JsResult, JsValue, value::JsVariant,
};
// This is an wrapper for `JsSet`
@ -61,7 +61,7 @@ impl JsSet {
/// Same as JavaScript's `set.clear()`.
#[inline]
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.
@ -74,9 +74,9 @@ impl JsSet {
where
T: Into<JsValue>,
{
match Set::delete(&self.inner.clone().into(), &[value.into()], context)? {
JsValue::Boolean(bool) => Ok(bool),
_ => Err(JsValue::Undefined),
match Set::delete(&self.inner.clone().into(), &[value.into()], context)?.variant() {
JsVariant::Boolean(bool) => Ok(bool),
_ => Err(JsValue::undefined()),
}
}
@ -89,9 +89,9 @@ impl JsSet {
where
T: Into<JsValue>,
{
match Set::has(&self.inner.clone().into(), &[value.into()], context)? {
JsValue::Boolean(bool) => Ok(bool),
_ => Err(JsValue::Undefined),
match Set::has(&self.inner.clone().into(), &[value.into()], context)?.variant() {
JsVariant::Boolean(bool) => Ok(bool),
_ => Err(JsValue::undefined()),
}
}
@ -101,7 +101,7 @@ impl JsSet {
/// Same as JavaScript's `set.values()`.
#[inline]
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)?;
JsSetIterator::from_object(iterator_object.iterator().clone(), context)
@ -114,7 +114,7 @@ impl JsSet {
/// Same as JavaScript's `set.keys()`.
#[inline]
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)?;
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`.
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.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsTypedArray {
inner: JsValue,
inner: JsObject,
}
impl JsTypedArray {
@ -20,9 +20,7 @@ impl JsTypedArray {
#[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_typed_array() {
Ok(Self {
inner: object.into(),
})
Ok(Self { inner: object })
} else {
context.throw_type_error("object is not a TypedArray")
}
@ -33,7 +31,7 @@ impl JsTypedArray {
/// Same a `array.length` in JavaScript.
#[inline]
pub fn length(&self, context: &mut Context) -> JsResult<usize> {
Ok(TypedArray::length(&self.inner, &[], context)?
Ok(TypedArray::length(&self.clone().into(), &[], context)?
.as_number()
.map(|x| x as usize)
.expect("length should return a number"))
@ -50,12 +48,12 @@ impl JsTypedArray {
where
T: Into<i64>,
{
TypedArray::at(&self.inner, &[index.into().into()], context)
TypedArray::at(&self.clone().into(), &[index.into().into()], context)
}
#[inline]
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()
.map(|x| x as usize)
.expect("byteLength should return a number"))
@ -63,7 +61,7 @@ impl JsTypedArray {
#[inline]
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()
.map(|x| x as usize)
.expect("byteLength should return a number"))
@ -81,7 +79,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
TypedArray::fill(
&self.inner,
&self.clone().into(),
&[
value.into(),
start.into_or_undefined(),
@ -99,7 +97,7 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<bool> {
let result = TypedArray::every(
&self.inner,
&self.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()],
context,
)?
@ -117,7 +115,7 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<bool> {
let result = TypedArray::some(
&self.inner,
&self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?
@ -129,7 +127,11 @@ impl JsTypedArray {
#[inline]
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())
}
@ -142,10 +144,14 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<Self> {
let object = TypedArray::filter(
&self.inner,
&self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?;
)?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object })
}
@ -158,10 +164,14 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<Self> {
let object = TypedArray::map(
&self.inner,
&self.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?;
)?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object })
}
@ -174,7 +184,7 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<JsValue> {
TypedArray::reduce(
&self.inner,
&self.clone().into(),
&[callback.into(), initial_value.into_or_undefined()],
context,
)
@ -188,7 +198,7 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<JsValue> {
TypedArray::reduceright(
&self.inner,
&self.clone().into(),
&[callback.into(), initial_value.into_or_undefined()],
context,
)
@ -196,7 +206,7 @@ impl JsTypedArray {
#[inline]
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> {
TypedArray::reverse(&self.inner, &[], context)?;
TypedArray::reverse(&self.clone().into(), &[], context)?;
Ok(self.clone())
}
@ -208,10 +218,14 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<Self> {
let object = TypedArray::slice(
&self.inner,
&self.clone().into(),
&[start.into_or_undefined(), end.into_or_undefined()],
context,
)?;
)?
.as_object()
.as_deref()
.cloned()
.expect("Filter must always return an array object on success");
Ok(Self { inner: object })
}
@ -224,7 +238,7 @@ impl JsTypedArray {
context: &mut Context,
) -> JsResult<JsValue> {
TypedArray::find(
&self.inner,
&self.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()],
context,
)
@ -241,7 +255,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
let index = TypedArray::index_of(
&self.inner,
&self.clone().into(),
&[search_element.into(), from_index.into_or_undefined()],
context,
)?
@ -267,7 +281,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
let index = TypedArray::last_index_of(
&self.inner,
&self.clone().into(),
&[search_element.into(), from_index.into_or_undefined()],
context,
)?
@ -284,8 +298,14 @@ impl JsTypedArray {
#[inline]
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()
.as_deref()
.cloned()
.expect("TypedArray.prototype.join always returns string")
})
@ -295,17 +315,14 @@ impl JsTypedArray {
impl From<JsTypedArray> for JsObject {
#[inline]
fn from(o: JsTypedArray) -> Self {
o.inner
.as_object()
.expect("should always be an object")
.clone()
o.inner.clone()
}
}
impl From<JsTypedArray> for JsValue {
#[inline]
fn from(o: JsTypedArray) -> Self {
o.inner.clone()
o.inner.clone().into()
}
}
@ -314,7 +331,7 @@ impl Deref for JsTypedArray {
#[inline]
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 {
#[inline]
fn from(o: $name) -> Self {
o.inner
.inner
.as_object()
.expect("should always be an object")
.clone()
o.inner.inner.clone()
}
}
impl From<$name> for JsValue {
#[inline]
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,
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::Type,
value::{JsVariant, Type},
Context, JsResult, JsValue,
};
@ -594,11 +594,14 @@ impl JsObject {
// 1. Assert: IsPropertyKey(P) is true.
// 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.
JsValue::Undefined | JsValue::Null => Ok(None),
JsVariant::Undefined | JsVariant::Null => Ok(None),
// 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.
_ => {
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.
if JsObject::equals(&object, prototype) {
if JsObject::equals(&object, &prototype) {
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 rustc_hash::{FxHashMap, FxHasher};
use std::{
@ -8,6 +8,7 @@ use std::{
hash::BuildHasherDefault,
hash::{Hash, Hasher},
marker::PhantomData,
mem::ManuallyDrop,
ops::Deref,
ptr::{copy_nonoverlapping, NonNull},
rc::Rc,
@ -635,12 +636,26 @@ pub struct JsString {
_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,
// so this is safe.
unsafe impl Trace for JsString {
unsafe_empty_trace!();
}
/// 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
/// 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
//! [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 std::{
cell::Cell,
fmt::{self, Display},
hash::{Hash, Hasher},
mem::{self, ManuallyDrop},
rc::Rc,
};
@ -253,12 +254,23 @@ pub struct JsSymbol {
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,
// so this is safe.
unsafe impl Trace for JsSymbol {
unsafe_empty_trace!();
}
impl JsSymbol {
/// Create a new symbol.
#[inline]

3
boa_engine/src/tests.rs

@ -874,6 +874,7 @@ mod in_operator {
.get("prototype", &mut context)
.unwrap()
.as_object()
.as_deref()
.cloned())
);
}
@ -1057,7 +1058,7 @@ fn to_integer_or_infinity() {
fn to_length() {
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!(
JsValue::new(f64::NEG_INFINITY)
.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 {
#[inline]
@ -15,7 +19,7 @@ where
fn from(value: T) -> Self {
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 {
#[inline]
fn from(value: JsSymbol) -> Self {
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")
Self::symbol(value)
}
}
@ -49,7 +44,7 @@ impl From<f32> for JsValue {
// if value as i32 as f64 == value {
// Self::Integer(value as i32)
// } 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 {
// Self::Integer(value as i32)
// } else {
Self::Rational(value)
Self::float64(value)
// }
}
}
@ -69,28 +64,28 @@ impl From<f64> for JsValue {
impl From<u8> for JsValue {
#[inline]
fn from(value: u8) -> Self {
Self::Integer(value.into())
Self::integer32(value.into())
}
}
impl From<i8> for JsValue {
#[inline]
fn from(value: i8) -> Self {
Self::Integer(value.into())
Self::integer32(value.into())
}
}
impl From<u16> for JsValue {
#[inline]
fn from(value: u16) -> Self {
Self::Integer(value.into())
Self::integer32(value.into())
}
}
impl From<i16> for JsValue {
#[inline]
fn from(value: i16) -> Self {
Self::Integer(value.into())
Self::integer32(value.into())
}
}
@ -98,9 +93,9 @@ impl From<u32> for JsValue {
#[inline]
fn from(value: u32) -> Self {
if let Ok(integer) = i32::try_from(value) {
Self::Integer(integer)
Self::integer32(integer)
} else {
Self::Rational(value.into())
Self::float64(value.into())
}
}
}
@ -108,14 +103,14 @@ impl From<u32> for JsValue {
impl From<i32> for JsValue {
#[inline]
fn from(value: i32) -> Self {
Self::Integer(value)
Self::integer32(value)
}
}
impl From<JsBigInt> for JsValue {
#[inline]
fn from(value: JsBigInt) -> Self {
Self::BigInt(value)
Self::bigint(value)
}
}
@ -123,9 +118,9 @@ impl From<usize> for JsValue {
#[inline]
fn from(value: usize) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
Self::integer32(value)
} else {
Self::Rational(value as f64)
Self::float64(value as f64)
}
}
}
@ -134,9 +129,9 @@ impl From<u64> for JsValue {
#[inline]
fn from(value: u64) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
Self::integer32(value)
} else {
Self::Rational(value as f64)
Self::float64(value as f64)
}
}
}
@ -145,9 +140,9 @@ impl From<i64> for JsValue {
#[inline]
fn from(value: i64) -> Self {
if let Ok(value) = i32::try_from(value) {
Self::Integer(value)
Self::integer32(value)
} 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 {
#[inline]
fn from(value: bool) -> Self {
Self::Boolean(value)
Self::boolean(value)
}
}
@ -163,7 +158,7 @@ impl From<JsObject> for JsValue {
#[inline]
fn from(object: JsObject) -> Self {
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 {
fn into_or_undefined(self) -> JsValue;
}
@ -183,6 +197,6 @@ where
T: Into<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 super::{fmt, Display, HashSet, JsValue, PropertyKey};
use super::{fmt, Display, HashSet, JsValue, JsVariant, PropertyKey};
/// This object is used for displaying a `Value`.
#[derive(Debug, Clone, Copy)]
@ -55,7 +55,7 @@ macro_rules! print_obj_value {
vec![format!(
"{:>width$}: {}",
"__proto__",
JsValue::Null.display(),
JsValue::null().display(),
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 {
match x {
match x.variant() {
// 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
// which type of Object it represents for special printing
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),
}
}
JsValue::Symbol(ref symbol) => symbol.to_string(),
JsVariant::Symbol(ref symbol) => symbol.to_string(),
_ => x.display().to_string(),
}
}
@ -216,7 +216,7 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
indent: usize,
print_internals: bool,
) -> String {
if let JsValue::Object(ref v) = *data {
if let Some(v) = data.as_object() {
// The in-memory address of the current object
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
let mut encounters = HashSet::new();
if let JsValue::Object(object) = v {
if let Some(object) = v.as_object() {
if object.borrow().is_error() {
let name = v
.get_property("name")
.as_ref()
.and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined)
.unwrap_or(&JsValue::undefined())
.display()
.to_string();
let message = v
.get_property("message")
.as_ref()
.and_then(PropertyDescriptor::value)
.unwrap_or(&JsValue::Undefined)
.unwrap_or(&JsValue::undefined())
.display()
.to_string();
return format!("{name}: {message}");
@ -279,21 +279,21 @@ pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.value {
JsValue::Null => write!(f, "null"),
JsValue::Undefined => write!(f, "undefined"),
JsValue::Boolean(v) => write!(f, "{v}"),
JsValue::Symbol(ref symbol) => match symbol.description() {
match self.value.variant() {
JsVariant::Null => write!(f, "null"),
JsVariant::Undefined => write!(f, "undefined"),
JsVariant::Boolean(v) => write!(f, "{v}"),
JsVariant::Symbol(symbol) => match symbol.description() {
Some(description) => write!(f, "Symbol({description})"),
None => write!(f, "Symbol()"),
},
JsValue::String(ref v) => write!(f, "\"{v}\""),
JsValue::Rational(v) => format_rational(*v, f),
JsValue::Object(_) => {
JsVariant::String(v) => write!(f, "\"{}\"", *v),
JsVariant::Float64(v) => format_rational(v, f),
JsVariant::Object(_) => {
write!(f, "{}", log_string_from(self.value, self.internals, true))
}
JsValue::Integer(v) => write!(f, "{v}"),
JsValue::BigInt(ref num) => write!(f, "{num}n"),
JsVariant::Integer32(v) => write!(f, "{v}"),
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};
impl JsValue {
@ -12,20 +12,20 @@ impl JsValue {
return false;
}
match (self, other) {
match (self.variant(), other.variant()) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::equal(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y),
(Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)),
(Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y),
(Self::Integer(x), Self::Integer(y)) => x == y,
(JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::equal(x, y),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(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
//this without a special case we would compare self and other as if they were actually
//objects which unfortunately fails
//Specification Link: https://tc39.es/ecma262/#sec-typeof-operator
(Self::Null, Self::Null) => true,
(JsVariant::Null, JsVariant::Null) => true,
// 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(self, other),
@ -44,17 +44,22 @@ impl JsValue {
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.
// 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).
// 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
(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 y = other.to_number(context)?;
Number::equal(x, y)
@ -64,32 +69,32 @@ impl JsValue {
// a. Let n be ! StringToBigInt(y).
// b. If n is NaN, return false.
// c. Return the result of the comparison x == n.
(Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) {
Some(ref b) => a == b,
(JsVariant::BigInt(a), JsVariant::String(ref b)) => match JsBigInt::from_string(b) {
Some(b) => *a == b,
None => false,
},
// 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) {
Some(ref a) => a == b,
(JsVariant::String(ref a), JsVariant::BigInt(b)) => match JsBigInt::from_string(a) {
Some(a) => a == *b,
None => false,
},
// 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).
(_, 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
// of the comparison x == ? ToPrimitive(y).
(
Self::Object(_),
Self::String(_)
| Self::Rational(_)
| Self::Integer(_)
| Self::BigInt(_)
| Self::Symbol(_),
JsVariant::Object(_),
JsVariant::String(_)
| JsVariant::Float64(_)
| JsVariant::Integer32(_)
| JsVariant::BigInt(_)
| JsVariant::Symbol(_),
) => {
let primitive = self.to_primitive(context, PreferredType::Default)?;
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
// of the comparison ? ToPrimitive(x) == y.
(
Self::String(_)
| Self::Rational(_)
| Self::Integer(_)
| Self::BigInt(_)
| Self::Symbol(_),
Self::Object(_),
JsVariant::String(_)
| JsVariant::Float64(_)
| JsVariant::Integer32(_)
| JsVariant::BigInt(_)
| JsVariant::Symbol(_),
JsVariant::Object(_),
) => {
let primitive = other.to_primitive(context, PreferredType::Default)?;
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
// 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.
(Self::BigInt(ref a), Self::Rational(ref b)) => a == b,
(Self::Rational(ref a), Self::BigInt(ref b)) => a == b,
(Self::BigInt(ref a), Self::Integer(ref b)) => a == b,
(Self::Integer(ref a), Self::BigInt(ref b)) => a == b,
(JsVariant::BigInt(a), JsVariant::Float64(b)) => *a == b,
(JsVariant::Float64(a), JsVariant::BigInt(b)) => a == *b,
(JsVariant::BigInt(a), JsVariant::Integer32(b)) => *a == b,
(JsVariant::Integer32(a), JsVariant::BigInt(b)) => a == *b,
// 13. Return false.
_ => false,
@ -139,14 +144,14 @@ impl JsValue {
return false;
}
match (x, y) {
match (x.variant(), y.variant()) {
// 2. If Type(x) is Number or BigInt, then
// a. Return ! Type(x)::SameValue(x, y).
(Self::BigInt(x), Self::BigInt(y)) => JsBigInt::same_value(x, y),
(Self::Rational(x), Self::Rational(y)) => Number::same_value(*x, *y),
(Self::Rational(x), Self::Integer(y)) => Number::same_value(*x, f64::from(*y)),
(Self::Integer(x), Self::Rational(y)) => Number::same_value(f64::from(*x), *y),
(Self::Integer(x), Self::Integer(y)) => x == y,
(JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => JsBigInt::same_value(x, y),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y),
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
// 3. Return ! SameValueNonNumeric(x, y).
(_, _) => Self::same_value_non_numeric(x, y),
@ -167,19 +172,19 @@ impl JsValue {
return false;
}
match (x, y) {
match (x.variant(), y.variant()) {
// 2. If Type(x) is Number or BigInt, then
// 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),
(JsValue::Rational(x), JsValue::Integer(y)) => {
Number::same_value_zero(*x, f64::from(*y))
(JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Number::same_value_zero(x, f64::from(y))
}
(JsValue::Integer(x), JsValue::Rational(y)) => {
Number::same_value_zero(f64::from(*x), *y)
(JsVariant::Integer32(x), JsVariant::Float64(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).
(_, _) => Self::same_value_non_numeric(x, y),
@ -188,12 +193,14 @@ impl JsValue {
fn same_value_non_numeric(x: &Self, y: &Self) -> bool {
debug_assert!(x.get_type() == y.get_type());
match (x, y) {
(Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true,
(Self::String(ref x), Self::String(ref y)) => x == y,
(Self::Boolean(x), Self::Boolean(y)) => x == y,
(Self::Object(ref x), Self::Object(ref y)) => JsObject::equals(x, y),
(Self::Symbol(ref x), Self::Symbol(ref y)) => x == y,
match (x.variant(), y.variant()) {
(JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => {
true
}
(JsVariant::String(x), JsVariant::String(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,
}
}

22
boa_engine/src/value/hash.rs

@ -1,4 +1,4 @@
use super::JsValue;
use super::{JsValue, JsVariant};
use crate::builtins::Number;
use std::hash::{Hash, Hasher};
@ -37,16 +37,16 @@ impl Hash for RationalHashable {
impl Hash for JsValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Undefined => UndefinedHashable.hash(state),
Self::Null => NullHashable.hash(state),
Self::String(ref string) => string.hash(state),
Self::Boolean(boolean) => boolean.hash(state),
Self::Integer(integer) => RationalHashable(f64::from(*integer)).hash(state),
Self::BigInt(ref bigint) => bigint.hash(state),
Self::Rational(rational) => RationalHashable(*rational).hash(state),
Self::Symbol(ref symbol) => Hash::hash(symbol, state),
Self::Object(ref object) => std::ptr::hash(object.as_ref(), state),
match self.variant() {
JsVariant::Undefined => UndefinedHashable.hash(state),
JsVariant::Null => NullHashable.hash(state),
JsVariant::String(string) => string.hash(state),
JsVariant::Boolean(boolean) => boolean.hash(state),
JsVariant::Integer32(integer) => RationalHashable(f64::from(integer)).hash(state),
JsVariant::BigInt(bigint) => bigint.hash(state),
JsVariant::Float64(rational) => RationalHashable(rational).hash(state),
JsVariant::Symbol(symbol) => Hash::hash(&*symbol, 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)]
mod tests;
@ -12,10 +33,9 @@ use crate::{
},
object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols},
symbol::WellKnownSymbols,
Context, JsBigInt, JsResult, JsString,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler;
use num_bigint::BigInt;
use num_integer::Integer;
@ -28,6 +48,11 @@ use std::{
str::FromStr,
};
mod sys;
#[doc(inline)]
pub use sys::{JsValue, JsVariant, Ref};
mod conversions;
pub(crate) mod display;
mod equality;
@ -44,6 +69,7 @@ pub use hash::*;
pub use integer::IntegerOrInfinity;
pub use operations::*;
pub use r#type::Type;
pub(crate) use sys::PointerType;
static TWO_E_64: Lazy<BigInt> = Lazy::new(|| {
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)
});
/// 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 {
/// Create a new [`JsValue`].
#[inline]
@ -96,48 +91,28 @@ impl JsValue {
value.into()
}
/// Creates a new `undefined` value.
#[inline]
pub fn undefined() -> Self {
Self::Undefined
}
/// Creates a new `null` value.
/// Returns true if the value is null or undefined.
#[inline]
pub fn null() -> Self {
Self::Null
pub fn is_null_or_undefined(&self) -> bool {
self.is_null() || self.is_undefined()
}
/// Creates a new number with `NaN` value.
#[inline]
pub fn nan() -> Self {
Self::Rational(f64::NAN)
Self::new(f64::NAN)
}
/// Creates a new number with `Infinity` value.
#[inline]
pub fn positive_infinity() -> Self {
Self::Rational(f64::INFINITY)
Self::new(f64::INFINITY)
}
/// Creates a new number with `-Infinity` value.
#[inline]
pub fn negative_infinity() -> Self {
Self::Rational(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,
}
Self::new(f64::NEG_INFINITY)
}
/// 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
#[inline]
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]
pub fn as_callable(&self) -> Option<&JsObject> {
pub fn as_callable(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_callable())
}
/// Returns true if the value is a constructor object.
#[inline]
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]
pub fn as_constructor(&self) -> Option<&JsObject> {
pub fn as_constructor(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_constructor())
}
/// Returns true if the value is a promise object.
#[inline]
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]
pub fn as_promise(&self) -> Option<&JsObject> {
pub fn as_promise(&self) -> Option<Ref<'_, JsObject>> {
self.as_object().filter(|obj| obj.is_promise())
}
/// Returns true if the value is a symbol.
#[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.
/// Returns true if the value is an integer, even if it's represented by an [`f64`].
#[inline]
#[allow(clippy::float_cmp)]
pub fn is_integer(&self) -> bool {
// If it can fit in a i32 and the truncated version is
// equal to the original then it is an integer.
let is_rational_integer = |n: f64| n == f64::from(n as i32);
match *self {
Self::Integer(_) => true,
Self::Rational(n) if is_rational_integer(n) => true,
_ => false,
if self.is_integer32() {
return true;
}
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.
#[inline]
pub fn is_number(&self) -> bool {
matches!(self, Self::Rational(_) | Self::Integer(_))
self.is_integer32() || self.is_float64()
}
#[inline]
pub fn as_number(&self) -> Option<f64> {
match *self {
Self::Integer(integer) => Some(integer.into()),
Self::Rational(rational) => Some(rational),
_ => None,
}
}
/// Returns true if the value is a string.
#[inline]
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),
match self.variant() {
JsVariant::Integer32(integer) => Some(integer.into()),
JsVariant::Float64(rational) => Some(rational),
_ => None,
}
}
@ -296,13 +196,13 @@ impl JsValue {
///
/// [spec]: https://tc39.es/ecma262/#sec-toboolean
pub fn to_boolean(&self) -> bool {
match *self {
Self::Symbol(_) | Self::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::BigInt(ref n) if !n.is_zero() => true,
Self::Boolean(v) => v,
match self.variant() {
JsVariant::Symbol(_) | JsVariant::Object(_) => true,
JsVariant::String(s) if !s.is_empty() => true,
JsVariant::Float64(n) if n != 0.0 && !n.is_nan() => true,
JsVariant::Integer32(n) if n != 0 => true,
JsVariant::BigInt(n) if !n.is_zero() => true,
JsVariant::Boolean(v) => v,
_ => false,
}
}
@ -316,28 +216,27 @@ impl JsValue {
{
let key = key.into();
let _timer = Profiler::global().start_event("Value::get_property", "value");
match self {
Self::Object(ref object) => {
// TODO: had to skip `__get_own_properties__` since we don't have context here
let property = object.borrow().properties().get(&key);
if property.is_some() {
return property;
}
object
.prototype()
.as_ref()
.map_or(Self::Null, |obj| obj.clone().into())
.get_property(key)
if let Some(object) = self.as_object() {
// TODO: had to skip `__get_own_properties__` since we don't have context here
let property = object.borrow().properties().get(&key);
if property.is_some() {
return property;
}
_ => None,
object
.prototype()
.as_ref()
.map_or(Self::null(), |obj| obj.clone().into())
.get_property(key)
} else {
None
}
}
/// Set the kind of an object.
#[inline]
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;
}
}
@ -352,9 +251,9 @@ impl JsValue {
) -> JsResult<Self> {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then
if self.is_object() {
if let Some(object) = self.as_object() {
// 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
if let Some(exotic_to_prim) = exotic_to_prim {
@ -388,9 +287,7 @@ impl JsValue {
};
// d. Return ? OrdinaryToPrimitive(input, preferredType).
self.as_object()
.expect("self was not an object")
.ordinary_to_primitive(context, preferred_type)
object.ordinary_to_primitive(context, preferred_type)
} else {
// 3. Return input.
Ok(self.clone())
@ -404,10 +301,13 @@ impl JsValue {
///
/// [spec]: https://tc39.es/ecma262/#sec-tobigint
pub fn to_bigint(&self, context: &mut Context) -> JsResult<JsBigInt> {
match self {
Self::Null => context.throw_type_error("cannot convert null to a BigInt"),
Self::Undefined => context.throw_type_error("cannot convert undefined to a BigInt"),
Self::String(ref string) => {
match self.variant() {
JsVariant::Null => context.throw_type_error("cannot convert null to a BigInt"),
JsVariant::Undefined => {
context.throw_type_error("cannot convert undefined to a BigInt")
}
JsVariant::String(string) => {
let string = &*string;
if let Some(value) = JsBigInt::from_string(string) {
Ok(value)
} else {
@ -416,17 +316,17 @@ impl JsValue {
))
}
}
Self::Boolean(true) => Ok(JsBigInt::one()),
Self::Boolean(false) => Ok(JsBigInt::zero()),
Self::Integer(_) | Self::Rational(_) => {
JsVariant::Boolean(true) => Ok(JsBigInt::one()),
JsVariant::Boolean(false) => Ok(JsBigInt::zero()),
JsVariant::Integer32(_) | JsVariant::Float64(_) => {
context.throw_type_error("cannot convert Number to a BigInt")
}
Self::BigInt(b) => Ok(b.clone()),
Self::Object(_) => {
JsVariant::BigInt(b) => Ok(b.clone()),
JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
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.
pub fn to_string(&self, context: &mut Context) -> JsResult<JsString> {
match self {
Self::Null => Ok("null".into()),
Self::Undefined => Ok("undefined".into()),
Self::Boolean(boolean) => Ok(boolean.to_string().into()),
Self::Rational(rational) => Ok(Number::to_native_string(*rational).into()),
Self::Integer(integer) => Ok(integer.to_string().into()),
Self::String(string) => Ok(string.clone()),
Self::Symbol(_) => context.throw_type_error("can't convert symbol to string"),
Self::BigInt(ref bigint) => Ok(bigint.to_string().into()),
Self::Object(_) => {
match self.variant() {
JsVariant::Null => Ok("null".into()),
JsVariant::Undefined => Ok("undefined".into()),
JsVariant::Boolean(boolean) => Ok(boolean.to_string().into()),
JsVariant::Float64(rational) => Ok(Number::to_native_string(rational).into()),
JsVariant::Integer32(integer) => Ok(integer.to_string().into()),
JsVariant::String(string) => Ok(string.clone()),
JsVariant::Symbol(_) => context.throw_type_error("can't convert symbol to string"),
JsVariant::BigInt(bigint) => Ok(bigint.to_string().into()),
JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::String)?;
primitive.to_string(context)
}
@ -478,32 +378,32 @@ impl JsValue {
///
/// See: <https://tc39.es/ecma262/#sec-toobject>
pub fn to_object(&self, context: &mut Context) -> JsResult<JsObject> {
match self {
Self::Undefined | Self::Null => {
match self.variant() {
JsVariant::Undefined | JsVariant::Null => {
context.throw_type_error("cannot convert 'null' or 'undefined' to object")
}
Self::Boolean(boolean) => {
JsVariant::Boolean(boolean) => {
let prototype = context.intrinsics().constructors().boolean().prototype();
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::boolean(*boolean),
ObjectData::boolean(boolean),
))
}
Self::Integer(integer) => {
JsVariant::Integer32(integer) => {
let prototype = context.intrinsics().constructors().number().prototype();
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::number(f64::from(*integer)),
ObjectData::number(f64::from(integer)),
))
}
Self::Rational(rational) => {
JsVariant::Float64(rational) => {
let prototype = context.intrinsics().constructors().number().prototype();
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::number(*rational),
ObjectData::number(rational),
))
}
Self::String(ref string) => {
JsVariant::String(string) => {
let prototype = context.intrinsics().constructors().string().prototype();
let object =
@ -519,14 +419,14 @@ impl JsValue {
);
Ok(object)
}
Self::Symbol(ref symbol) => {
JsVariant::Symbol(symbol) => {
let prototype = context.intrinsics().constructors().symbol().prototype();
Ok(JsObject::from_proto_and_data(
prototype,
ObjectData::symbol(symbol.clone()),
))
}
Self::BigInt(ref bigint) => {
JsVariant::BigInt(bigint) => {
let prototype = context
.intrinsics()
.constructors()
@ -537,7 +437,7 @@ impl JsValue {
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>
pub fn to_property_key(&self, context: &mut Context) -> JsResult<PropertyKey> {
Ok(match self {
Ok(match self.variant() {
// Fast path:
Self::String(string) => string.clone().into(),
Self::Symbol(symbol) => symbol.clone().into(),
Self::Integer(integer) => (*integer).into(),
JsVariant::String(string) => string.clone().into(),
JsVariant::Symbol(symbol) => symbol.clone().into(),
JsVariant::Integer32(integer) => integer.into(),
// Slow path:
Self::Object(_) => match self.to_primitive(context, PreferredType::String)? {
Self::String(ref string) => string.clone().into(),
Self::Symbol(ref symbol) => symbol.clone().into(),
Self::Integer(integer) => integer.into(),
primitive => primitive.to_string(context)?.into(),
},
primitive => primitive.to_string(context)?.into(),
JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::String)?;
match primitive.variant() {
JsVariant::String(string) => string.clone().into(),
JsVariant::Symbol(symbol) => symbol.clone().into(),
JsVariant::Integer32(integer) => integer.into(),
_ => primitive.to_string(context)?.into(),
}
},
_ => self.to_string(context)?.into(),
})
}
@ -579,7 +482,7 @@ impl JsValue {
/// See: <https://tc39.es/ecma262/#sec-touint32>
pub fn to_u32(&self, context: &mut Context) -> JsResult<u32> {
// This is the fast path, if the value is Integer we can just return it.
if let JsValue::Integer(number) = *self {
if let Some(number) = self.as_integer32() {
return Ok(number as u32);
}
let number = self.to_number(context)?;
@ -592,7 +495,7 @@ impl JsValue {
/// See: <https://tc39.es/ecma262/#sec-toint32>
pub fn to_i32(&self, context: &mut Context) -> JsResult<i32> {
// This is the fast path, if the value is Integer we can just return it.
if let JsValue::Integer(number) = *self {
if let Some(number) = self.as_integer32() {
return Ok(number);
}
let number = self.to_number(context)?;
@ -873,16 +776,16 @@ impl JsValue {
///
/// See: <https://tc39.es/ecma262/#sec-tonumber>
pub fn to_number(&self, context: &mut Context) -> JsResult<f64> {
match *self {
Self::Null => Ok(0.0),
Self::Undefined => Ok(f64::NAN),
Self::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
Self::String(ref string) => Ok(string.string_to_number()),
Self::Rational(number) => Ok(number),
Self::Integer(integer) => Ok(f64::from(integer)),
Self::Symbol(_) => context.throw_type_error("argument must not be a symbol"),
Self::BigInt(_) => context.throw_type_error("argument must not be a bigint"),
Self::Object(_) => {
match self.variant() {
JsVariant::Null => Ok(0.0),
JsVariant::Undefined => Ok(f64::NAN),
JsVariant::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
JsVariant::String(string) => Ok(string.string_to_number()),
JsVariant::Float64(number) => Ok(number),
JsVariant::Integer32(integer) => Ok(f64::from(integer)),
JsVariant::Symbol(_) => context.throw_type_error("argument must not be a symbol"),
JsVariant::BigInt(_) => context.throw_type_error("argument must not be a bigint"),
JsVariant::Object(_) => {
let primitive = self.to_primitive(context, PreferredType::Number)?;
primitive.to_number(context)
}
@ -942,15 +845,15 @@ impl JsValue {
///
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
pub fn type_of(&self) -> JsString {
match *self {
Self::Rational(_) | Self::Integer(_) => "number",
Self::String(_) => "string",
Self::Boolean(_) => "boolean",
Self::Symbol(_) => "symbol",
Self::Null => "object",
Self::Undefined => "undefined",
Self::BigInt(_) => "bigint",
Self::Object(ref object) => {
match self.variant() {
JsVariant::Float64(_) | JsVariant::Integer32(_) => "number",
JsVariant::String(_) => "string",
JsVariant::Boolean(_) => "boolean",
JsVariant::Symbol(_) => "symbol",
JsVariant::Null => "object",
JsVariant::Undefined => "undefined",
JsVariant::BigInt(_) => "bigint",
JsVariant::Object(object) => {
if object.is_callable() {
"function"
} else {
@ -986,7 +889,7 @@ impl JsValue {
impl Default for JsValue {
fn default() -> Self {
Self::Undefined
Self::undefined()
}
}

310
boa_engine/src/value/operations.rs

@ -1,5 +1,5 @@
use super::{
Context, FromStr, JsBigInt, JsResult, JsString, JsValue, Numeric, PreferredType,
Context, FromStr, JsBigInt, JsResult, JsString, JsValue, JsVariant, Numeric, PreferredType,
WellKnownSymbols,
};
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 {
#[inline]
pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
// Numeric add
(Self::Integer(x), Self::Integer(y)) => x
.checked_add(*y)
.map_or_else(|| Self::new(f64::from(*x) + f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x + y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x + f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => Self::new(JsBigInt::add(x, y)),
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_add(y)
.map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)),
(JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&*x, &*y)),
// String concat
(Self::String(ref x), Self::String(ref y)) => Self::from(JsString::concat(x, y)),
(Self::String(ref x), y) => Self::from(JsString::concat(x, y.to_string(context)?)),
(x, Self::String(ref y)) => Self::from(JsString::concat(x.to_string(context)?, y)),
(JsVariant::String(x), JsVariant::String(y)) => Self::from(JsString::concat(&*x, &*y)),
(JsVariant::String(x), _) => {
Self::new(JsString::concat(&*x, other.to_string(context)?))
}
(_, JsVariant::String(y)) => Self::new(JsString::concat(self.to_string(context)?, &*y)),
// Slow path:
(_, _) => match (
self.to_primitive(context, PreferredType::Default)?,
other.to_primitive(context, PreferredType::Default)?,
) {
(Self::String(ref x), ref y) => {
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))
(_, _) => {
let x = self.to_primitive(context, PreferredType::Default)?;
let y = other.to_primitive(context, PreferredType::Default)?;
match (x.variant(), y.variant()) {
(JsVariant::String(x), _) => {
Self::from(JsString::concat(&*x, y.to_string(context)?))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
)
(_, JsVariant::String(y)) => {
Self::from(JsString::concat(x.to_string(context)?, &*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))
}
(_, _) => {
return context.throw_type_error(
"cannot mix BigInt and other types, use explicit conversions",
)
}
},
}
}
})
}
#[inline]
pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_sub(*y)
.map_or_else(|| Self::new(f64::from(*x) - f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x - y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) - y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x - f64::from(*y)),
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_sub(y)
.map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - 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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -77,16 +80,16 @@ impl JsValue {
#[inline]
pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_mul(*y)
.map_or_else(|| Self::new(f64::from(*x) * f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x * y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) * y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x * f64::from(*y)),
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_mul(y)
.map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * 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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -103,17 +106,17 @@ impl JsValue {
#[inline]
pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => x
.checked_div(*y)
.filter(|div| *y * div == *x)
.map_or_else(|| Self::new(f64::from(*x) / f64::from(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x / y),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x) / y),
(Self::Rational(x), Self::Integer(y)) => Self::new(x / f64::from(*y)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
.checked_div(y)
.filter(|div| y * div == x)
.map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)),
(JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
@ -140,33 +143,32 @@ impl JsValue {
#[inline]
pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => {
if *y == 0 {
Self::nan()
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
if y == 0 {
Self::from(f64::NAN)
} else {
match x % *y {
rem if rem == 0 && *x < 0 => Self::new(-0.0),
match x % y {
rem if rem == 0 && x < 0 => Self::new(-0.0),
rem => Self::new(rem),
}
}
}
(Self::Rational(x), Self::Rational(y)) => Self::new((x % y).copysign(*x)),
(Self::Integer(x), Self::Rational(y)) => {
let x = f64::from(*x);
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => {
let x = f64::from(x);
Self::new((x % y).copysign(x))
}
(Self::Rational(x), Self::Integer(y)) => Self::new((x % f64::from(*y)).copysign(*x)),
(Self::BigInt(ref x), Self::BigInt(ref y)) => {
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Self::new((x % f64::from(y)).copysign(x))
}
(JsVariant::BigInt(ref x), JsVariant::BigInt(ref y)) => {
if y.is_zero() {
return context.throw_range_error("BigInt division by zero");
}
Self::new(JsBigInt::rem(x, y))
}
// Slow path:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b),
@ -187,17 +189,19 @@ impl JsValue {
#[inline]
pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => u32::try_from(*y)
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y)
.ok()
.and_then(|y| x.checked_pow(y))
.map_or_else(|| Self::new(f64::from(*x).powi(*y)), Self::new),
(Self::Rational(x), Self::Rational(y)) => Self::new(x.powf(*y)),
(Self::Integer(x), Self::Rational(y)) => Self::new(f64::from(*x).powf(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(x.powi(*y)),
.map_or_else(|| Self::new(f64::from(x).powi(y)), Self::new),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x.powf(y)),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x).powf(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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -216,16 +220,18 @@ impl JsValue {
#[inline]
pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x & y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) & f64_to_int32(*y))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y),
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(x) & f64_to_int32(y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x & f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) & y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -246,16 +252,18 @@ impl JsValue {
#[inline]
pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x | y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) | f64_to_int32(*y))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y),
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(x) | f64_to_int32(y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x | f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) | y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -276,16 +284,18 @@ impl JsValue {
#[inline]
pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x ^ y),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x) ^ f64_to_int32(*y))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y),
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(x) ^ f64_to_int32(y))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x ^ f64_to_int32(*y)),
(Self::Rational(x), Self::Integer(y)) => Self::new(f64_to_int32(*x) ^ y),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(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:
(_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
@ -306,18 +316,22 @@ impl JsValue {
#[inline]
pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shl(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y)))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
Self::new(x.wrapping_shl(y as u32))
}
(Self::Integer(x), Self::Rational(y)) => Self::new(x.wrapping_shl(f64_to_uint32(*y))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shl(*y as u32))
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
}
(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)?)
}
@ -340,18 +354,22 @@ impl JsValue {
#[inline]
pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new(x.wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y)))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
Self::new(x.wrapping_shr(y as u32))
}
(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))),
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_int32(*x).wrapping_shr(*y as u32))
(JsVariant::Integer32(x), JsVariant::Float64(y)) => {
Self::new(x.wrapping_shr(f64_to_uint32(y)))
}
(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)?)
}
@ -374,17 +392,19 @@ impl JsValue {
#[inline]
pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path:
(Self::Integer(x), Self::Integer(y)) => Self::new((*x as u32).wrapping_shr(*y as u32)),
(Self::Rational(x), Self::Rational(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y)))
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
Self::new((x as u32).wrapping_shr(y as u32))
}
(JsVariant::Float64(x), JsVariant::Float64(y)) => {
Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
}
(Self::Integer(x), Self::Rational(y)) => {
Self::new((*x as u32).wrapping_shr(f64_to_uint32(*y)))
(JsVariant::Integer32(x), JsVariant::Float64(y)) => {
Self::new((x as u32).wrapping_shr(f64_to_uint32(y)))
}
(Self::Rational(x), Self::Integer(y)) => {
Self::new(f64_to_uint32(*x).wrapping_shr(*y as u32))
(JsVariant::Float64(x), JsVariant::Integer32(y)) => {
Self::new(f64_to_uint32(x).wrapping_shr(y as u32))
}
// Slow path:
@ -443,22 +463,22 @@ impl JsValue {
#[inline]
pub fn neg(&self, context: &mut Context) -> JsResult<Self> {
Ok(match *self {
Self::Symbol(_) | Self::Undefined => Self::new(f64::NAN),
Self::Object(_) => Self::new(match self.to_numeric_number(context) {
Ok(match self.variant() {
JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN),
JsVariant::Object(_) => Self::new(match self.to_numeric_number(context) {
Ok(num) => -num,
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,
Err(_) => f64::NAN,
}),
Self::Rational(num) => Self::new(-num),
Self::Integer(num) if num == 0 => Self::new(-f64::from(0)),
Self::Integer(num) => Self::new(-num),
Self::Boolean(true) => Self::new(1),
Self::Boolean(false) | Self::Null => Self::new(0),
Self::BigInt(ref x) => Self::new(JsBigInt::neg(x)),
JsVariant::Float64(num) => Self::new(-num),
JsVariant::Integer32(num) if num == 0 => Self::new(-f64::from(0)),
JsVariant::Integer32(num) => Self::new(-num),
JsVariant::Boolean(true) => Self::new(1),
JsVariant::Boolean(false) | JsVariant::Null => Self::new(0),
JsVariant::BigInt(ref x) => Self::new(JsBigInt::neg(x)),
})
}
@ -490,13 +510,13 @@ impl JsValue {
left_first: bool,
context: &mut Context,
) -> JsResult<AbstractRelation> {
Ok(match (self, other) {
Ok(match (self.variant(), other.variant()) {
// Fast path (for some common operations):
(Self::Integer(x), Self::Integer(y)) => (x < y).into(),
(Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(),
(JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(),
(JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y),
(JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)),
(JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y),
(JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(),
// Slow path:
(_, _) => {
@ -511,8 +531,8 @@ impl JsValue {
(px, py)
};
match (px, py) {
(Self::String(ref x), Self::String(ref y)) => {
match (px.variant(), py.variant()) {
(JsVariant::String(x), JsVariant::String(y)) => {
if x.starts_with(y.as_str()) {
return Ok(AbstractRelation::False);
}
@ -526,21 +546,21 @@ impl JsValue {
}
unreachable!()
}
(Self::BigInt(ref x), Self::String(ref y)) => {
(JsVariant::BigInt(x), JsVariant::String(ref y)) => {
if let Some(y) = JsBigInt::from_string(y) {
(*x < y).into()
} else {
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) {
(x < *y).into()
} else {
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::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(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`].
use super::JsValue;
use super::{JsValue, JsVariant};
use crate::{
builtins::Array,
property::{PropertyDescriptor, PropertyKey},
@ -41,13 +41,13 @@ impl JsValue {
const MIN_INT: i64 = i32::MIN as i64;
match json {
Value::Null => Ok(Self::Null),
Value::Bool(b) => Ok(Self::Boolean(*b)),
Value::Null => Ok(Self::null()),
Value::Bool(b) => Ok(Self::from(*b)),
Value::Number(num) => num
.as_i64()
.filter(|n| (MIN_INT..=MAX_INT).contains(n))
.map(|i| Self::Integer(i as i32))
.or_else(|| num.as_f64().map(Self::Rational))
.map(|i| Self::from(i as i32))
.or_else(|| num.as_f64().map(Self::from))
.ok_or_else(|| {
context.construct_type_error(format!(
"could not convert JSON number {num} to JsValue"
@ -104,15 +104,15 @@ impl JsValue {
/// # assert_eq!(json, back_to_json);
/// ```
pub fn to_json(&self, context: &mut Context) -> JsResult<Value> {
match self {
Self::Null => Ok(Value::Null),
Self::Undefined => todo!("undefined to JSON"),
&Self::Boolean(b) => Ok(b.into()),
Self::String(string) => Ok(string.as_str().into()),
&Self::Rational(rat) => Ok(rat.into()),
&Self::Integer(int) => Ok(int.into()),
Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"),
Self::Object(obj) => {
match self.variant() {
JsVariant::Null => Ok(Value::Null),
JsVariant::Undefined => todo!("undefined to JSON"),
JsVariant::Boolean(b) => Ok(b.into()),
JsVariant::String(string) => Ok(string.as_str().into()),
JsVariant::Float64(rat) => Ok(rat.into()),
JsVariant::Integer32(int) => Ok(int.into()),
JsVariant::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"),
JsVariant::Object(obj) => {
if obj.is_array() {
let len = obj.length_of_array_like(context)?;
let mut arr = Vec::with_capacity(len as usize);
@ -120,9 +120,12 @@ impl JsValue {
let obj = obj.borrow();
for k in 0..len as u32 {
let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| {
desc.value().cloned().unwrap_or(Self::Null)
});
let val = obj
.properties()
.get(&k.into())
.map_or(Self::null(), |desc| {
desc.value().cloned().unwrap_or(Self::null())
});
arr.push(val.to_json(context)?);
}
@ -149,7 +152,7 @@ impl JsValue {
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 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
@ -711,7 +711,7 @@ mod cyclic_conversions {
let value = forward_val(&mut context, src).unwrap();
let result = value.as_string().unwrap();
assert_eq!(result, "");
assert_eq!(*result, "");
}
#[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>.
#[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.
pub fn get_type(&self) -> Type {
match *self {
Self::Rational(_) | Self::Integer(_) => Type::Number,
Self::String(_) => Type::String,
Self::Boolean(_) => Type::Boolean,
Self::Symbol(_) => Type::Symbol,
Self::Null => Type::Null,
Self::Undefined => Type::Undefined,
Self::BigInt(_) => Type::BigInt,
Self::Object(_) => Type::Object,
match self.variant() {
JsVariant::Float64(_) | JsVariant::Integer32(_) => Type::Number,
JsVariant::String(_) => Type::String,
JsVariant::Boolean(_) => Type::Boolean,
JsVariant::Symbol(_) => Type::Symbol,
JsVariant::Null => Type::Null,
JsVariant::Undefined => Type::Undefined,
JsVariant::BigInt(_) => Type::BigInt,
JsVariant::Object(_) => Type::Object,
}
}
}

24
boa_engine/src/vm/code_block.rs

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

52
boa_engine/src/vm/mod.rs

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

2
boa_engine/src/vm/tests.rs

@ -62,7 +62,7 @@ fn multiple_catches() {
assert_eq!(
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);
if let Err(..) = buffer {
println!("Error: {}", buffer.unwrap_err());
Ok(JsValue::Rational(-1.0))
Ok(JsValue::from(-1.0))
} else {
// Load and parse the module source
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);
// 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");
}

Loading…
Cancel
Save