Browse Source

Implement `change-array-by-copy` methods (#3412)

* Implement `change-array-by-copy` methods

* Apply review
pull/3417/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
432deeee5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 741
      boa_engine/src/builtins/array/mod.rs
  2. 2
      boa_engine/src/builtins/array/tests.rs
  3. 338
      boa_engine/src/builtins/typed_array/builtin.rs
  4. 2
      boa_engine/src/builtins/typed_array/mod.rs
  5. 49
      boa_engine/src/object/builtins/jsarray.rs
  6. 147
      boa_engine/src/object/builtins/jstypedarray.rs
  7. 38
      boa_engine/src/object/internal_methods/integer_indexed.rs
  8. 5
      test262_config.toml

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

@ -82,6 +82,10 @@ impl IntrinsicObject for Array {
let unscopables_object = Self::unscopables_object();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
// Static Methods
.static_method(Self::from, js_string!("from"), 1)
.static_method(Self::is_array, js_string!("isArray"), 1)
.static_method(Self::of, js_string!("of"), 0)
.static_accessor(
JsSymbol::species(),
Some(get_species),
@ -93,62 +97,62 @@ impl IntrinsicObject for Array {
0,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.property(
utf16!("values"),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
symbol_iterator,
values_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
symbol_unscopables,
unscopables_object,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::at, js_string!("at"), 1)
.method(Self::concat, js_string!("concat"), 1)
.method(Self::push, js_string!("push"), 1)
.method(Self::index_of, js_string!("indexOf"), 1)
.method(Self::last_index_of, js_string!("lastIndexOf"), 1)
.method(Self::includes_value, js_string!("includes"), 1)
.method(Self::map, js_string!("map"), 1)
.method(Self::copy_within, js_string!("copyWithin"), 2)
.method(Self::entries, js_string!("entries"), 0)
.method(Self::every, js_string!("every"), 1)
.method(Self::fill, js_string!("fill"), 1)
.method(Self::for_each, js_string!("forEach"), 1)
.method(Self::filter, js_string!("filter"), 1)
.method(Self::pop, js_string!("pop"), 0)
.method(Self::join, js_string!("join"), 1)
.property(
utf16!("toString"),
to_string_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::reverse, js_string!("reverse"), 0)
.method(Self::shift, js_string!("shift"), 0)
.method(Self::unshift, js_string!("unshift"), 1)
.method(Self::every, js_string!("every"), 1)
.method(Self::find, js_string!("find"), 1)
.method(Self::find_index, js_string!("findIndex"), 1)
.method(Self::find_last, js_string!("findLast"), 1)
.method(Self::find_last_index, js_string!("findLastIndex"), 1)
.method(Self::flat, js_string!("flat"), 0)
.method(Self::flat_map, js_string!("flatMap"), 1)
.method(Self::for_each, js_string!("forEach"), 1)
.method(Self::includes_value, js_string!("includes"), 1)
.method(Self::index_of, js_string!("indexOf"), 1)
.method(Self::join, js_string!("join"), 1)
.method(Self::keys, js_string!("keys"), 0)
.method(Self::last_index_of, js_string!("lastIndexOf"), 1)
.method(Self::map, js_string!("map"), 1)
.method(Self::pop, js_string!("pop"), 0)
.method(Self::push, js_string!("push"), 1)
.method(Self::reduce, js_string!("reduce"), 1)
.method(Self::reduce_right, js_string!("reduceRight"), 1)
.method(Self::reverse, js_string!("reverse"), 0)
.method(Self::shift, js_string!("shift"), 0)
.method(Self::slice, js_string!("slice"), 2)
.method(Self::some, js_string!("some"), 1)
.method(Self::sort, js_string!("sort"), 1)
.method(Self::splice, js_string!("splice"), 2)
.method(Self::to_locale_string, js_string!("toLocaleString"), 0)
.method(Self::reduce, js_string!("reduce"), 1)
.method(Self::reduce_right, js_string!("reduceRight"), 1)
.method(Self::keys, js_string!("keys"), 0)
.method(Self::entries, js_string!("entries"), 0)
.method(Self::copy_within, js_string!("copyWithin"), 2)
// Static Methods
.static_method(Self::from, js_string!("from"), 1)
.static_method(Self::is_array, js_string!("isArray"), 1)
.static_method(Self::of, js_string!("of"), 0)
.method(Self::to_reversed, js_string!("toReversed"), 0)
.method(Self::to_sorted, js_string!("toSorted"), 1)
.method(Self::to_spliced, js_string!("toSpliced"), 2)
.method(Self::unshift, js_string!("unshift"), 1)
.method(Self::with, js_string!("with"), 2)
.property(
utf16!("toString"),
to_string_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
utf16!("values"),
values_function.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
symbol_iterator,
values_function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
symbol_unscopables,
unscopables_object,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
}
@ -1130,6 +1134,46 @@ impl Array {
Ok(o.into())
}
/// [`Array.prototype.toReversed()`][spec]
///
/// Reverses this array, returning the result into a copy of the array.
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.toreversed
pub(crate) fn to_reversed(
this: &JsValue,
_: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. Let A be ? ArrayCreate(len).
let a = Array::array_create(len, None, context)?;
// 4. Let k be 0.
// 5. Repeat, while k < len,
for i in 0..len {
// a. Let from be ! ToString(𝔽(len - k - 1)).
let from = len - i - 1;
// b. Let Pk be ! ToString(𝔽(k)).
// c. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue).
a.create_data_property_or_throw(i, from_value, context)
.expect("cannot fail per the spec");
// e. Set k to k + 1.
}
// 6. Return A.
Ok(a.into())
}
/// `Array.prototype.shift()`
///
/// The first element of the array is removed from the array and returned.
@ -1938,13 +1982,13 @@ impl Array {
// 4. If relativeStart is -∞, let k be 0.
// 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
// 6. Else, let k be min(relativeStart, len).
let mut k = Self::get_relative_start(context, args.get(1), len)?;
let mut k = Self::get_relative_start(context, args.get_or_undefined(1), len)?;
// 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 8. If relativeEnd is -∞, let final be 0.
// 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
// 10. Else, let final be min(relativeEnd, len).
let final_ = Self::get_relative_end(context, args.get(2), len)?;
let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)?;
let value = args.get_or_undefined(0);
@ -2063,13 +2107,13 @@ impl Array {
// 4. If relativeStart is -∞, let k be 0.
// 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
// 6. Else, let k be min(relativeStart, len).
let mut k = Self::get_relative_start(context, args.get(0), len)?;
let mut k = Self::get_relative_start(context, args.get_or_undefined(0), len)?;
// 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 8. If relativeEnd is -∞, let final be 0.
// 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
// 10. Else, let final be min(relativeEnd, len).
let final_ = Self::get_relative_end(context, args.get(1), len)?;
let final_ = Self::get_relative_end(context, args.get_or_undefined(1), len)?;
// 11. Let count be max(final - k, 0).
let count = final_.saturating_sub(k);
@ -2171,6 +2215,43 @@ impl Array {
Ok(js_string!(r).into())
}
/// Gets the delete count of a splice operation.
fn get_delete_count(
len: u64,
actual_start: u64,
start: Option<&JsValue>,
delete_count: Option<&JsValue>,
context: &mut Context<'_>,
) -> JsResult<u64> {
// 8. If start is not present, then
let actual_delete_count = if start.is_none() {
// a. Let actualDeleteCount be 0.
0
}
// 10. Else,
else if let Some(delete_count) = delete_count {
// a. Let dc be ? ToIntegerOrInfinity(deleteCount).
let dc = delete_count.to_integer_or_infinity(context)?;
// b. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart.
let max = len - actual_start;
match dc {
IntegerOrInfinity::Integer(i) => u64::try_from(i)
.unwrap_or_default()
.clamp(0, len - actual_start),
IntegerOrInfinity::PositiveInfinity => max,
IntegerOrInfinity::NegativeInfinity => 0,
}
}
// 9. Else if deleteCount is not present, then
else {
// a. Let actualDeleteCount be len - actualStart.
len - actual_start
};
Ok(actual_delete_count)
}
/// `Array.prototype.splice ( start, [deleteCount[, ...items]] )`
///
/// Splices an array by following
@ -2188,78 +2269,53 @@ impl Array {
let start = args.get(0);
let delete_count = args.get(1);
let items = args.get(2..).unwrap_or(&[]);
let items = args.get(2..).unwrap_or_default();
// 3. Let relativeStart be ? ToIntegerOrInfinity(start).
// 4. If relativeStart is -∞, let actualStart be 0.
// 4. If relativeStart = -∞, let actualStart be 0.
// 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
// 6. Else, let actualStart be min(relativeStart, len).
let actual_start = Self::get_relative_start(context, start, len)?;
// 7. If start is not present, then
let insert_count = if start.is_none() || delete_count.is_none() {
// 7a. Let insertCount be 0.
// 8. Else if deleteCount is not present, then
// a. Let insertCount be 0.
0
// 9. Else,
} else {
// 9a. Let insertCount be the number of elements in items.
items.len() as u64
};
let actual_delete_count = if start.is_none() {
// 7b. Let actualDeleteCount be 0.
0
// 8. Else if deleteCount is not present, then
} else if delete_count.is_none() {
// 8b. Let actualDeleteCount be len - actualStart.
len - actual_start
// 9. Else,
} else {
// b. Let dc be ? ToIntegerOrInfinity(deleteCount).
let dc = delete_count
.cloned()
.unwrap_or_default()
.to_integer_or_infinity(context)?;
// c. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart.
let max = len - actual_start;
match dc {
IntegerOrInfinity::Integer(i) => u64::try_from(i).unwrap_or_default().clamp(0, max),
IntegerOrInfinity::PositiveInfinity => max,
IntegerOrInfinity::NegativeInfinity => 0,
}
};
let actual_start =
Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?;
// 7. Let itemCount be the number of elements in items.
let item_count = items.len() as u64;
// 10. If len + insertCount - actualDeleteCount > 2^53 - 1, throw a TypeError exception.
if len + insert_count - actual_delete_count > Number::MAX_SAFE_INTEGER as u64 {
let actual_delete_count =
Self::get_delete_count(len, actual_start, start, delete_count, context)?;
// If len + itemCount - actualDeleteCount > 2**53 - 1, throw a TypeError exception.
if len + item_count - actual_delete_count > Number::MAX_SAFE_INTEGER as u64 {
return Err(JsNativeError::typ()
.with_message("Target splice exceeded max safe integer value")
.into());
}
// 11. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
// 12. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
let arr = Self::array_species_create(&o, actual_delete_count, context)?;
// 12. Let k be 0.
// 13. Repeat, while k < actualDeleteCount,
// 13. Let k be 0.
// 14. Repeat, while k < actualDeleteCount,
for k in 0..actual_delete_count {
// a. Let from be ! ToString(𝔽(actualStart + k)).
// b. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(actual_start + k, context)?;
// c. If fromPresent is true, then
if from_present {
// b. If ? HasProperty(O, from) is true, then
if o.has_property(actual_start + k, context)? {
// i. Let fromValue be ? Get(O, from).
let from_value = o.get(actual_start + k, context)?;
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(k)), fromValue).
arr.create_data_property_or_throw(k, from_value, context)?;
}
// d. Set k to k + 1.
// c. Set k to k + 1.
}
// 14. Perform ? Set(A, "length", 𝔽(actualDeleteCount), true).
// 15. Perform ? Set(A, "length", 𝔽(actualDeleteCount), true).
Self::set_length(&arr, actual_delete_count, context)?;
// 15. Let itemCount be the number of elements in items.
let item_count = items.len() as u64;
match item_count.cmp(&actual_delete_count) {
Ordering::Equal => {}
// 16. If itemCount < actualDeleteCount, then
Ordering::Less => {
// a. Set k to actualStart.
@ -2267,30 +2323,31 @@ impl Array {
for k in actual_start..(len - actual_delete_count) {
// i. Let from be ! ToString(𝔽(k + actualDeleteCount)).
let from = k + actual_delete_count;
// ii. Let to be ! ToString(𝔽(k + itemCount)).
let to = k + item_count;
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// iii. If ? HasProperty(O, from) is true, then
if o.has_property(from, context)? {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
// v. Else,
} else {
// 1. Assert: fromPresent is false.
debug_assert!(!from_present);
// 2. Perform ? DeletePropertyOrThrow(O, to).
// iv. Else,
// 1. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// vi. Set k to k + 1.
// v. Set k to k + 1.
}
// c. Set k to len.
// d. Repeat, while k > (len - actualDeleteCount + itemCount),
for k in ((len - actual_delete_count + item_count)..len).rev() {
// i. Perform ? DeletePropertyOrThrow(O, ! ToString(𝔽(k - 1))).
o.delete_property_or_throw(k, context)?;
// ii. Set k to k - 1.
}
}
@ -2301,41 +2358,34 @@ impl Array {
for k in (actual_start..len - actual_delete_count).rev() {
// i. Let from be ! ToString(𝔽(k + actualDeleteCount - 1)).
let from = k + actual_delete_count;
// ii. Let to be ! ToString(𝔽(k + itemCount - 1)).
let to = k + item_count;
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// iii. If ? HasProperty(O, from) is true, then
if o.has_property(from, context)? {
// 1. Let fromValue be ? Get(O, from).
let from_value = o.get(from, context)?;
// 2. Perform ? Set(O, to, fromValue, true).
o.set(to, from_value, true, context)?;
// v. Else,
} else {
// 1. Assert: fromPresent is false.
debug_assert!(!from_present);
// 2. Perform ? DeletePropertyOrThrow(O, to).
}
// iv. Else,
else {
// 1. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// vi. Set k to k - 1.
// v. Set k to k - 1.
}
}
}
Ordering::Equal => {}
};
// 18. Set k to actualStart.
// 19. For each element E of items, do
if item_count > 0 {
for (k, item) in items
.iter()
.enumerate()
.map(|(i, val)| (i as u64 + actual_start, val))
{
for (i, item) in items.iter().enumerate() {
// a. Perform ? Set(O, ! ToString(𝔽(k)), E, true).
o.set(k, item.clone(), true, context)?;
// b. Set k to k + 1.
}
o.set(actual_start + i as u64, item.clone(), true, context)?;
}
// 20. Perform ? Set(O, "length", 𝔽(len - actualDeleteCount + itemCount), true).
@ -2345,6 +2395,103 @@ impl Array {
Ok(JsValue::from(arr))
}
/// [`Array.prototype.toSpliced ( start, skipCount, ...items )`][spec]
///
/// Splices the target array, returning the result as a new array.
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tospliced
fn to_spliced(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
let start = args.get(0);
let skip_count = args.get(1);
let items = args.get(2..).unwrap_or_default();
// 3. Let relativeStart be ? ToIntegerOrInfinity(start).
// 4. If relativeStart is -∞, let actualStart be 0.
// 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
// 6. Else, let actualStart be min(relativeStart, len).
let actual_start =
Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?;
// 7. Let insertCount be the number of elements in items.
let insert_count = items.len() as u64;
let actual_skip_count =
Self::get_delete_count(len, actual_start, start, skip_count, context)?;
// 11. Let newLen be len + insertCount - actualSkipCount.
let new_len = len + insert_count - actual_skip_count;
// 12. If newLen > 2**53 - 1, throw a TypeError exception.
if new_len > Number::MAX_SAFE_INTEGER as u64 {
return Err(JsNativeError::typ()
.with_message("Target splice exceeded max safe integer value")
.into());
}
// 13. Let A be ? ArrayCreate(newLen).
let arr = Array::array_create(new_len, None, context)?;
// 14. Let i be 0.
let mut i = 0;
// 16. Repeat, while i < actualStart,
while i < actual_start {
// a. Let Pi be ! ToString(𝔽(i)).
// b. Let iValue be ? Get(O, Pi).
let value = o.get(i, context)?;
// c. Perform ! CreateDataPropertyOrThrow(A, Pi, iValue).
arr.create_data_property_or_throw(i, value, context)
.expect("cannot fail for a newly created array");
// d. Set i to i + 1.
i += 1;
}
// 17. For each element E of items, do
for item in items.iter().cloned() {
// a. Let Pi be ! ToString(𝔽(i)).
// b. Perform ! CreateDataPropertyOrThrow(A, Pi, E).
arr.create_data_property_or_throw(i, item, context)
.expect("cannot fail for a newly created array");
// c. Set i to i + 1.
i += 1;
}
// 15. Let r be actualStart + actualSkipCount.
let mut r = actual_start + actual_skip_count;
// 18. Repeat, while i < newLen,
while i < new_len {
// a. Let Pi be ! ToString(𝔽(i)).
// b. Let from be ! ToString(𝔽(r)).
// c. Let fromValue be ? Get(O, from).
let from_value = o.get(r, context)?;
// d. Perform ! CreateDataPropertyOrThrow(A, Pi, fromValue).
arr.create_data_property_or_throw(i, from_value, context)
.expect("cannot fail for a newly created array");
// e. Set i to i + 1.
i += 1;
// f. Set r to r + 1.
r += 1;
}
// 19. Return A.
Ok(arr.into())
}
/// `Array.prototype.filter( callback, [ thisArg ] )`
///
/// For each element in the array the callback function is called, and a new
@ -2462,6 +2609,65 @@ impl Array {
Ok(JsValue::new(false))
}
/// [`SortIndexedProperties ( obj, len, SortCompare, holes )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-sortindexedproperties
pub(crate) fn sort_indexed_properties<F>(
obj: &JsObject,
len: u64,
sort_compare: F,
skip_holes: bool,
context: &mut Context<'_>,
) -> JsResult<Vec<JsValue>>
where
F: Fn(&JsValue, &JsValue, &mut Context<'_>) -> JsResult<Ordering>,
{
// 1. Let items be a new empty List.
// doesn't matter if it clamps since it's just a best-effort optimization
let mut items = Vec::with_capacity(len as usize);
// 2. Let k be 0.
// 3. Repeat, while k < len,
for i in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. If holes is skip-holes, then
let read = if skip_holes {
// i. Let kRead be ? HasProperty(obj, Pk).
obj.has_property(i, context)?
}
// c. Else,
else {
// i. Assert: holes is read-through-holes.
// ii. Let kRead be true.
true
};
// d. If kRead is true, then
if read {
// i. Let kValue be ? Get(obj, Pk).
// ii. Append kValue to items.
items.push(obj.get(i, context)?);
}
// e. Set k to k + 1.
}
// 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare and return that Completion Record.
let mut sort_err = Ok(());
items.sort_by(|x, y| {
if sort_err.is_ok() {
sort_compare(x, y, context).unwrap_or_else(|err| {
sort_err = Err(err);
Ordering::Equal
})
} else {
Ordering::Equal
}
});
sort_err?;
// 5. Return items.
Ok(items)
}
/// Array.prototype.sort ( comparefn )
///
/// The sort method sorts the elements of an array in place and returns the sorted array.
@ -2490,113 +2696,102 @@ impl Array {
}
};
// Abstract method `SortCompare`.
//
// More information:
// - [ECMAScript reference][spec]
//
// [spec]: https://tc39.es/ecma262/#sec-sortcompare
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context<'_>| -> JsResult<Ordering> {
match (x.is_undefined(), y.is_undefined()) {
// 1. If x and y are both undefined, return +0𝔽.
(true, true) => return Ok(Ordering::Equal),
// 2. If x is undefined, return 1𝔽.
(true, false) => return Ok(Ordering::Greater),
// 3. If y is undefined, return -1𝔽.
(false, true) => return Ok(Ordering::Less),
_ => {}
}
// 4. If comparefn is not undefined, then
if let Some(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)?
.to_number(context)?;
// b. If v is NaN, return +0𝔽.
// c. Return v.
return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal));
}
// 5. Let xString be ? ToString(x).
// 6. Let yString be ? ToString(y).
let x_str = x.to_string(context)?;
let y_str = y.to_string(context)?;
// 7. Let xSmaller be IsLessThan(xString, yString, true).
// 8. If xSmaller is true, return -1𝔽.
// 9. Let ySmaller be IsLessThan(yString, xString, true).
// 10. If ySmaller is true, return 1𝔽.
// 11. Return +0𝔽.
// NOTE: skipped IsLessThan because it just makes a lexicographic comparison
// when x and y are strings
Ok(x_str.cmp(&y_str))
};
// 2. Let obj be ? ToObject(this value).
let obj = this.to_object(context)?;
// 3. Let len be ? LengthOfArrayLike(obj).
let length = obj.length_of_array_like(context)?;
let len = obj.length_of_array_like(context)?;
// 4. Let items be a new empty List.
let mut items = Vec::with_capacity(length as usize);
// 4. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context<'_>| -> JsResult<Ordering> {
// a. Return ? CompareArrayElements(x, y, comparefn).
compare_array_elements(x, y, comparefn, context)
};
// 5. Let k be 0.
// 6. Repeat, while k < len,
for k in 0..length {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(obj, Pk).
// c. If kPresent is true, then
if obj.has_property(k, context)? {
// i. Let kValue be ? Get(obj, Pk).
let kval = obj.get(k, context)?;
// ii. Append kValue to items.
items.push(kval);
}
// d. Set k to k + 1.
}
// 5. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, skip-holes).
let sorted = Self::sort_indexed_properties(&obj, len, sort_compare, true, context)?;
// 7. Let itemCount be the number of elements in items.
let item_count = items.len() as u64;
let sorted_len = sorted.len() as u64;
// 8. Sort items using an implementation-defined sequence of calls to SortCompare.
// If any such call returns an abrupt completion, stop before performing any further
// calls to SortCompare or steps in this algorithm and return that completion.
let mut sort_err = Ok(());
items.sort_by(|x, y| {
if sort_err.is_ok() {
sort_compare(x, y, context).unwrap_or_else(|err| {
sort_err = Err(err);
Ordering::Equal
})
} else {
Ordering::Equal
}
});
sort_err?;
// 9. Let j be 0.
// 10. Repeat, while j < itemCount,
for (j, item) in items.into_iter().enumerate() {
// a. Perform ? Set(obj, ! ToString(𝔽(j)), items[j], true).
// 6. Let itemCount be the number of elements in sortedList.
// 7. Let j be 0.
// 8. Repeat, while j < itemCount,
for (j, item) in sorted.into_iter().enumerate() {
// a. Perform ? Set(obj, ! ToString(𝔽(j)), sortedList[j], true).
obj.set(j, item, true, context)?;
// b. Set j to j + 1.
}
// 11. Repeat, while j < len,
for j in item_count..length {
// 9. NOTE: The call to SortIndexedProperties in step 5 uses skip-holes. The remaining indices
// are deleted to preserve the number of holes that were detected and excluded from the sort.
// 10. Repeat, while j < len,
for j in sorted_len..len {
// a. Perform ? DeletePropertyOrThrow(obj, ! ToString(𝔽(j))).
obj.delete_property_or_throw(j, context)?;
// b. Set j to j + 1.
}
// 12. Return obj.
// 11. Return obj.
Ok(obj.into())
}
/// [`Array.prototype.toSorted ( comparefn )`][spec]
///
/// Orders the target array, returning the result in a new array.
///
/// [spec]: https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.tosorted
pub(crate) fn to_sorted(
this: &JsValue,
args: &[JsValue],
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,
_ => {
return Err(JsNativeError::typ()
.with_message("The comparison function must be either a function or undefined")
.into())
}
};
// 2. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 3. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 4. Let A be ? ArrayCreate(len).
let arr = Array::array_create(len, None, context)?;
// 5. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context<'_>| -> JsResult<Ordering> {
// a. Return ? CompareArrayElements(x, y, comparefn).
compare_array_elements(x, y, comparefn, context)
};
// 6. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes).
let sorted = Self::sort_indexed_properties(&o, len, sort_compare, false, context)?;
// 7. Let j be 0.
// 8. Repeat, while j < len,
for (i, item) in sorted.into_iter().enumerate() {
// a. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(j)), sortedList[j]).
arr.create_data_property_or_throw(i, item, context)
.expect("cannot fail for a newly created array");
// b. Set j to j + 1.
}
// 9. Return A.
Ok(arr.into())
}
/// `Array.prototype.reduce( callbackFn [ , initialValue ] )`
///
/// More information:
@ -2811,19 +3006,19 @@ impl Array {
// 4. If relativeTarget is -∞, let to be 0.
// 5. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0).
// 6. Else, let to be min(relativeTarget, len).
let mut to = Self::get_relative_start(context, args.get(0), len)? as i64;
let mut to = Self::get_relative_start(context, args.get_or_undefined(0), len)? as i64;
// 7. Let relativeStart be ? ToIntegerOrInfinity(start).
// 8. If relativeStart is -∞, let from be 0.
// 9. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
// 10. Else, let from be min(relativeStart, len).
let mut from = Self::get_relative_start(context, args.get(1), len)? as i64;
let mut from = Self::get_relative_start(context, args.get_or_undefined(1), len)? as i64;
// 11. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
// 12. If relativeEnd is -∞, let final be 0.
// 13. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
// 14. Else, let final be min(relativeEnd, len).
let final_ = Self::get_relative_end(context, args.get(2), len)? as i64;
let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)? as i64;
// 15. Let count be min(final - from, len - to).
let mut count = min(final_ - from, len as i64 - to);
@ -2954,17 +3149,78 @@ impl Array {
))
}
/// [`Array.prototype.with ( index, value )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.with
pub(crate) fn with(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Let len be ? LengthOfArrayLike(O).
let len = o.length_of_array_like(context)?;
// 3. Let relativeIndex be ? ToIntegerOrInfinity(index).
let IntegerOrInfinity::Integer(relative_index) =
args.get_or_undefined(0).to_integer_or_infinity(context)?
else {
return Err(JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
.into());
};
let value = args.get_or_undefined(1);
// 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex.
let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0`
.ok()
// 5. Else, let actualIndex be len + relativeIndex.
.or_else(|| len.checked_add_signed(relative_index))
.filter(|&rel| rel < len)
.ok_or_else(|| {
// 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception.
JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
})?;
// 7. Let A be ? ArrayCreate(len).
let new_array = Array::array_create(len, None, context)?;
// 8. Let k be 0.
// 9. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
let from_value = if k == actual_index {
// b. If k is actualIndex, let fromValue be value.
value.clone()
} else {
// c. Else, let fromValue be ? Get(O, Pk).
o.get(k, context)?
};
// d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue).
new_array
.create_data_property_or_throw(k, from_value, context)
.expect("cannot fail for a newly created array");
// e. Set k to k + 1.
}
// 10. Return A.
Ok(new_array.into())
}
/// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions.
pub(super) fn get_relative_start(
context: &mut Context<'_>,
arg: Option<&JsValue>,
arg: &JsValue,
len: u64,
) -> JsResult<u64> {
// 1. Let relativeStart be ? ToIntegerOrInfinity(start).
let relative_start = arg
.cloned()
.unwrap_or_default()
.to_integer_or_infinity(context)?;
let relative_start = arg.to_integer_or_infinity(context)?;
match relative_start {
// 2. If relativeStart is -∞, let k be 0.
IntegerOrInfinity::NegativeInfinity => Ok(0),
@ -2982,11 +3238,9 @@ impl Array {
/// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions.
pub(super) fn get_relative_end(
context: &mut Context<'_>,
arg: Option<&JsValue>,
value: &JsValue,
len: u64,
) -> JsResult<u64> {
let default_value = JsValue::undefined();
let value = arg.unwrap_or(&default_value);
// 1. If end is undefined, let relativeEnd be len [and return it]
if value.is_undefined() {
Ok(len)
@ -3065,6 +3319,53 @@ impl Array {
}
}
/// [`CompareArrayElements ( x, y, comparefn )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-comparearrayelements
fn compare_array_elements(
x: &JsValue,
y: &JsValue,
comparefn: Option<&JsObject>,
context: &mut Context<'_>,
) -> JsResult<Ordering> {
match (x.is_undefined(), y.is_undefined()) {
// 1. If x and y are both undefined, return +0𝔽.
(true, true) => return Ok(Ordering::Equal),
// 2. If x is undefined, return 1𝔽.
(true, false) => return Ok(Ordering::Greater),
// 3. If y is undefined, return -1𝔽.
(false, true) => return Ok(Ordering::Less),
_ => {}
}
// 4. If comparefn is not undefined, then
if let Some(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)?
.to_number(context)?;
// b. If v is NaN, return +0𝔽.
// c. Return v.
return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal));
}
// 5. Let xString be ? ToString(x).
let x_str = x.to_string(context)?;
// 6. Let yString be ? ToString(y).
let y_str = y.to_string(context)?;
// 7. Let xSmaller be ! IsLessThan(xString, yString, true).
// 8. If xSmaller is true, return -1𝔽.
// 9. Let ySmaller be ! IsLessThan(yString, xString, true).
// 10. If ySmaller is true, return 1𝔽.
// 11. Return +0𝔽.
// NOTE: skipped IsLessThan because it just makes a lexicographic comparison
// when x and y are strings
Ok(x_str.cmp(&y_str))
}
/// `FindViaPredicate ( O, len, direction, predicate, thisArg )`
///
/// More information:

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

@ -874,6 +874,7 @@ fn array_spread_non_iterable() {
fn get_relative_start() {
#[track_caller]
fn assert(context: &mut Context<'_>, arg: Option<&JsValue>, len: u64, expected: u64) {
let arg = arg.unwrap_or(&JsValue::Undefined);
assert_eq!(
Array::get_relative_start(context, arg, len).unwrap(),
expected
@ -900,6 +901,7 @@ fn get_relative_start() {
fn get_relative_end() {
#[track_caller]
fn assert(context: &mut Context<'_>, arg: Option<&JsValue>, len: u64, expected: u64) {
let arg = arg.unwrap_or(&JsValue::Undefined);
assert_eq!(
Array::get_relative_end(context, arg, len).unwrap(),
expected

338
boa_engine/src/builtins/typed_array/builtin.rs

@ -1,5 +1,6 @@
use std::{cmp::Ordering, ptr, sync::atomic};
use boa_gc::GcRef;
use boa_macros::utf16;
use num_traits::Zero;
@ -11,13 +12,13 @@ use crate::{
ArrayBuffer, BufferRef,
},
iterable::iterable_to_list,
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
js_string,
object::{
internal_methods::{get_prototype_from_constructor, integer_indexed_element_set},
ObjectData,
Object, ObjectData,
},
property::{Attribute, PropertyNameKind},
realm::Realm,
@ -135,6 +136,9 @@ impl IntrinsicObject for BuiltinTypedArray {
.method(Self::sort, js_string!("sort"), 1)
.method(Self::subarray, js_string!("subarray"), 2)
.method(Self::to_locale_string, js_string!("toLocaleString"), 0)
.method(Self::to_reversed, js_string!("toReversed"), 0)
.method(Self::to_sorted, js_string!("toSorted"), 1)
.method(Self::with, js_string!("with"), 2)
// 23.2.3.29 %TypedArray%.prototype.toString ( )
// The initial value of the %TypedArray%.prototype.toString data property is the same
// built-in function object as the Array.prototype.toString method defined in 23.1.3.30.
@ -291,6 +295,20 @@ impl BuiltinTypedArray {
Ok(target_obj.into())
}
/// [`TypedArrayCreateSameType ( exemplar, argumentList )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-typedarray-create-same-type
fn from_kind_and_length(
kind: TypedArrayKind,
length: u64,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
let constructor =
kind.standard_constructor()(context.intrinsics().constructors()).constructor();
Self::create(&constructor, &[length.into()], context)
}
/// `23.2.2.2 %TypedArray%.of ( ...items )`
///
/// More information:
@ -1823,19 +1841,19 @@ impl BuiltinTypedArray {
}
// 3. Let len be O.[[ArrayLength]].
let len = o.array_length() as f64;
let len = o.array_length();
drop(obj_borrow);
// 4. Let middle be floor(len / 2).
let middle = (len / 2.0).floor();
let middle = len / 2;
// 5. Let lower be 0.
let mut lower = 0.0;
let mut lower = 0;
// 6. Repeat, while lower ≠ middle,
while lower != middle {
// a. Let upper be len - lower - 1.
let upper = len - lower - 1.0;
let upper = len - lower - 1;
// b. Let upperP be ! ToString(𝔽(upper)).
// c. Let lowerP be ! ToString(𝔽(lower)).
@ -1852,13 +1870,64 @@ impl BuiltinTypedArray {
.expect("Set cannot fail here");
// h. Set lower to lower + 1.
lower += 1.0;
lower += 1;
}
// 7. Return O.
Ok(this.clone())
}
/// [`%TypedArray%.prototype.toReversed ( )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed
pub(crate) fn to_reversed(
this: &JsValue,
_: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
// 2. Let iieoRecord be ? ValidateTypedArray(O, seq-cst).
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
if array.is_detached() {
return Err(JsNativeError::typ()
.with_message("Buffer of the typed array is detached")
.into());
}
// 3. Let length be IntegerIndexedObjectLength(iieoRecord).
let length = array.array_length();
// 4. Let A be ? TypedArrayCreateSameType(O, « 𝔽(length) »).
let new_array = Self::from_kind_and_length(array.kind(), length, context)?;
drop(array);
// 5. Let k be 0.
// 6. Repeat, while k < length,
for k in 0..length {
// a. Let from be ! ToString(𝔽(length - k - 1)).
// b. Let Pk be ! ToString(𝔽(k)).
// c. Let fromValue be ! Get(O, from).
let value = obj
.get(length - k - 1, context)
.expect("cannot fail per the spec");
// d. Perform ! Set(A, Pk, fromValue, true).
new_array
.set(k, value, true, context)
.expect("cannot fail per the spec");
// e. Set k to k + 1.
}
// 7. Return A.
Ok(new_array.into())
}
/// `23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )`
///
/// More information:
@ -2489,12 +2558,13 @@ impl BuiltinTypedArray {
};
// 2. Let obj be the this value.
// 3. Perform ? ValidateTypedArray(obj).
// 4. Let len be obj.[[ArrayLength]].
// 3. Let iieoRecord be ? ValidateTypedArray(obj, seq-cst).
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("TypedArray.sort must be called on typed array object")
})?;
// 4. Let len be IntegerIndexedObjectLength(iieoRecord).
let len =
{
let obj_borrow = obj.borrow();
@ -2513,56 +2583,18 @@ impl BuiltinTypedArray {
// 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30.
// 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare = |x: &JsValue,
y: &JsValue,
compare_fn: Option<&JsObject>,
context: &mut Context<'_>|
-> JsResult<Ordering> {
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context<'_>| -> JsResult<Ordering> {
// a. Return ? CompareTypedArrayElements(x, y, comparefn).
compare_typed_array_elements(x, y, compare_fn, context)
};
// Note: This step is currently inlined.
// 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes).
// 1. Let items be a new empty List.
let mut items = Vec::with_capacity(len as usize);
// 2. Let k be 0.
// 3. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. If holes is skip-holes, then
// i. Let kRead be ? HasProperty(obj, Pk).
// c. Else,
// i. Assert: holes is read-through-holes.
// ii. Let kRead be true.
// d. If kRead is true, then
// i. Let kValue be ? Get(obj, Pk).
let k_value = obj.get(k, context)?;
// ii. Append kValue to items.
items.push(k_value);
// e. Set k to k + 1.
}
// 4. Sort items using an implementation-defined sequence of calls to SortCompare. If any such call returns an abrupt completion, stop before performing any further calls to SortCompare and return that Completion Record.
// 5. Return items.
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_err = Err(err);
Ordering::Equal
})
} else {
Ordering::Equal
}
});
sort_err?;
let sorted = Array::sort_indexed_properties(obj, len, sort_compare, false, context)?;
// 8. Let j be 0.
// 9. Repeat, while j < len,
for (j, item) in items.into_iter().enumerate() {
for (j, item) in sorted.into_iter().enumerate() {
// a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true).
obj.set(j, item, true, context)
.expect("cannot fail per spec");
@ -2574,6 +2606,74 @@ impl BuiltinTypedArray {
Ok(obj.clone().into())
}
/// [`%TypedArray%.prototype.toSorted ( comparefn )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.tosorted
pub(crate) fn to_sorted(
this: &JsValue,
args: &[JsValue],
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),
_ => {
return Err(JsNativeError::typ()
.with_message("TypedArray.sort called with non-callable comparefn")
.into())
}
};
// 2. Let O be the this value.
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("TypedArray.sort must be called on typed array object")
})?;
// 3. Let iieoRecord be ? ValidateTypedArray(O, seq-cst).
let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
if array.is_detached() {
return Err(JsNativeError::typ()
.with_message("Buffer of the typed array is detached")
.into());
}
// 4. Let len be IntegerIndexedObjectLength(iieoRecord).
let len = array.array_length();
// 5. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »).
let new_array = Self::from_kind_and_length(array.kind(), len, context)?;
drop(array);
// 6. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.34.
// 7. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
let sort_compare =
|x: &JsValue, y: &JsValue, context: &mut Context<'_>| -> JsResult<Ordering> {
// a. Return ? CompareTypedArrayElements(x, y, comparefn).
compare_typed_array_elements(x, y, compare_fn, context)
};
// 8. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes).
let sorted = Array::sort_indexed_properties(obj, len, sort_compare, false, context)?;
// 9. Let j be 0.
// 10. Repeat, while j < len
for (j, item) in sorted.into_iter().enumerate() {
// a. Perform ! Set(A, ! ToString(𝔽(j)), sortedList[j], true).
new_array
.set(j, item, true, context)
.expect("cannot fail per spec");
// b. Set j to j + 1.
}
// 11. Return A.
Ok(new_array.into())
}
/// `23.2.3.28 %TypedArray%.prototype.subarray ( begin, end )`
///
/// More information:
@ -2779,6 +2879,99 @@ impl BuiltinTypedArray {
))
}
/// [`%TypedArray%.prototype.with ( index, value )`][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.with
pub(crate) fn with(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
// 1. Let O be the this value.
let obj = this.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("TypedArray.sort must be called on typed array object")
})?;
// 2. Let iieoRecord be ? ValidateTypedArray(O, seq-cst).
let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| {
JsNativeError::typ().with_message("Value is not a typed array object")
})?;
if array.is_detached() {
return Err(JsNativeError::typ()
.with_message("Buffer of the typed array is detached")
.into());
}
// 3. Let len be IntegerIndexedObjectLength(iieoRecord).
let len = array.array_length();
let kind = array.kind();
drop(array);
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
// triggers any conversion errors before throwing range errors.
let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?;
let value = args.get_or_undefined(1);
// 7. If O.[[ContentType]] is bigint, let numericValue be ? ToBigInt(value).
let numeric_value: JsValue = if kind.content_type() == ContentType::BigInt {
value.to_bigint(context)?.into()
} else {
// 8. Else, let numericValue be ? ToNumber(value).
value.to_number(context)?.into()
};
// 9. If IsValidIntegerIndex(O, 𝔽(actualIndex)) is false, throw a RangeError exception.
let IntegerOrInfinity::Integer(relative_index) = relative_index else {
return Err(JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
.into());
};
let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0`
.ok()
.or_else(|| len.checked_add_signed(relative_index))
.filter(|&rel| rel < len)
.ok_or_else(|| {
JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
})?;
// TODO: Replace with `is_valid_integer_index_u64` or equivalent.
if !is_valid_integer_index(obj, actual_index as f64) {
return Err(JsNativeError::range()
.with_message("invalid integer index for TypedArray operation")
.into());
}
// 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »).
let new_array = Self::from_kind_and_length(kind, len, context)?;
// 11. Let k be 0.
// 12. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
let value = if k == actual_index {
// b. If k is actualIndex, let fromValue be numericValue.
numeric_value.clone()
} else {
// c. Else, let fromValue be ! Get(O, Pk).
obj.get(k, context).expect("cannot fail per the spec")
};
// d. Perform ! Set(A, Pk, fromValue, true).
new_array
.set(k, value, true, context)
.expect("cannot fail per the spec");
// e. Set k to k + 1.
}
// 13. Return A.
Ok(new_array.into())
}
/// `23.2.3.33 get %TypedArray%.prototype [ @@toStringTag ]`
///
/// More information:
@ -3291,6 +3484,12 @@ impl BuiltinTypedArray {
}
}
#[derive(Debug)]
enum U64OrPositiveInfinity {
U64(u64),
PositiveInfinity,
}
/// `CompareTypedArrayElements ( x, y, comparefn )`
///
/// More information:
@ -3382,7 +3581,38 @@ fn compare_typed_array_elements(
}
}
enum U64OrPositiveInfinity {
U64(u64),
PositiveInfinity,
/// Abstract operation `IsValidIntegerIndex ( O, index )`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex
pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool {
let obj = obj.borrow();
let inner = obj.as_typed_array().expect(
"integer indexed exotic method should only be callable from integer indexed objects",
);
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false.
if inner.is_detached() {
return false;
}
// 2. If IsIntegralNumber(index) is false, return false.
if index.is_nan() || index.is_infinite() || index.fract() != 0.0 {
return false;
}
// 3. If index is -0𝔽, return false.
if index == 0.0 && index.is_sign_negative() {
return false;
}
// 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false.
if index < 0.0 || index >= inner.array_length() as f64 {
return false;
}
// 5. Return true.
true
}

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

@ -34,7 +34,7 @@ mod builtin;
mod element;
mod integer_indexed_object;
pub(crate) use builtin::BuiltinTypedArray;
pub(crate) use builtin::{is_valid_integer_index, BuiltinTypedArray};
pub(crate) use element::{ClampedU8, Element};
pub use integer_indexed_object::IntegerIndexed;

49
boa_engine/src/object/builtins/jsarray.rs

@ -51,7 +51,7 @@ impl JsArray {
/// Get the length of the array.
///
/// Same a `array.length` in JavaScript.
/// Same as `array.length` in JavaScript.
#[inline]
pub fn length(&self, context: &mut Context<'_>) -> JsResult<u64> {
self.inner.length_of_array_like(context)
@ -373,6 +373,53 @@ impl JsArray {
context,
)
}
/// Calls `Array.prototype.toReversed`.
#[inline]
pub fn to_reversed(&self, context: &mut Context<'_>) -> JsResult<Self> {
let array = Array::to_reversed(&self.inner.clone().into(), &[], context)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`to_reversed` must always return an `Array` on success"),
})
}
/// Calls `Array.prototype.toSorted`.
#[inline]
pub fn to_sorted(
&self,
compare_fn: Option<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
let array = Array::to_sorted(
&self.inner.clone().into(),
&[compare_fn.into_or_undefined()],
context,
)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`to_sorted` must always return an `Array` on success"),
})
}
/// Calls `Array.prototype.with`.
#[inline]
pub fn with(&self, index: u64, value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
let array = Array::with(&self.inner.clone().into(), &[index.into(), value], context)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`with` must always return an `Array` on success"),
})
}
}
impl From<JsArray> for JsObject {

147
boa_engine/src/object/builtins/jstypedarray.rs

@ -14,7 +14,7 @@ use std::ops::Deref;
/// builtin object.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsTypedArray {
inner: JsValue,
inner: JsObject,
}
impl JsTypedArray {
@ -26,9 +26,7 @@ impl JsTypedArray {
#[inline]
pub fn from_object(object: JsObject) -> JsResult<Self> {
if object.is_typed_array() {
Ok(Self {
inner: object.into(),
})
Ok(Self { inner: object })
} else {
Err(JsNativeError::typ()
.with_message("object is not a TypedArray")
@ -38,13 +36,15 @@ impl JsTypedArray {
/// Get the length of the array.
///
/// Same a `array.length` in JavaScript.
/// Same as `array.length` in JavaScript.
#[inline]
pub fn length(&self, context: &mut Context<'_>) -> JsResult<usize> {
Ok(BuiltinTypedArray::length(&self.inner, &[], context)?
Ok(
BuiltinTypedArray::length(&self.inner.clone().into(), &[], context)?
.as_number()
.map(|x| x as usize)
.expect("length should return a number"))
.expect("length should return a number"),
)
}
/// Check if the array is empty, i.e. the `length` is zero.
@ -58,25 +58,29 @@ impl JsTypedArray {
where
T: Into<i64>,
{
BuiltinTypedArray::at(&self.inner, &[index.into().into()], context)
BuiltinTypedArray::at(&self.inner.clone().into(), &[index.into().into()], context)
}
/// Returns `TypedArray.prototype.byteLength`.
#[inline]
pub fn byte_length(&self, context: &mut Context<'_>) -> JsResult<usize> {
Ok(BuiltinTypedArray::byte_length(&self.inner, &[], context)?
Ok(
BuiltinTypedArray::byte_length(&self.inner.clone().into(), &[], context)?
.as_number()
.map(|x| x as usize)
.expect("byteLength should return a number"))
.expect("byteLength should return a number"),
)
}
/// Returns `TypedArray.prototype.byteOffset`.
#[inline]
pub fn byte_offset(&self, context: &mut Context<'_>) -> JsResult<usize> {
Ok(BuiltinTypedArray::byte_offset(&self.inner, &[], context)?
Ok(
BuiltinTypedArray::byte_offset(&self.inner.clone().into(), &[], context)?
.as_number()
.map(|x| x as usize)
.expect("byteLength should return a number"))
.expect("byteLength should return a number"),
)
}
/// Calls `TypedArray.prototype.fill()`.
@ -91,7 +95,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
BuiltinTypedArray::fill(
&self.inner,
&self.inner.clone().into(),
&[
value.into(),
start.into_or_undefined(),
@ -110,7 +114,7 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<bool> {
let result = BuiltinTypedArray::every(
&self.inner,
&self.inner.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()],
context,
)?
@ -129,7 +133,7 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<bool> {
let result = BuiltinTypedArray::some(
&self.inner,
&self.inner.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?
@ -146,7 +150,11 @@ impl JsTypedArray {
compare_fn: Option<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
BuiltinTypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?;
BuiltinTypedArray::sort(
&self.inner.clone().into(),
&[compare_fn.into_or_undefined()],
context,
)?;
Ok(self.clone())
}
@ -160,12 +168,17 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<Self> {
let object = BuiltinTypedArray::filter(
&self.inner,
&self.inner.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?;
Ok(Self { inner: object })
Ok(Self {
inner: object
.as_object()
.cloned()
.expect("`filter` must always return a `TypedArray` on success"),
})
}
/// Calls `TypedArray.prototype.map()`.
@ -177,12 +190,17 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<Self> {
let object = BuiltinTypedArray::map(
&self.inner,
&self.inner.clone().into(),
&[callback.into(), this_arg.into_or_undefined()],
context,
)?;
Ok(Self { inner: object })
Ok(Self {
inner: object
.as_object()
.cloned()
.expect("`map` must always return a `TypedArray` on success"),
})
}
/// Calls `TypedArray.prototype.reduce()`.
@ -194,7 +212,7 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
BuiltinTypedArray::reduce(
&self.inner,
&self.inner.clone().into(),
&[callback.into(), initial_value.into_or_undefined()],
context,
)
@ -209,7 +227,7 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
BuiltinTypedArray::reduceright(
&self.inner,
&self.inner.clone().into(),
&[callback.into(), initial_value.into_or_undefined()],
context,
)
@ -218,7 +236,7 @@ impl JsTypedArray {
/// Calls `TypedArray.prototype.reverse()`.
#[inline]
pub fn reverse(&self, context: &mut Context<'_>) -> JsResult<Self> {
BuiltinTypedArray::reverse(&self.inner, &[], context)?;
BuiltinTypedArray::reverse(&self.inner.clone().into(), &[], context)?;
Ok(self.clone())
}
@ -231,12 +249,17 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<Self> {
let object = BuiltinTypedArray::slice(
&self.inner,
&self.inner.clone().into(),
&[start.into_or_undefined(), end.into_or_undefined()],
context,
)?;
Ok(Self { inner: object })
Ok(Self {
inner: object
.as_object()
.cloned()
.expect("`slice` must always return a `TypedArray` on success"),
})
}
/// Calls `TypedArray.prototype.find()`.
@ -248,7 +271,7 @@ impl JsTypedArray {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
BuiltinTypedArray::find(
&self.inner,
&self.inner.clone().into(),
&[predicate.into(), this_arg.into_or_undefined()],
context,
)
@ -265,7 +288,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
let index = BuiltinTypedArray::index_of(
&self.inner,
&self.inner.clone().into(),
&[search_element.into(), from_index.into_or_undefined()],
context,
)?
@ -291,7 +314,7 @@ impl JsTypedArray {
T: Into<JsValue>,
{
let index = BuiltinTypedArray::last_index_of(
&self.inner,
&self.inner.clone().into(),
&[search_element.into(), from_index.into_or_undefined()],
context,
)?
@ -313,28 +336,78 @@ impl JsTypedArray {
separator: Option<JsString>,
context: &mut Context<'_>,
) -> JsResult<JsString> {
BuiltinTypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| {
BuiltinTypedArray::join(
&self.inner.clone().into(),
&[separator.into_or_undefined()],
context,
)
.map(|x| {
x.as_string()
.cloned()
.expect("TypedArray.prototype.join always returns string")
})
}
/// Calls `TypedArray.prototype.toReversed ( )`.
#[inline]
pub fn to_reversed(&self, context: &mut Context<'_>) -> JsResult<Self> {
let array = BuiltinTypedArray::to_reversed(&self.inner.clone().into(), &[], context)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`to_reversed` must always return a `TypedArray` on success"),
})
}
/// Calls `TypedArray.prototype.toSorted ( comparefn )`.
#[inline]
pub fn to_sorted(
&self,
compare_fn: Option<JsFunction>,
context: &mut Context<'_>,
) -> JsResult<Self> {
let array = BuiltinTypedArray::to_sorted(
&self.inner.clone().into(),
&[compare_fn.into_or_undefined()],
context,
)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`to_sorted` must always return a `TypedArray` on success"),
})
}
/// Calls `TypedArray.prototype.with ( index, value )`.
#[inline]
pub fn with(&self, index: u64, value: JsValue, context: &mut Context<'_>) -> JsResult<Self> {
let array =
BuiltinTypedArray::with(&self.inner.clone().into(), &[index.into(), value], context)?;
Ok(Self {
inner: array
.as_object()
.cloned()
.expect("`with` must always return a `TypedArray` on success"),
})
}
}
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()
}
}
@ -343,7 +416,7 @@ impl Deref for JsTypedArray {
#[inline]
fn deref(&self) -> &Self::Target {
self.inner.as_object().expect("should always be an object")
&self.inner
}
}
@ -463,8 +536,6 @@ macro_rules! JsTypedArrayType {
fn from(o: $name) -> Self {
o.inner
.inner
.as_object()
.expect("should always be an object")
.clone()
}
}
@ -472,7 +543,7 @@ macro_rules! JsTypedArrayType {
impl From<$name> for JsValue {
#[inline]
fn from(o: $name) -> Self {
o.inner.inner.clone()
o.inner.inner.clone().into()
}
}

38
boa_engine/src/object/internal_methods/integer_indexed.rs

@ -3,7 +3,7 @@ use std::sync::atomic;
use boa_macros::utf16;
use crate::{
builtins::Number,
builtins::{typed_array::is_valid_integer_index, Number},
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult, JsString, JsValue,
@ -331,42 +331,6 @@ pub(crate) fn integer_indexed_exotic_own_property_keys(
Ok(keys)
}
/// Abstract operation `IsValidIntegerIndex ( O, index )`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex
pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool {
let obj = obj.borrow();
let inner = obj.as_typed_array().expect(
"integer indexed exotic method should only be callable from integer indexed objects",
);
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false.
if inner.is_detached() {
return false;
}
// 2. If IsIntegralNumber(index) is false, return false.
if index.is_nan() || index.is_infinite() || index.fract() != 0.0 {
return false;
}
// 3. If index is -0𝔽, return false.
if index == 0.0 && index.is_sign_negative() {
return false;
}
// 4. If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false.
if index < 0.0 || index >= inner.array_length() as f64 {
return false;
}
// 5. Return true.
true
}
/// Abstract operation `IntegerIndexedElementGet ( O, index )`.
///
/// More information:

5
test262_config.toml

@ -10,7 +10,7 @@ features = [
"FinalizationRegistry",
"IsHTMLDDA",
"Atomics",
"change-array-by-copy",
"resizable-arraybuffer",
"symbols-as-weakmap-keys",
"intl-normative-optional",
"Intl.DisplayNames",
@ -32,9 +32,6 @@ features = [
# https://github.com/tc39/proposal-json-modules
"json-modules",
# https://github.com/tc39/proposal-resizablearraybuffer
"resizable-arraybuffer",
# https://github.com/tc39/proposal-arraybuffer-transfer
"arraybuffer-transfer",

Loading…
Cancel
Save