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

3685 lines
141 KiB

//! Boa's implementation of ECMAScript's global `Array` object.
//!
//! The ECMAScript `Array` class is a global object that is used in the construction of arrays; which are high-level, list-like objects.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-array-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_profiler::Profiler;
use thin_vec::ThinVec;
use crate::{
builtins::{
iterable::{if_abrupt_close_iterator, IteratorHint},
BuiltInObject, Number,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{
internal_methods::{
get_prototype_from_constructor, ordinary_define_own_property,
ordinary_get_own_property, InternalMethodContext, InternalObjectMethods,
ORDINARY_INTERNAL_METHODS,
},
IndexedProperties, JsData, JsObject, CONSTRUCTOR,
},
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
realm::Realm,
string::common::StaticJsStrings,
symbol::JsSymbol,
value::{IntegerOrInfinity, JsValue},
Context, JsArgs, JsResult, JsString,
};
use std::cmp::{min, Ordering};
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
mod array_iterator;
pub(crate) use array_iterator::ArrayIterator;
#[cfg(test)]
mod tests;
/// Direction for `find_via_predicate`
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum Direction {
Ascending,
Descending,
}
/// JavaScript `Array` built-in implementation.
#[derive(Debug, Clone, Copy, Trace, Finalize)]
#[boa_gc(empty_trace)]
pub(crate) struct Array;
/// Definitions of the internal object methods for array exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__define_own_property__: array_exotic_define_own_property,
..ORDINARY_INTERNAL_METHODS
};
impl JsData for Array {
fn internal_methods(&self) -> &'static InternalObjectMethods {
&ARRAY_EXOTIC_INTERNAL_METHODS
}
}
impl IntrinsicObject for Array {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
let symbol_iterator = JsSymbol::iterator();
let symbol_unscopables = JsSymbol::unscopables();
let get_species = BuiltInBuilder::callable(realm, Self::get_species)
.name(js_string!("get [Symbol.species]"))
.build();
let values_function = BuiltInBuilder::callable_with_object(
realm,
realm.intrinsics().objects().array_prototype_values().into(),
Self::values,
)
.name(js_string!("values"))
.build();
let to_string_function = BuiltInBuilder::callable_with_object(
realm,
realm
.intrinsics()
.objects()
.array_prototype_to_string()
.into(),
Self::to_string,
)
.name(js_string!("toString"))
.build();
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),
None,
Attribute::CONFIGURABLE,
)
.property(
StaticJsStrings::LENGTH,
0,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
)
.method(Self::at, js_string!("at"), 1)
.method(Self::concat, js_string!("concat"), 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::filter, js_string!("filter"), 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::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();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for Array {
const NAME: JsString = StaticJsStrings::ARRAY;
}
impl BuiltInConstructor for Array {
const LENGTH: usize = 1;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::array;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
let new_target = &if new_target.is_undefined() {
context
.active_function_object()
.unwrap_or_else(|| context.intrinsics().constructors().array().constructor())
.into()
} else {
new_target.clone()
};
// 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%").
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::array, context)?;
// 3. Let numberOfArgs be the number of elements in values.
let number_of_args = args.len();
// 4. If numberOfArgs = 0, then
if number_of_args == 0 {
// 4.a. Return ! ArrayCreate(0, proto).
Ok(Self::array_create(0, Some(prototype), context)
.expect("this ArrayCreate call must not fail")
.into())
// 5. Else if numberOfArgs = 1, then
} else if number_of_args == 1 {
// a. Let len be values[0].
let len = &args[0];
// b. Let array be ! ArrayCreate(0, proto).
let array = Self::array_create(0, Some(prototype), context)
.expect("this ArrayCreate call must not fail");
// c. If Type(len) is not Number, then
#[allow(clippy::if_not_else)]
let int_len = if !len.is_number() {
// i. Perform ! CreateDataPropertyOrThrow(array, "0", len).
array
.create_data_property_or_throw(0, len.clone(), context)
.expect("this CreateDataPropertyOrThrow call must not fail");
// ii. Let intLen be 1𝔽.
1
// d. Else,
} else {
// i. Let intLen be ! ToUint32(len).
let int_len = len
.to_u32(context)
.expect("this ToUint32 call must not fail");
// ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
if !JsValue::same_value_zero(&int_len.into(), len) {
return Err(JsNativeError::range()
.with_message("invalid array length")
.into());
}
int_len
};
// e. Perform ! Set(array, "length", intLen, true).
array
.set(StaticJsStrings::LENGTH, int_len, true, context)
.expect("this Set call must not fail");
// f. Return array.
Ok(array.into())
// 6. Else,
} else {
// 6.a. Assert: numberOfArgs ≥ 2.
debug_assert!(number_of_args >= 2);
// b. Let array be ? ArrayCreate(numberOfArgs, proto).
let array = Self::array_create(number_of_args as u64, Some(prototype), context)?;
// c. Let k be 0.
// d. Repeat, while k < numberOfArgs,
// i. Let Pk be ! ToString(𝔽(k)).
// ii. Let itemK be values[k].
// iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK).
// iv. Set k to k + 1.
array
.borrow_mut()
.properties_mut()
.override_indexed_properties(args.iter().cloned().collect());
// e. Assert: The mathematical value of array's "length" property is numberOfArgs.
// f. Return array.
Ok(array.into())
}
}
}
impl Array {
/// Optimized helper function, that sets the length of the array.
fn set_length(o: &JsObject, len: u64, context: &mut Context) -> JsResult<()> {
if o.is_array() && len < (2u64.pow(32) - 1) {
let mut borrowed_object = o.borrow_mut();
if borrowed_object.properties().shape.to_addr_usize()
== context
.intrinsics()
.templates()
.array()
.shape()
.to_addr_usize()
{
// NOTE: The "length" property is the first element.
borrowed_object.properties_mut().storage[0] = JsValue::new(len);
return Ok(());
}
}
o.set(StaticJsStrings::LENGTH, len, true, context)?;
Ok(())
}
/// Utility for constructing `Array` objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-arraycreate
pub(crate) fn array_create(
length: u64,
prototype: Option<JsObject>,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. If length > 2^32 - 1, throw a RangeError exception.
if length > 2u64.pow(32) - 1 {
return Err(JsNativeError::range()
.with_message("array exceeded max size")
.into());
}
// Fast path:
if prototype.is_none() {
return Ok(context
.intrinsics()
.templates()
.array()
.create(Array, vec![JsValue::new(length)]));
}
// 7. Return A.
// 2. If proto is not present, set proto to %Array.prototype%.
// 3. Let A be ! MakeBasicObject(« [[Prototype]], [[Extensible]] »).
// 4. Set A.[[Prototype]] to proto.
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1.
let prototype =
prototype.unwrap_or_else(|| context.intrinsics().constructors().array().prototype());
// Fast path:
if context
.intrinsics()
.templates()
.array()
.has_prototype(&prototype)
{
return Ok(context
.intrinsics()
.templates()
.array()
.create(Array, vec![JsValue::new(length)]));
}
let array =
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, Array);
// 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
ordinary_define_own_property(
&array,
&StaticJsStrings::LENGTH.into(),
PropertyDescriptor::builder()
.value(length)
.writable(true)
.enumerable(false)
.configurable(false)
.build(),
&mut InternalMethodContext::new(context),
)?;
Ok(array)
}
/// Utility for constructing `Array` objects from an iterator of `JsValue`s.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist
pub(crate) fn create_array_from_list<I>(elements: I, context: &mut Context) -> JsObject
where
I: IntoIterator<Item = JsValue>,
{
// 1. Assert: elements is a List whose elements are all ECMAScript language values.
// 2. Let array be ! ArrayCreate(0).
// 3. Let n be 0.
// 4. For each element e of elements, do
// a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e).
// b. Set n to n + 1.
// 5. Return array.
// NOTE: This deviates from the spec, but it should have the same behaviour.
let elements: ThinVec<_> = elements.into_iter().collect();
let length = elements.len();
context
.intrinsics()
.templates()
.array()
.create_with_indexed_properties(
Array,
vec![JsValue::new(length)],
IndexedProperties::from_dense_js_value(elements),
)
}
/// Utility function for concatenating array objects.
///
/// Returns a Boolean valued property that if `true` indicates that
/// an object should be flattened to its array elements
/// by `Array.prototype.concat`.
fn is_concat_spreadable(o: &JsValue, context: &mut Context) -> JsResult<bool> {
// 1. If Type(O) is not Object, return false.
let Some(o) = o.as_object() else {
return Ok(false);
};
// 2. Let spreadable be ? Get(O, @@isConcatSpreadable).
let spreadable = o.get(JsSymbol::is_concat_spreadable(), context)?;
// 3. If spreadable is not undefined, return ! ToBoolean(spreadable).
if !spreadable.is_undefined() {
return Ok(spreadable.to_boolean());
}
// 4. Return ? IsArray(O).
o.is_array_abstract()
}
/// `get Array [ @@species ]`
///
/// The `Array [ @@species ]` accessor property returns the Array constructor.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-array-@@species
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species
#[allow(clippy::unnecessary_wraps)]
fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
// 1. Return the this value.
Ok(this.clone())
}
/// Utility function used to specify the creation of a new Array object using a constructor
/// function that is derived from `original_array`.
///
/// see: <https://tc39.es/ecma262/#sec-arrayspeciescreate>
pub(crate) fn array_species_create(
original_array: &JsObject,
length: u64,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. Let isArray be ? IsArray(originalArray).
// 2. If isArray is false, return ? ArrayCreate(length).
if !original_array.is_array_abstract()? {
return Self::array_create(length, None, context);
}
// 3. Let C be ? Get(originalArray, "constructor").
let c = original_array.get(CONSTRUCTOR, context)?;
// 4. If IsConstructor(C) is true, then
if let Some(c) = c.as_constructor() {
// a. Let thisRealm be the current Realm Record.
let this_realm = context.realm().clone();
// b. Let realmC be ? GetFunctionRealm(C).
let realm_c = c.get_function_realm(context)?;
// c. If thisRealm and realmC are not the same Realm Record, then
if this_realm != realm_c
&& *c == realm_c.intrinsics().constructors().array().constructor()
{
// i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined.
// Note: fast path to step 6.
return Self::array_create(length, None, context);
}
}
// 5. If Type(C) is Object, then
let c = if let Some(c) = c.as_object() {
// 5.a. Set C to ? Get(C, @@species).
let c = c.get(JsSymbol::species(), context)?;
// 5.b. If C is null, set C to undefined.
if c.is_null_or_undefined() {
JsValue::undefined()
} else {
c
}
} else {
c
};
// 6. If C is undefined, return ? ArrayCreate(length).
if c.is_undefined() {
return Self::array_create(length, None, context);
}
if let Some(c) = c.as_constructor() {
// 8. Return ? Construct(C, « 𝔽(length) »).
return c.construct(&[JsValue::new(length)], Some(c), context);
}
// 7. If IsConstructor(C) is false, throw a TypeError exception.
Err(JsNativeError::typ()
.with_message("Symbol.species must be a constructor")
.into())
}
/// `Array.from(arrayLike)`
///
/// The `Array.from()` static method creates a new,
/// shallow-copied Array instance from an array-like or iterable object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.from
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
pub(crate) fn from(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let items = args.get_or_undefined(0);
let mapfn = args.get_or_undefined(1);
let this_arg = args.get_or_undefined(2);
// 2. If mapfn is undefined, let mapping be false
// 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),
_ => {
return Err(JsNativeError::typ()
.with_message(format!("`{}` is not callable", mapfn.type_of()))
.into())
}
};
// 4. Let usingIterator be ? GetMethod(items, @@iterator).
let using_iterator = items.get_method(JsSymbol::iterator(), context)?;
let Some(using_iterator) = using_iterator else {
// 6. NOTE: items is not an Iterable so assume it is an array-like object.
// 7. Let arrayLike be ! ToObject(items).
let array_like = items
.to_object(context)
.expect("should not fail according to spec");
// 8. Let len be ? LengthOfArrayLike(arrayLike).
let len = array_like.length_of_array_like(context)?;
// 9. If IsConstructor(C) is true, then
// a. Let A be ? Construct(C, « 𝔽(len) »).
// 10. Else,
// a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?,
_ => Self::array_create(len, None, context)?,
};
// 11. Let k be 0.
// 12. Repeat, while k < len,
// ...
// f. Set k to k + 1.
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kValue be ? Get(arrayLike, Pk).
let k_value = array_like.get(k, context)?;
let mapped_value = if let Some(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)?
} else {
// d. Else, let mappedValue be kValue.
k_value
};
// e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
// 13. Perform ? Set(A, "length", 𝔽(len), true).
a.set(StaticJsStrings::LENGTH, len, true, context)?;
// 14. Return A.
return Ok(a.into());
};
// 5. If usingIterator is not undefined, then
// a. If IsConstructor(C) is true, then
// i. Let A be ? Construct(C).
// b. Else,
// i. Let A be ? ArrayCreate(0en).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[], None, context)?,
_ => Self::array_create(0, None, context)?,
};
// c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
let mut iterator_record =
items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?;
// d. Let k be 0.
// e. Repeat,
// i. If k ≥ 2^53 - 1 (MAX_SAFE_INTEGER), then
// ...
// x. Set k to k + 1.
for k in 0..9_007_199_254_740_991_u64 {
// iii. Let next be ? IteratorStep(iteratorRecord).
if iterator_record.step(context)? {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set(StaticJsStrings::LENGTH, k, true, context)?;
// 2. Return A.
return Ok(a.into());
}
// iv. If next is false, then
// v. Let nextValue be ? IteratorValue(next).
let next_value = iterator_record.value(context)?;
// vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping {
// 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »).
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)
} else {
// vii. Else, let mappedValue be nextValue.
next_value
};
// viii. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue).
let define_status = a.create_data_property_or_throw(k, mapped_value, context);
// ix. IfAbruptCloseIterator(defineStatus, iteratorRecord).
if_abrupt_close_iterator!(define_status, iterator_record, context);
}
// NOTE: The loop above has to return before it reaches iteration limit,
// which is why it's safe to have this as the fallback return
//
// 1. Let error be ThrowCompletion(a newly created TypeError object).
let error = Err(JsNativeError::typ()
.with_message("Invalid array length")
.into());
// 2. Return ? IteratorClose(iteratorRecord, error).
iterator_record.close(error, context)
}
/// `Array.isArray( arg )`
///
/// The isArray function takes one argument arg, and returns the Boolean value true
/// if the argument is an object whose class internal property is "Array"; otherwise it returns false.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.isarray
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
pub(crate) fn is_array(
_: &JsValue,
args: &[JsValue],
_context: &mut Context,
) -> JsResult<JsValue> {
// 1. Return ? IsArray(arg).
args.get_or_undefined(0).is_array().map(Into::into)
}
/// `Array.of(...items)`
///
/// The Array.of method creates a new Array instance from a variable number of arguments,
/// regardless of the number or type of arguments.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.of
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of
pub(crate) fn of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let len be the number of elements in items.
// 2. Let lenNumber be 𝔽(len).
let len = args.len();
// 3. Let C be the this value.
// 4. If IsConstructor(C) is true, then
// a. Let A be ? Construct(C, « lenNumber »).
// 5. Else,
// a. Let A be ? ArrayCreate(len).
let a = match this.as_constructor() {
Some(constructor) => constructor.construct(&[len.into()], None, context)?,
_ => Self::array_create(len as u64, None, context)?,
};
// 6. Let k be 0.
// 7. Repeat, while k < len,
for (k, value) in args.iter().enumerate() {
// a. Let kValue be items[k].
// b. Let Pk be ! ToString(𝔽(k)).
// c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue).
a.create_data_property_or_throw(k, value.clone(), context)?;
// d. Set k to k + 1.
}
// 8. Perform ? Set(A, "length", lenNumber, true).
Self::set_length(&a, len as u64, context)?;
// 9. Return A.
Ok(a.into())
}
///'Array.prototype.at(index)'
///
/// The `at()` method takes an integer value and returns the item at that
/// index, allowing for positive and negative integers. Negative integers
/// count back from the last item in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.at
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
//1. let O be ? ToObject(this value)
let obj = this.to_object(context)?;
//2. let len be ? LengthOfArrayLike(O)
let len = obj.length_of_array_like(context)? as i64;
//3. let relativeIndex be ? ToIntegerOrInfinity(index)
let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?;
let k = match relative_index {
//4. if relativeIndex >= 0, then let k be relativeIndex
//check if positive and if below length of array
IntegerOrInfinity::Integer(i) if i >= 0 && i < len => i,
//5. Else, let k be len + relativeIndex
//integer should be negative, so abs() and check if less than or equal to length of array
IntegerOrInfinity::Integer(i) if i < 0 && i.abs() <= len => len + i,
//handle most likely impossible case of
//IntegerOrInfinity::NegativeInfinity || IntegerOrInfinity::PositiveInfinity
//by returning undefined
_ => return Ok(JsValue::undefined()),
};
//6. if k < 0 or k >= len,
//handled by the above match guards
//7. Return ? Get(O, !ToString(𝔽(k)))
obj.get(k, context)
}
/// `Array.prototype.concat(...arguments)`
///
/// When the concat method is called with zero or more arguments, it returns an
/// array containing the array elements of the object followed by the array
/// elements of each argument in order.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
pub(crate) fn concat(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let obj = this.to_object(context)?;
// 2. Let A be ? ArraySpeciesCreate(O, 0).
let arr = Self::array_species_create(&obj, 0, context)?;
// 3. Let n be 0.
let mut n = 0;
// 4. Prepend O to items.
// 5. For each element E of items, do
for item in std::iter::once(&JsValue::new(obj)).chain(args.iter()) {
// a. Let spreadable be ? IsConcatSpreadable(E).
let spreadable = Self::is_concat_spreadable(item, context)?;
// b. If spreadable is true, then
if spreadable {
// item is guaranteed to be an object since is_concat_spreadable checks it,
// so we can call `.unwrap()`
let item = item.as_object().expect("guaranteed to be an object");
// i. Let k be 0.
// ii. Let len be ? LengthOfArrayLike(E).
let len = item.length_of_array_like(context)?;
// iii. If n + len > 2^53 - 1, throw a TypeError exception.
if n + len > Number::MAX_SAFE_INTEGER as u64 {
return Err(JsNativeError::typ()
.with_message(
"length + number of arguments exceeds the max safe integer limit",
)
.into());
}
// iv. Repeat, while k < len,
for k in 0..len {
// 1. Let P be ! ToString(𝔽(k)).
// 2. Let exists be ? HasProperty(E, P).
let exists = item.has_property(k, context)?;
// 3. If exists is true, then
if exists {
// a. Let subElement be ? Get(E, P).
let sub_element = item.get(k, context)?;
// b. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), subElement).
arr.create_data_property_or_throw(n, sub_element, context)?;
}
// 4. Set n to n + 1.
n += 1;
// 5. Set k to k + 1.
}
}
// c. Else,
else {
// i. NOTE: E is added as a single item rather than spread.
// ii. If n ≥ 2^53 - 1, throw a TypeError exception.
if n >= Number::MAX_SAFE_INTEGER as u64 {
return Err(JsNativeError::typ()
.with_message("length exceeds the max safe integer limit")
.into());
}
// iii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), E).
arr.create_data_property_or_throw(n, item.clone(), context)?;
// iv. Set n to n + 1.
n += 1;
}
}
// 6. Perform ? Set(A, "length", 𝔽(n), true).
Self::set_length(&arr, n, context)?;
// 7. Return A.
Ok(JsValue::new(arr))
}
/// `Array.prototype.push( ...items )`
///
/// The arguments are appended to the end of the array, in the order in which
/// they appear. The new length of the array is returned as the result of the
/// call.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
pub(crate) fn push(
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 mut len = o.length_of_array_like(context)?;
// 3. Let argCount be the number of elements in items.
let arg_count = args.len() as u64;
// 4. If len + argCount > 2^53 - 1, throw a TypeError exception.
if len + arg_count > 2u64.pow(53) - 1 {
return Err(JsNativeError::typ()
.with_message(
"the length + the number of arguments exceed the maximum safe integer limit",
)
.into());
}
// 5. For each element E of items, do
for element in args.iter().cloned() {
// a. Perform ? Set(O, ! ToString(𝔽(len)), E, true).
o.set(len, element, true, context)?;
// b. Set len to len + 1.
len += 1;
}
// 6. Perform ? Set(O, "length", 𝔽(len), true).
Self::set_length(&o, len, context)?;
// 7. Return 𝔽(len).
Ok(len.into())
}
/// `Array.prototype.pop()`
///
/// The last element of the array is removed from the array and returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
pub(crate) fn pop(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. If len = 0, then
if len == 0 {
// a. Perform ? Set(O, "length", +0𝔽, true).
Self::set_length(&o, 0, context)?;
// b. Return undefined.
Ok(JsValue::undefined())
// 4. Else,
} else {
// a. Assert: len > 0.
// b. Let newLen be 𝔽(len - 1).
let new_len = len - 1;
// c. Let index be ! ToString(newLen).
let index = new_len;
// d. Let element be ? Get(O, index).
let element = o.get(index, context)?;
// e. Perform ? DeletePropertyOrThrow(O, index).
o.delete_property_or_throw(index, context)?;
// f. Perform ? Set(O, "length", newLen, true).
Self::set_length(&o, new_len, context)?;
// g. Return element.
Ok(element)
}
}
/// `Array.prototype.forEach( callbackFn [ , thisArg ] )`
///
/// This method executes the provided callback function for each element in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
pub(crate) fn for_each(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("Array.prototype.forEach: invalid callback function")
})?;
// 4. Let k be 0.
// 5. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if present {
// i. Let kValue be ? Get(O, Pk).
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)?;
}
// d. Set k to k + 1.
}
// 6. Return undefined.
Ok(JsValue::undefined())
}
/// `Array.prototype.join( separator )`
///
/// The elements of the array are converted to Strings, and these Strings are
/// then concatenated, separated by occurrences of the separator. If no
/// separator is provided, a single comma is used as the separator.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
pub(crate) fn join(
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. If separator is undefined, let sep be the single-element String ",".
// 4. Else, let sep be ? ToString(separator).
let separator = args.get_or_undefined(0);
let separator = if separator.is_undefined() {
js_string!(",")
} else {
separator.to_string(context)?
};
// 5. Let R be the empty String.
let mut r = Vec::new();
// 6. Let k be 0.
// 7. Repeat, while k < len,
for k in 0..len {
// a. If k > 0, set R to the string-concatenation of R and sep.
if k > 0 {
r.extend_from_slice(&separator);
}
// b. Let element be ? Get(O, ! ToString(𝔽(k))).
let element = o.get(k, context)?;
// c. If element is undefined, null or the array itself, let next be the empty String; otherwise, let next be ? ToString(element).
let next = if element.is_null_or_undefined() || &element == this {
js_string!()
} else {
element.to_string(context)?
};
// d. Set R to the string-concatenation of R and next.
r.extend_from_slice(&next);
// e. Set k to k + 1.
}
// 8. Return R.
Ok(js_string!(&r[..]).into())
}
/// `Array.prototype.toString( separator )`
///
/// The toString function is intentionally generic; it does not require that
/// its this value be an Array object. Therefore it can be transferred to
/// other kinds of objects for use as a method.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let array be ? ToObject(this value).
let array = this.to_object(context)?;
// 2. Let func be ? Get(array, "join").
let func = array.get(utf16!("join"), context)?;
// 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
// 4. Return ? Call(func, array).
if let Some(func) = func.as_callable() {
func.call(&array.into(), &[], context)
} else {
crate::builtins::object::OrdinaryObject::to_string(&array.into(), &[], context)
}
}
/// `Array.prototype.reverse()`
///
/// The elements of the array are rearranged so as to reverse their order.
/// The object is returned as the result of the call.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
#[allow(clippy::else_if_without_else)]
pub(crate) fn reverse(
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 middle be floor(len / 2).
let middle = len / 2;
// 4. Let lower be 0.
let mut lower = 0;
// 5. Repeat, while lower ≠ middle,
while lower != middle {
// a. Let upper be len - lower - 1.
let upper = len - lower - 1;
// Skipped: b. Let upperP be ! ToString(𝔽(upper)).
// Skipped: c. Let lowerP be ! ToString(𝔽(lower)).
// d. Let lowerExists be ? HasProperty(O, lowerP).
let lower_exists = o.has_property(lower, context)?;
// e. If lowerExists is true, then
let lower_value = if lower_exists {
// i. Let lowerValue be ? Get(O, lowerP).
o.get(lower, context)?
} else {
JsValue::undefined()
};
// f. Let upperExists be ? HasProperty(O, upperP).
let upper_exists = o.has_property(upper, context)?;
// g. If upperExists is true, then
let upper_value = if upper_exists {
// i. Let upperValue be ? Get(O, upperP).
o.get(upper, context)?
} else {
JsValue::undefined()
};
match (lower_exists, upper_exists) {
// h. If lowerExists is true and upperExists is true, then
(true, true) => {
// i. Perform ? Set(O, lowerP, upperValue, true).
o.set(lower, upper_value, true, context)?;
// ii. Perform ? Set(O, upperP, lowerValue, true).
o.set(upper, lower_value, true, context)?;
}
// i. Else if lowerExists is false and upperExists is true, then
(false, true) => {
// i. Perform ? Set(O, lowerP, upperValue, true).
o.set(lower, upper_value, true, context)?;
// ii. Perform ? DeletePropertyOrThrow(O, upperP).
o.delete_property_or_throw(upper, context)?;
}
// j. Else if lowerExists is true and upperExists is false, then
(true, false) => {
// i. Perform ? DeletePropertyOrThrow(O, lowerP).
o.delete_property_or_throw(lower, context)?;
// ii. Perform ? Set(O, upperP, lowerValue, true).
o.set(upper, lower_value, true, context)?;
}
// k. Else,
(false, false) => {
// i. Assert: lowerExists and upperExists are both false.
// ii. No action is required.
}
}
// l. Set lower to lower + 1.
lower += 1;
}
// 6. Return O.
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.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
pub(crate) fn shift(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. If len = 0, then
if len == 0 {
// a. Perform ? Set(O, "length", +0𝔽, true).
Self::set_length(&o, 0, context)?;
// b. Return undefined.
return Ok(JsValue::undefined());
}
// Small optimization for arrays using dense properties
// TODO: this optimization could be generalized to many other objects with
// slot-based dense property maps.
if o.is_array() {
let mut o_borrow = o.borrow_mut();
if let IndexedProperties::DenseI32(dense) =
&mut o_borrow.properties_mut().indexed_properties
{
if len <= dense.len() as u64 {
let v = dense.remove(0);
drop(o_borrow);
Self::set_length(&o, len - 1, context)?;
return Ok(v.into());
}
}
if let IndexedProperties::DenseF64(dense) =
&mut o_borrow.properties_mut().indexed_properties
{
if len <= dense.len() as u64 {
let v = dense.remove(0);
drop(o_borrow);
Self::set_length(&o, len - 1, context)?;
return Ok(v.into());
}
}
if let Some(dense) = o_borrow.properties_mut().dense_indexed_properties_mut() {
if len <= dense.len() as u64 {
let v = dense.remove(0);
drop(o_borrow);
Self::set_length(&o, len - 1, context)?;
return Ok(v);
}
}
}
// 4. Let first be ? Get(O, "0").
let first = o.get(0, context)?;
// 5. Let k be 1.
// 6. Repeat, while k < len,
for k in 1..len {
// a. Let from be ! ToString(𝔽(k)).
let from = k;
// b. Let to be ! ToString(𝔽(k - 1)).
let to = k - 1;
// c. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// d. If fromPresent is true, then
if from_present {
// i. Let fromVal be ? Get(O, from).
let from_val = o.get(from, context)?;
// ii. Perform ? Set(O, to, fromVal, true).
o.set(to, from_val, true, context)?;
// e. Else,
} else {
// i. Assert: fromPresent is false.
// ii. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// f. Set k to k + 1.
}
// 7. Perform ? DeletePropertyOrThrow(O, ! ToString(𝔽(len - 1))).
o.delete_property_or_throw(len - 1, context)?;
// 8. Perform ? Set(O, "length", 𝔽(len - 1), true).
Self::set_length(&o, len - 1, context)?;
// 9. Return first.
Ok(first)
}
/// `Array.prototype.unshift( ...items )`
///
/// The arguments are prepended to the start of the array, such that their order
/// within the array is the same as the order in which they appear in the
/// argument list.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
pub(crate) fn unshift(
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 argCount be the number of elements in items.
let arg_count = args.len() as u64;
// 4. If argCount > 0, then
if arg_count > 0 {
// a. If len + argCount > 2^53 - 1, throw a TypeError exception.
if len + arg_count > 2u64.pow(53) - 1 {
return Err(JsNativeError::typ()
.with_message("length + number of arguments exceeds the max safe integer limit")
.into());
}
// b. Let k be len.
let mut k = len;
// c. Repeat, while k > 0,
while k > 0 {
// i. Let from be ! ToString(𝔽(k - 1)).
let from = k - 1;
// ii. Let to be ! ToString(𝔽(k + argCount - 1)).
let to = k + arg_count - 1;
// iii. Let fromPresent be ? HasProperty(O, from).
let from_present = o.has_property(from, context)?;
// iv. If fromPresent is true, then
if from_present {
// 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.
// 2. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// vi. Set k to k - 1.
k -= 1;
}
// d. Let j be +0𝔽.
// e. For each element E of items, do
for (j, e) in args.iter().enumerate() {
// i. Perform ? Set(O, ! ToString(j), E, true).
o.set(j, e.clone(), true, context)?;
// ii. Set j to j + 1𝔽.
}
}
// 5. Perform ? Set(O, "length", 𝔽(len + argCount), true).
Self::set_length(&o, len + arg_count, context)?;
// 6. Return 𝔽(len + argCount).
Ok((len + arg_count).into())
}
/// `Array.prototype.every( callback, [ thisArg ] )`
///
/// The every method executes the provided callback function once for each
/// element present in the array until it finds the one where callback returns
/// a falsy value. It returns `false` if it finds such element, otherwise it
/// returns `true`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
pub(crate) fn every(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("Array.prototype.every: callback is not callable")
})?;
let this_arg = args.get_or_undefined(1);
// 4. Let k be 0.
// 5. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
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)?
.to_boolean();
// iii. If testResult is false, return false.
if !test_result {
return Ok(JsValue::new(false));
}
}
// d. Set k to k + 1.
}
// 6. Return true.
Ok(JsValue::new(true))
}
/// `Array.prototype.map( callback, [ thisArg ] )`
///
/// For each element in the array the callback function is called, and a new
/// array is constructed from the return values of these calls.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
pub(crate) fn map(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("Array.prototype.map: Callbackfn is not callable")
})?;
// 4. Let A be ? ArraySpeciesCreate(O, len).
let a = Self::array_species_create(&o, len, context)?;
let this_arg = args.get_or_undefined(1);
// 5. Let k be 0.
// 6. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let k_present be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If k_present is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
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(), o.clone().into()], context)?;
// iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
a.create_data_property_or_throw(k, mapped_value, context)?;
}
// d. Set k to k + 1.
}
// 7. Return A.
Ok(a.into())
}
/// `Array.prototype.indexOf( searchElement[, fromIndex ] )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
pub(crate) fn index_of(
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)? as i64;
// 3. If len is 0, return -1𝔽.
if len == 0 {
return Ok(JsValue::new(-1));
}
// 4. Let n be ? ToIntegerOrInfinity(fromIndex).
let n = args
.get(1)
.cloned()
.unwrap_or_default()
.to_integer_or_infinity(context)?;
// 5. Assert: If fromIndex is undefined, then n is 0.
let n = match n {
// 6. If n is +∞, return -1𝔽.
IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(-1)),
// 7. Else if n is -∞, set n to 0.
IntegerOrInfinity::NegativeInfinity => 0,
IntegerOrInfinity::Integer(value) => value,
};
// 8. If n ≥ 0, then
let mut k;
if n >= 0 {
// a. Let k be n.
k = n;
// 9. Else,
} else {
// a. Let k be len + n.
k = len + n;
// b. If k < 0, set k to 0.
if k < 0 {
k = 0;
}
};
let search_element = args.get_or_undefined(0);
// 10. Repeat, while k < len,
while k < len {
// a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
let k_present = o.has_property(k, context)?;
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
let element_k = o.get(k, context)?;
// ii. Let same be IsStrictlyEqual(searchElement, elementK).
// iii. If same is true, return 𝔽(k).
if search_element.strict_equals(&element_k) {
return Ok(JsValue::new(k));
}
}
// c. Set k to k + 1.
k += 1;
}
// 11. Return -1𝔽.
Ok(JsValue::new(-1))
}
/// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )`
///
///
/// `lastIndexOf` compares searchElement to the elements of the array in descending order
/// using the Strict Equality Comparison algorithm, and if found at one or more indices,
/// returns the largest such index; otherwise, -1 is returned.
///
/// The optional second argument fromIndex defaults to the array's length minus one
/// (i.e. the whole array is searched). If it is greater than or equal to the length of the array,
/// the whole array will be searched. If it is negative, it is used as the offset from the end
/// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
pub(crate) fn last_index_of(
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)? as i64;
// 3. If len is 0, return -1𝔽.
if len == 0 {
return Ok(JsValue::new(-1));
}
// 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1.
let n = if let Some(from_index) = args.get(1) {
from_index.to_integer_or_infinity(context)?
} else {
IntegerOrInfinity::Integer(len - 1)
};
let mut k = match n {
// 5. If n is -∞, return -1𝔽.
IntegerOrInfinity::NegativeInfinity => return Ok(JsValue::new(-1)),
// 6. If n ≥ 0, then
// a. Let k be min(n, len - 1).
IntegerOrInfinity::Integer(n) if n >= 0 => min(n, len - 1),
IntegerOrInfinity::PositiveInfinity => len - 1,
// 7. Else,
// a. Let k be len + n.
IntegerOrInfinity::Integer(n) => len + n,
};
let search_element = args.get_or_undefined(0);
// 8. Repeat, while k ≥ 0,
while k >= 0 {
// a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
let k_present = o.has_property(k, context)?;
// b. If kPresent is true, then
if k_present {
// i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
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) {
return Ok(JsValue::new(k));
}
}
// c. Set k to k - 1.
k -= 1;
}
// 9. Return -1𝔽.
Ok(JsValue::new(-1))
}
/// `Array.prototype.find( callback, [thisArg] )`
///
/// The find method executes the callback function once for each index of the array
/// until the callback returns a truthy value. If so, find immediately returns the value
/// of that element. Otherwise, find returns undefined.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
pub(crate) fn find(
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 predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
let (_, value) = find_via_predicate(
&o,
len,
Direction::Ascending,
predicate,
this_arg,
context,
"Array.prototype.find",
)?;
// 4. Return findRec.[[Value]].
Ok(value)
}
/// `Array.prototype.findIndex( predicate [ , thisArg ] )`
///
/// This method executes the provided predicate function for each element of the array.
/// If the predicate function returns `true` for an element, this method returns the index of the element.
/// If all elements return `false`, the value `-1` is returned.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
pub(crate) fn find_index(
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 predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
let (index, _) = find_via_predicate(
&o,
len,
Direction::Ascending,
predicate,
this_arg,
context,
"Array.prototype.findIndex",
)?;
// 4. Return findRec.[[Index]].
Ok(index)
}
/// `Array.prototype.findLast( predicate, [thisArg] )`
///
/// findLast calls predicate once for each element of the array, in descending order,
/// until it finds one where predicate returns true. If such an element is found, findLast
/// immediately returns that element value. Otherwise, findLast returns undefined.
///
/// More information:
/// - [ECMAScript proposal][spec]
///
/// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlast
pub(crate) fn find_last(
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 predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
let (_, value) = find_via_predicate(
&o,
len,
Direction::Descending,
predicate,
this_arg,
context,
"Array.prototype.findLast",
)?;
// 4. Return findRec.[[Value]].
Ok(value)
}
/// `Array.prototype.findLastIndex( predicate [ , thisArg ] )`
///
/// `findLastIndex` calls predicate once for each element of the array, in descending order,
/// until it finds one where predicate returns true. If such an element is found, `findLastIndex`
/// immediately returns the index of that element value. Otherwise, `findLastIndex` returns -1.
///
/// More information:
/// - [ECMAScript proposal][spec]
///
/// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlastindex
pub(crate) fn find_last_index(
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 predicate = args.get_or_undefined(0);
let this_arg = args.get_or_undefined(1);
// 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
let (index, _) = find_via_predicate(
&o,
len,
Direction::Descending,
predicate,
this_arg,
context,
"Array.prototype.findLastIndex",
)?;
// 4. Return findRec.[[Index]].
Ok(index)
}
/// `Array.prototype.flat( [depth] )`
///
/// This method creates a new array with all sub-array elements concatenated into it
/// recursively up to the specified depth.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flat
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
pub(crate) fn flat(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be ToObject(this value)
let o = this.to_object(context)?;
// 2. Let sourceLen be LengthOfArrayLike(O)
let source_len = o.length_of_array_like(context)?;
// 3. Let depthNum be 1
let mut depth_num = 1;
// 4. If depth is not undefined, then set depthNum to IntegerOrInfinity(depth)
if let Some(depth) = args.first() {
// a. Set depthNum to ? ToIntegerOrInfinity(depth).
// b. If depthNum < 0, set depthNum to 0.
match depth.to_integer_or_infinity(context)? {
IntegerOrInfinity::Integer(value) if value >= 0 => depth_num = value as u64,
IntegerOrInfinity::PositiveInfinity => depth_num = u64::MAX,
_ => depth_num = 0,
}
};
// 5. Let A be ArraySpeciesCreate(O, 0)
let a = Self::array_species_create(&o, 0, context)?;
// 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum)
Self::flatten_into_array(
&a,
&o,
source_len,
0,
depth_num,
None,
&JsValue::undefined(),
context,
)?;
Ok(a.into())
}
/// `Array.prototype.flatMap( callback, [ thisArg ] )`
///
/// This method returns a new array formed by applying a given callback function to
/// each element of the array, and then flattening the result by one level. It is
/// identical to a `map()` followed by a `flat()` of depth 1, but slightly more
/// efficient than calling those two methods separately.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flatMap
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
pub(crate) fn flat_map(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be ToObject(this value)
let o = this.to_object(context)?;
// 2. Let sourceLen be LengthOfArrayLike(O)
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(|| {
JsNativeError::typ().with_message("flatMap mapper function is not callable")
})?;
// 4. Let A be ? ArraySpeciesCreate(O, 0).
let a = Self::array_species_create(&o, 0, context)?;
// 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg).
Self::flatten_into_array(
&a,
&o,
source_len,
0,
1,
Some(mapper_function),
args.get_or_undefined(1),
context,
)?;
// 6. Return A
Ok(a.into())
}
/// Abstract method `FlattenIntoArray`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-flattenintoarray
#[allow(clippy::too_many_arguments)]
fn flatten_into_array(
target: &JsObject,
source: &JsObject,
source_len: u64,
start: u64,
depth: u64,
mapper_function: Option<&JsObject>,
this_arg: &JsValue,
context: &mut Context,
) -> JsResult<u64> {
// 1. Assert target is Object
// 2. Assert source is Object
// 3. Assert if mapper_function is present, then:
// - IsCallable(mapper_function) is true
// - thisArg is present
// - depth is 1
// 4. Let targetIndex be start
let mut target_index = start;
// 5. Let sourceIndex be 0
let mut source_index = 0;
// 6. Repeat, while R(sourceIndex) < sourceLen
while source_index < source_len {
// a. Let P be ToString(sourceIndex)
let p = source_index;
// b. Let exists be ? HasProperty(source, P).
let exists = source.has_property(p, context)?;
// c. If exists is true, then
if exists {
// i. Let element be Get(source, P)
let mut element = source.get(p, context)?;
// ii. If mapperFunction is present, then
if let Some(mapper_function) = mapper_function {
// 1. Set element to ? Call(mapperFunction, thisArg, <<element, sourceIndex, source>>)
element = mapper_function.call(
this_arg,
&[element, source_index.into(), source.clone().into()],
context,
)?;
}
// iii. Let shouldFlatten be false
// iv. If depth > 0, then
let should_flatten = if depth > 0 {
// 1. Set shouldFlatten to ? IsArray(element).
element.is_array()?
} else {
false
};
// v. If shouldFlatten is true
if should_flatten {
// For `should_flatten` to be true, element must be an object.
let element = element.as_object().expect("must be an object");
// 1. If depth is +Infinity let newDepth be +Infinity
let new_depth = if depth == u64::MAX {
u64::MAX
// 2. Else, let newDepth be depth - 1
} else {
depth - 1
};
// 3. Let elementLen be ? LengthOfArrayLike(element)
let element_len = element.length_of_array_like(context)?;
// 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
target_index = Self::flatten_into_array(
target,
element,
element_len,
target_index,
new_depth,
None,
&JsValue::undefined(),
context,
)?;
// vi. Else
} else {
// 1. If targetIndex >= 2^53 - 1, throw a TypeError exception
if target_index >= Number::MAX_SAFE_INTEGER as u64 {
return Err(JsNativeError::typ()
.with_message("Target index exceeded max safe integer value")
.into());
}
// 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element)
target.create_data_property_or_throw(target_index, element, context)?;
// 3. Set targetIndex to targetIndex + 1
target_index += 1;
}
}
// d. Set sourceIndex to sourceIndex + 1
source_index += 1;
}
// 7. Return targetIndex
Ok(target_index)
}
/// `Array.prototype.fill( value[, start[, end]] )`
///
/// The method fills (modifies) all the elements of an array from start index (default 0)
/// to an end index (default array length) with a static value. It returns the modified array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
pub(crate) fn fill(
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 relativeStart be ? ToIntegerOrInfinity(start).
// 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_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_or_undefined(2), len)?;
let value = args.get_or_undefined(0);
// 11. Repeat, while k < final,
while k < final_ {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Perform ? Set(O, Pk, value, true).
o.set(pk, value.clone(), true, context)?;
// c. Set k to k + 1.
k += 1;
}
// 12. Return O.
Ok(o.into())
}
/// `Array.prototype.includes( valueToFind [, fromIndex] )`
///
/// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
pub(crate) fn includes_value(
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)? as i64;
// 3. If len is 0, return false.
if len == 0 {
return Ok(JsValue::new(false));
}
// 4. Let n be ? ToIntegerOrInfinity(fromIndex).
let n = args
.get(1)
.cloned()
.unwrap_or_default()
.to_integer_or_infinity(context)?;
// 5. Assert: If fromIndex is undefined, then n is 0.
// 6. If n is +∞, return false.
// 7. Else if n is -∞, set n to 0.
let n = match n {
IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(false)),
IntegerOrInfinity::NegativeInfinity => 0,
IntegerOrInfinity::Integer(value) => value,
};
// 8. If n ≥ 0, then
let mut k;
if n >= 0 {
// a. Let k be n.
k = n;
// 9. Else,
} else {
// a. Let k be len + n.
k = len + n;
// b. If k < 0, set k to 0.
if k < 0 {
k = 0;
}
}
let search_element = args.get_or_undefined(0);
// 10. Repeat, while k < len,
while k < len {
// 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) {
return Ok(JsValue::new(true));
}
// c. Set k to k + 1.
k += 1;
}
// 11. Return false.
Ok(JsValue::new(false))
}
/// `Array.prototype.slice( [begin[, end]] )`
///
/// The slice method takes two arguments, start and end, and returns an array containing the
/// elements of the array from element start up to, but not including, element end (or through the
/// end of the array if end is undefined). If start is negative, it is treated as length + start
/// where length is the length of the array. If end is negative, it is treated as length + end where
/// length is the length of the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
pub(crate) fn slice(
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 relativeStart be ? ToIntegerOrInfinity(start).
// 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_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_or_undefined(1), len)?;
// 11. Let count be max(final - k, 0).
let count = final_.saturating_sub(k);
// 12. Let A be ? ArraySpeciesCreate(O, count).
let a = Self::array_species_create(&o, count, context)?;
// 13. Let n be 0.
let mut n: u64 = 0;
// 14. Repeat, while k < final,
while k < final_ {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), kValue).
a.create_data_property_or_throw(n, k_value, context)?;
}
// d. Set k to k + 1.
k += 1;
// e. Set n to n + 1.
n += 1;
}
// 15. Perform ? Set(A, "length", 𝔽(n), true).
Self::set_length(&a, n, context)?;
// 16. Return A.
Ok(a.into())
}
/// [`Array.prototype.toLocaleString ( [ locales [ , options ] ] )`][spec].
///
/// Returns a string representing the elements of the array. The elements are converted to
/// strings using their `toLocaleString` methods and these strings are separated by a
/// locale-specific string (such as a comma ",").
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma402/#sup-array.prototype.tolocalestring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toLocaleString
pub(crate) fn to_locale_string(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let array be ? ToObject(this value).
let array = this.to_object(context)?;
// 2. Let len be ? ToLength(? Get(array, "length")).
let len = array.length_of_array_like(context)?;
// 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", ").
let separator = {
#[cfg(feature = "intl")]
{
// TODO: this should eventually return a locale-sensitive separator.
utf16!(", ")
}
#[cfg(not(feature = "intl"))]
{
utf16!(", ")
}
};
// 4. Let R be the empty String.
let mut r = Vec::new();
// 5. Let k be 0.
// 6. Repeat, while k < len,
for k in 0..len {
// a. If k > 0, then
if k > 0 {
// i. Set R to the string-concatenation of R and separator.
r.extend_from_slice(separator);
}
// b. Let nextElement be ? Get(array, ! ToString(k)).
let next = array.get(k, context)?;
// c. If nextElement is not undefined or null, then
if !next.is_null_or_undefined() {
// i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)).
let s = next
.invoke(utf16!("toLocaleString"), args, context)?
.to_string(context)?;
// ii. Set R to the string-concatenation of R and S.
r.extend_from_slice(&s);
}
// d. Increase k by 1.
}
// 7. Return R.
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
/// The deleteCount elements of the array starting at integer index start are replaced by the elements of items.
/// An Array object containing the deleted elements (if any) is returned.
pub(crate) fn splice(
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.first();
let delete_count = args.get(1);
let items = args.get(2..).unwrap_or_default();
// 3. Let relativeStart be ? ToIntegerOrInfinity(start).
// 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.unwrap_or(&JsValue::undefined()), len)?;
// 7. Let itemCount be the number of elements in items.
let item_count = items.len() 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());
}
// 12. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
let arr = Self::array_species_create(&o, actual_delete_count, context)?;
// 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. 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)?;
}
// c. Set k to k + 1.
}
// 15. Perform ? Set(A, "length", 𝔽(actualDeleteCount), true).
Self::set_length(&arr, actual_delete_count, context)?;
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.
// b. Repeat, while k < (len - actualDeleteCount),
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. 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)?;
} else {
// iv. Else,
// 1. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// 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.
}
}
// 17. Else if itemCount > actualDeleteCount, then
Ordering::Greater => {
// a. Set k to (len - actualDeleteCount).
// b. Repeat, while k > actualStart,
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. 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)?;
}
// iv. Else,
else {
// 1. Perform ? DeletePropertyOrThrow(O, to).
o.delete_property_or_throw(to, context)?;
}
// v. Set k to k - 1.
}
}
}
// 18. Set k to actualStart.
// 19. For each element E of items, do
for (i, item) in items.iter().enumerate() {
// a. Perform ? Set(O, ! ToString(𝔽(k)), E, true).
// 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).
Self::set_length(&o, len - actual_delete_count + item_count, context)?;
// 21. Return A.
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.first();
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
/// array is constructed for every value whose callback returned a truthy value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
pub(crate) fn filter(
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 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(|| {
JsNativeError::typ().with_message("Array.prototype.filter: `callback` must be callable")
})?;
let this_arg = args.get_or_undefined(1);
// 4. Let A be ? ArraySpeciesCreate(O, 0).
let a = Self::array_species_create(&o, 0, context)?;
// 5. Let k be 0.
// 6. Let to be 0.
let mut to = 0u32;
// 7. Repeat, while k < len,
for idx in 0..length {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
// c. If kPresent is true, then
if o.has_property(idx, context)? {
// i. Let kValue be ? Get(O, Pk).
let element = o.get(idx, context)?;
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();
// iii. If selected is true, then
if selected {
// 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue).
a.create_data_property_or_throw(to, element, context)?;
// 2. Set to to to + 1.
to += 1;
}
}
}
// 8. Return A.
Ok(a.into())
}
/// Array.prototype.some ( callbackfn [ , thisArg ] )
///
/// The some method tests whether at least one element in the array passes
/// the test implemented by the provided callback function. It returns a Boolean value,
/// true if the callback function returns a truthy value for at least one element
/// in the array. Otherwise, false.
///
/// Caution: Calling this method on an empty array returns false for any condition!
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
pub(crate) fn some(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("Array.prototype.some: callback is not callable")
})?;
// 4. Let k be 0.
// 5. Repeat, while k < len,
for k in 0..len {
// a. Let Pk be ! ToString(𝔽(k)).
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(k, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(k, context)?;
// 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)?
.to_boolean();
// iii. If testResult is true, return true.
if test_result {
return Ok(JsValue::new(true));
}
}
// d. Set k to k + 1.
}
// 6. Return false.
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.
/// The default sort order is ascending, built upon converting the elements into strings,
/// then comparing their sequences of UTF-16 code units values.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.sort
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
pub(crate) fn sort(
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 obj be ? ToObject(this value).
let obj = this.to_object(context)?;
// 3. Let len be ? LengthOfArrayLike(obj).
let len = obj.length_of_array_like(context)?;
// 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 sortedList be ? SortIndexedProperties(obj, len, SortCompare, skip-holes).
let sorted = Self::sort_indexed_properties(&obj, len, sort_compare, true, context)?;
let sorted_len = sorted.len() as u64;
// 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.
}
// 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.
}
// 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:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
pub(crate) fn reduce(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ()
.with_message("Array.prototype.reduce: callback function is not callable")
})?;
// 4. If len = 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
return Err(JsNativeError::typ()
.with_message(
"Array.prototype.reduce: called on an empty array and with no initial value",
)
.into());
}
// 5. Let k be 0.
let mut k = 0;
// 6. Let accumulator be undefined.
let mut accumulator = JsValue::undefined();
// 7. If initialValue is present, then
if let Some(initial_value) = args.get(1) {
// a. Set accumulator to initialValue.
accumulator = initial_value.clone();
// 8. Else,
} else {
// a. Let kPresent be false.
let mut k_present = false;
// b. Repeat, while kPresent is false and k < len,
while !k_present && k < len {
// i. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// ii. Set kPresent to ? HasProperty(O, Pk).
k_present = o.has_property(pk, context)?;
// iii. If kPresent is true, then
if k_present {
// 1. Set accumulator to ? Get(O, Pk).
accumulator = o.get(pk, context)?;
}
// iv. Set k to k + 1.
k += 1;
}
// c. If kPresent is false, throw a TypeError exception.
if !k_present {
return Err(JsNativeError::typ().with_message(
"Array.prototype.reduce: called on an empty array and with no initial value",
).into());
}
}
// 9. Repeat, while k < len,
while k < len {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
accumulator = callback.call(
&JsValue::undefined(),
&[accumulator, k_value, k.into(), o.clone().into()],
context,
)?;
}
// d. Set k to k + 1.
k += 1;
}
// 10. Return accumulator.
Ok(accumulator)
}
/// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )`
///
/// The reduceRight method traverses right to left starting from the last defined value in the array,
/// accumulating a value using a given callback function. It returns the accumulated value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
pub(crate) fn reduce_right(
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. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
JsNativeError::typ()
.with_message("Array.prototype.reduceRight: callback function is not callable")
})?;
// 4. If len is 0 and initialValue is not present, throw a TypeError exception.
if len == 0 && args.get(1).is_none() {
return Err(JsNativeError::typ().with_message(
"Array.prototype.reduceRight: called on an empty array and with no initial value",
).into());
}
// 5. Let k be len - 1.
let mut k = len as i64 - 1;
// 6. Let accumulator be undefined.
let mut accumulator = JsValue::undefined();
// 7. If initialValue is present, then
if let Some(initial_value) = args.get(1) {
// a. Set accumulator to initialValue.
accumulator = initial_value.clone();
// 8. Else,
} else {
// a. Let kPresent be false.
let mut k_present = false;
// b. Repeat, while kPresent is false and k ≥ 0,
while !k_present && k >= 0 {
// i. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// ii. Set kPresent to ? HasProperty(O, Pk).
k_present = o.has_property(pk, context)?;
// iii. If kPresent is true, then
if k_present {
// 1. Set accumulator to ? Get(O, Pk).
accumulator = o.get(pk, context)?;
}
// iv. Set k to k - 1.
k -= 1;
}
// c. If kPresent is false, throw a TypeError exception.
if !k_present {
return Err(JsNativeError::typ().with_message(
"Array.prototype.reduceRight: called on an empty array and with no initial value",
).into());
}
}
// 9. Repeat, while k ≥ 0,
while k >= 0 {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. Let kPresent be ? HasProperty(O, Pk).
let k_present = o.has_property(pk, context)?;
// c. If kPresent is true, then
if k_present {
// i. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
accumulator = callback.call(
&JsValue::undefined(),
&[accumulator.clone(), k_value, k.into(), o.clone().into()],
context,
)?;
}
// d. Set k to k - 1.
k -= 1;
}
// 10. Return accumulator.
Ok(accumulator)
}
/// `Array.prototype.copyWithin ( target, start [ , end ] )`
///
/// The `copyWithin()` method shallow copies part of an array to another location
/// in the same array and returns it without modifying its length.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.copywithin
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
pub(crate) fn copy_within(
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 relativeTarget be ? ToIntegerOrInfinity(target).
// 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_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_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_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);
// 16. If from < to and to < from + count, then
let direction = if from < to && to < from + count {
// b. Set from to from + count - 1.
from = from + count - 1;
// c. Set to to to + count - 1.
to = to + count - 1;
// a. Let direction be -1.
-1
// 17. Else,
} else {
// a. Let direction be 1.
1
};
// 18. Repeat, while count > 0,
while count > 0 {
// a. Let fromKey be ! ToString(𝔽(from)).
let from_key = from;
// b. Let toKey be ! ToString(𝔽(to)).
let to_key = to;
// c. Let fromPresent be ? HasProperty(O, fromKey).
let from_present = o.has_property(from_key, context)?;
// d. If fromPresent is true, then
if from_present {
// i. Let fromVal be ? Get(O, fromKey).
let from_val = o.get(from_key, context)?;
// ii. Perform ? Set(O, toKey, fromVal, true).
o.set(to_key, from_val, true, context)?;
// e. Else,
} else {
// i. Assert: fromPresent is false.
// ii. Perform ? DeletePropertyOrThrow(O, toKey).
o.delete_property_or_throw(to_key, context)?;
}
// f. Set from to from + direction.
from += direction;
// g. Set to to to + direction.
to += direction;
// h. Set count to count - 1.
count -= 1;
}
// 19. Return O.
Ok(o.into())
}
/// `Array.prototype.values( )`
///
/// The values method returns an iterable that iterates over the values in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn values(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, value).
Ok(ArrayIterator::create_array_iterator(
o,
PropertyNameKind::Value,
context,
))
}
/// `Array.prototype.keys( )`
///
/// The keys method returns an iterable that iterates over the indexes in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.keys
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, key).
Ok(ArrayIterator::create_array_iterator(
o,
PropertyNameKind::Key,
context,
))
}
/// `Array.prototype.entries( )`
///
/// The entries method returns an iterable that iterates over the key-value pairs in the array.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.entries
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
pub(crate) fn entries(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Let O be ? ToObject(this value).
let o = this.to_object(context)?;
// 2. Return CreateArrayIterator(O, key+value).
Ok(ArrayIterator::create_array_iterator(
o,
PropertyNameKind::KeyAndValue,
context,
))
}
/// [`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: &JsValue,
len: u64,
) -> JsResult<u64> {
// 1. Let relativeStart be ? ToIntegerOrInfinity(start).
let relative_start = arg.to_integer_or_infinity(context)?;
let start = match relative_start {
// 2. If relativeStart is -∞, let k be 0.
IntegerOrInfinity::NegativeInfinity => 0,
// 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0),
// 4. Else, let k be min(relativeStart, len).
IntegerOrInfinity::Integer(i) => min(i as u64, len),
// Special case - positive infinity. `len` is always smaller than +inf, thus from (4)
IntegerOrInfinity::PositiveInfinity => len,
};
Ok(start)
}
/// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions.
pub(super) fn get_relative_end(
context: &mut Context,
value: &JsValue,
len: u64,
) -> JsResult<u64> {
// 1. If end is undefined, let relativeEnd be len [and return it]
if value.is_undefined() {
Ok(len)
} else {
// 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end).
let relative_end = value.to_integer_or_infinity(context)?;
let end = match relative_end {
// 2. If relativeEnd is -∞, let final be 0.
IntegerOrInfinity::NegativeInfinity => 0,
// 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0),
// 4. Else, let final be min(relativeEnd, len).
// Both `as` casts are safe as both variables are non-negative
IntegerOrInfinity::Integer(i) => min(i as u64, len),
// Special case - positive infinity. `len` is always smaller than +inf, thus from (4)
IntegerOrInfinity::PositiveInfinity => len,
};
Ok(end)
}
}
/// `Array.prototype [ @@unscopables ]`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables
pub(crate) fn unscopables_object() -> JsObject {
// 1. Let unscopableList be OrdinaryObjectCreate(null).
let unscopable_list = JsObject::with_null_proto();
let true_prop = PropertyDescriptor::builder()
.value(true)
.writable(true)
.enumerable(true)
.configurable(true);
{
let mut obj = unscopable_list.borrow_mut();
// 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
obj.insert(utf16!("at"), true_prop.clone());
// 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true).
obj.insert(utf16!("copyWithin"), true_prop.clone());
// 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true).
obj.insert(utf16!("entries"), true_prop.clone());
// 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true).
obj.insert(utf16!("fill"), true_prop.clone());
// 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true).
obj.insert(utf16!("find"), true_prop.clone());
// 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true).
obj.insert(utf16!("findIndex"), true_prop.clone());
// 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true).
obj.insert(utf16!("findLast"), true_prop.clone());
// 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true).
obj.insert(utf16!("findLastIndex"), true_prop.clone());
// 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true).
obj.insert(utf16!("flat"), true_prop.clone());
// 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true).
obj.insert(utf16!("flatMap"), true_prop.clone());
// 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true).
obj.insert(utf16!("includes"), true_prop.clone());
// 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true).
obj.insert(utf16!("keys"), true_prop.clone());
// 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true).
obj.insert(utf16!("toReversed"), true_prop.clone());
// 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true).
obj.insert(utf16!("toSorted"), true_prop.clone());
// 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true).
obj.insert(utf16!("toSpliced"), true_prop.clone());
// 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true).
obj.insert(utf16!("values"), true_prop);
}
// 13. Return unscopableList.
unscopable_list
}
}
/// [`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:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-findviapredicate
pub(crate) fn find_via_predicate(
o: &JsObject,
len: u64,
direction: Direction,
predicate: &JsValue,
this_arg: &JsValue,
context: &mut Context,
caller_name: &str,
) -> JsResult<(JsValue, JsValue)> {
// 1. If IsCallable(predicate) is false, throw a TypeError exception.
let predicate = predicate.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message(format!("{caller_name}: predicate is not callable"))
})?;
let indices = match direction {
// 2. If direction is ascending, then
// a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in ascending order.
Direction::Ascending => itertools::Either::Left(0..len),
// 3. Else,
// a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in descending order.
Direction::Descending => itertools::Either::Right((0..len).rev()),
};
// 4. For each integer k of indices, do
for k in indices {
// a. Let Pk be ! ToString(𝔽(k)).
let pk = k;
// b. NOTE: If O is a TypedArray, the following invocation of Get will return a normal completion.
// c. Let kValue be ? Get(O, Pk).
let k_value = o.get(pk, context)?;
// d. Let testResult be ? Call(predicate, thisArg, « kValue, 𝔽(k), O »).
let test_result = predicate
.call(
this_arg,
&[k_value.clone(), k.into(), o.clone().into()],
context,
)?
.to_boolean();
if test_result {
// e. If ToBoolean(testResult) is true, return the Record { [[Index]]: 𝔽(k), [[Value]]: kValue }.
return Ok((JsValue::new(k), k_value));
}
}
// 5. Return the Record { [[Index]]: -1𝔽, [[Value]]: undefined }
Ok((JsValue::new(-1), JsValue::undefined()))
}
/// Define an own property for an array exotic object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
fn array_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
match key {
// 2. If P is "length", then
PropertyKey::String(ref s) if s == &StaticJsStrings::LENGTH => {
// a. Return ? ArraySetLength(A, Desc).
array_set_length(obj, desc, context)
}
// 3. Else if P is an array index, then
PropertyKey::Index(index) => {
let index = index.get();
// a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc =
ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)?
.expect("the property descriptor must exist");
// b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// c. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// d. Let oldLen be oldLenDesc.[[Value]].
// e. Assert: oldLen is a non-negative integral Number.
// f. Let index be ! ToUint32(P).
let old_len = old_len_desc
.expect_value()
.to_u32(context)
.expect("this ToUint32 call must not fail");
// g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false.
if index >= old_len && !old_len_desc.expect_writable() {
return Ok(false);
}
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
if ordinary_define_own_property(obj, key, desc, context)? {
// j. If index ≥ oldLen, then
if index >= old_len {
// i. Set oldLenDesc.[[Value]] to index + 1𝔽.
let old_len_desc = PropertyDescriptor::builder()
.value(index + 1)
.maybe_writable(old_len_desc.writable())
.maybe_enumerable(old_len_desc.enumerable())
.maybe_configurable(old_len_desc.configurable());
// ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
let succeeded = ordinary_define_own_property(
obj,
&StaticJsStrings::LENGTH.into(),
old_len_desc.into(),
context,
)?;
// iii. Assert: succeeded is true.
debug_assert!(succeeded);
}
// k. Return true.
Ok(true)
} else {
// i. If succeeded is false, return false.
Ok(false)
}
}
// 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
_ => ordinary_define_own_property(obj, key, desc, context),
}
}
/// Abstract operation `ArraySetLength ( A, Desc )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength
fn array_set_length(
obj: &JsObject,
desc: PropertyDescriptor,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If Desc.[[Value]] is absent, then
let Some(new_len_val) = desc.value() else {
// a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
return ordinary_define_own_property(obj, &StaticJsStrings::LENGTH.into(), desc, context);
};
// 3. Let newLen be ? ToUint32(Desc.[[Value]]).
let new_len = new_len_val.to_u32(context)?;
// 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
let number_len = new_len_val.to_number(context)?;
// 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
#[allow(clippy::float_cmp)]
if f64::from(new_len) != number_len {
return Err(JsNativeError::range()
.with_message("bad length for array")
.into());
}
// 2. Let newLenDesc be a copy of Desc.
// 6. Set newLenDesc.[[Value]] to newLen.
let mut new_len_desc = PropertyDescriptor::builder()
.value(new_len)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable());
// 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc = ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)?
.expect("the property descriptor must exist");
// 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// 9. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// 10. Let oldLen be oldLenDesc.[[Value]].
let old_len = old_len_desc.expect_value();
// 11. If newLen ≥ oldLen, then
if new_len >= old_len.to_u32(context)? {
// a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
return ordinary_define_own_property(
obj,
&StaticJsStrings::LENGTH.into(),
new_len_desc.build(),
context,
);
}
// 12. If oldLenDesc.[[Writable]] is false, return false.
if !old_len_desc.expect_writable() {
return Ok(false);
}
// 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
}
// 14. Else,
else {
// a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
// elements cannot be deleted.
// c. Set newLenDesc.[[Writable]] to true.
new_len_desc = new_len_desc.writable(true);
// b. Let newWritable be false.
false
};
// 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
// 16. If succeeded is false, return false.
if !ordinary_define_own_property(
obj,
&StaticJsStrings::LENGTH.into(),
new_len_desc.clone().build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail")
{
return Ok(false);
}
// 17. For each own property key P of A that is an array index, whose numeric value is
// greater than or equal to newLen, in descending numeric index order, do
let ordered_keys = {
let mut keys: Vec<_> = obj
.borrow()
.properties
.index_property_keys()
.filter(|idx| new_len <= *idx && *idx < u32::MAX)
.collect();
keys.sort_unstable_by(|x, y| y.cmp(x));
keys
};
for index in ordered_keys {
// a. Let deleteSucceeded be ! A.[[Delete]](P).
// b. If deleteSucceeded is false, then
if !obj.__delete__(&index.into(), context)? {
// i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽.
new_len_desc = new_len_desc.value(index + 1);
// ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
// iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
ordinary_define_own_property(
obj,
&StaticJsStrings::LENGTH.into(),
new_len_desc.build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
// iv. Return false.
return Ok(false);
}
}
// 18. If newWritable is false, then
if !new_writable {
// a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
// PropertyDescriptor { [[Writable]]: false }).
let succeeded = ordinary_define_own_property(
obj,
&StaticJsStrings::LENGTH.into(),
PropertyDescriptor::builder().writable(false).build(),
context,
)
.expect("this OrdinaryDefineOwnProperty call must not fail");
// b. Assert: succeeded is true.
debug_assert!(succeeded);
}
// 19. Return true.
Ok(true)
}