Skip to main content

boa_engine/builtins/array/
mod.rs

1//! Boa's implementation of ECMAScript's global `Array` object.
2//!
3//! The ECMAScript `Array` class is a global object that is used in the construction of arrays; which are high-level, list-like objects.
4//!
5//! More information:
6//!  - [ECMAScript reference][spec]
7//!  - [MDN documentation][mdn]
8//!
9//! [spec]: https://tc39.es/ecma262/#sec-array-objects
10//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
11
12use boa_gc::{Finalize, Trace};
13use thin_vec::ThinVec;
14
15use crate::{
16    Context, JsArgs, JsExpect, JsResult, JsString,
17    builtins::{BuiltInObject, Number, iterable::if_abrupt_close_iterator},
18    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
19    error::JsNativeError,
20    js_string,
21    object::{
22        CONSTRUCTOR, IndexedProperties, JsData, JsObject,
23        internal_methods::{
24            InternalMethodPropertyContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
25            get_prototype_from_constructor, ordinary_define_own_property,
26            ordinary_get_own_property,
27        },
28    },
29    property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
30    realm::Realm,
31    string::StaticJsStrings,
32    symbol::JsSymbol,
33    value::{IntegerOrInfinity, JsValue},
34};
35use std::cmp::{Ordering, min};
36
37use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
38
39mod array_iterator;
40use crate::value::JsVariant;
41pub(crate) use array_iterator::ArrayIterator;
42
43#[cfg(feature = "experimental")]
44mod from_async;
45
46#[cfg(test)]
47mod tests;
48
49/// Direction for `find_via_predicate`
50#[derive(Clone, Copy, Eq, PartialEq)]
51pub(crate) enum Direction {
52    Ascending,
53    Descending,
54}
55
56/// JavaScript `Array` built-in implementation.
57#[derive(Debug, Clone, Copy, Trace, Finalize)]
58#[boa_gc(empty_trace)]
59pub(crate) struct Array;
60
61/// Definitions of the internal object methods for array exotic objects.
62///
63/// More information:
64///  - [ECMAScript reference][spec]
65///
66/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
67pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
68    __define_own_property__: array_exotic_define_own_property,
69    ..ORDINARY_INTERNAL_METHODS
70};
71
72impl JsData for Array {
73    fn internal_methods(&self) -> &'static InternalObjectMethods {
74        &ARRAY_EXOTIC_INTERNAL_METHODS
75    }
76}
77
78impl IntrinsicObject for Array {
79    fn init(realm: &Realm) {
80        let symbol_iterator = JsSymbol::iterator();
81        let symbol_unscopables = JsSymbol::unscopables();
82
83        let get_species = BuiltInBuilder::callable(realm, Self::get_species)
84            .name(js_string!("get [Symbol.species]"))
85            .build();
86
87        let values_function = BuiltInBuilder::callable_with_object(
88            realm,
89            realm.intrinsics().objects().array_prototype_values().into(),
90            Self::values,
91        )
92        .name(js_string!("values"))
93        .build();
94
95        let to_string_function = BuiltInBuilder::callable_with_object(
96            realm,
97            realm
98                .intrinsics()
99                .objects()
100                .array_prototype_to_string()
101                .into(),
102            Self::to_string,
103        )
104        .name(js_string!("toString"))
105        .build();
106
107        let unscopables_object = Self::unscopables_object();
108
109        let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
110            // Static Methods
111            .static_method(Self::from, js_string!("from"), 1)
112            .static_method(Self::is_array, js_string!("isArray"), 1)
113            .static_method(Self::of, js_string!("of"), 0)
114            .static_accessor(
115                JsSymbol::species(),
116                Some(get_species),
117                None,
118                Attribute::CONFIGURABLE,
119            )
120            .property(
121                StaticJsStrings::LENGTH,
122                0,
123                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
124            )
125            .method(Self::at, js_string!("at"), 1)
126            .method(Self::concat, js_string!("concat"), 1)
127            .method(Self::copy_within, js_string!("copyWithin"), 2)
128            .method(Self::entries, js_string!("entries"), 0)
129            .method(Self::every, js_string!("every"), 1)
130            .method(Self::fill, js_string!("fill"), 1)
131            .method(Self::filter, js_string!("filter"), 1)
132            .method(Self::find, js_string!("find"), 1)
133            .method(Self::find_index, js_string!("findIndex"), 1)
134            .method(Self::find_last, js_string!("findLast"), 1)
135            .method(Self::find_last_index, js_string!("findLastIndex"), 1)
136            .method(Self::flat, js_string!("flat"), 0)
137            .method(Self::flat_map, js_string!("flatMap"), 1)
138            .method(Self::for_each, js_string!("forEach"), 1)
139            .method(Self::includes_value, js_string!("includes"), 1)
140            .method(Self::index_of, js_string!("indexOf"), 1)
141            .method(Self::join, js_string!("join"), 1)
142            .method(Self::keys, js_string!("keys"), 0)
143            .method(Self::last_index_of, js_string!("lastIndexOf"), 1)
144            .method(Self::map, js_string!("map"), 1)
145            .method(Self::pop, js_string!("pop"), 0)
146            .method(Self::push, js_string!("push"), 1)
147            .method(Self::reduce, js_string!("reduce"), 1)
148            .method(Self::reduce_right, js_string!("reduceRight"), 1)
149            .method(Self::reverse, js_string!("reverse"), 0)
150            .method(Self::shift, js_string!("shift"), 0)
151            .method(Self::slice, js_string!("slice"), 2)
152            .method(Self::some, js_string!("some"), 1)
153            .method(Self::sort, js_string!("sort"), 1)
154            .method(Self::splice, js_string!("splice"), 2)
155            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
156            .method(Self::to_reversed, js_string!("toReversed"), 0)
157            .method(Self::to_sorted, js_string!("toSorted"), 1)
158            .method(Self::to_spliced, js_string!("toSpliced"), 2)
159            .method(Self::unshift, js_string!("unshift"), 1)
160            .method(Self::with, js_string!("with"), 2)
161            .property(
162                js_string!("toString"),
163                to_string_function,
164                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
165            )
166            .property(
167                js_string!("values"),
168                values_function.clone(),
169                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
170            )
171            .property(
172                symbol_iterator,
173                values_function,
174                Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
175            )
176            .property(
177                symbol_unscopables,
178                unscopables_object,
179                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
180            );
181
182        #[cfg(feature = "experimental")]
183        let builder = builder.static_method(Self::from_async, js_string!("fromAsync"), 1);
184
185        builder.build();
186    }
187
188    fn get(intrinsics: &Intrinsics) -> JsObject {
189        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
190    }
191}
192
193impl BuiltInObject for Array {
194    const NAME: JsString = StaticJsStrings::ARRAY;
195}
196
197impl BuiltInConstructor for Array {
198    const PROTOTYPE_STORAGE_SLOTS: usize = 41;
199    const CONSTRUCTOR_STORAGE_SLOTS: usize = 6;
200
201    const CONSTRUCTOR_ARGUMENTS: usize = 1;
202
203    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
204        StandardConstructors::array;
205
206    fn constructor(
207        new_target: &JsValue,
208        args: &[JsValue],
209        context: &mut Context,
210    ) -> JsResult<JsValue> {
211        // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
212        let new_target = &if new_target.is_undefined() {
213            context
214                .active_function_object()
215                .unwrap_or_else(|| context.intrinsics().constructors().array().constructor())
216                .into()
217        } else {
218            new_target.clone()
219        };
220
221        // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%").
222        let prototype =
223            get_prototype_from_constructor(new_target, StandardConstructors::array, context)?;
224
225        // 3. Let numberOfArgs be the number of elements in values.
226        let number_of_args = args.len();
227
228        // 4. If numberOfArgs = 0, then
229        if number_of_args == 0 {
230            // 4.a. Return ! ArrayCreate(0, proto).
231            Ok(Self::array_create(0, Some(prototype), context)?.into())
232        // 5. Else if numberOfArgs = 1, then
233        } else if number_of_args == 1 {
234            // a. Let len be values[0].
235            let len = &args[0];
236            // b. Let array be ! ArrayCreate(0, proto).
237            let array = Self::array_create(0, Some(prototype), context)?;
238            // c. If Type(len) is not Number, then
239            #[allow(clippy::if_not_else)]
240            let int_len = if !len.is_number() {
241                // i. Perform ! CreateDataPropertyOrThrow(array, "0", len).
242                array
243                    .create_data_property_or_throw(0, len.clone(), context)
244                    .js_expect("this CreateDataPropertyOrThrow call must not fail")?;
245                // ii. Let intLen be 1๐”ฝ.
246                1
247            // d. Else,
248            } else {
249                // i. Let intLen be ! ToUint32(len).
250                let int_len = len
251                    .to_u32(context)
252                    .js_expect("this ToUint32 call must not fail")?;
253                // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
254                if !JsValue::same_value_zero(&int_len.into(), len) {
255                    return Err(JsNativeError::range()
256                        .with_message("invalid array length")
257                        .into());
258                }
259                int_len
260            };
261            // e. Perform ! Set(array, "length", intLen, true).
262            array
263                .set(StaticJsStrings::LENGTH, int_len, true, context)
264                .js_expect("this Set call must not fail")?;
265            // f. Return array.
266            Ok(array.into())
267        // 6. Else,
268        } else {
269            // 6.a. Assert: numberOfArgs โ‰ฅ 2.
270            debug_assert!(number_of_args >= 2);
271
272            // b. Let array be ? ArrayCreate(numberOfArgs, proto).
273            let array = Self::array_create(number_of_args as u64, Some(prototype), context)?;
274
275            // c. Let k be 0.
276            // d. Repeat, while k < numberOfArgs,
277            //    i. Let Pk be ! ToString(๐”ฝ(k)).
278            //    ii. Let itemK be values[k].
279            //    iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK).
280            //    iv. Set k to k + 1.
281            array
282                .borrow_mut()
283                .properties_mut()
284                .override_indexed_properties(args.iter().cloned().collect());
285
286            // e. Assert: The mathematical value of array's "length" property is numberOfArgs.
287            // f. Return array.
288            Ok(array.into())
289        }
290    }
291}
292
293impl Array {
294    /// Optimized helper function, that sets the length of the array.
295    fn set_length(o: &JsObject, len: u64, context: &mut Context) -> JsResult<()> {
296        if o.is_array() && len < (2u64.pow(32) - 1) {
297            let mut borrowed_object = o.borrow_mut();
298            if borrowed_object.properties().shape.to_addr_usize()
299                == context
300                    .intrinsics()
301                    .templates()
302                    .array()
303                    .shape()
304                    .to_addr_usize()
305            {
306                // NOTE: The "length" property is the first element.
307                borrowed_object.properties_mut().storage[0] = JsValue::new(len);
308                return Ok(());
309            }
310        }
311
312        o.set(StaticJsStrings::LENGTH, len, true, context)?;
313        Ok(())
314    }
315
316    /// Utility for constructing `Array` objects.
317    ///
318    /// More information:
319    ///  - [ECMAScript reference][spec]
320    ///
321    /// [spec]: https://tc39.es/ecma262/#sec-arraycreate
322    pub(crate) fn array_create(
323        length: u64,
324        prototype: Option<JsObject>,
325        context: &mut Context,
326    ) -> JsResult<JsObject> {
327        // 1. If length > 2^32 - 1, throw a RangeError exception.
328        if length > 2u64.pow(32) - 1 {
329            return Err(JsNativeError::range()
330                .with_message("array exceeded max size")
331                .into());
332        }
333
334        // Fast path:
335        if prototype.is_none() {
336            return Ok(context
337                .intrinsics()
338                .templates()
339                .array()
340                .create(Array, vec![JsValue::new(length)]));
341        }
342
343        // 7. Return A.
344        // 2. If proto is not present, set proto to %Array.prototype%.
345        // 3. Let A be ! MakeBasicObject(ยซ [[Prototype]], [[Extensible]] ยป).
346        // 4. Set A.[[Prototype]] to proto.
347        // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1.
348        let prototype =
349            prototype.unwrap_or_else(|| context.intrinsics().constructors().array().prototype());
350
351        // Fast path:
352        if context
353            .intrinsics()
354            .templates()
355            .array()
356            .has_prototype(&prototype)
357        {
358            return Ok(context
359                .intrinsics()
360                .templates()
361                .array()
362                .create(Array, vec![JsValue::new(length)]));
363        }
364
365        let array =
366            JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, Array)
367                .upcast();
368
369        // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: ๐”ฝ(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
370        ordinary_define_own_property(
371            &array,
372            &StaticJsStrings::LENGTH.into(),
373            PropertyDescriptor::builder()
374                .value(length)
375                .writable(true)
376                .enumerable(false)
377                .configurable(false)
378                .build(),
379            &mut InternalMethodPropertyContext::new(context),
380        )?;
381
382        Ok(array)
383    }
384
385    /// Utility for constructing `Array` objects from an iterator of `JsValue`s.
386    ///
387    /// More information:
388    ///  - [ECMAScript reference][spec]
389    ///
390    /// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist
391    pub(crate) fn create_array_from_list<I>(elements: I, context: &Context) -> JsObject
392    where
393        I: IntoIterator<Item = JsValue>,
394    {
395        // 1. Assert: elements is a List whose elements are all ECMAScript language values.
396        // 2. Let array be ! ArrayCreate(0).
397        // 3. Let n be 0.
398        // 4. For each element e of elements, do
399        //     a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(๐”ฝ(n)), e).
400        //     b. Set n to n + 1.
401        // 5. Return array.
402        // NOTE: This deviates from the spec, but it should have the same behaviour.
403        let elements: ThinVec<_> = elements.into_iter().collect();
404        let length = elements.len();
405
406        context
407            .intrinsics()
408            .templates()
409            .array()
410            .create_with_indexed_properties(
411                Array,
412                vec![JsValue::new(length)],
413                IndexedProperties::from_dense_js_value(elements),
414            )
415    }
416
417    /// Utility function for concatenating array objects.
418    ///
419    /// Returns a Boolean valued property that if `true` indicates that
420    /// an object should be flattened to its array elements
421    /// by `Array.prototype.concat`.
422    fn is_concat_spreadable(o: &JsValue, context: &mut Context) -> JsResult<bool> {
423        // 1. If Type(O) is not Object, return false.
424        let Some(o) = o.as_object() else {
425            return Ok(false);
426        };
427
428        // 2. Let spreadable be ? Get(O, @@isConcatSpreadable).
429        let spreadable = o.get(JsSymbol::is_concat_spreadable(), context)?;
430
431        // 3. If spreadable is not undefined, return ! ToBoolean(spreadable).
432        if !spreadable.is_undefined() {
433            return Ok(spreadable.to_boolean());
434        }
435
436        // 4. Return ? IsArray(O).
437        o.is_array_abstract()
438    }
439
440    /// `get Array [ @@species ]`
441    ///
442    /// The `Array [ @@species ]` accessor property returns the Array constructor.
443    ///
444    /// More information:
445    ///  - [ECMAScript reference][spec]
446    ///  - [MDN documentation][mdn]
447    ///
448    /// [spec]: https://tc39.es/ecma262/#sec-get-array-@@species
449    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species
450    #[allow(clippy::unnecessary_wraps)]
451    fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
452        // 1. Return the this value.
453        Ok(this.clone())
454    }
455
456    /// Utility function used to specify the creation of a new Array object using a constructor
457    /// function that is derived from `original_array`.
458    ///
459    /// see: <https://tc39.es/ecma262/#sec-arrayspeciescreate>
460    pub(crate) fn array_species_create(
461        original_array: &JsObject,
462        length: u64,
463        context: &mut Context,
464    ) -> JsResult<JsObject> {
465        // 1. Let isArray be ? IsArray(originalArray).
466        // 2. If isArray is false, return ? ArrayCreate(length).
467        if !original_array.is_array_abstract()? {
468            return Self::array_create(length, None, context);
469        }
470        // 3. Let C be ? Get(originalArray, "constructor").
471        let c = original_array.get(CONSTRUCTOR, context)?;
472
473        // 4. If IsConstructor(C) is true, then
474        if let Some(c) = c.as_constructor() {
475            // a. Let thisRealm be the current Realm Record.
476            let this_realm = context.realm().clone();
477            // b. Let realmC be ? GetFunctionRealm(C).
478            let realm_c = c.get_function_realm(context)?;
479
480            // c. If thisRealm and realmC are not the same Realm Record, then
481            if this_realm != realm_c
482                && c == realm_c.intrinsics().constructors().array().constructor()
483            {
484                // i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined.
485                // Note: fast path to step 6.
486                return Self::array_create(length, None, context);
487            }
488        }
489
490        // 5. If Type(C) is Object, then
491        let c = if let Some(c) = c.as_object() {
492            // 5.a. Set C to ? Get(C, @@species).
493            let c = c.get(JsSymbol::species(), context)?;
494            // 5.b. If C is null, set C to undefined.
495            if c.is_null_or_undefined() {
496                JsValue::undefined()
497            } else {
498                c
499            }
500        } else {
501            c
502        };
503
504        // 6. If C is undefined, return ? ArrayCreate(length).
505        if c.is_undefined() {
506            return Self::array_create(length, None, context);
507        }
508
509        if let Some(c) = c.as_constructor() {
510            // 8. Return ? Construct(C, ยซ ๐”ฝ(length) ยป).
511            return c.construct(&[JsValue::new(length)], Some(&c), context);
512        }
513
514        // 7. If IsConstructor(C) is false, throw a TypeError exception.
515        Err(JsNativeError::typ()
516            .with_message("Symbol.species must be a constructor")
517            .into())
518    }
519
520    /// `Array.from(arrayLike)`
521    ///
522    /// The `Array.from()` static method creates a new,
523    /// shallow-copied Array instance from an array-like or iterable object.
524    ///
525    /// More information:
526    ///  - [ECMAScript reference][spec]
527    ///  - [MDN documentation][mdn]
528    ///
529    /// [spec]: https://tc39.es/ecma262/#sec-array.from
530    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
531    pub(crate) fn from(
532        this: &JsValue,
533        args: &[JsValue],
534        context: &mut Context,
535    ) -> JsResult<JsValue> {
536        let items = args.get_or_undefined(0);
537        let mapfn = args.get_or_undefined(1);
538        let this_arg = args.get_or_undefined(2);
539
540        // 2. If mapfn is undefined, let mapping be false
541        // 3. Else,
542        //     a. If IsCallable(mapfn) is false, throw a TypeError exception.
543        //     b. Let mapping be true.
544        let mapping = match mapfn.variant() {
545            JsVariant::Undefined => None,
546            JsVariant::Object(o) if o.is_callable() => Some(o),
547            _ => {
548                return Err(JsNativeError::typ()
549                    .with_message(format!("`{}` is not callable", mapfn.type_of()))
550                    .into());
551            }
552        };
553
554        // 4. Let usingIterator be ? GetMethod(items, @@iterator).
555        let using_iterator = items.get_method(JsSymbol::iterator(), context)?;
556
557        let Some(using_iterator) = using_iterator else {
558            // 6. NOTE: items is not an Iterable so assume it is an array-like object.
559            // 7. Let arrayLike be ! ToObject(items).
560            let array_like = items
561                .to_object(context)
562                .js_expect("should not fail according to spec")?;
563
564            // 8. Let len be ? LengthOfArrayLike(arrayLike).
565            let len = array_like.length_of_array_like(context)?;
566
567            // 9. If IsConstructor(C) is true, then
568            //     a. Let A be ? Construct(C, ยซ ๐”ฝ(len) ยป).
569            // 10. Else,
570            //     a. Let A be ? ArrayCreate(len).
571            let a = match this.as_constructor() {
572                Some(constructor) => constructor.construct(&[len.into()], None, context)?,
573                _ => Self::array_create(len, None, context)?,
574            };
575
576            // 11. Let k be 0.
577            // 12. Repeat, while k < len,
578            //     ...
579            //     f. Set k to k + 1.
580            for k in 0..len {
581                // a. Let Pk be ! ToString(๐”ฝ(k)).
582                // b. Let kValue be ? Get(arrayLike, Pk).
583                let k_value = array_like.get(k, context)?;
584
585                let mapped_value = if let Some(ref mapfn) = mapping {
586                    // c. If mapping is true, then
587                    //     i. Let mappedValue be ? Call(mapfn, thisArg, ยซ kValue, ๐”ฝ(k) ยป).
588                    mapfn.call(this_arg, &[k_value, k.into()], context)?
589                } else {
590                    // d. Else, let mappedValue be kValue.
591                    k_value
592                };
593
594                // e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
595                a.create_data_property_or_throw(k, mapped_value, context)?;
596            }
597
598            // 13. Perform ? Set(A, "length", ๐”ฝ(len), true).
599            a.set(StaticJsStrings::LENGTH, len, true, context)?;
600
601            // 14. Return A.
602            return Ok(a.into());
603        };
604
605        // 5. If usingIterator is not undefined, then
606
607        // a. If IsConstructor(C) is true, then
608        //     i. Let A be ? Construct(C).
609        // b. Else,
610        //     i. Let A be ? ArrayCreate(0en).
611        let a = match this.as_constructor() {
612            Some(constructor) => constructor.construct(&[], None, context)?,
613            _ => Self::array_create(0, None, context)?,
614        };
615
616        // c. Let iteratorRecord be ? GetIteratorFromMethod(items, usingIterator).
617        let mut iterator_record = items.get_iterator_from_method(&using_iterator, context)?;
618
619        // d. Let k be 0.
620        // e. Repeat,
621        //     i. If k โ‰ฅ 2^53 - 1 (MAX_SAFE_INTEGER), then
622        //     ...
623        //     ix. Set k to k + 1.
624        for k in 0..9_007_199_254_740_991_u64 {
625            // iii. Let next be ? IteratorStepValue(iteratorRecord).
626            let Some(next) = iterator_record.step_value(context)? else {
627                // iv. If next is done, then
628                //     1. Perform ? Set(A, "length", ๐”ฝ(k), true).
629                a.set(StaticJsStrings::LENGTH, k, true, context)?;
630                //     2. Return A.
631                return Ok(a.into());
632            };
633
634            // v. If mapping is true, then
635            let mapped_value = if let Some(ref mapfn) = mapping {
636                // 1. Let mappedValue be Completion(Call(mapper, thisArg, ยซ next, ๐”ฝ(k) ยป)).
637                let mapped_value = mapfn.call(this_arg, &[next, k.into()], context);
638
639                // 2. IfAbruptCloseIterator(mappedValue, iteratorRecord).
640                if_abrupt_close_iterator!(mapped_value, iterator_record, context)
641            } else {
642                // vi. Else,
643                //     1. Let mappedValue be next.
644                next
645            };
646
647            // vii. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)).
648            let define_status = a.create_data_property_or_throw(k, mapped_value, context);
649
650            // viii. IfAbruptCloseIterator(defineStatus, iteratorRecord).
651            if_abrupt_close_iterator!(define_status, iterator_record, context);
652        }
653
654        // NOTE: The loop above has to return before it reaches iteration limit,
655        // which is why it's safe to have this as the fallback return
656        //
657        // 1. Let error be ThrowCompletion(a newly created TypeError object).
658        let error = Err(JsNativeError::typ()
659            .with_message("Invalid array length")
660            .into());
661
662        // 2. Return ? IteratorClose(iteratorRecord, error).
663        iterator_record.close(error, context)
664    }
665
666    /// `Array.isArray( arg )`
667    ///
668    /// The isArray function takes one argument arg, and returns the Boolean value true
669    /// if the argument is an object whose class internal property is "Array"; otherwise it returns false.
670    ///
671    /// More information:
672    ///  - [ECMAScript reference][spec]
673    ///  - [MDN documentation][mdn]
674    ///
675    /// [spec]: https://tc39.es/ecma262/#sec-array.isarray
676    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
677    pub(crate) fn is_array(
678        _: &JsValue,
679        args: &[JsValue],
680        _context: &mut Context,
681    ) -> JsResult<JsValue> {
682        // 1. Return ? IsArray(arg).
683        args.get_or_undefined(0).is_array().map(Into::into)
684    }
685
686    /// `Array.of(...items)`
687    ///
688    /// The Array.of method creates a new Array instance from a variable number of arguments,
689    /// regardless of the number or type of arguments.
690    ///
691    /// More information:
692    ///  - [ECMAScript reference][spec]
693    ///  - [MDN documentation][mdn]
694    ///
695    /// [spec]: https://tc39.es/ecma262/#sec-array.of
696    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of
697    pub(crate) fn of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
698        // 1. Let len be the number of elements in items.
699        // 2. Let lenNumber be ๐”ฝ(len).
700        let len = args.len();
701
702        // 3. Let C be the this value.
703        // 4. If IsConstructor(C) is true, then
704        //     a. Let A be ? Construct(C, ยซ lenNumber ยป).
705        // 5. Else,
706        //     a. Let A be ? ArrayCreate(len).
707        let a = match this.as_constructor() {
708            Some(constructor) => constructor.construct(&[len.into()], None, context)?,
709            _ => Self::array_create(len as u64, None, context)?,
710        };
711
712        // 6. Let k be 0.
713        // 7. Repeat, while k < len,
714        for (k, value) in args.iter().enumerate() {
715            // a. Let kValue be items[k].
716            // b. Let Pk be ! ToString(๐”ฝ(k)).
717            // c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue).
718            a.create_data_property_or_throw(k, value.clone(), context)?;
719            // d. Set k to k + 1.
720        }
721
722        // 8. Perform ? Set(A, "length", lenNumber, true).
723        Self::set_length(&a, len as u64, context)?;
724
725        // 9. Return A.
726        Ok(a.into())
727    }
728
729    ///'Array.prototype.at(index)'
730    ///
731    /// The `at()` method takes an integer value and returns the item at that
732    /// index, allowing for positive and negative integers. Negative integers
733    /// count back from the last item in the array.
734    ///
735    /// More information:
736    ///  - [ECMAScript reference][spec]
737    ///  - [MDN documentation][mdn]
738    ///
739    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.at
740    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
741    pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
742        //1. let O be ? ToObject(this value)
743        let obj = this.to_object(context)?;
744        //2. let len be ? LengthOfArrayLike(O)
745        let len = obj.length_of_array_like(context)? as i64;
746        //3. let relativeIndex be ? ToIntegerOrInfinity(index)
747        let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?;
748        let k = match relative_index {
749            //4. if relativeIndex >= 0, then let k be relativeIndex
750            //check if positive and if below length of array
751            IntegerOrInfinity::Integer(i) if i >= 0 && i < len => i,
752            //5. Else, let k be len + relativeIndex
753            //integer should be negative, so abs() and check if less than or equal to length of array
754            IntegerOrInfinity::Integer(i) if i < 0 && i.abs() <= len => len + i,
755            //handle most likely impossible case of
756            //IntegerOrInfinity::NegativeInfinity || IntegerOrInfinity::PositiveInfinity
757            //by returning undefined
758            _ => return Ok(JsValue::undefined()),
759        };
760        //6. if k < 0  or k >= len,
761        //handled by the above match guards
762        //7. Return ? Get(O, !ToString(๐”ฝ(k)))
763        obj.get(k, context)
764    }
765
766    /// `Array.prototype.concat(...arguments)`
767    ///
768    /// When the concat method is called with zero or more arguments, it returns an
769    /// array containing the array elements of the object followed by the array
770    /// elements of each argument in order.
771    ///
772    /// More information:
773    ///  - [ECMAScript reference][spec]
774    ///  - [MDN documentation][mdn]
775    ///
776    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat
777    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
778    pub(crate) fn concat(
779        this: &JsValue,
780        args: &[JsValue],
781        context: &mut Context,
782    ) -> JsResult<JsValue> {
783        // 1. Let O be ? ToObject(this value).
784        let obj = this.to_object(context)?;
785        // 2. Let A be ? ArraySpeciesCreate(O, 0).
786        let arr = Self::array_species_create(&obj, 0, context)?;
787        // 3. Let n be 0.
788        let mut n = 0;
789        // 4. Prepend O to items.
790        // 5. For each element E of items, do
791        for item in std::iter::once(&JsValue::new(obj)).chain(args.iter()) {
792            // a. Let spreadable be ? IsConcatSpreadable(E).
793            let spreadable = Self::is_concat_spreadable(item, context)?;
794            // b. If spreadable is true, then
795            if spreadable {
796                // item is guaranteed to be an object since is_concat_spreadable checks it,
797                // so we can call `.unwrap()`
798                let item = item.as_object().js_expect("guaranteed to be an object")?;
799                // i. Let k be 0.
800                // ii. Let len be ? LengthOfArrayLike(E).
801                let len = item.length_of_array_like(context)?;
802                // iii. If n + len > 2^53 - 1, throw a TypeError exception.
803                if n + len > Number::MAX_SAFE_INTEGER as u64 {
804                    return Err(JsNativeError::typ()
805                        .with_message(
806                            "length + number of arguments exceeds the max safe integer limit",
807                        )
808                        .into());
809                }
810                // iv. Repeat, while k < len,
811                for k in 0..len {
812                    // 1. Let P be ! ToString(๐”ฝ(k)).
813                    // 2. Let exists be ? HasProperty(E, P).
814                    // 3. If exists is true, then
815                    // 3.a. Let subElement be ? Get(E, P).
816                    if let Some(sub_element) = item.try_get(k, context)? {
817                        // b. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), subElement).
818                        arr.create_data_property_or_throw(n, sub_element, context)?;
819                    }
820                    // 4. Set n to n + 1.
821                    n += 1;
822                    // 5. Set k to k + 1.
823                }
824            }
825            // c. Else,
826            else {
827                // i. NOTE: E is added as a single item rather than spread.
828                // ii. If n โ‰ฅ 2^53 - 1, throw a TypeError exception.
829                if n >= Number::MAX_SAFE_INTEGER as u64 {
830                    return Err(JsNativeError::typ()
831                        .with_message("length exceeds the max safe integer limit")
832                        .into());
833                }
834                // iii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), E).
835                arr.create_data_property_or_throw(n, item.clone(), context)?;
836                // iv. Set n to n + 1.
837                n += 1;
838            }
839        }
840        // 6. Perform ? Set(A, "length", ๐”ฝ(n), true).
841        Self::set_length(&arr, n, context)?;
842
843        // 7. Return A.
844        Ok(JsValue::new(arr))
845    }
846
847    /// `Array.prototype.push( ...items )`
848    ///
849    /// The arguments are appended to the end of the array, in the order in which
850    /// they appear. The new length of the array is returned as the result of the
851    /// call.
852    ///
853    /// More information:
854    ///  - [ECMAScript reference][spec]
855    ///  - [MDN documentation][mdn]
856    ///
857    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push
858    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
859    pub(crate) fn push(
860        this: &JsValue,
861        args: &[JsValue],
862        context: &mut Context,
863    ) -> JsResult<JsValue> {
864        // 1. Let O be ? ToObject(this value).
865        let o = this.to_object(context)?;
866        // 2. Let len be ? LengthOfArrayLike(O).
867        let mut len = o.length_of_array_like(context)?;
868        // 3. Let argCount be the number of elements in items.
869        let arg_count = args.len() as u64;
870        // 4. If len + argCount > 2^53 - 1, throw a TypeError exception.
871        if len + arg_count > 2u64.pow(53) - 1 {
872            return Err(JsNativeError::typ()
873                .with_message(
874                    "the length + the number of arguments exceed the maximum safe integer limit",
875                )
876                .into());
877        }
878        // 5. For each element E of items, do
879        for element in args.iter().cloned() {
880            // a. Perform ? Set(O, ! ToString(๐”ฝ(len)), E, true).
881            o.set(len, element, true, context)?;
882            // b. Set len to len + 1.
883            len += 1;
884        }
885        // 6. Perform ? Set(O, "length", ๐”ฝ(len), true).
886        Self::set_length(&o, len, context)?;
887
888        // 7. Return ๐”ฝ(len).
889        Ok(len.into())
890    }
891
892    /// `Array.prototype.pop()`
893    ///
894    /// The last element of the array is removed from the array and returned.
895    ///
896    /// More information:
897    ///  - [ECMAScript reference][spec]
898    ///  - [MDN documentation][mdn]
899    ///
900    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
901    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
902    pub(crate) fn pop(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
903        // 1. Let O be ? ToObject(this value).
904        let o = this.to_object(context)?;
905        // 2. Let len be ? LengthOfArrayLike(O).
906        let len = o.length_of_array_like(context)?;
907        // 3. If len = 0, then
908        if len == 0 {
909            // a. Perform ? Set(O, "length", +0๐”ฝ, true).
910            Self::set_length(&o, 0, context)?;
911
912            // b. Return undefined.
913            Ok(JsValue::undefined())
914        // 4. Else,
915        } else {
916            // a. Assert: len > 0.
917            // b. Let newLen be ๐”ฝ(len - 1).
918            let new_len = len - 1;
919            // c. Let index be ! ToString(newLen).
920            let index = new_len;
921            // d. Let element be ? Get(O, index).
922            let element = o.get(index, context)?;
923            // e. Perform ? DeletePropertyOrThrow(O, index).
924            o.delete_property_or_throw(index, context)?;
925            // f. Perform ? Set(O, "length", newLen, true).
926            Self::set_length(&o, new_len, context)?;
927            // g. Return element.
928            Ok(element)
929        }
930    }
931
932    /// `Array.prototype.forEach( callbackFn [ , thisArg ] )`
933    ///
934    /// This method executes the provided callback function for each element in the array.
935    ///
936    /// More information:
937    ///  - [ECMAScript reference][spec]
938    ///  - [MDN documentation][mdn]
939    ///
940    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach
941    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
942    pub(crate) fn for_each(
943        this: &JsValue,
944        args: &[JsValue],
945        context: &mut Context,
946    ) -> JsResult<JsValue> {
947        // 1. Let O be ? ToObject(this value).
948        let o = this.to_object(context)?;
949        // 2. Let len be ? LengthOfArrayLike(O).
950        let len = o.length_of_array_like(context)?;
951        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
952        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
953            JsNativeError::typ().with_message("Array.prototype.forEach: invalid callback function")
954        })?;
955        // 4. Let k be 0.
956        // 5. Repeat, while k < len,
957        for k in 0..len {
958            // a. Let Pk be ! ToString(๐”ฝ(k)).
959            let pk = k;
960            // b. Let kPresent be ? HasProperty(O, Pk).
961            // c. If kPresent is true, then
962            // c.i. Let kValue be ? Get(O, Pk).
963            if let Some(k_value) = o.try_get(pk, context)? {
964                // ii. Perform ? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป).
965                let this_arg = args.get_or_undefined(1);
966                callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
967            }
968            // d. Set k to k + 1.
969        }
970        // 6. Return undefined.
971        Ok(JsValue::undefined())
972    }
973
974    /// `Array.prototype.join( separator )`
975    ///
976    /// The elements of the array are converted to Strings, and these Strings are
977    /// then concatenated, separated by occurrences of the separator. If no
978    /// separator is provided, a single comma is used as the separator.
979    ///
980    /// More information:
981    ///  - [ECMAScript reference][spec]
982    ///  - [MDN documentation][mdn]
983    ///
984    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join
985    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
986    pub(crate) fn join(
987        this: &JsValue,
988        args: &[JsValue],
989        context: &mut Context,
990    ) -> JsResult<JsValue> {
991        // 1. Let O be ? ToObject(this value).
992        let o = this.to_object(context)?;
993        // 2. Let len be ? LengthOfArrayLike(O).
994        let len = o.length_of_array_like(context)?;
995        // 3. If separator is undefined, let sep be the single-element String ",".
996        // 4. Else, let sep be ? ToString(separator).
997        let separator = args.get_or_undefined(0);
998        let separator = if separator.is_undefined() {
999            js_string!(",")
1000        } else {
1001            separator.to_string(context)?
1002        };
1003
1004        // 5. Let R be the empty String.
1005        let mut r = Vec::with_capacity(len as usize + len.saturating_sub(1) as usize);
1006        // 6. Let k be 0.
1007        // 7. Repeat, while k < len,
1008        for k in 0..len {
1009            // a. If k > 0, set R to the string-concatenation of R and sep.
1010            if k > 0 {
1011                r.push(separator.clone());
1012            }
1013            // b. Let element be ? Get(O, ! ToString(๐”ฝ(k))).
1014            let element = o.get(k, context)?;
1015            // c. If element is undefined, null or the array itself, let next be the empty String; otherwise, let next be ? ToString(element).
1016            let next = if element.is_null_or_undefined() || &element == this {
1017                js_string!()
1018            } else {
1019                element.to_string(context)?
1020            };
1021            // d. Set R to the string-concatenation of R and next.
1022            r.push(next.clone());
1023            // e. Set k to k + 1.
1024        }
1025        // 8. Return R.
1026        Ok(js_string!(&r[..]).into())
1027    }
1028
1029    /// `Array.prototype.toString( separator )`
1030    ///
1031    /// The toString function is intentionally generic; it does not require that
1032    /// its this value be an Array object. Therefore it can be transferred to
1033    /// other kinds of objects for use as a method.
1034    ///
1035    /// More information:
1036    ///  - [ECMAScript reference][spec]
1037    ///  - [MDN documentation][mdn]
1038    ///
1039    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring
1040    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
1041    #[allow(clippy::wrong_self_convention)]
1042    pub(crate) fn to_string(
1043        this: &JsValue,
1044        _: &[JsValue],
1045        context: &mut Context,
1046    ) -> JsResult<JsValue> {
1047        // 1. Let array be ? ToObject(this value).
1048        let array = this.to_object(context)?;
1049        // 2. Let func be ? Get(array, "join").
1050        let func = array.get(js_string!("join"), context)?;
1051        // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
1052        // 4. Return ? Call(func, array).
1053        if let Some(func) = func.as_callable() {
1054            func.call(&array.into(), &[], context)
1055        } else {
1056            crate::builtins::object::OrdinaryObject::to_string(&array.into(), &[], context)
1057        }
1058    }
1059
1060    /// `Array.prototype.reverse()`
1061    ///
1062    /// The elements of the array are rearranged so as to reverse their order.
1063    /// The object is returned as the result of the call.
1064    ///
1065    /// More information:
1066    ///  - [ECMAScript reference][spec]
1067    ///  - [MDN documentation][mdn]
1068    ///
1069    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse
1070    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
1071    #[allow(clippy::else_if_without_else)]
1072    pub(crate) fn reverse(
1073        this: &JsValue,
1074        _: &[JsValue],
1075        context: &mut Context,
1076    ) -> JsResult<JsValue> {
1077        // 1. Let O be ? ToObject(this value).
1078        let o = this.to_object(context)?;
1079        // 2. Let len be ? LengthOfArrayLike(O).
1080        let len = o.length_of_array_like(context)?;
1081        // 3. Let middle be floor(len / 2).
1082        let middle = len / 2;
1083        // 4. Let lower be 0.
1084        let mut lower = 0;
1085        // 5. Repeat, while lower โ‰  middle,
1086        while lower != middle {
1087            // a. Let upper be len - lower - 1.
1088            let upper = len - lower - 1;
1089            // Skipped: b. Let upperP be ! ToString(๐”ฝ(upper)).
1090            // Skipped: c. Let lowerP be ! ToString(๐”ฝ(lower)).
1091            // d. Let lowerExists be ? HasProperty(O, lowerP).
1092            // e. If lowerExists is true, then
1093            // e.i. Let lowerValue be ? Get(O, lowerP).
1094            let lower_value = o.try_get(lower, context)?;
1095            // f. Let upperExists be ? HasProperty(O, upperP).
1096            // g. If upperExists is true, then
1097            // g.i. Let upperValue be ? Get(O, upperP).
1098            let upper_value = o.try_get(upper, context)?;
1099            match (lower_value, upper_value) {
1100                // h. If lowerExists is true and upperExists is true, then
1101                (Some(lower_value), Some(upper_value)) => {
1102                    // i. Perform ? Set(O, lowerP, upperValue, true).
1103                    o.set(lower, upper_value, true, context)?;
1104                    // ii. Perform ? Set(O, upperP, lowerValue, true).
1105                    o.set(upper, lower_value, true, context)?;
1106                }
1107                // i. Else if lowerExists is false and upperExists is true, then
1108                (None, Some(upper_value)) => {
1109                    // i. Perform ? Set(O, lowerP, upperValue, true).
1110                    o.set(lower, upper_value, true, context)?;
1111                    // ii. Perform ? DeletePropertyOrThrow(O, upperP).
1112                    o.delete_property_or_throw(upper, context)?;
1113                }
1114                // j. Else if lowerExists is true and upperExists is false, then
1115                (Some(lower_value), None) => {
1116                    // i. Perform ? DeletePropertyOrThrow(O, lowerP).
1117                    o.delete_property_or_throw(lower, context)?;
1118                    // ii. Perform ? Set(O, upperP, lowerValue, true).
1119                    o.set(upper, lower_value, true, context)?;
1120                }
1121                // k. Else,
1122                (None, None) => {
1123                    // i. Assert: lowerExists and upperExists are both false.
1124                    // ii. No action is required.
1125                }
1126            }
1127
1128            // l. Set lower to lower + 1.
1129            lower += 1;
1130        }
1131        // 6. Return O.
1132        Ok(o.into())
1133    }
1134
1135    /// [`Array.prototype.toReversed()`][spec]
1136    ///
1137    /// Reverses this array, returning the result into a copy of the array.
1138    ///
1139    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.toreversed
1140    pub(crate) fn to_reversed(
1141        this: &JsValue,
1142        _: &[JsValue],
1143        context: &mut Context,
1144    ) -> JsResult<JsValue> {
1145        // 1. Let O be ? ToObject(this value).
1146        let o = this.to_object(context)?;
1147
1148        // 2. Let len be ? LengthOfArrayLike(O).
1149        let len = o.length_of_array_like(context)?;
1150
1151        // 3. Let A be ? ArrayCreate(len).
1152        let a = Array::array_create(len, None, context)?;
1153
1154        // 4. Let k be 0.
1155        // 5. Repeat, while k < len,
1156        for i in 0..len {
1157            // a. Let from be ! ToString(๐”ฝ(len - k - 1)).
1158            let from = len - i - 1;
1159
1160            // b. Let Pk be ! ToString(๐”ฝ(k)).
1161            // c. Let fromValue be ? Get(O, from).
1162            let from_value = o.get(from, context)?;
1163
1164            // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue).
1165            a.create_data_property_or_throw(i, from_value, context)
1166                .js_expect("cannot fail per the spec")?;
1167
1168            // e. Set k to k + 1.
1169        }
1170
1171        // 6. Return A.
1172        Ok(a.into())
1173    }
1174
1175    /// `Array.prototype.shift()`
1176    ///
1177    /// The first element of the array is removed from the array and returned.
1178    ///
1179    /// More information:
1180    ///  - [ECMAScript reference][spec]
1181    ///  - [MDN documentation][mdn]
1182    ///
1183    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
1184    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
1185    pub(crate) fn shift(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1186        // 1. Let O be ? ToObject(this value).
1187        let o = this.to_object(context)?;
1188        // 2. Let len be ? LengthOfArrayLike(O).
1189        let len = o.length_of_array_like(context)?;
1190        // 3. If len = 0, then
1191        if len == 0 {
1192            // a. Perform ? Set(O, "length", +0๐”ฝ, true).
1193            Self::set_length(&o, 0, context)?;
1194
1195            // b. Return undefined.
1196            return Ok(JsValue::undefined());
1197        }
1198
1199        // Small optimization for arrays using dense properties
1200        // TODO: this optimization could be generalized to many other objects with
1201        // slot-based dense property maps.
1202        if o.is_array() {
1203            let mut o_borrow = o.borrow_mut();
1204            if let IndexedProperties::DenseI32(dense) =
1205                &mut o_borrow.properties_mut().indexed_properties
1206                && len <= dense.len() as u64
1207            {
1208                let v = dense.remove(0);
1209                drop(o_borrow);
1210                Self::set_length(&o, len - 1, context)?;
1211                return Ok(v.into());
1212            }
1213            if let IndexedProperties::DenseF64(dense) =
1214                &mut o_borrow.properties_mut().indexed_properties
1215                && len <= dense.len() as u64
1216            {
1217                let v = dense.remove(0);
1218                drop(o_borrow);
1219                Self::set_length(&o, len - 1, context)?;
1220                return Ok(v.into());
1221            }
1222            if let Some(dense) = o_borrow.properties_mut().dense_indexed_properties_mut()
1223                && len <= dense.len() as u64
1224            {
1225                let v = dense.remove(0);
1226                drop(o_borrow);
1227                Self::set_length(&o, len - 1, context)?;
1228                return Ok(v);
1229            }
1230        }
1231
1232        // 4. Let first be ? Get(O, "0").
1233        let first = o.get(0, context)?;
1234        // 5. Let k be 1.
1235        // 6. Repeat, while k < len,
1236        for k in 1..len {
1237            // a. Let from be ! ToString(๐”ฝ(k)).
1238            let from = k;
1239            // b. Let to be ! ToString(๐”ฝ(k - 1)).
1240            let to = k - 1;
1241            // c. Let fromPresent be ? HasProperty(O, from).
1242            // d. If fromPresent is true, then
1243            // d.i. Let fromVal be ? Get(O, from).
1244            if let Some(from_val) = o.try_get(from, context)? {
1245                // ii. Perform ? Set(O, to, fromVal, true).
1246                o.set(to, from_val, true, context)?;
1247            // e. Else,
1248            } else {
1249                // i. Assert: fromPresent is false.
1250                // ii. Perform ? DeletePropertyOrThrow(O, to).
1251                o.delete_property_or_throw(to, context)?;
1252            }
1253            // f. Set k to k + 1.
1254        }
1255        // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(๐”ฝ(len - 1))).
1256        o.delete_property_or_throw(len - 1, context)?;
1257        // 8. Perform ? Set(O, "length", ๐”ฝ(len - 1), true).
1258        Self::set_length(&o, len - 1, context)?;
1259        // 9. Return first.
1260        Ok(first)
1261    }
1262
1263    /// `Array.prototype.unshift( ...items )`
1264    ///
1265    /// The arguments are prepended to the start of the array, such that their order
1266    /// within the array is the same as the order in which they appear in the
1267    /// argument list.
1268    ///
1269    /// More information:
1270    ///  - [ECMAScript reference][spec]
1271    ///  - [MDN documentation][mdn]
1272    ///
1273    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
1274    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
1275    pub(crate) fn unshift(
1276        this: &JsValue,
1277        args: &[JsValue],
1278        context: &mut Context,
1279    ) -> JsResult<JsValue> {
1280        // 1. Let O be ? ToObject(this value).
1281        let o = this.to_object(context)?;
1282        // 2. Let len be ? LengthOfArrayLike(O).
1283        let len = o.length_of_array_like(context)?;
1284        // 3. Let argCount be the number of elements in items.
1285        let arg_count = args.len() as u64;
1286        // 4. If argCount > 0, then
1287        if arg_count > 0 {
1288            // a. If len + argCount > 2^53 - 1, throw a TypeError exception.
1289            if len + arg_count > 2u64.pow(53) - 1 {
1290                return Err(JsNativeError::typ()
1291                    .with_message("length + number of arguments exceeds the max safe integer limit")
1292                    .into());
1293            }
1294            // b. Let k be len.
1295            let mut k = len;
1296            // c. Repeat, while k > 0,
1297            while k > 0 {
1298                // i. Let from be ! ToString(๐”ฝ(k - 1)).
1299                let from = k - 1;
1300                // ii. Let to be ! ToString(๐”ฝ(k + argCount - 1)).
1301                let to = k + arg_count - 1;
1302                // iii. Let fromPresent be ? HasProperty(O, from).
1303                // iv. If fromPresent is true, then
1304                // iv.1. Let fromValue be ? Get(O, from).
1305                if let Some(from_value) = o.try_get(from, context)? {
1306                    // 2. Perform ? Set(O, to, fromValue, true).
1307                    o.set(to, from_value, true, context)?;
1308                // v. Else,
1309                } else {
1310                    // 1. Assert: fromPresent is false.
1311                    // 2. Perform ? DeletePropertyOrThrow(O, to).
1312                    o.delete_property_or_throw(to, context)?;
1313                }
1314                // vi. Set k to k - 1.
1315                k -= 1;
1316            }
1317            // d. Let j be +0๐”ฝ.
1318            // e. For each element E of items, do
1319            for (j, e) in args.iter().enumerate() {
1320                // i. Perform ? Set(O, ! ToString(j), E, true).
1321                o.set(j, e.clone(), true, context)?;
1322                // ii. Set j to j + 1๐”ฝ.
1323            }
1324        }
1325        // 5. Perform ? Set(O, "length", ๐”ฝ(len + argCount), true).
1326        Self::set_length(&o, len + arg_count, context)?;
1327
1328        // 6. Return ๐”ฝ(len + argCount).
1329        Ok((len + arg_count).into())
1330    }
1331
1332    /// `Array.prototype.every( callback, [ thisArg ] )`
1333    ///
1334    /// The every method executes the provided callback function once for each
1335    /// element present in the array until it finds the one where callback returns
1336    /// a falsy value. It returns `false` if it finds such element, otherwise it
1337    /// returns `true`.
1338    ///
1339    /// More information:
1340    ///  - [ECMAScript reference][spec]
1341    ///  - [MDN documentation][mdn]
1342    ///
1343    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every
1344    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
1345    pub(crate) fn every(
1346        this: &JsValue,
1347        args: &[JsValue],
1348        context: &mut Context,
1349    ) -> JsResult<JsValue> {
1350        // 1. Let O be ? ToObject(this value).
1351        let o = this.to_object(context)?;
1352        // 2. Let len be ? LengthOfArrayLike(O).
1353        let len = o.length_of_array_like(context)?;
1354        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1355        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
1356            JsNativeError::typ().with_message("Array.prototype.every: callback is not callable")
1357        })?;
1358
1359        let this_arg = args.get_or_undefined(1);
1360
1361        // 4. Let k be 0.
1362        // 5. Repeat, while k < len,
1363        for k in 0..len {
1364            // a. Let Pk be ! ToString(๐”ฝ(k)).
1365            // b. Let kPresent be ? HasProperty(O, Pk).
1366            // c. If kPresent is true, then
1367            // c.i. Let kValue be ? Get(O, Pk).
1368            if let Some(k_value) = o.try_get(k, context)? {
1369                // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
1370                let test_result = callback
1371                    .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
1372                    .to_boolean();
1373                // iii. If testResult is false, return false.
1374                if !test_result {
1375                    return Ok(JsValue::new(false));
1376                }
1377            }
1378            // d. Set k to k + 1.
1379        }
1380        // 6. Return true.
1381        Ok(JsValue::new(true))
1382    }
1383
1384    /// `Array.prototype.map( callback, [ thisArg ] )`
1385    ///
1386    /// For each element in the array the callback function is called, and a new
1387    /// array is constructed from the return values of these calls.
1388    ///
1389    /// More information:
1390    ///  - [ECMAScript reference][spec]
1391    ///  - [MDN documentation][mdn]
1392    ///
1393    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map
1394    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
1395    pub(crate) fn map(
1396        this: &JsValue,
1397        args: &[JsValue],
1398        context: &mut Context,
1399    ) -> JsResult<JsValue> {
1400        // 1. Let O be ? ToObject(this value).
1401        let o = this.to_object(context)?;
1402        // 2. Let len be ? LengthOfArrayLike(O).
1403        let len = o.length_of_array_like(context)?;
1404        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1405        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
1406            JsNativeError::typ().with_message("Array.prototype.map: Callbackfn is not callable")
1407        })?;
1408
1409        // 4. Let A be ? ArraySpeciesCreate(O, len).
1410        let a = Self::array_species_create(&o, len, context)?;
1411
1412        let this_arg = args.get_or_undefined(1);
1413
1414        // 5. Let k be 0.
1415        // 6. Repeat, while k < len,
1416        for k in 0..len {
1417            // a. Let Pk be ! ToString(๐”ฝ(k)).
1418            // b. Let k_present be ? HasProperty(O, Pk).
1419            // c. If k_present is true, then
1420            // c.i. Let kValue be ? Get(O, Pk).
1421            if let Some(k_value) = o.try_get(k, context)? {
1422                // ii. Let mappedValue be ? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป).
1423                let mapped_value =
1424                    callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
1425                // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
1426                a.create_data_property_or_throw(k, mapped_value, context)?;
1427            }
1428            // d. Set k to k + 1.
1429        }
1430        // 7. Return A.
1431        Ok(a.into())
1432    }
1433
1434    /// `Array.prototype.indexOf( searchElement[, fromIndex ] )`
1435    ///
1436    /// More information:
1437    ///  - [ECMAScript reference][spec]
1438    ///  - [MDN documentation][mdn]
1439    ///
1440    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof
1441    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
1442    pub(crate) fn index_of(
1443        this: &JsValue,
1444        args: &[JsValue],
1445        context: &mut Context,
1446    ) -> JsResult<JsValue> {
1447        // 1. Let O be ? ToObject(this value).
1448        let o = this.to_object(context)?;
1449
1450        // 2. Let len be ? LengthOfArrayLike(O).
1451        let len = o.length_of_array_like(context)? as i64;
1452
1453        // 3. If len is 0, return -1๐”ฝ.
1454        if len == 0 {
1455            return Ok(JsValue::new(-1));
1456        }
1457
1458        // 4. Let n be ? ToIntegerOrInfinity(fromIndex).
1459        let n = args
1460            .get(1)
1461            .cloned()
1462            .unwrap_or_default()
1463            .to_integer_or_infinity(context)?;
1464        // 5. Assert: If fromIndex is undefined, then n is 0.
1465        let n = match n {
1466            // 6. If n is +โˆž, return -1๐”ฝ.
1467            IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(-1)),
1468            // 7. Else if n is -โˆž, set n to 0.
1469            IntegerOrInfinity::NegativeInfinity => 0,
1470            IntegerOrInfinity::Integer(value) => value,
1471        };
1472
1473        // 8. If n โ‰ฅ 0, then
1474        let mut k;
1475        if n >= 0 {
1476            // a. Let k be n.
1477            k = n;
1478        // 9. Else,
1479        } else {
1480            // a. Let k be len + n.
1481            k = len + n;
1482            // b. If k < 0, set k to 0.
1483            if k < 0 {
1484                k = 0;
1485            }
1486        }
1487
1488        let search_element = args.get_or_undefined(0);
1489
1490        // 10. Repeat, while k < len,
1491        while k < len {
1492            // a. Let kPresent be ? HasProperty(O, ! ToString(๐”ฝ(k))).
1493            // b. If kPresent is true, then
1494            // b.i. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
1495            if let Some(element_k) = o.try_get(k, context)? {
1496                // ii. Let same be IsStrictlyEqual(searchElement, elementK).
1497                // iii. If same is true, return ๐”ฝ(k).
1498                if search_element.strict_equals(&element_k) {
1499                    return Ok(JsValue::new(k));
1500                }
1501            }
1502            // c. Set k to k + 1.
1503            k += 1;
1504        }
1505        // 11. Return -1๐”ฝ.
1506        Ok(JsValue::new(-1))
1507    }
1508
1509    /// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )`
1510    ///
1511    ///
1512    /// `lastIndexOf` compares searchElement to the elements of the array in descending order
1513    /// using the Strict Equality Comparison algorithm, and if found at one or more indices,
1514    /// returns the largest such index; otherwise, -1 is returned.
1515    ///
1516    /// The optional second argument fromIndex defaults to the array's length minus one
1517    /// (i.e. the whole array is searched). If it is greater than or equal to the length of the array,
1518    /// the whole array will be searched. If it is negative, it is used as the offset from the end
1519    /// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned.
1520    ///
1521    /// More information:
1522    ///  - [ECMAScript reference][spec]
1523    ///  - [MDN documentation][mdn]
1524    ///
1525    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof
1526    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
1527    pub(crate) fn last_index_of(
1528        this: &JsValue,
1529        args: &[JsValue],
1530        context: &mut Context,
1531    ) -> JsResult<JsValue> {
1532        // 1. Let O be ? ToObject(this value).
1533        let o = this.to_object(context)?;
1534
1535        // 2. Let len be ? LengthOfArrayLike(O).
1536        let len = o.length_of_array_like(context)? as i64;
1537
1538        // 3. If len is 0, return -1๐”ฝ.
1539        if len == 0 {
1540            return Ok(JsValue::new(-1));
1541        }
1542
1543        // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1.
1544        let n = if let Some(from_index) = args.get(1) {
1545            from_index.to_integer_or_infinity(context)?
1546        } else {
1547            IntegerOrInfinity::Integer(len - 1)
1548        };
1549
1550        let mut k = match n {
1551            // 5. If n is -โˆž, return -1๐”ฝ.
1552            IntegerOrInfinity::NegativeInfinity => return Ok(JsValue::new(-1)),
1553            // 6. If n โ‰ฅ 0, then
1554            //     a. Let k be min(n, len - 1).
1555            IntegerOrInfinity::Integer(n) if n >= 0 => min(n, len - 1),
1556            IntegerOrInfinity::PositiveInfinity => len - 1,
1557            // 7. Else,
1558            //     a. Let k be len + n.
1559            IntegerOrInfinity::Integer(n) => len + n,
1560        };
1561
1562        let search_element = args.get_or_undefined(0);
1563
1564        // 8. Repeat, while k โ‰ฅ 0,
1565        while k >= 0 {
1566            // a. Let kPresent be ? HasProperty(O, ! ToString(๐”ฝ(k))).
1567            // b. If kPresent is true, then
1568            // b.i. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
1569            if let Some(element_k) = o.try_get(k, context)? {
1570                // ii. Let same be IsStrictlyEqual(searchElement, elementK).
1571                // iii. If same is true, return ๐”ฝ(k).
1572                if JsValue::strict_equals(search_element, &element_k) {
1573                    return Ok(JsValue::new(k));
1574                }
1575            }
1576            // c. Set k to k - 1.
1577            k -= 1;
1578        }
1579        // 9. Return -1๐”ฝ.
1580        Ok(JsValue::new(-1))
1581    }
1582
1583    /// `Array.prototype.find( callback, [thisArg] )`
1584    ///
1585    /// The find method executes the callback function once for each index of the array
1586    /// until the callback returns a truthy value. If so, find immediately returns the value
1587    /// of that element. Otherwise, find returns undefined.
1588    ///
1589    /// More information:
1590    ///  - [ECMAScript reference][spec]
1591    ///  - [MDN documentation][mdn]
1592    ///
1593    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find
1594    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
1595    pub(crate) fn find(
1596        this: &JsValue,
1597        args: &[JsValue],
1598        context: &mut Context,
1599    ) -> JsResult<JsValue> {
1600        // 1. Let O be ? ToObject(this value).
1601        let o = this.to_object(context)?;
1602
1603        // 2. Let len be ? LengthOfArrayLike(O).
1604        let len = o.length_of_array_like(context)?;
1605
1606        let predicate = args.get_or_undefined(0);
1607        let this_arg = args.get_or_undefined(1);
1608
1609        // 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
1610        let (_, value) = find_via_predicate(
1611            &o,
1612            len,
1613            Direction::Ascending,
1614            predicate,
1615            this_arg,
1616            context,
1617            "Array.prototype.find",
1618        )?;
1619
1620        // 4. Return findRec.[[Value]].
1621        Ok(value)
1622    }
1623
1624    /// `Array.prototype.findIndex( predicate [ , thisArg ] )`
1625    ///
1626    /// This method executes the provided predicate function for each element of the array.
1627    /// If the predicate function returns `true` for an element, this method returns the index of the element.
1628    /// If all elements return `false`, the value `-1` is returned.
1629    ///
1630    /// More information:
1631    ///  - [ECMAScript reference][spec]
1632    ///  - [MDN documentation][mdn]
1633    ///
1634    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex
1635    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
1636    pub(crate) fn find_index(
1637        this: &JsValue,
1638        args: &[JsValue],
1639        context: &mut Context,
1640    ) -> JsResult<JsValue> {
1641        // 1. Let O be ? ToObject(this value).
1642        let o = this.to_object(context)?;
1643
1644        // 2. Let len be ? LengthOfArrayLike(O).
1645        let len = o.length_of_array_like(context)?;
1646
1647        let predicate = args.get_or_undefined(0);
1648        let this_arg = args.get_or_undefined(1);
1649
1650        // 3. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg).
1651        let (index, _) = find_via_predicate(
1652            &o,
1653            len,
1654            Direction::Ascending,
1655            predicate,
1656            this_arg,
1657            context,
1658            "Array.prototype.findIndex",
1659        )?;
1660
1661        // 4. Return findRec.[[Index]].
1662        Ok(index)
1663    }
1664
1665    /// `Array.prototype.findLast( predicate, [thisArg] )`
1666    ///
1667    /// findLast calls predicate once for each element of the array, in descending order,
1668    /// until it finds one where predicate returns true. If such an element is found, findLast
1669    /// immediately returns that element value. Otherwise, findLast returns undefined.
1670    ///
1671    /// More information:
1672    ///  - [ECMAScript proposal][spec]
1673    ///
1674    /// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlast
1675    pub(crate) fn find_last(
1676        this: &JsValue,
1677        args: &[JsValue],
1678        context: &mut Context,
1679    ) -> JsResult<JsValue> {
1680        // 1. Let O be ? ToObject(this value).
1681        let o = this.to_object(context)?;
1682
1683        // 2. Let len be ? LengthOfArrayLike(O).
1684        let len = o.length_of_array_like(context)?;
1685
1686        let predicate = args.get_or_undefined(0);
1687        let this_arg = args.get_or_undefined(1);
1688
1689        // 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
1690        let (_, value) = find_via_predicate(
1691            &o,
1692            len,
1693            Direction::Descending,
1694            predicate,
1695            this_arg,
1696            context,
1697            "Array.prototype.findLast",
1698        )?;
1699
1700        // 4. Return findRec.[[Value]].
1701        Ok(value)
1702    }
1703
1704    /// `Array.prototype.findLastIndex( predicate [ , thisArg ] )`
1705    ///
1706    /// `findLastIndex` calls predicate once for each element of the array, in descending order,
1707    /// until it finds one where predicate returns true. If such an element is found, `findLastIndex`
1708    /// immediately returns the index of that element value. Otherwise, `findLastIndex` returns -1.
1709    ///
1710    /// More information:
1711    ///  - [ECMAScript proposal][spec]
1712    ///
1713    /// [spec]: https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlastindex
1714    pub(crate) fn find_last_index(
1715        this: &JsValue,
1716        args: &[JsValue],
1717        context: &mut Context,
1718    ) -> JsResult<JsValue> {
1719        // 1. Let O be ? ToObject(this value).
1720        let o = this.to_object(context)?;
1721
1722        // 2. Let len be ? LengthOfArrayLike(O).
1723        let len = o.length_of_array_like(context)?;
1724
1725        let predicate = args.get_or_undefined(0);
1726        let this_arg = args.get_or_undefined(1);
1727
1728        // 3. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg).
1729        let (index, _) = find_via_predicate(
1730            &o,
1731            len,
1732            Direction::Descending,
1733            predicate,
1734            this_arg,
1735            context,
1736            "Array.prototype.findLastIndex",
1737        )?;
1738
1739        // 4. Return findRec.[[Index]].
1740        Ok(index)
1741    }
1742
1743    /// `Array.prototype.flat( [depth] )`
1744    ///
1745    /// This method creates a new array with all sub-array elements concatenated into it
1746    /// recursively up to the specified depth.
1747    ///
1748    /// More information:
1749    ///  - [ECMAScript reference][spec]
1750    ///  - [MDN documentation][mdn]
1751    ///
1752    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flat
1753    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
1754    pub(crate) fn flat(
1755        this: &JsValue,
1756        args: &[JsValue],
1757        context: &mut Context,
1758    ) -> JsResult<JsValue> {
1759        // 1. Let O be ToObject(this value)
1760        let o = this.to_object(context)?;
1761
1762        // 2. Let sourceLen be LengthOfArrayLike(O)
1763        let source_len = o.length_of_array_like(context)?;
1764
1765        // 3. Let depthNum be 1
1766        // 4. If depth is not undefined, then set depthNum to IntegerOrInfinity(depth)
1767        let depth = args.get_or_undefined(0);
1768        let depth_num = if depth.is_undefined() {
1769            1
1770        } else {
1771            // a. Set depthNum to ? ToIntegerOrInfinity(depth).
1772            // b. If depthNum < 0, set depthNum to 0.
1773            match depth.to_integer_or_infinity(context)? {
1774                IntegerOrInfinity::Integer(value) if value >= 0 => value as u64,
1775                IntegerOrInfinity::PositiveInfinity => u64::MAX,
1776                _ => 0,
1777            }
1778        };
1779
1780        // 5. Let A be ArraySpeciesCreate(O, 0)
1781        let a = Self::array_species_create(&o, 0, context)?;
1782
1783        // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum)
1784        Self::flatten_into_array(
1785            &a,
1786            &o,
1787            source_len,
1788            0,
1789            depth_num,
1790            None,
1791            &JsValue::undefined(),
1792            context,
1793        )?;
1794
1795        Ok(a.into())
1796    }
1797
1798    /// `Array.prototype.flatMap( callback, [ thisArg ] )`
1799    ///
1800    /// This method returns a new array formed by applying a given callback function to
1801    /// each element of the array, and then flattening the result by one level. It is
1802    /// identical to a `map()` followed by a `flat()` of depth 1, but slightly more
1803    /// efficient than calling those two methods separately.
1804    ///
1805    /// More information:
1806    ///  - [ECMAScript reference][spec]
1807    ///  - [MDN documentation][mdn]
1808    ///
1809    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flatMap
1810    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
1811    pub(crate) fn flat_map(
1812        this: &JsValue,
1813        args: &[JsValue],
1814        context: &mut Context,
1815    ) -> JsResult<JsValue> {
1816        // 1. Let O be ToObject(this value)
1817        let o = this.to_object(context)?;
1818
1819        // 2. Let sourceLen be LengthOfArrayLike(O)
1820        let source_len = o.length_of_array_like(context)?;
1821
1822        // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
1823        let mapper_function = args.get_or_undefined(0).as_callable().ok_or_else(|| {
1824            JsNativeError::typ().with_message("flatMap mapper function is not callable")
1825        })?;
1826
1827        // 4. Let A be ? ArraySpeciesCreate(O, 0).
1828        let a = Self::array_species_create(&o, 0, context)?;
1829
1830        // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg).
1831        Self::flatten_into_array(
1832            &a,
1833            &o,
1834            source_len,
1835            0,
1836            1,
1837            Some(&mapper_function),
1838            args.get_or_undefined(1),
1839            context,
1840        )?;
1841
1842        // 6. Return A
1843        Ok(a.into())
1844    }
1845
1846    /// Abstract method `FlattenIntoArray`.
1847    ///
1848    /// More information:
1849    ///  - [ECMAScript reference][spec]
1850    ///
1851    /// [spec]: https://tc39.es/ecma262/#sec-flattenintoarray
1852    #[allow(clippy::too_many_arguments)]
1853    fn flatten_into_array(
1854        target: &JsObject,
1855        source: &JsObject,
1856        source_len: u64,
1857        start: u64,
1858        depth: u64,
1859        mapper_function: Option<&JsObject>,
1860        this_arg: &JsValue,
1861        context: &mut Context,
1862    ) -> JsResult<u64> {
1863        // 1. Assert target is Object
1864        // 2. Assert source is Object
1865
1866        // 3. Assert if mapper_function is present, then:
1867        // - IsCallable(mapper_function) is true
1868        // - thisArg is present
1869        // - depth is 1
1870
1871        // 4. Let targetIndex be start
1872        let mut target_index = start;
1873
1874        // 5. Let sourceIndex be 0
1875        let mut source_index = 0;
1876
1877        // 6. Repeat, while R(sourceIndex) < sourceLen
1878        while source_index < source_len {
1879            // a. Let P be ToString(sourceIndex)
1880            let p = source_index;
1881
1882            // b. Let exists be ? HasProperty(source, P).
1883            // c. If exists is true, then
1884            // c.i. Let element be Get(source, P)
1885            if let Some(mut element) = source.try_get(p, context)? {
1886                // ii. If mapperFunction is present, then
1887                if let Some(mapper_function) = mapper_function {
1888                    // 1. Set element to ? Call(mapperFunction, thisArg, <<element, sourceIndex, source>>)
1889                    element = mapper_function.call(
1890                        this_arg,
1891                        &[element, source_index.into(), source.clone().into()],
1892                        context,
1893                    )?;
1894                }
1895
1896                // iii. Let shouldFlatten be false
1897                // iv. If depth > 0, then
1898                let should_flatten = if depth > 0 {
1899                    // 1. Set shouldFlatten to ? IsArray(element).
1900                    element.is_array()?
1901                } else {
1902                    false
1903                };
1904
1905                // v. If shouldFlatten is true
1906                if should_flatten {
1907                    // For `should_flatten` to be true, element must be an object.
1908                    let element = element.as_object().js_expect("must be an object")?;
1909
1910                    // 1. If depth is +Infinity let newDepth be +Infinity
1911                    let new_depth = if depth == u64::MAX {
1912                        u64::MAX
1913                    // 2. Else, let newDepth be depth - 1
1914                    } else {
1915                        depth - 1
1916                    };
1917
1918                    // 3. Let elementLen be ? LengthOfArrayLike(element)
1919                    let element_len = element.length_of_array_like(context)?;
1920
1921                    // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
1922                    target_index = Self::flatten_into_array(
1923                        target,
1924                        &element,
1925                        element_len,
1926                        target_index,
1927                        new_depth,
1928                        None,
1929                        &JsValue::undefined(),
1930                        context,
1931                    )?;
1932
1933                // vi. Else
1934                } else {
1935                    // 1. If targetIndex >= 2^53 - 1, throw a TypeError exception
1936                    if target_index >= Number::MAX_SAFE_INTEGER as u64 {
1937                        return Err(JsNativeError::typ()
1938                            .with_message("Target index exceeded max safe integer value")
1939                            .into());
1940                    }
1941
1942                    // 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element)
1943                    target.create_data_property_or_throw(target_index, element, context)?;
1944
1945                    // 3. Set targetIndex to targetIndex + 1
1946                    target_index += 1;
1947                }
1948            }
1949            // d. Set sourceIndex to sourceIndex + 1
1950            source_index += 1;
1951        }
1952
1953        // 7. Return targetIndex
1954        Ok(target_index)
1955    }
1956
1957    /// `Array.prototype.fill( value[, start[, end]] )`
1958    ///
1959    /// The method fills (modifies) all the elements of an array from start index (default 0)
1960    /// to an end index (default array length) with a static value. It returns the modified array.
1961    ///
1962    /// More information:
1963    ///  - [ECMAScript reference][spec]
1964    ///  - [MDN documentation][mdn]
1965    ///
1966    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
1967    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
1968    pub(crate) fn fill(
1969        this: &JsValue,
1970        args: &[JsValue],
1971        context: &mut Context,
1972    ) -> JsResult<JsValue> {
1973        // 1. Let O be ? ToObject(this value).
1974        let o = this.to_object(context)?;
1975
1976        // 2. Let len be ? LengthOfArrayLike(O).
1977        let len = o.length_of_array_like(context)?;
1978
1979        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
1980        // 4. If relativeStart is -โˆž, let k be 0.
1981        // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
1982        // 6. Else, let k be min(relativeStart, len).
1983        let mut k = Self::get_relative_start(context, args.get_or_undefined(1), len)?;
1984
1985        // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
1986        // 8. If relativeEnd is -โˆž, let final be 0.
1987        // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
1988        // 10. Else, let final be min(relativeEnd, len).
1989        let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)?;
1990
1991        let value = args.get_or_undefined(0);
1992
1993        // 11. Repeat, while k < final,
1994        while k < final_ {
1995            // a. Let Pk be ! ToString(๐”ฝ(k)).
1996            let pk = k;
1997            // b. Perform ? Set(O, Pk, value, true).
1998            o.set(pk, value.clone(), true, context)?;
1999            // c. Set k to k + 1.
2000            k += 1;
2001        }
2002        // 12. Return O.
2003        Ok(o.into())
2004    }
2005
2006    /// `Array.prototype.includes( valueToFind [, fromIndex] )`
2007    ///
2008    /// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate.
2009    ///
2010    /// More information:
2011    ///  - [ECMAScript reference][spec]
2012    ///  - [MDN documentation][mdn]
2013    ///
2014    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes
2015    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
2016    pub(crate) fn includes_value(
2017        this: &JsValue,
2018        args: &[JsValue],
2019        context: &mut Context,
2020    ) -> JsResult<JsValue> {
2021        // 1. Let O be ? ToObject(this value).
2022        let o = this.to_object(context)?;
2023
2024        // 2. Let len be ? LengthOfArrayLike(O).
2025        let len = o.length_of_array_like(context)? as i64;
2026
2027        // 3. If len is 0, return false.
2028        if len == 0 {
2029            return Ok(JsValue::new(false));
2030        }
2031
2032        // 4. Let n be ? ToIntegerOrInfinity(fromIndex).
2033        let n = args
2034            .get(1)
2035            .cloned()
2036            .unwrap_or_default()
2037            .to_integer_or_infinity(context)?;
2038        // 5. Assert: If fromIndex is undefined, then n is 0.
2039        // 6. If n is +โˆž, return false.
2040        // 7. Else if n is -โˆž, set n to 0.
2041        let n = match n {
2042            IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(false)),
2043            IntegerOrInfinity::NegativeInfinity => 0,
2044            IntegerOrInfinity::Integer(value) => value,
2045        };
2046
2047        // 8. If n โ‰ฅ 0, then
2048        let mut k;
2049        if n >= 0 {
2050            // a. Let k be n.
2051            k = n;
2052        // 9. Else,
2053        } else {
2054            // a. Let k be len + n.
2055            k = len + n;
2056            // b. If k < 0, set k to 0.
2057            if k < 0 {
2058                k = 0;
2059            }
2060        }
2061
2062        let search_element = args.get_or_undefined(0);
2063
2064        // 10. Repeat, while k < len,
2065        while k < len {
2066            // a. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
2067            let element_k = o.get(k, context)?;
2068            // b. If SameValueZero(searchElement, elementK) is true, return true.
2069            if JsValue::same_value_zero(search_element, &element_k) {
2070                return Ok(JsValue::new(true));
2071            }
2072            // c. Set k to k + 1.
2073            k += 1;
2074        }
2075        // 11. Return false.
2076        Ok(JsValue::new(false))
2077    }
2078
2079    /// `Array.prototype.slice( [begin[, end]] )`
2080    ///
2081    /// The slice method takes two arguments, start and end, and returns an array containing the
2082    /// elements of the array from element start up to, but not including, element end (or through the
2083    /// end of the array if end is undefined). If start is negative, it is treated as length + start
2084    /// where length is the length of the array. If end is negative, it is treated as length + end where
2085    /// length is the length of the array.
2086    ///
2087    /// More information:
2088    ///  - [ECMAScript reference][spec]
2089    ///  - [MDN documentation][mdn]
2090    ///
2091    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice
2092    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
2093    pub(crate) fn slice(
2094        this: &JsValue,
2095        args: &[JsValue],
2096        context: &mut Context,
2097    ) -> JsResult<JsValue> {
2098        // 1. Let O be ? ToObject(this value).
2099        let o = this.to_object(context)?;
2100
2101        // 2. Let len be ? LengthOfArrayLike(O).
2102        let len = o.length_of_array_like(context)?;
2103
2104        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
2105        // 4. If relativeStart is -โˆž, let k be 0.
2106        // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
2107        // 6. Else, let k be min(relativeStart, len).
2108        let mut k = Self::get_relative_start(context, args.get_or_undefined(0), len)?;
2109
2110        // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
2111        // 8. If relativeEnd is -โˆž, let final be 0.
2112        // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
2113        // 10. Else, let final be min(relativeEnd, len).
2114        let final_ = Self::get_relative_end(context, args.get_or_undefined(1), len)?;
2115
2116        // 11. Let count be max(final - k, 0).
2117        let count = final_.saturating_sub(k);
2118
2119        // 12. Let A be ? ArraySpeciesCreate(O, count).
2120        let a = Self::array_species_create(&o, count, context)?;
2121
2122        // 13. Let n be 0.
2123        let mut n: u64 = 0;
2124        // 14. Repeat, while k < final,
2125        while k < final_ {
2126            // a. Let Pk be ! ToString(๐”ฝ(k)).
2127            let pk = k;
2128            // b. Let kPresent be ? HasProperty(O, Pk).
2129            // c. If kPresent is true, then
2130            // c.i. Let kValue be ? Get(O, Pk).
2131            if let Some(k_value) = o.try_get(pk, context)? {
2132                // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), kValue).
2133                a.create_data_property_or_throw(n, k_value, context)?;
2134            }
2135            // d. Set k to k + 1.
2136            k += 1;
2137            // e. Set n to n + 1.
2138            n += 1;
2139        }
2140
2141        // 15. Perform ? Set(A, "length", ๐”ฝ(n), true).
2142        Self::set_length(&a, n, context)?;
2143
2144        // 16. Return A.
2145        Ok(a.into())
2146    }
2147
2148    /// [`Array.prototype.toLocaleString ( [ locales [ , options ] ] )`][spec].
2149    ///
2150    /// Returns a string representing the elements of the array. The elements are converted to
2151    /// strings using their `toLocaleString` methods and these strings are separated by a
2152    /// locale-specific string (such as a comma ",").
2153    ///
2154    /// More information:
2155    ///  - [MDN documentation][mdn]
2156    ///
2157    /// [spec]: https://tc39.es/ecma402/#sup-array.prototype.tolocalestring
2158    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toLocaleString
2159    pub(crate) fn to_locale_string(
2160        this: &JsValue,
2161        args: &[JsValue],
2162        context: &mut Context,
2163    ) -> JsResult<JsValue> {
2164        // 1. Let array be ?ย ToObject(this value).
2165        let array = this.to_object(context)?;
2166        // 2. Let len be ?ย ToLength(? Get(array, "length")).
2167        let len = array.length_of_array_like(context)?;
2168
2169        let locales = args.get_or_undefined(0);
2170        let options = args.get_or_undefined(1);
2171
2172        // 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", ").
2173        let separator = {
2174            #[cfg(feature = "intl")]
2175            {
2176                use crate::builtins::intl::locale::default_locale;
2177                use icu_list::{
2178                    ListFormatter, ListFormatterPreferences, options::ListFormatterOptions,
2179                };
2180
2181                let locale = default_locale(context.intl_provider().locale_canonicalizer()?);
2182                let preferences = ListFormatterPreferences::from(&locale);
2183                let formatter = ListFormatter::try_new_unit_with_buffer_provider(
2184                    context.intl_provider().erased_provider(),
2185                    preferences,
2186                    ListFormatterOptions::default(),
2187                )
2188                .map_err(|e| JsNativeError::typ().with_message(e.to_string()))?;
2189
2190                // Ask ICU for the list pattern literal by formatting two empty elements.
2191                // For many locales this yields ", ", but it may differ.
2192                js_string!(
2193                    formatter.format_to_string(std::iter::once("").chain(std::iter::once("")))
2194                )
2195            }
2196
2197            #[cfg(not(feature = "intl"))]
2198            {
2199                js_string!(", ")
2200            }
2201        };
2202
2203        // 4. Let R be the empty String.
2204        let mut r = Vec::with_capacity(len as usize + len.saturating_sub(1) as usize);
2205
2206        // 5. Let k be 0.
2207        // 6. Repeat, while k < len,
2208        for k in 0..len {
2209            // a. If k > 0, then
2210            if k > 0 {
2211                // i. Set R to the string-concatenation of R and separator.
2212                r.extend(separator.iter());
2213            }
2214
2215            // b. Let nextElement be ?ย Get(array, !ย ToString(k)).
2216            let next = array.get(k, context)?;
2217
2218            // c. If nextElement is not undefined or null, then
2219            if !next.is_null_or_undefined() {
2220                // i. Let S be ?ย ToString(? Invoke(nextElement, "toLocaleString", ยซ locales, options ยป)).
2221                let s = next
2222                    .invoke(
2223                        js_string!("toLocaleString"),
2224                        &[locales.clone(), options.clone()],
2225                        context,
2226                    )?
2227                    .to_string(context)?;
2228
2229                // ii. Set R to the string-concatenation of R and S.
2230                r.extend(s.iter());
2231            }
2232            //     d. Increase k by 1.
2233        }
2234        // 7. Return R.
2235        Ok(js_string!(&r[..]).into())
2236    }
2237
2238    /// Gets the delete count of a splice operation.
2239    fn get_delete_count(
2240        len: u64,
2241        actual_start: u64,
2242        start: Option<&JsValue>,
2243        delete_count: Option<&JsValue>,
2244        context: &mut Context,
2245    ) -> JsResult<u64> {
2246        // 8. If start is not present, then
2247        let actual_delete_count = if start.is_none() {
2248            // a. Let actualDeleteCount be 0.
2249            0
2250        }
2251        // 10. Else,
2252        else if let Some(delete_count) = delete_count {
2253            // a. Let dc be ? ToIntegerOrInfinity(deleteCount).
2254            let dc = delete_count.to_integer_or_infinity(context)?;
2255
2256            // b. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart.
2257            let max = len - actual_start;
2258            match dc {
2259                IntegerOrInfinity::Integer(i) => u64::try_from(i)
2260                    .unwrap_or_default()
2261                    .clamp(0, len - actual_start),
2262                IntegerOrInfinity::PositiveInfinity => max,
2263                IntegerOrInfinity::NegativeInfinity => 0,
2264            }
2265        }
2266        // 9. Else if deleteCount is not present, then
2267        else {
2268            // a. Let actualDeleteCount be len - actualStart.
2269            len - actual_start
2270        };
2271
2272        Ok(actual_delete_count)
2273    }
2274
2275    /// `Array.prototype.splice ( start, [deleteCount[, ...items]] )`
2276    ///
2277    /// Splices an array by following
2278    /// The deleteCount elements of the array starting at integer index start are replaced by the elements of items.
2279    /// An Array object containing the deleted elements (if any) is returned.
2280    pub(crate) fn splice(
2281        this: &JsValue,
2282        args: &[JsValue],
2283        context: &mut Context,
2284    ) -> JsResult<JsValue> {
2285        let start = args.first();
2286        let delete_count = args.get(1);
2287        let items = args.get(2..).unwrap_or_default();
2288
2289        Self::splice_internal(this, start, delete_count, items, context)
2290    }
2291
2292    pub(crate) fn splice_internal(
2293        this: &JsValue,
2294        start: Option<&JsValue>,
2295        delete_count: Option<&JsValue>,
2296        items: &[JsValue],
2297        context: &mut Context,
2298    ) -> JsResult<JsValue> {
2299        // 1. Let O be ? ToObject(this value).
2300        let o = this.to_object(context)?;
2301        // 2. Let len be ? LengthOfArrayLike(O).
2302        let len = o.length_of_array_like(context)?;
2303
2304        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
2305        // 4. If relativeStart = -โˆž, let actualStart be 0.
2306        // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
2307        // 6. Else, let actualStart be min(relativeStart, len).
2308        let actual_start =
2309            Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?;
2310
2311        // 7. Let itemCount be the number of elements in items.
2312        let item_count = items.len() as u64;
2313
2314        let actual_delete_count =
2315            Self::get_delete_count(len, actual_start, start, delete_count, context)?;
2316
2317        // If len + itemCount - actualDeleteCount > 2**53 - 1, throw a TypeError exception.
2318        if len + item_count - actual_delete_count > Number::MAX_SAFE_INTEGER as u64 {
2319            return Err(JsNativeError::typ()
2320                .with_message("Target splice exceeded max safe integer value")
2321                .into());
2322        }
2323
2324        // 12. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
2325        let arr = Self::array_species_create(&o, actual_delete_count, context)?;
2326
2327        // 13. Let k be 0.
2328        // 14. Repeat, while k < actualDeleteCount,
2329        for k in 0..actual_delete_count {
2330            // a. Let from be ! ToString(๐”ฝ(actualStart + k)).
2331            // b. If ? HasProperty(O, from) is true, then
2332            // b.i. Let fromValue be ? Get(O, from).
2333            if let Some(from_value) = o.try_get(actual_start + k, context)? {
2334                // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(k)), fromValue).
2335                arr.create_data_property_or_throw(k, from_value, context)?;
2336            }
2337            // c. Set k to k + 1.
2338        }
2339
2340        // 15. Perform ? Set(A, "length", ๐”ฝ(actualDeleteCount), true).
2341        Self::set_length(&arr, actual_delete_count, context)?;
2342
2343        let item_count = items.len() as u64;
2344
2345        match item_count.cmp(&actual_delete_count) {
2346            Ordering::Equal => {}
2347            // 16. If itemCount < actualDeleteCount, then
2348            Ordering::Less => {
2349                // a. Set k to actualStart.
2350                // b. Repeat, while k < (len - actualDeleteCount),
2351                for k in actual_start..(len - actual_delete_count) {
2352                    // i. Let from be ! ToString(๐”ฝ(k + actualDeleteCount)).
2353                    let from = k + actual_delete_count;
2354
2355                    // ii. Let to be ! ToString(๐”ฝ(k + itemCount)).
2356                    let to = k + item_count;
2357
2358                    // iii. If ? HasProperty(O, from) is true, then
2359                    // iii.1. Let fromValue be ? Get(O, from).
2360                    if let Some(from_value) = o.try_get(from, context)? {
2361                        // 2. Perform ? Set(O, to, fromValue, true).
2362                        o.set(to, from_value, true, context)?;
2363                    } else {
2364                        // iv. Else,
2365                        //     1. Perform ? DeletePropertyOrThrow(O, to).
2366                        o.delete_property_or_throw(to, context)?;
2367                    }
2368                    // v. Set k to k + 1.
2369                }
2370
2371                // c. Set k to len.
2372                // d. Repeat, while k > (len - actualDeleteCount + itemCount),
2373                for k in ((len - actual_delete_count + item_count)..len).rev() {
2374                    // i. Perform ? DeletePropertyOrThrow(O, ! ToString(๐”ฝ(k - 1))).
2375                    o.delete_property_or_throw(k, context)?;
2376
2377                    // ii. Set k to k - 1.
2378                }
2379            }
2380            // 17. Else if itemCount > actualDeleteCount, then
2381            Ordering::Greater => {
2382                // a. Set k to (len - actualDeleteCount).
2383                // b. Repeat, while k > actualStart,
2384                for k in (actual_start..len - actual_delete_count).rev() {
2385                    // i. Let from be ! ToString(๐”ฝ(k + actualDeleteCount - 1)).
2386                    let from = k + actual_delete_count;
2387
2388                    // ii. Let to be ! ToString(๐”ฝ(k + itemCount - 1)).
2389                    let to = k + item_count;
2390
2391                    // iii. If ? HasProperty(O, from) is true, then
2392                    // iii.1. Let fromValue be ? Get(O, from).
2393                    if let Some(from_value) = o.try_get(from, context)? {
2394                        // 2. Perform ? Set(O, to, fromValue, true).
2395                        o.set(to, from_value, true, context)?;
2396                    }
2397                    // iv. Else,
2398                    else {
2399                        // 1. Perform ? DeletePropertyOrThrow(O, to).
2400                        o.delete_property_or_throw(to, context)?;
2401                    }
2402                    // v. Set k to k - 1.
2403                }
2404            }
2405        }
2406
2407        // 18. Set k to actualStart.
2408        // 19. For each element E of items, do
2409        for (i, item) in items.iter().enumerate() {
2410            //     a. Perform ? Set(O, ! ToString(๐”ฝ(k)), E, true).
2411            //     b. Set k to k + 1.
2412            o.set(actual_start + i as u64, item.clone(), true, context)?;
2413        }
2414
2415        // 20. Perform ? Set(O, "length", ๐”ฝ(len - actualDeleteCount + itemCount), true).
2416        Self::set_length(&o, len - actual_delete_count + item_count, context)?;
2417
2418        // 21. Return A.
2419        Ok(JsValue::from(arr))
2420    }
2421
2422    /// [`Array.prototype.toSpliced ( start, skipCount, ...items )`][spec]
2423    ///
2424    /// Splices the target array, returning the result as a new array.
2425    ///
2426    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tospliced
2427    fn to_spliced(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
2428        // 1. Let O be ? ToObject(this value).
2429        let o = this.to_object(context)?;
2430
2431        // 2. Let len be ? LengthOfArrayLike(O).
2432        let len = o.length_of_array_like(context)?;
2433
2434        let start = args.first();
2435        let skip_count = args.get(1);
2436        let items = args.get(2..).unwrap_or_default();
2437
2438        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
2439        // 4. If relativeStart is -โˆž, let actualStart be 0.
2440        // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
2441        // 6. Else, let actualStart be min(relativeStart, len).
2442        let actual_start =
2443            Self::get_relative_start(context, start.unwrap_or(&JsValue::undefined()), len)?;
2444
2445        // 7. Let insertCount be the number of elements in items.
2446        let insert_count = items.len() as u64;
2447
2448        let actual_skip_count =
2449            Self::get_delete_count(len, actual_start, start, skip_count, context)?;
2450
2451        // 11. Let newLen be len + insertCount - actualSkipCount.
2452        let new_len = len + insert_count - actual_skip_count;
2453
2454        // 12. If newLen > 2**53 - 1, throw a TypeError exception.
2455        if new_len > Number::MAX_SAFE_INTEGER as u64 {
2456            return Err(JsNativeError::typ()
2457                .with_message("Target splice exceeded max safe integer value")
2458                .into());
2459        }
2460
2461        // 13. Let A be ? ArrayCreate(newLen).
2462        let arr = Array::array_create(new_len, None, context)?;
2463
2464        // 14. Let i be 0.
2465        let mut i = 0;
2466        // 16. Repeat, while i < actualStart,
2467        while i < actual_start {
2468            //     a. Let Pi be ! ToString(๐”ฝ(i)).
2469            //     b. Let iValue be ? Get(O, Pi).
2470            let value = o.get(i, context)?;
2471
2472            //     c. Perform ! CreateDataPropertyOrThrow(A, Pi, iValue).
2473            arr.create_data_property_or_throw(i, value, context)
2474                .js_expect("cannot fail for a newly created array")?;
2475
2476            //     d. Set i to i + 1.
2477            i += 1;
2478        }
2479
2480        // 17. For each element E of items, do
2481        for item in items.iter().cloned() {
2482            //     a. Let Pi be ! ToString(๐”ฝ(i)).
2483            //     b. Perform ! CreateDataPropertyOrThrow(A, Pi, E).
2484            arr.create_data_property_or_throw(i, item, context)
2485                .js_expect("cannot fail for a newly created array")?;
2486
2487            //     c. Set i to i + 1.
2488            i += 1;
2489        }
2490
2491        // 15. Let r be actualStart + actualSkipCount.
2492        let mut r = actual_start + actual_skip_count;
2493
2494        // 18. Repeat, while i < newLen,
2495        while i < new_len {
2496            //     a. Let Pi be ! ToString(๐”ฝ(i)).
2497            //     b. Let from be ! ToString(๐”ฝ(r)).
2498            //     c. Let fromValue be ? Get(O, from).
2499            let from_value = o.get(r, context)?;
2500
2501            //     d. Perform ! CreateDataPropertyOrThrow(A, Pi, fromValue).
2502            arr.create_data_property_or_throw(i, from_value, context)
2503                .js_expect("cannot fail for a newly created array")?;
2504
2505            //     e. Set i to i + 1.
2506            i += 1;
2507            //     f. Set r to r + 1.
2508            r += 1;
2509        }
2510
2511        // 19. Return A.
2512        Ok(arr.into())
2513    }
2514
2515    /// `Array.prototype.filter( callback, [ thisArg ] )`
2516    ///
2517    /// For each element in the array the callback function is called, and a new
2518    /// array is constructed for every value whose callback returned a truthy value.
2519    ///
2520    /// More information:
2521    ///  - [ECMAScript reference][spec]
2522    ///  - [MDN documentation][mdn]
2523    ///
2524    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter
2525    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
2526    pub(crate) fn filter(
2527        this: &JsValue,
2528        args: &[JsValue],
2529        context: &mut Context,
2530    ) -> JsResult<JsValue> {
2531        // 1. Let O be ? ToObject(this value).
2532        let o = this.to_object(context)?;
2533
2534        // 2. Let len be ? LengthOfArrayLike(O).
2535        let length = o.length_of_array_like(context)?;
2536
2537        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2538        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
2539            JsNativeError::typ().with_message("Array.prototype.filter: `callback` must be callable")
2540        })?;
2541        let this_arg = args.get_or_undefined(1);
2542
2543        // 4. Let A be ? ArraySpeciesCreate(O, 0).
2544        let a = Self::array_species_create(&o, 0, context)?;
2545
2546        // 5. Let k be 0.
2547        // 6. Let to be 0.
2548        let mut to = 0u32;
2549        // 7. Repeat, while k < len,
2550        for idx in 0..length {
2551            // a. Let Pk be ! ToString(๐”ฝ(k)).
2552            // b. Let kPresent be ? HasProperty(O, Pk).
2553            // c. If kPresent is true, then
2554            // c.i. Let kValue be ? Get(O, Pk).
2555            if let Some(element) = o.try_get(idx, context)? {
2556                let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())];
2557
2558                // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
2559                let selected = callback.call(this_arg, &args, context)?.to_boolean();
2560
2561                // iii. If selected is true, then
2562                if selected {
2563                    // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(to)), kValue).
2564                    a.create_data_property_or_throw(to, element, context)?;
2565                    // 2. Set to to to + 1.
2566                    to += 1;
2567                }
2568            }
2569        }
2570
2571        // 8. Return A.
2572        Ok(a.into())
2573    }
2574
2575    /// Array.prototype.some ( callbackfn [ , thisArg ] )
2576    ///
2577    /// The some method tests whether at least one element in the array passes
2578    /// the test implemented by the provided callback function. It returns a Boolean value,
2579    /// true if the callback function returns a truthy value for at least one element
2580    /// in the array. Otherwise, false.
2581    ///
2582    /// Caution: Calling this method on an empty array returns false for any condition!
2583    ///
2584    /// More information:
2585    ///  - [ECMAScript reference][spec]
2586    ///  - [MDN documentation][mdn]
2587    ///
2588    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some
2589    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
2590    pub(crate) fn some(
2591        this: &JsValue,
2592        args: &[JsValue],
2593        context: &mut Context,
2594    ) -> JsResult<JsValue> {
2595        // 1. Let O be ? ToObject(this value).
2596        let o = this.to_object(context)?;
2597        // 2. Let len be ? LengthOfArrayLike(O).
2598        let len = o.length_of_array_like(context)?;
2599        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2600        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
2601            JsNativeError::typ().with_message("Array.prototype.some: callback is not callable")
2602        })?;
2603
2604        // 4. Let k be 0.
2605        // 5. Repeat, while k < len,
2606        for k in 0..len {
2607            // a. Let Pk be ! ToString(๐”ฝ(k)).
2608            // b. Let kPresent be ? HasProperty(O, Pk).
2609            // c. If kPresent is true, then
2610            // c.i. Let kValue be ? Get(O, Pk).
2611            if let Some(k_value) = o.try_get(k, context)? {
2612                // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
2613                let this_arg = args.get_or_undefined(1);
2614                let test_result = callback
2615                    .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
2616                    .to_boolean();
2617                // iii. If testResult is true, return true.
2618                if test_result {
2619                    return Ok(JsValue::new(true));
2620                }
2621            }
2622            // d. Set k to k + 1.
2623        }
2624        // 6. Return false.
2625        Ok(JsValue::new(false))
2626    }
2627
2628    /// [`SortIndexedProperties ( obj, len, SortCompare, holes )`][spec]
2629    ///
2630    /// [spec]: https://tc39.es/ecma262/#sec-sortindexedproperties
2631    pub(crate) fn sort_indexed_properties<F>(
2632        obj: &JsObject,
2633        len: u64,
2634        sort_compare: F,
2635        skip_holes: bool,
2636        context: &mut Context,
2637    ) -> JsResult<Vec<JsValue>>
2638    where
2639        F: Fn(&JsValue, &JsValue, &mut Context) -> JsResult<Ordering>,
2640    {
2641        // 1. Let items be a new empty List.
2642        // doesn't matter if it clamps since it's just a best-effort optimization
2643        let mut items = Vec::with_capacity(len as usize);
2644
2645        // 2. Let k be 0.
2646        // 3. Repeat, while k < len,
2647        for i in 0..len {
2648            // a. Let Pk be ! ToString(๐”ฝ(k)).
2649            // b. If holes is skip-holes, then
2650            let read = if skip_holes {
2651                // i. Let kRead be ? HasProperty(obj, Pk).
2652                obj.has_property(i, context)?
2653            }
2654            // c. Else,
2655            else {
2656                // i. Assert: holes is read-through-holes.
2657                // ii. Let kRead be true.
2658                true
2659            };
2660
2661            // d. If kRead is true, then
2662            if read {
2663                // i. Let kValue be ? Get(obj, Pk).
2664                // ii. Append kValue to items.
2665                items.push(obj.get(i, context)?);
2666            }
2667            // e. Set k to k + 1.
2668        }
2669        // 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.
2670        let mut sort_err = Ok(());
2671        items.sort_by(|x, y| {
2672            if sort_err.is_ok() {
2673                sort_compare(x, y, context).unwrap_or_else(|err| {
2674                    sort_err = Err(err);
2675                    Ordering::Equal
2676                })
2677            } else {
2678                Ordering::Equal
2679            }
2680        });
2681        sort_err?;
2682
2683        // 5. Return items.
2684        Ok(items)
2685    }
2686
2687    /// Array.prototype.sort ( comparefn )
2688    ///
2689    /// The sort method sorts the elements of an array in place and returns the sorted array.
2690    /// The default sort order is ascending, built upon converting the elements into strings,
2691    /// then comparing their sequences of UTF-16 code units values.
2692    ///
2693    /// More information:
2694    ///  - [ECMAScript reference][spec]
2695    ///  - [MDN documentation][mdn]
2696    ///
2697    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.sort
2698    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
2699    pub(crate) fn sort(
2700        this: &JsValue,
2701        args: &[JsValue],
2702        context: &mut Context,
2703    ) -> JsResult<JsValue> {
2704        // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
2705        let comparefn = match args.get_or_undefined(0).variant() {
2706            JsVariant::Object(obj) if obj.is_callable() => Some(obj),
2707            JsVariant::Undefined => None,
2708            _ => {
2709                return Err(JsNativeError::typ()
2710                    .with_message("The comparison function must be either a function or undefined")
2711                    .into());
2712            }
2713        };
2714
2715        // 2. Let obj be ? ToObject(this value).
2716        let obj = this.to_object(context)?;
2717
2718        // 3. Let len be ? LengthOfArrayLike(obj).
2719        let len = obj.length_of_array_like(context)?;
2720
2721        // 4. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
2722        let sort_compare =
2723            |x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult<Ordering> {
2724                // a. Return ? CompareArrayElements(x, y, comparefn).
2725                compare_array_elements(x, y, comparefn.as_ref(), context)
2726            };
2727
2728        // 5. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, skip-holes).
2729        let sorted = Self::sort_indexed_properties(&obj, len, sort_compare, true, context)?;
2730
2731        let sorted_len = sorted.len() as u64;
2732
2733        // 6. Let itemCount be the number of elements in sortedList.
2734        // 7. Let j be 0.
2735        // 8. Repeat, while j < itemCount,
2736        for (j, item) in sorted.into_iter().enumerate() {
2737            // a. Perform ? Set(obj, ! ToString(๐”ฝ(j)), sortedList[j], true).
2738            obj.set(j, item, true, context)?;
2739
2740            // b. Set j to j + 1.
2741        }
2742
2743        // 9. NOTE: The call to SortIndexedProperties in step 5 uses skip-holes. The remaining indices
2744        //    are deleted to preserve the number of holes that were detected and excluded from the sort.
2745        // 10. Repeat, while j < len,
2746        for j in sorted_len..len {
2747            // a. Perform ? DeletePropertyOrThrow(obj, ! ToString(๐”ฝ(j))).
2748            obj.delete_property_or_throw(j, context)?;
2749
2750            // b. Set j to j + 1.
2751        }
2752
2753        // 11. Return obj.
2754        Ok(obj.into())
2755    }
2756
2757    /// [`Array.prototype.toSorted ( comparefn )`][spec]
2758    ///
2759    /// Orders the target array, returning the result in a new array.
2760    ///
2761    /// [spec]: https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.tosorted
2762    pub(crate) fn to_sorted(
2763        this: &JsValue,
2764        args: &[JsValue],
2765        context: &mut Context,
2766    ) -> JsResult<JsValue> {
2767        // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
2768        let comparefn = match args.get_or_undefined(0).variant() {
2769            JsVariant::Object(obj) if obj.is_callable() => Some(obj),
2770            JsVariant::Undefined => None,
2771            _ => {
2772                return Err(JsNativeError::typ()
2773                    .with_message("The comparison function must be either a function or undefined")
2774                    .into());
2775            }
2776        };
2777
2778        // 2. Let O be ? ToObject(this value).
2779        let o = this.to_object(context)?;
2780
2781        // 3. Let len be ? LengthOfArrayLike(O).
2782        let len = o.length_of_array_like(context)?;
2783
2784        // 4. Let A be ? ArrayCreate(len).
2785        let arr = Array::array_create(len, None, context)?;
2786
2787        // 5. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called:
2788        let sort_compare =
2789            |x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult<Ordering> {
2790                // a. Return ? CompareArrayElements(x, y, comparefn).
2791                compare_array_elements(x, y, comparefn.as_ref(), context)
2792            };
2793
2794        // 6. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes).
2795        let sorted = Self::sort_indexed_properties(&o, len, sort_compare, false, context)?;
2796
2797        // 7. Let j be 0.
2798        // 8. Repeat, while j < len,
2799        for (i, item) in sorted.into_iter().enumerate() {
2800            //     a. Perform ! CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(j)), sortedList[j]).
2801            arr.create_data_property_or_throw(i, item, context)
2802                .js_expect("cannot fail for a newly created array")?;
2803
2804            //     b. Set j to j + 1.
2805        }
2806
2807        // 9. Return A.
2808        Ok(arr.into())
2809    }
2810
2811    /// `Array.prototype.reduce( callbackFn [ , initialValue ] )`
2812    ///
2813    /// More information:
2814    ///  - [ECMAScript reference][spec]
2815    ///  - [MDN documentation][mdn]
2816    ///
2817    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce
2818    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
2819    pub(crate) fn reduce(
2820        this: &JsValue,
2821        args: &[JsValue],
2822        context: &mut Context,
2823    ) -> JsResult<JsValue> {
2824        // 1. Let O be ? ToObject(this value).
2825        let o = this.to_object(context)?;
2826
2827        // 2. Let len be ? LengthOfArrayLike(O).
2828        let len = o.length_of_array_like(context)?;
2829
2830        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2831        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
2832            JsNativeError::typ()
2833                .with_message("Array.prototype.reduce: callback function is not callable")
2834        })?;
2835
2836        // 4. If len = 0 and initialValue is not present, throw a TypeError exception.
2837        if len == 0 && args.get(1).is_none() {
2838            return Err(JsNativeError::typ()
2839                .with_message(
2840                    "Array.prototype.reduce: called on an empty array and with no initial value",
2841                )
2842                .into());
2843        }
2844
2845        // 5. Let k be 0.
2846        let mut k = 0;
2847        // 6. Let accumulator be undefined.
2848        let mut accumulator = JsValue::undefined();
2849
2850        // 7. If initialValue is present, then
2851        if let Some(initial_value) = args.get(1) {
2852            // a. Set accumulator to initialValue.
2853            accumulator = initial_value.clone();
2854        // 8. Else,
2855        } else {
2856            // a. Let kPresent be false.
2857            let mut k_present = false;
2858            // b. Repeat, while kPresent is false and k < len,
2859            while !k_present && k < len {
2860                // i. Let Pk be ! ToString(๐”ฝ(k)).
2861                let pk = k;
2862                // ii. Set kPresent to ? HasProperty(O, Pk).
2863                // iii. If kPresent is true, then
2864                // iii.1. Set accumulator to ? Get(O, Pk).
2865                if let Some(v) = o.try_get(pk, context)? {
2866                    accumulator = v;
2867                    k_present = true;
2868                } else {
2869                    k_present = false;
2870                }
2871                // iv. Set k to k + 1.
2872                k += 1;
2873            }
2874            // c. If kPresent is false, throw a TypeError exception.
2875            if !k_present {
2876                return Err(JsNativeError::typ().with_message(
2877                    "Array.prototype.reduce: called on an empty array and with no initial value",
2878                ).into());
2879            }
2880        }
2881
2882        // 9. Repeat, while k < len,
2883        while k < len {
2884            // a. Let Pk be ! ToString(๐”ฝ(k)).
2885            let pk = k;
2886            // b. Let kPresent be ? HasProperty(O, Pk).
2887            // c. If kPresent is true, then
2888            // c.i. Let kValue be ? Get(O, Pk).
2889            if let Some(k_value) = o.try_get(pk, context)? {
2890                // ii. Set accumulator to ? Call(callbackfn, undefined, ยซ accumulator, kValue, ๐”ฝ(k), O ยป).
2891                accumulator = callback.call(
2892                    &JsValue::undefined(),
2893                    &[accumulator, k_value, k.into(), o.clone().into()],
2894                    context,
2895                )?;
2896            }
2897            // d. Set k to k + 1.
2898            k += 1;
2899        }
2900
2901        // 10. Return accumulator.
2902        Ok(accumulator)
2903    }
2904
2905    /// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )`
2906    ///
2907    /// The reduceRight method traverses right to left starting from the last defined value in the array,
2908    /// accumulating a value using a given callback function. It returns the accumulated value.
2909    ///
2910    /// More information:
2911    ///  - [ECMAScript reference][spec]
2912    ///  - [MDN documentation][mdn]
2913    ///
2914    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright
2915    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
2916    pub(crate) fn reduce_right(
2917        this: &JsValue,
2918        args: &[JsValue],
2919        context: &mut Context,
2920    ) -> JsResult<JsValue> {
2921        // 1. Let O be ? ToObject(this value).
2922        let o = this.to_object(context)?;
2923
2924        // 2. Let len be ? LengthOfArrayLike(O).
2925        let len = o.length_of_array_like(context)?;
2926
2927        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2928        let callback = args.get_or_undefined(0).as_callable().ok_or_else(|| {
2929            JsNativeError::typ()
2930                .with_message("Array.prototype.reduceRight: callback function is not callable")
2931        })?;
2932
2933        // 4. If len is 0 and initialValue is not present, throw a TypeError exception.
2934        if len == 0 && args.get(1).is_none() {
2935            return Err(JsNativeError::typ().with_message(
2936                "Array.prototype.reduceRight: called on an empty array and with no initial value",
2937            ).into());
2938        }
2939
2940        // 5. Let k be len - 1.
2941        let mut k = len as i64 - 1;
2942        // 6. Let accumulator be undefined.
2943        let mut accumulator = JsValue::undefined();
2944        // 7. If initialValue is present, then
2945        if let Some(initial_value) = args.get(1) {
2946            // a. Set accumulator to initialValue.
2947            accumulator = initial_value.clone();
2948        // 8. Else,
2949        } else {
2950            // a. Let kPresent be false.
2951            let mut k_present = false;
2952            // b. Repeat, while kPresent is false and k โ‰ฅ 0,
2953            while !k_present && k >= 0 {
2954                // i. Let Pk be ! ToString(๐”ฝ(k)).
2955                let pk = k;
2956                // ii. Set kPresent to ? HasProperty(O, Pk).
2957                // iii. If kPresent is true, then
2958                // iii.1. Set accumulator to ? Get(O, Pk).
2959                if let Some(v) = o.try_get(pk, context)? {
2960                    k_present = true;
2961                    accumulator = v;
2962                } else {
2963                    k_present = false;
2964                }
2965                // iv. Set k to k - 1.
2966                k -= 1;
2967            }
2968            // c. If kPresent is false, throw a TypeError exception.
2969            if !k_present {
2970                return Err(JsNativeError::typ().with_message(
2971                    "Array.prototype.reduceRight: called on an empty array and with no initial value",
2972                ).into());
2973            }
2974        }
2975
2976        // 9. Repeat, while k โ‰ฅ 0,
2977        while k >= 0 {
2978            // a. Let Pk be ! ToString(๐”ฝ(k)).
2979            let pk = k;
2980            // b. Let kPresent be ? HasProperty(O, Pk).
2981            // c. If kPresent is true, then
2982            // c.i. Let kValue be ? Get(O, Pk).
2983            if let Some(k_value) = o.try_get(pk, context)? {
2984                // ii. Set accumulator to ? Call(callbackfn, undefined, ยซ accumulator, kValue, ๐”ฝ(k), O ยป).
2985                accumulator = callback.call(
2986                    &JsValue::undefined(),
2987                    &[accumulator.clone(), k_value, k.into(), o.clone().into()],
2988                    context,
2989                )?;
2990            }
2991            // d. Set k to k - 1.
2992            k -= 1;
2993        }
2994
2995        // 10. Return accumulator.
2996        Ok(accumulator)
2997    }
2998
2999    /// `Array.prototype.copyWithin ( target, start [ , end ] )`
3000    ///
3001    /// The `copyWithin()` method shallow copies part of an array to another location
3002    /// in the same array and returns it without modifying its length.
3003    ///
3004    /// More information:
3005    ///  - [ECMAScript reference][spec]
3006    ///  - [MDN documentation][mdn]
3007    ///
3008    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.copywithin
3009    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
3010    pub(crate) fn copy_within(
3011        this: &JsValue,
3012        args: &[JsValue],
3013        context: &mut Context,
3014    ) -> JsResult<JsValue> {
3015        // 1. Let O be ? ToObject(this value).
3016        let o = this.to_object(context)?;
3017
3018        // 2. Let len be ? LengthOfArrayLike(O).
3019        let len = o.length_of_array_like(context)?;
3020
3021        // 3. Let relativeTarget be ? ToIntegerOrInfinity(target).
3022        // 4. If relativeTarget is -โˆž, let to be 0.
3023        // 5. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0).
3024        // 6. Else, let to be min(relativeTarget, len).
3025        let mut to = Self::get_relative_start(context, args.get_or_undefined(0), len)? as i64;
3026
3027        // 7. Let relativeStart be ? ToIntegerOrInfinity(start).
3028        // 8. If relativeStart is -โˆž, let from be 0.
3029        // 9. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
3030        // 10. Else, let from be min(relativeStart, len).
3031        let mut from = Self::get_relative_start(context, args.get_or_undefined(1), len)? as i64;
3032
3033        // 11. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
3034        // 12. If relativeEnd is -โˆž, let final be 0.
3035        // 13. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
3036        // 14. Else, let final be min(relativeEnd, len).
3037        let final_ = Self::get_relative_end(context, args.get_or_undefined(2), len)? as i64;
3038
3039        // 15. Let count be min(final - from, len - to).
3040        let mut count = min(final_ - from, len as i64 - to);
3041
3042        // 16. If from < to and to < from + count, then
3043        let direction = if from < to && to < from + count {
3044            // b. Set from to from + count - 1.
3045            from = from + count - 1;
3046            // c. Set to to to + count - 1.
3047            to = to + count - 1;
3048
3049            // a. Let direction be -1.
3050            -1
3051        // 17. Else,
3052        } else {
3053            // a. Let direction be 1.
3054            1
3055        };
3056
3057        // 18. Repeat, while count > 0,
3058        while count > 0 {
3059            // a. Let fromKey be ! ToString(๐”ฝ(from)).
3060            let from_key = from;
3061
3062            // b. Let toKey be ! ToString(๐”ฝ(to)).
3063            let to_key = to;
3064
3065            // c. Let fromPresent be ? HasProperty(O, fromKey).
3066            // d. If fromPresent is true, then
3067            // d.i. Let fromVal be ? Get(O, fromKey).
3068            if let Some(from_val) = o.try_get(from_key, context)? {
3069                // ii. Perform ? Set(O, toKey, fromVal, true).
3070                o.set(to_key, from_val, true, context)?;
3071            // e. Else,
3072            } else {
3073                // i. Assert: fromPresent is false.
3074                // ii. Perform ? DeletePropertyOrThrow(O, toKey).
3075                o.delete_property_or_throw(to_key, context)?;
3076            }
3077            // f. Set from to from + direction.
3078            from += direction;
3079            // g. Set to to to + direction.
3080            to += direction;
3081            // h. Set count to count - 1.
3082            count -= 1;
3083        }
3084        // 19. Return O.
3085        Ok(o.into())
3086    }
3087
3088    /// `Array.prototype.values( )`
3089    ///
3090    /// The values method returns an iterable that iterates over the values in the array.
3091    ///
3092    /// More information:
3093    ///  - [ECMAScript reference][spec]
3094    ///  - [MDN documentation][mdn]
3095    ///
3096    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
3097    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
3098    pub(crate) fn values(
3099        this: &JsValue,
3100        _: &[JsValue],
3101        context: &mut Context,
3102    ) -> JsResult<JsValue> {
3103        // 1. Let O be ? ToObject(this value).
3104        let o = this.to_object(context)?;
3105
3106        // 2. Return CreateArrayIterator(O, value).
3107        Ok(ArrayIterator::create_array_iterator(
3108            o,
3109            PropertyNameKind::Value,
3110            context,
3111        ))
3112    }
3113
3114    /// `Array.prototype.keys( )`
3115    ///
3116    /// The keys method returns an iterable that iterates over the indexes in the array.
3117    ///
3118    /// More information:
3119    ///  - [ECMAScript reference][spec]
3120    ///  - [MDN documentation][mdn]
3121    ///
3122    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.keys
3123    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
3124    pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
3125        // 1. Let O be ? ToObject(this value).
3126        let o = this.to_object(context)?;
3127
3128        // 2. Return CreateArrayIterator(O, key).
3129        Ok(ArrayIterator::create_array_iterator(
3130            o,
3131            PropertyNameKind::Key,
3132            context,
3133        ))
3134    }
3135
3136    /// `Array.prototype.entries( )`
3137    ///
3138    /// The entries method returns an iterable that iterates over the key-value pairs in the array.
3139    ///
3140    /// More information:
3141    ///  - [ECMAScript reference][spec]
3142    ///  - [MDN documentation][mdn]
3143    ///
3144    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.entries
3145    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
3146    pub(crate) fn entries(
3147        this: &JsValue,
3148        _: &[JsValue],
3149        context: &mut Context,
3150    ) -> JsResult<JsValue> {
3151        // 1. Let O be ? ToObject(this value).
3152        let o = this.to_object(context)?;
3153
3154        // 2. Return CreateArrayIterator(O, key+value).
3155        Ok(ArrayIterator::create_array_iterator(
3156            o,
3157            PropertyNameKind::KeyAndValue,
3158            context,
3159        ))
3160    }
3161
3162    /// [`Array.prototype.with ( index, value )`][spec]
3163    ///
3164    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.with
3165    pub(crate) fn with(
3166        this: &JsValue,
3167        args: &[JsValue],
3168        context: &mut Context,
3169    ) -> JsResult<JsValue> {
3170        // 1. Let O be ? ToObject(this value).
3171        let o = this.to_object(context)?;
3172
3173        // 2. Let len be ? LengthOfArrayLike(O).
3174        let len = o.length_of_array_like(context)?;
3175
3176        // 3. Let relativeIndex be ? ToIntegerOrInfinity(index).
3177        let IntegerOrInfinity::Integer(relative_index) =
3178            args.get_or_undefined(0).to_integer_or_infinity(context)?
3179        else {
3180            return Err(JsNativeError::range()
3181                .with_message("invalid integer index for TypedArray operation")
3182                .into());
3183        };
3184
3185        let value = args.get_or_undefined(1);
3186
3187        // 4. If relativeIndex โ‰ฅ 0, let actualIndex be relativeIndex.
3188        let actual_index = u64::try_from(relative_index) // should succeed if `relative_index >= 0`
3189            .ok()
3190            // 5. Else, let actualIndex be len + relativeIndex.
3191            .or_else(|| len.checked_add_signed(relative_index))
3192            .filter(|&rel| rel < len)
3193            .ok_or_else(|| {
3194                // 6. If actualIndex โ‰ฅ len or actualIndex < 0, throw a RangeError exception.
3195                JsNativeError::range()
3196                    .with_message("invalid integer index for TypedArray operation")
3197            })?;
3198
3199        // 7. Let A be ? ArrayCreate(len).
3200        let new_array = Array::array_create(len, None, context)?;
3201
3202        // 8. Let k be 0.
3203        // 9. Repeat, while k < len,
3204        for k in 0..len {
3205            // a. Let Pk be ! ToString(๐”ฝ(k)).
3206            let from_value = if k == actual_index {
3207                // b. If k is actualIndex, let fromValue be value.
3208                value.clone()
3209            } else {
3210                // c. Else, let fromValue be ? Get(O, Pk).
3211                o.get(k, context)?
3212            };
3213
3214            // d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue).
3215            new_array
3216                .create_data_property_or_throw(k, from_value, context)
3217                .js_expect("cannot fail for a newly created array")?;
3218
3219            // e. Set k to k + 1.
3220        }
3221
3222        // 10. Return A.
3223        Ok(new_array.into())
3224    }
3225
3226    /// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions.
3227    pub(super) fn get_relative_start(
3228        context: &mut Context,
3229        arg: &JsValue,
3230        len: u64,
3231    ) -> JsResult<u64> {
3232        // 1. Let relativeStart be ? ToIntegerOrInfinity(start).
3233        let relative_start = arg.to_integer_or_infinity(context)?;
3234        let start = match relative_start {
3235            // 2. If relativeStart is -โˆž, let k be 0.
3236            IntegerOrInfinity::NegativeInfinity => 0,
3237            // 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
3238            IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0),
3239            // 4. Else, let k be min(relativeStart, len).
3240            IntegerOrInfinity::Integer(i) => min(i as u64, len),
3241
3242            // Special case - positive infinity. `len` is always smaller than +inf, thus from (4)
3243            IntegerOrInfinity::PositiveInfinity => len,
3244        };
3245
3246        Ok(start)
3247    }
3248
3249    /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions.
3250    pub(super) fn get_relative_end(
3251        context: &mut Context,
3252        value: &JsValue,
3253        len: u64,
3254    ) -> JsResult<u64> {
3255        // 1. If end is undefined, let relativeEnd be len [and return it]
3256        if value.is_undefined() {
3257            Ok(len)
3258        } else {
3259            // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end).
3260            let relative_end = value.to_integer_or_infinity(context)?;
3261            let end = match relative_end {
3262                // 2. If relativeEnd is -โˆž, let final be 0.
3263                IntegerOrInfinity::NegativeInfinity => 0,
3264                // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
3265                IntegerOrInfinity::Integer(i) if i < 0 => len.checked_add_signed(i).unwrap_or(0),
3266                // 4. Else, let final be min(relativeEnd, len).
3267                // Both `as` casts are safe as both variables are non-negative
3268                IntegerOrInfinity::Integer(i) => min(i as u64, len),
3269
3270                // Special case - positive infinity. `len` is always smaller than +inf, thus from (4)
3271                IntegerOrInfinity::PositiveInfinity => len,
3272            };
3273
3274            Ok(end)
3275        }
3276    }
3277
3278    /// `Array.prototype [ @@unscopables ]`
3279    ///
3280    /// More information:
3281    ///  - [ECMAScript reference][spec]
3282    ///  - [MDN documentation][mdn]
3283    ///
3284    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
3285    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables
3286    pub(crate) fn unscopables_object() -> JsObject {
3287        // 1. Let unscopableList be OrdinaryObjectCreate(null).
3288        let unscopable_list = JsObject::with_null_proto();
3289        let true_prop = PropertyDescriptor::builder()
3290            .value(true)
3291            .writable(true)
3292            .enumerable(true)
3293            .configurable(true);
3294        {
3295            let mut obj = unscopable_list.borrow_mut();
3296            // 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true).
3297            obj.insert(js_string!("at"), true_prop.clone());
3298            // 3. Perform ! CreateDataPropertyOrThrow(unscopableList, "copyWithin", true).
3299            obj.insert(js_string!("copyWithin"), true_prop.clone());
3300            // 4. Perform ! CreateDataPropertyOrThrow(unscopableList, "entries", true).
3301            obj.insert(js_string!("entries"), true_prop.clone());
3302            // 5. Perform ! CreateDataPropertyOrThrow(unscopableList, "fill", true).
3303            obj.insert(js_string!("fill"), true_prop.clone());
3304            // 6. Perform ! CreateDataPropertyOrThrow(unscopableList, "find", true).
3305            obj.insert(js_string!("find"), true_prop.clone());
3306            // 7. Perform ! CreateDataPropertyOrThrow(unscopableList, "findIndex", true).
3307            obj.insert(js_string!("findIndex"), true_prop.clone());
3308            // 8. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLast", true).
3309            obj.insert(js_string!("findLast"), true_prop.clone());
3310            // 9. Perform ! CreateDataPropertyOrThrow(unscopableList, "findLastIndex", true).
3311            obj.insert(js_string!("findLastIndex"), true_prop.clone());
3312            // 10. Perform ! CreateDataPropertyOrThrow(unscopableList, "flat", true).
3313            obj.insert(js_string!("flat"), true_prop.clone());
3314            // 11. Perform ! CreateDataPropertyOrThrow(unscopableList, "flatMap", true).
3315            obj.insert(js_string!("flatMap"), true_prop.clone());
3316            // 12. Perform ! CreateDataPropertyOrThrow(unscopableList, "includes", true).
3317            obj.insert(js_string!("includes"), true_prop.clone());
3318            // 13. Perform ! CreateDataPropertyOrThrow(unscopableList, "keys", true).
3319            obj.insert(js_string!("keys"), true_prop.clone());
3320            // 14. Perform ! CreateDataPropertyOrThrow(unscopableList, "toReversed", true).
3321            obj.insert(js_string!("toReversed"), true_prop.clone());
3322            // 15. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSorted", true).
3323            obj.insert(js_string!("toSorted"), true_prop.clone());
3324            // 16. Perform ! CreateDataPropertyOrThrow(unscopableList, "toSpliced", true).
3325            obj.insert(js_string!("toSpliced"), true_prop.clone());
3326            // 17. Perform ! CreateDataPropertyOrThrow(unscopableList, "values", true).
3327            obj.insert(js_string!("values"), true_prop);
3328        }
3329
3330        // 13. Return unscopableList.
3331        unscopable_list
3332    }
3333}
3334
3335/// [`CompareArrayElements ( x, y, comparefn )`][spec]
3336///
3337/// [spec]: https://tc39.es/ecma262/#sec-comparearrayelements
3338fn compare_array_elements(
3339    x: &JsValue,
3340    y: &JsValue,
3341    comparefn: Option<&JsObject>,
3342    context: &mut Context,
3343) -> JsResult<Ordering> {
3344    match (x.is_undefined(), y.is_undefined()) {
3345        // 1. If x and y are both undefined, return +0๐”ฝ.
3346        (true, true) => return Ok(Ordering::Equal),
3347        // 2. If x is undefined, return 1๐”ฝ.
3348        (true, false) => return Ok(Ordering::Greater),
3349        // 3. If y is undefined, return -1๐”ฝ.
3350        (false, true) => return Ok(Ordering::Less),
3351        _ => {}
3352    }
3353
3354    // 4. If comparefn is not undefined, then
3355    if let Some(cmp) = comparefn {
3356        let args = [x.clone(), y.clone()];
3357        //     a. Let v be ? ToNumber(? Call(comparefn, undefined, ยซ x, y ยป)).
3358        let v = cmp
3359            .call(&JsValue::undefined(), &args, context)?
3360            .to_number(context)?;
3361        //     b. If v is NaN, return +0๐”ฝ.
3362        //     c. Return v.
3363        return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal));
3364    }
3365
3366    // 5. Let xString be ? ToString(x).
3367    let x_str = x.to_string(context)?;
3368
3369    // 6. Let yString be ? ToString(y).
3370    let y_str = y.to_string(context)?;
3371
3372    // 7. Let xSmaller be ! IsLessThan(xString, yString, true).
3373    // 8. If xSmaller is true, return -1๐”ฝ.
3374    // 9. Let ySmaller be ! IsLessThan(yString, xString, true).
3375    // 10. If ySmaller is true, return 1๐”ฝ.
3376    // 11. Return +0๐”ฝ.
3377    // NOTE: skipped IsLessThan because it just makes a lexicographic comparison
3378    // when x and y are strings
3379    Ok(x_str.cmp(&y_str))
3380}
3381
3382/// `FindViaPredicate ( O, len, direction, predicate, thisArg )`
3383///
3384/// More information:
3385///  - [ECMAScript reference][spec]
3386///
3387/// [spec]: https://tc39.es/ecma262/#sec-findviapredicate
3388pub(crate) fn find_via_predicate(
3389    o: &JsObject,
3390    len: u64,
3391    direction: Direction,
3392    predicate: &JsValue,
3393    this_arg: &JsValue,
3394    context: &mut Context,
3395    caller_name: &str,
3396) -> JsResult<(JsValue, JsValue)> {
3397    // 1. If IsCallable(predicate) is false, throw a TypeError exception.
3398    let predicate = predicate.as_callable().ok_or_else(|| {
3399        JsNativeError::typ().with_message(format!("{caller_name}: predicate is not callable"))
3400    })?;
3401
3402    let indices = match direction {
3403        // 2. If direction is ascending, then
3404        // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in ascending order.
3405        Direction::Ascending => itertools::Either::Left(0..len),
3406        // 3. Else,
3407        // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in descending order.
3408        Direction::Descending => itertools::Either::Right((0..len).rev()),
3409    };
3410
3411    // 4. For each integer k of indices, do
3412    for k in indices {
3413        // a. Let Pk be ! ToString(๐”ฝ(k)).
3414        let pk = k;
3415
3416        // b. NOTE: If O is a TypedArray, the following invocation of Get will return a normal completion.
3417        // c. Let kValue be ? Get(O, Pk).
3418        let k_value = o.get(pk, context)?;
3419
3420        // d. Let testResult be ? Call(predicate, thisArg, ยซ kValue, ๐”ฝ(k), O ยป).
3421        let test_result = predicate
3422            .call(
3423                this_arg,
3424                &[k_value.clone(), k.into(), o.clone().into()],
3425                context,
3426            )?
3427            .to_boolean();
3428
3429        if test_result {
3430            // e. If ToBoolean(testResult) is true, return the Record { [[Index]]: ๐”ฝ(k), [[Value]]: kValue }.
3431            return Ok((JsValue::new(k), k_value));
3432        }
3433    }
3434
3435    // 5. Return the Record { [[Index]]: -1๐”ฝ, [[Value]]: undefined }
3436    Ok((JsValue::new(-1), JsValue::undefined()))
3437}
3438
3439/// Define an own property for an array exotic object.
3440///
3441/// More information:
3442///  - [ECMAScript reference][spec]
3443///
3444/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
3445fn array_exotic_define_own_property(
3446    obj: &JsObject,
3447    key: &PropertyKey,
3448    desc: PropertyDescriptor,
3449    context: &mut InternalMethodPropertyContext<'_>,
3450) -> JsResult<bool> {
3451    // 1. Assert: IsPropertyKey(P) is true.
3452    match key {
3453        // 2. If P is "length", then
3454        PropertyKey::String(s) if s == &StaticJsStrings::LENGTH => {
3455            // a. Return ? ArraySetLength(A, Desc).
3456
3457            array_set_length(obj, desc, context)
3458        }
3459        // 3. Else if P is an array index, then
3460        PropertyKey::Index(index) => {
3461            let index = index.get();
3462            let new_len = index + 1;
3463
3464            // Optimization: If the shape of the object is the array template shape,
3465            // we know the position of the "length" property.
3466            if u64::from(new_len) < (2u64.pow(32) - 1) {
3467                let borrowed_object = obj.borrow();
3468                if borrowed_object.properties().shape.to_addr_usize()
3469                    == context
3470                        .intrinsics()
3471                        .templates()
3472                        .array()
3473                        .shape()
3474                        .to_addr_usize()
3475                {
3476                    let old_len = borrowed_object.properties().storage[0].clone();
3477                    drop(borrowed_object);
3478                    let old_len = old_len.to_u32(context)?;
3479                    if new_len >= old_len {
3480                        if ordinary_define_own_property(obj, key, desc, context)? {
3481                            let mut borrowed_object = obj.borrow_mut();
3482                            borrowed_object.properties_mut().storage[0] = JsValue::new(new_len);
3483                            return Ok(true);
3484                        }
3485                        return Ok(false);
3486                    }
3487                }
3488            }
3489
3490            // a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
3491            let old_len_desc =
3492                ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)?
3493                    .expect("the property descriptor must exist");
3494
3495            // b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
3496            debug_assert!(old_len_desc.is_data_descriptor());
3497
3498            // c. Assert: oldLenDesc.[[Configurable]] is false.
3499            debug_assert!(!old_len_desc.expect_configurable());
3500
3501            // d. Let oldLen be oldLenDesc.[[Value]].
3502            // e. Assert: oldLen is a non-negative integral Number.
3503            // f. Let index be ! ToUint32(P).
3504            let old_len = old_len_desc
3505                .expect_value()
3506                .to_u32(context)
3507                .js_expect("this ToUint32 call must not fail")?;
3508
3509            // g. If index โ‰ฅ oldLen and oldLenDesc.[[Writable]] is false, return false.
3510            if index >= old_len && !old_len_desc.expect_writable() {
3511                return Ok(false);
3512            }
3513
3514            // h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
3515            if ordinary_define_own_property(obj, key, desc, context)? {
3516                // j. If index โ‰ฅ oldLen, then
3517                if index >= old_len {
3518                    // i. Set oldLenDesc.[[Value]] to index + 1๐”ฝ.
3519                    let old_len_desc = PropertyDescriptor::builder()
3520                        .value(new_len)
3521                        .maybe_writable(old_len_desc.writable())
3522                        .maybe_enumerable(old_len_desc.enumerable())
3523                        .maybe_configurable(old_len_desc.configurable());
3524
3525                    // ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
3526                    let succeeded = ordinary_define_own_property(
3527                        obj,
3528                        &StaticJsStrings::LENGTH.into(),
3529                        old_len_desc.into(),
3530                        context,
3531                    )?;
3532
3533                    // iii. Assert: succeeded is true.
3534                    debug_assert!(succeeded);
3535                }
3536
3537                // k. Return true.
3538                Ok(true)
3539            } else {
3540                // i. If succeeded is false, return false.
3541                Ok(false)
3542            }
3543        }
3544        // 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
3545        _ => ordinary_define_own_property(obj, key, desc, context),
3546    }
3547}
3548
3549/// Abstract operation `ArraySetLength ( A, Desc )`
3550///
3551/// More information:
3552///  - [ECMAScript reference][spec]
3553///
3554/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength
3555fn array_set_length(
3556    obj: &JsObject,
3557    desc: PropertyDescriptor,
3558    context: &mut InternalMethodPropertyContext<'_>,
3559) -> JsResult<bool> {
3560    // 1. If Desc.[[Value]] is absent, then
3561    let Some(new_len_val) = desc.value() else {
3562        // a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
3563        return ordinary_define_own_property(obj, &StaticJsStrings::LENGTH.into(), desc, context);
3564    };
3565
3566    // 3. Let newLen be ? ToUint32(Desc.[[Value]]).
3567    let new_len = new_len_val.to_u32(context)?;
3568
3569    // 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
3570    let number_len = new_len_val.to_number(context)?;
3571
3572    // 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
3573    #[allow(clippy::float_cmp)]
3574    if f64::from(new_len) != number_len {
3575        return Err(JsNativeError::range()
3576            .with_message("bad length for array")
3577            .into());
3578    }
3579
3580    // 2. Let newLenDesc be a copy of Desc.
3581    // 6. Set newLenDesc.[[Value]] to newLen.
3582    let mut new_len_desc = PropertyDescriptor::builder()
3583        .value(new_len)
3584        .maybe_writable(desc.writable())
3585        .maybe_enumerable(desc.enumerable())
3586        .maybe_configurable(desc.configurable());
3587
3588    // 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
3589    let old_len_desc = ordinary_get_own_property(obj, &StaticJsStrings::LENGTH.into(), context)?
3590        .expect("the property descriptor must exist");
3591
3592    // 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
3593    debug_assert!(old_len_desc.is_data_descriptor());
3594
3595    // 9. Assert: oldLenDesc.[[Configurable]] is false.
3596    debug_assert!(!old_len_desc.expect_configurable());
3597
3598    // 10. Let oldLen be oldLenDesc.[[Value]].
3599    let old_len = old_len_desc.expect_value();
3600
3601    // 11. If newLen โ‰ฅ oldLen, then
3602    if new_len >= old_len.to_u32(context)? {
3603        // a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
3604        return ordinary_define_own_property(
3605            obj,
3606            &StaticJsStrings::LENGTH.into(),
3607            new_len_desc.build(),
3608            context,
3609        );
3610    }
3611
3612    // 12. If oldLenDesc.[[Writable]] is false, return false.
3613    if !old_len_desc.expect_writable() {
3614        return Ok(false);
3615    }
3616
3617    // 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
3618    let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
3619        true
3620    }
3621    // 14. Else,
3622    else {
3623        // a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
3624        // elements cannot be deleted.
3625        // c. Set newLenDesc.[[Writable]] to true.
3626        new_len_desc = new_len_desc.writable(true);
3627
3628        // b. Let newWritable be false.
3629        false
3630    };
3631
3632    // 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
3633    // 16. If succeeded is false, return false.
3634    if !ordinary_define_own_property(
3635        obj,
3636        &StaticJsStrings::LENGTH.into(),
3637        new_len_desc.clone().build(),
3638        context,
3639    )
3640    .js_expect("this OrdinaryDefineOwnProperty call must not fail")?
3641    {
3642        return Ok(false);
3643    }
3644
3645    // 17. For each own property key P of A that is an array index, whose numeric value is
3646    // greater than or equal to newLen, in descending numeric index order, do
3647    let ordered_keys = {
3648        let mut keys: Vec<_> = obj
3649            .borrow()
3650            .properties
3651            .index_property_keys()
3652            .filter(|idx| new_len <= *idx && *idx < u32::MAX)
3653            .collect();
3654        keys.sort_unstable_by(|x, y| y.cmp(x));
3655        keys
3656    };
3657
3658    for index in ordered_keys {
3659        // a. Let deleteSucceeded be ! A.[[Delete]](P).
3660        // b. If deleteSucceeded is false, then
3661        if !obj.__delete__(&index.into(), context)? {
3662            // i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1๐”ฝ.
3663            new_len_desc = new_len_desc.value(index + 1);
3664
3665            // ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
3666            if !new_writable {
3667                new_len_desc = new_len_desc.writable(false);
3668            }
3669
3670            // iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
3671            ordinary_define_own_property(
3672                obj,
3673                &StaticJsStrings::LENGTH.into(),
3674                new_len_desc.build(),
3675                context,
3676            )
3677            .js_expect("this OrdinaryDefineOwnProperty call must not fail")?;
3678
3679            // iv. Return false.
3680            return Ok(false);
3681        }
3682    }
3683
3684    // 18. If newWritable is false, then
3685    if !new_writable {
3686        // a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
3687        // PropertyDescriptor { [[Writable]]: false }).
3688        let succeeded = ordinary_define_own_property(
3689            obj,
3690            &StaticJsStrings::LENGTH.into(),
3691            PropertyDescriptor::builder().writable(false).build(),
3692            context,
3693        )
3694        .js_expect("this OrdinaryDefineOwnProperty call must not fail")?;
3695
3696        // b. Assert: succeeded is true.
3697        debug_assert!(succeeded);
3698    }
3699
3700    // 19. Return true.
3701    Ok(true)
3702}