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