boa/builtins/array/
mod.rs

1//! This module implements the global `Array` object.
2//!
3//! The JavaScript `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
12pub mod array_iterator;
13#[cfg(test)]
14mod tests;
15
16use crate::{
17    builtins::array::array_iterator::ArrayIterator,
18    builtins::BuiltIn,
19    builtins::Number,
20    context::StandardObjects,
21    object::{
22        internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
23        JsObject, ObjectData,
24    },
25    property::{Attribute, PropertyDescriptor, PropertyNameKind},
26    symbol::WellKnownSymbols,
27    value::{IntegerOrInfinity, JsValue},
28    BoaProfiler, Context, JsResult, JsString,
29};
30use std::cmp::{max, min, Ordering};
31
32use super::JsArgs;
33
34/// JavaScript `Array` built-in implementation.
35#[derive(Debug, Clone, Copy)]
36pub(crate) struct Array;
37
38impl BuiltIn for Array {
39    const NAME: &'static str = "Array";
40
41    fn attribute() -> Attribute {
42        Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
43    }
44
45    fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
46        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
47
48        let symbol_iterator = WellKnownSymbols::iterator();
49
50        let get_species = FunctionBuilder::native(context, Self::get_species)
51            .name("get [Symbol.species]")
52            .constructable(false)
53            .build();
54
55        let values_function = FunctionBuilder::native(context, Self::values)
56            .name("values")
57            .length(0)
58            .constructable(false)
59            .build();
60
61        let array = ConstructorBuilder::with_standard_object(
62            context,
63            Self::constructor,
64            context.standard_objects().array_object().clone(),
65        )
66        .name(Self::NAME)
67        .length(Self::LENGTH)
68        .static_accessor(
69            WellKnownSymbols::species(),
70            Some(get_species),
71            None,
72            Attribute::CONFIGURABLE,
73        )
74        .property(
75            "length",
76            0,
77            Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
78        )
79        .property(
80            "values",
81            values_function.clone(),
82            Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
83        )
84        .property(
85            symbol_iterator,
86            values_function,
87            Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
88        )
89        .method(Self::concat, "concat", 1)
90        .method(Self::push, "push", 1)
91        .method(Self::index_of, "indexOf", 1)
92        .method(Self::last_index_of, "lastIndexOf", 1)
93        .method(Self::includes_value, "includes", 1)
94        .method(Self::map, "map", 1)
95        .method(Self::fill, "fill", 1)
96        .method(Self::for_each, "forEach", 1)
97        .method(Self::filter, "filter", 1)
98        .method(Self::pop, "pop", 0)
99        .method(Self::join, "join", 1)
100        .method(Self::to_string, "toString", 0)
101        .method(Self::reverse, "reverse", 0)
102        .method(Self::shift, "shift", 0)
103        .method(Self::unshift, "unshift", 1)
104        .method(Self::every, "every", 1)
105        .method(Self::find, "find", 1)
106        .method(Self::find_index, "findIndex", 1)
107        .method(Self::flat, "flat", 0)
108        .method(Self::flat_map, "flatMap", 1)
109        .method(Self::slice, "slice", 2)
110        .method(Self::some, "some", 2)
111        .method(Self::sort, "sort", 1)
112        .method(Self::splice, "splice", 3)
113        .method(Self::reduce, "reduce", 2)
114        .method(Self::reduce_right, "reduceRight", 2)
115        .method(Self::keys, "keys", 0)
116        .method(Self::entries, "entries", 0)
117        .method(Self::copy_within, "copyWithin", 3)
118        // Static Methods
119        .static_method(Self::is_array, "isArray", 1)
120        .static_method(Self::of, "of", 0)
121        .build();
122
123        (Self::NAME, array.into(), Self::attribute())
124    }
125}
126
127impl Array {
128    const LENGTH: usize = 1;
129
130    fn constructor(
131        new_target: &JsValue,
132        args: &[JsValue],
133        context: &mut Context,
134    ) -> JsResult<JsValue> {
135        // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
136        // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%").
137        let prototype =
138            get_prototype_from_constructor(new_target, StandardObjects::array_object, context)?;
139
140        // 3. Let numberOfArgs be the number of elements in values.
141        let number_of_args = args.len();
142
143        // 4. If numberOfArgs = 0, then
144        if number_of_args == 0 {
145            // 4.a. Return ! ArrayCreate(0, proto).
146            Ok(Array::array_create(0, Some(prototype), context)
147                .unwrap()
148                .into())
149        // 5. Else if numberOfArgs = 1, then
150        } else if number_of_args == 1 {
151            // a. Let len be values[0].
152            let len = &args[0];
153            // b. Let array be ! ArrayCreate(0, proto).
154            let array = Array::array_create(0, Some(prototype), context).unwrap();
155            // c. If Type(len) is not Number, then
156            let int_len = if !len.is_number() {
157                // i. Perform ! CreateDataPropertyOrThrow(array, "0", len).
158                array
159                    .create_data_property_or_throw(0, len, context)
160                    .unwrap();
161                // ii. Let intLen be 1๐”ฝ.
162                1
163            // d. Else,
164            } else {
165                // i. Let intLen be ! ToUint32(len).
166                let int_len = len.to_u32(context).unwrap();
167                // ii. If SameValueZero(intLen, len) is false, throw a RangeError exception.
168                if !JsValue::same_value_zero(&int_len.into(), len) {
169                    return Err(context.construct_range_error("invalid array length"));
170                }
171                int_len
172            };
173            // e. Perform ! Set(array, "length", intLen, true).
174            array.set("length", int_len, true, context).unwrap();
175            // f. Return array.
176            Ok(array.into())
177        // 6. Else,
178        } else {
179            // 6.a. Assert: numberOfArgs โ‰ฅ 2.
180            debug_assert!(number_of_args >= 2);
181
182            // b. Let array be ? ArrayCreate(numberOfArgs, proto).
183            let array = Array::array_create(number_of_args, Some(prototype), context)?;
184            // c. Let k be 0.
185            // d. Repeat, while k < numberOfArgs,
186            for (i, item) in args.iter().cloned().enumerate() {
187                // i. Let Pk be ! ToString(๐”ฝ(k)).
188                // ii. Let itemK be values[k].
189                // iii. Perform ! CreateDataPropertyOrThrow(array, Pk, itemK).
190                array
191                    .create_data_property_or_throw(i, item, context)
192                    .unwrap();
193                // iv. Set k to k + 1.
194            }
195            // e. Assert: The mathematical value of array's "length" property is numberOfArgs.
196            // f. Return array.
197            Ok(array.into())
198        }
199    }
200
201    /// Utility for constructing `Array` objects.
202    ///
203    /// More information:
204    ///  - [ECMAScript reference][spec]
205    ///
206    /// [spec]: https://tc39.es/ecma262/#sec-arraycreate
207    pub(crate) fn array_create(
208        length: usize,
209        prototype: Option<JsObject>,
210        context: &mut Context,
211    ) -> JsResult<JsObject> {
212        // 1. If length > 2^32 - 1, throw a RangeError exception.
213        if length > 2usize.pow(32) - 1 {
214            return Err(context.construct_range_error("array exceeded max size"));
215        }
216        // 7. Return A.
217        // 2. If proto is not present, set proto to %Array.prototype%.
218        // 3. Let A be ! MakeBasicObject(ยซ [[Prototype]], [[Extensible]] ยป).
219        // 4. Set A.[[Prototype]] to proto.
220        // 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1.
221        let prototype = match prototype {
222            Some(prototype) => prototype,
223            None => context.standard_objects().array_object().prototype(),
224        };
225        let array = context.construct_object();
226
227        array.set_prototype_instance(prototype.into());
228        // This value is used by console.log and other routines to match Object type
229        // to its Javascript Identifier (global constructor method name)
230        array.borrow_mut().data = ObjectData::array();
231
232        // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: ๐”ฝ(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
233        crate::object::internal_methods::ordinary_define_own_property(
234            &array,
235            "length".into(),
236            PropertyDescriptor::builder()
237                .value(length)
238                .writable(true)
239                .enumerable(false)
240                .configurable(false)
241                .build(),
242            context,
243        )?;
244
245        Ok(array)
246    }
247
248    /// Utility for constructing `Array` objects from an iterator of `JsValue`s.
249    ///
250    /// More information:
251    ///  - [ECMAScript reference][spec]
252    ///
253    /// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist
254    pub(crate) fn create_array_from_list<I>(elements: I, context: &mut Context) -> JsObject
255    where
256        I: IntoIterator<Item = JsValue>,
257    {
258        // 1. Assert: elements is a List whose elements are all ECMAScript language values.
259        // 2. Let array be ! ArrayCreate(0).
260        let array = Self::array_create(0, None, context)
261            .expect("creating an empty array with the default prototype must not fail");
262        // 3. Let n be 0.
263        // 4. For each element e of elements, do
264        for (i, elem) in elements.into_iter().enumerate() {
265            // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(๐”ฝ(n)), e).
266            array
267                .create_data_property_or_throw(i, elem, context)
268                .expect("new array must be extensible");
269            // b. Set n to n + 1.
270        }
271        // 5. Return array.
272        array
273    }
274
275    /// Creates a new `Array` instance.
276    pub(crate) fn new_array(context: &mut Context) -> JsValue {
277        let array = JsValue::new_object(context);
278        array.set_data(ObjectData::array());
279        array
280            .as_object()
281            .expect("'array' should be an object")
282            .set_prototype_instance(context.standard_objects().array_object().prototype().into());
283        array.set_property(
284            "length",
285            PropertyDescriptor::builder()
286                .value(0)
287                .writable(true)
288                .enumerable(false)
289                .configurable(false)
290                .build(),
291        );
292        array
293    }
294
295    /// Utility function for concatenating array objects.
296    ///
297    /// Returns a Boolean valued property that if `true` indicates that
298    /// an object should be flattened to its array elements
299    /// by `Array.prototype.concat`.
300    fn is_concat_spreadable(this: &JsValue, context: &mut Context) -> JsResult<bool> {
301        // 1. If Type(O) is not Object, return false.
302        if !this.is_object() {
303            return Ok(false);
304        }
305        // 2. Let spreadable be ? Get(O, @@isConcatSpreadable).
306        let spreadable = this.get_field(WellKnownSymbols::is_concat_spreadable(), context)?;
307
308        // 3. If spreadable is not undefined, return ! ToBoolean(spreadable).
309        if !spreadable.is_undefined() {
310            return Ok(spreadable.to_boolean());
311        }
312        // 4. Return ? IsArray(O).
313        match this.as_object() {
314            Some(obj) => Ok(obj.is_array()),
315            _ => Ok(false),
316        }
317    }
318
319    /// `get Array [ @@species ]`
320    ///
321    /// The `Array [ @@species ]` accessor property returns the Array constructor.
322    ///
323    /// More information:
324    ///  - [ECMAScript reference][spec]
325    ///  - [MDN documentation][mdn]
326    ///
327    /// [spec]: https://tc39.es/ecma262/#sec-get-array-@@species
328    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species
329    fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
330        // 1. Return the this value.
331        Ok(this.clone())
332    }
333
334    /// Utility function used to specify the creation of a new Array object using a constructor
335    /// function that is derived from original_array.
336    ///
337    /// see: <https://tc39.es/ecma262/#sec-arrayspeciescreate>
338    pub(crate) fn array_species_create(
339        original_array: &JsObject,
340        length: usize,
341        context: &mut Context,
342    ) -> JsResult<JsObject> {
343        // 1. Let isArray be ? IsArray(originalArray).
344        // 2. If isArray is false, return ? ArrayCreate(length).
345        if !original_array.is_array() {
346            return Self::array_create(length, None, context);
347        }
348        // 3. Let C be ? Get(originalArray, "constructor").
349        let c = original_array.get("constructor", context)?;
350
351        // 4. If IsConstructor(C) is true, then
352        //     a. Let thisRealm be the current Realm Record.
353        //     b. Let realmC be ? GetFunctionRealm(C).
354        //     c. If thisRealm and realmC are not the same Realm Record, then
355        //         i. If SameValue(C, realmC.[[Intrinsics]].[[%Array%]]) is true, set C to undefined.
356        // TODO: Step 4 is ignored, as there are no different realms for now
357
358        // 5. If Type(C) is Object, then
359        let c = if let Some(c) = c.as_object() {
360            // 5.a. Set C to ? Get(C, @@species).
361            let c = c.get(WellKnownSymbols::species(), context)?;
362            // 5.b. If C is null, set C to undefined.
363            if c.is_null_or_undefined() {
364                JsValue::undefined()
365            } else {
366                c
367            }
368        } else {
369            c
370        };
371
372        // 6. If C is undefined, return ? ArrayCreate(length).
373        if c.is_undefined() {
374            return Self::array_create(length, None, context);
375        }
376
377        // 7. If IsConstructor(C) is false, throw a TypeError exception.
378        if let Some(c) = c.as_object() {
379            if !c.is_constructable() {
380                return Err(context.construct_type_error("Symbol.species must be a constructor"));
381            }
382            // 8. Return ? Construct(C, ยซ ๐”ฝ(length) ยป).
383            Ok(
384                c.construct(&[JsValue::new(length)], &c.clone().into(), context)?
385                    .as_object()
386                    .unwrap(),
387            )
388        } else {
389            Err(context.construct_type_error("Symbol.species must be a constructor"))
390        }
391    }
392
393    /// Utility function which takes an existing array object and puts additional
394    /// values on the end, correctly rewriting the length
395    pub(crate) fn add_to_array_object(
396        array_ptr: &JsValue,
397        add_values: &[JsValue],
398        context: &mut Context,
399    ) -> JsResult<JsValue> {
400        let orig_length = array_ptr.get_field("length", context)?.to_length(context)?;
401
402        for (n, value) in add_values.iter().enumerate() {
403            let new_index = orig_length.wrapping_add(n);
404            array_ptr.set_property(
405                new_index,
406                PropertyDescriptor::builder()
407                    .value(value)
408                    .configurable(true)
409                    .enumerable(true)
410                    .writable(true),
411            );
412        }
413
414        array_ptr.set_field(
415            "length",
416            JsValue::new(orig_length.wrapping_add(add_values.len())),
417            false,
418            context,
419        )?;
420
421        Ok(array_ptr.clone())
422    }
423
424    /// `Array.isArray( arg )`
425    ///
426    /// The isArray function takes one argument arg, and returns the Boolean value true
427    /// if the argument is an object whose class internal property is "Array"; otherwise it returns false.
428    ///
429    /// More information:
430    ///  - [ECMAScript reference][spec]
431    ///  - [MDN documentation][mdn]
432    ///
433    /// [spec]: https://tc39.es/ecma262/#sec-array.isarray
434    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
435    pub(crate) fn is_array(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
436        match args.get(0).and_then(|x| x.as_object()) {
437            Some(object) => Ok(JsValue::new(object.borrow().is_array())),
438            None => Ok(JsValue::new(false)),
439        }
440    }
441
442    /// `Array.of(...items)`
443    ///
444    /// The Array.of method creates a new Array instance from a variable number of arguments,
445    /// regardless of the number or type of arguments.
446    ///
447    /// More information:
448    ///  - [ECMAScript reference][spec]
449    ///  - [MDN documentation][mdn]
450    ///
451    /// [spec]: https://tc39.es/ecma262/#sec-array.of
452    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/of
453    pub(crate) fn of(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
454        // 1. Let len be the number of elements in items.
455        // 2. Let lenNumber be ๐”ฝ(len).
456        let len = args.len();
457
458        // 3. Let C be the this value.
459        // 4. If IsConstructor(C) is true, then
460        //     a. Let A be ? Construct(C, ยซ lenNumber ยป).
461        // 5. Else,
462        //     a. Let A be ? ArrayCreate(len).
463        let a = match this.as_object() {
464            Some(object) if object.is_constructable() => object
465                .construct(&[len.into()], this, context)?
466                .as_object()
467                .ok_or_else(|| {
468                    context.construct_type_error("object constructor didn't return an object")
469                })?,
470            _ => Array::array_create(len, None, context)?,
471        };
472
473        // 6. Let k be 0.
474        // 7. Repeat, while k < len,
475        for (k, value) in args.iter().enumerate() {
476            // a. Let kValue be items[k].
477            // b. Let Pk be ! ToString(๐”ฝ(k)).
478            // c. Perform ? CreateDataPropertyOrThrow(A, Pk, kValue).
479            a.create_data_property_or_throw(k, value, context)?;
480            // d. Set k to k + 1.
481        }
482
483        // 8. Perform ? Set(A, "length", lenNumber, true).
484        a.set("length", len, true, context)?;
485
486        // 9. Return A.
487        Ok(a.into())
488    }
489
490    /// `Array.prototype.concat(...arguments)`
491    ///
492    /// When the concat method is called with zero or more arguments, it returns an
493    /// array containing the array elements of the object followed by the array
494    /// elements of each argument in order.
495    ///
496    /// More information:
497    ///  - [ECMAScript reference][spec]
498    ///  - [MDN documentation][mdn]
499    ///
500    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat
501    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
502    pub(crate) fn concat(
503        this: &JsValue,
504        args: &[JsValue],
505        context: &mut Context,
506    ) -> JsResult<JsValue> {
507        // 1. Let O be ? ToObject(this value).
508        let obj = this.to_object(context)?;
509        // 2. Let A be ? ArraySpeciesCreate(O, 0).
510        let arr = Self::array_species_create(&obj, 0, context)?;
511        // 3. Let n be 0.
512        let mut n = 0;
513        // 4. Prepend O to items.
514        // 5. For each element E of items, do
515        for item in [JsValue::new(obj)].iter().chain(args.iter()) {
516            // a. Let spreadable be ? IsConcatSpreadable(E).
517            let spreadable = Self::is_concat_spreadable(item, context)?;
518            // b. If spreadable is true, then
519            if spreadable {
520                // item is guaranteed to be an object since is_concat_spreadable checks it,
521                // so we can call `.unwrap()`
522                let item = item.as_object().unwrap();
523                // i. Let k be 0.
524                // ii. Let len be ? LengthOfArrayLike(E).
525                let len = item.length_of_array_like(context)?;
526                // iii. If n + len > 2^53 - 1, throw a TypeError exception.
527                if n + len > Number::MAX_SAFE_INTEGER as usize {
528                    return context.throw_type_error(
529                        "length + number of arguments exceeds the max safe integer limit",
530                    );
531                }
532                // iv. Repeat, while k < len,
533                for k in 0..len {
534                    // 1. Let P be ! ToString(๐”ฝ(k)).
535                    // 2. Let exists be ? HasProperty(E, P).
536                    let exists = item.has_property(k, context)?;
537                    // 3. If exists is true, then
538                    if exists {
539                        // a. Let subElement be ? Get(E, P).
540                        let sub_element = item.get(k, context)?;
541                        // b. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), subElement).
542                        arr.create_data_property_or_throw(n, sub_element, context)?;
543                    }
544                    // 4. Set n to n + 1.
545                    n += 1;
546                    // 5. Set k to k + 1.
547                }
548            }
549            // c. Else,
550            else {
551                // i. NOTE: E is added as a single item rather than spread.
552                // ii. If n โ‰ฅ 2^53 - 1, throw a TypeError exception.
553                if n >= Number::MAX_SAFE_INTEGER as usize {
554                    return context.throw_type_error("length exceeds the max safe integer limit");
555                }
556                // iii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), E).
557                arr.create_data_property_or_throw(n, item, context)?;
558                // iv. Set n to n + 1.
559                n += 1
560            }
561        }
562        // 6. Perform ? Set(A, "length", ๐”ฝ(n), true).
563        arr.set("length", n, true, context)?;
564
565        // 7. Return A.
566        Ok(JsValue::new(arr))
567    }
568
569    /// `Array.prototype.push( ...items )`
570    ///
571    /// The arguments are appended to the end of the array, in the order in which
572    /// they appear. The new length of the array is returned as the result of the
573    /// call.
574    ///
575    /// More information:
576    ///  - [ECMAScript reference][spec]
577    ///  - [MDN documentation][mdn]
578    ///
579    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push
580    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push
581    pub(crate) fn push(
582        this: &JsValue,
583        args: &[JsValue],
584        context: &mut Context,
585    ) -> JsResult<JsValue> {
586        // 1. Let O be ? ToObject(this value).
587        let o = this.to_object(context)?;
588        // 2. Let len be ? LengthOfArrayLike(O).
589        let mut len = o.length_of_array_like(context)? as u64;
590        // 3. Let argCount be the number of elements in items.
591        let arg_count = args.len() as u64;
592        // 4. If len + argCount > 2^53 - 1, throw a TypeError exception.
593        if len + arg_count > 2u64.pow(53) - 1 {
594            return context.throw_type_error(
595                "the length + the number of arguments exceed the maximum safe integer limit",
596            );
597        }
598        // 5. For each element E of items, do
599        for element in args.iter().cloned() {
600            // a. Perform ? Set(O, ! ToString(๐”ฝ(len)), E, true).
601            o.set(len, element, true, context)?;
602            // b. Set len to len + 1.
603            len += 1;
604        }
605        // 6. Perform ? Set(O, "length", ๐”ฝ(len), true).
606        o.set("length", len, true, context)?;
607        // 7. Return ๐”ฝ(len).
608        Ok(len.into())
609    }
610
611    /// `Array.prototype.pop()`
612    ///
613    /// The last element of the array is removed from the array and returned.
614    ///
615    /// More information:
616    ///  - [ECMAScript reference][spec]
617    ///  - [MDN documentation][mdn]
618    ///
619    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
620    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
621    pub(crate) fn pop(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
622        // 1. Let O be ? ToObject(this value).
623        let o = this.to_object(context)?;
624        // 2. Let len be ? LengthOfArrayLike(O).
625        let len = o.length_of_array_like(context)?;
626        // 3. If len = 0, then
627        if len == 0 {
628            // a. Perform ? Set(O, "length", +0๐”ฝ, true).
629            o.set("length", 0, true, context)?;
630            // b. Return undefined.
631            Ok(JsValue::undefined())
632        // 4. Else,
633        } else {
634            // a. Assert: len > 0.
635            // b. Let newLen be ๐”ฝ(len - 1).
636            let new_len = len - 1;
637            // c. Let index be ! ToString(newLen).
638            let index = new_len;
639            // d. Let element be ? Get(O, index).
640            let element = o.get(index, context)?;
641            // e. Perform ? DeletePropertyOrThrow(O, index).
642            o.delete_property_or_throw(index, context)?;
643            // f. Perform ? Set(O, "length", newLen, true).
644            o.set("length", new_len, true, context)?;
645            // g. Return element.
646            Ok(element)
647        }
648    }
649
650    /// `Array.prototype.forEach( callbackFn [ , thisArg ] )`
651    ///
652    /// This method executes the provided callback function for each element in the array.
653    ///
654    /// More information:
655    ///  - [ECMAScript reference][spec]
656    ///  - [MDN documentation][mdn]
657    ///
658    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach
659    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
660    pub(crate) fn for_each(
661        this: &JsValue,
662        args: &[JsValue],
663        context: &mut Context,
664    ) -> JsResult<JsValue> {
665        // 1. Let O be ? ToObject(this value).
666        let o = this.to_object(context)?;
667        // 2. Let len be ? LengthOfArrayLike(O).
668        let len = o.length_of_array_like(context)?;
669        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
670        let callback = if let Some(arg) = args
671            .get(0)
672            .and_then(JsValue::as_object)
673            .filter(JsObject::is_callable)
674        {
675            arg
676        } else {
677            return context.throw_type_error("Array.prototype.forEach: invalid callback function");
678        };
679        // 4. Let k be 0.
680        // 5. Repeat, while k < len,
681        for k in 0..len {
682            // a. Let Pk be ! ToString(๐”ฝ(k)).
683            let pk = k;
684            // b. Let kPresent be ? HasProperty(O, Pk).
685            let present = o.has_property(pk, context)?;
686            // c. If kPresent is true, then
687            if present {
688                // i. Let kValue be ? Get(O, Pk).
689                let k_value = o.get(pk, context)?;
690                // ii. Perform ? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป).
691                let this_arg = args.get_or_undefined(1);
692                callback.call(this_arg, &[k_value, k.into(), o.clone().into()], context)?;
693            }
694            // d. Set k to k + 1.
695        }
696        // 6. Return undefined.
697        Ok(JsValue::undefined())
698    }
699
700    /// `Array.prototype.join( separator )`
701    ///
702    /// The elements of the array are converted to Strings, and these Strings are
703    /// then concatenated, separated by occurrences of the separator. If no
704    /// separator is provided, a single comma is used as the separator.
705    ///
706    /// More information:
707    ///  - [ECMAScript reference][spec]
708    ///  - [MDN documentation][mdn]
709    ///
710    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join
711    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
712    pub(crate) fn join(
713        this: &JsValue,
714        args: &[JsValue],
715        context: &mut Context,
716    ) -> JsResult<JsValue> {
717        // 1. Let O be ? ToObject(this value).
718        let o = this.to_object(context)?;
719        // 2. Let len be ? LengthOfArrayLike(O).
720        let len = o.length_of_array_like(context)?;
721        // 3. If separator is undefined, let sep be the single-element String ",".
722        // 4. Else, let sep be ? ToString(separator).
723        let separator = if let Some(separator) = args.get(0) {
724            separator.to_string(context)?
725        } else {
726            JsString::new(",")
727        };
728
729        // 5. Let R be the empty String.
730        let mut r = String::new();
731        // 6. Let k be 0.
732        // 7. Repeat, while k < len,
733        for k in 0..len {
734            // a. If k > 0, set R to the string-concatenation of R and sep.
735            if k > 0 {
736                r.push_str(&separator);
737            }
738            // b. Let element be ? Get(O, ! ToString(๐”ฝ(k))).
739            let element = o.get(k, context)?;
740            // c. If element is undefined or null, let next be the empty String; otherwise, let next be ? ToString(element).
741            let next = if element.is_null_or_undefined() {
742                JsString::new("")
743            } else {
744                element.to_string(context)?
745            };
746            // d. Set R to the string-concatenation of R and next.
747            r.push_str(&next);
748            // e. Set k to k + 1.
749        }
750        // 8. Return R.
751        Ok(r.into())
752    }
753
754    /// `Array.prototype.toString( separator )`
755    ///
756    /// The toString function is intentionally generic; it does not require that
757    /// its this value be an Array object. Therefore it can be transferred to
758    /// other kinds of objects for use as a method.
759    ///
760    /// More information:
761    ///  - [ECMAScript reference][spec]
762    ///  - [MDN documentation][mdn]
763    ///
764    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring
765    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
766    #[allow(clippy::wrong_self_convention)]
767    pub(crate) fn to_string(
768        this: &JsValue,
769        _: &[JsValue],
770        context: &mut Context,
771    ) -> JsResult<JsValue> {
772        // 1. Let array be ? ToObject(this value).
773        let array = this.to_object(context)?;
774        // 2. Let func be ? Get(array, "join").
775        let func = array.get("join", context)?;
776        // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
777        // 4. Return ? Call(func, array).
778        if let Some(func) = func.as_object().filter(JsObject::is_callable) {
779            func.call(&array.into(), &[], context)
780        } else {
781            crate::builtins::object::Object::to_string(&array.into(), &[], context)
782        }
783    }
784
785    /// `Array.prototype.reverse()`
786    ///
787    /// The elements of the array are rearranged so as to reverse their order.
788    /// The object is returned as the result of the call.
789    ///
790    /// More information:
791    ///  - [ECMAScript reference][spec]
792    ///  - [MDN documentation][mdn]
793    ///
794    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse
795    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
796    #[allow(clippy::else_if_without_else)]
797    pub(crate) fn reverse(
798        this: &JsValue,
799        _: &[JsValue],
800        context: &mut Context,
801    ) -> JsResult<JsValue> {
802        // 1. Let O be ? ToObject(this value).
803        let o = this.to_object(context)?;
804        // 2. Let len be ? LengthOfArrayLike(O).
805        let len = o.length_of_array_like(context)?;
806        // 3. Let middle be floor(len / 2).
807        let middle = len / 2;
808        // 4. Let lower be 0.
809        let mut lower = 0;
810        // 5. Repeat, while lower โ‰  middle,
811        while lower != middle {
812            // a. Let upper be len - lower - 1.
813            let upper = len - lower - 1;
814            // Skiped: b. Let upperP be ! ToString(๐”ฝ(upper)).
815            // Skiped: c. Let lowerP be ! ToString(๐”ฝ(lower)).
816            // d. Let lowerExists be ? HasProperty(O, lowerP).
817            let lower_exists = o.has_property(lower, context)?;
818            // e. If lowerExists is true, then
819            let mut lower_value = JsValue::undefined();
820            if lower_exists {
821                // i. Let lowerValue be ? Get(O, lowerP).
822                lower_value = o.get(lower, context)?;
823            }
824            // f. Let upperExists be ? HasProperty(O, upperP).
825            let upper_exists = o.has_property(upper, context)?;
826            // g. If upperExists is true, then
827            let mut upper_value = JsValue::undefined();
828            if upper_exists {
829                // i. Let upperValue be ? Get(O, upperP).
830                upper_value = o.get(upper, context)?;
831            }
832            match (lower_exists, upper_exists) {
833                // h. If lowerExists is true and upperExists is true, then
834                (true, true) => {
835                    // i. Perform ? Set(O, lowerP, upperValue, true).
836                    o.set(lower, upper_value, true, context)?;
837                    // ii. Perform ? Set(O, upperP, lowerValue, true).
838                    o.set(upper, lower_value, true, context)?;
839                }
840                // i. Else if lowerExists is false and upperExists is true, then
841                (false, true) => {
842                    // i. Perform ? Set(O, lowerP, upperValue, true).
843                    o.set(lower, upper_value, true, context)?;
844                    // ii. Perform ? DeletePropertyOrThrow(O, upperP).
845                    o.delete_property_or_throw(upper, context)?;
846                }
847                // j. Else if lowerExists is true and upperExists is false, then
848                (true, false) => {
849                    // i. Perform ? DeletePropertyOrThrow(O, lowerP).
850                    o.delete_property_or_throw(lower, context)?;
851                    // ii. Perform ? Set(O, upperP, lowerValue, true).
852                    o.set(upper, lower_value, true, context)?;
853                }
854                // k. Else,
855                (false, false) => {
856                    // i. Assert: lowerExists and upperExists are both false.
857                    // ii. No action is required.
858                }
859            }
860
861            // l. Set lower to lower + 1.
862            lower += 1;
863        }
864        // 6. Return O.
865        Ok(o.into())
866    }
867
868    /// `Array.prototype.shift()`
869    ///
870    /// The first element of the array is removed from the array and returned.
871    ///
872    /// More information:
873    ///  - [ECMAScript reference][spec]
874    ///  - [MDN documentation][mdn]
875    ///
876    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
877    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
878    pub(crate) fn shift(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
879        // 1. Let O be ? ToObject(this value).
880        let o = this.to_object(context)?;
881        // 2. Let len be ? LengthOfArrayLike(O).
882        let len = o.length_of_array_like(context)?;
883        // 3. If len = 0, then
884        if len == 0 {
885            // a. Perform ? Set(O, "length", +0๐”ฝ, true).
886            o.set("length", 0, true, context)?;
887            // b. Return undefined.
888            return Ok(JsValue::undefined());
889        }
890        // 4. Let first be ? Get(O, "0").
891        let first = o.get(0, context)?;
892        // 5. Let k be 1.
893        // 6. Repeat, while k < len,
894        for k in 1..len {
895            // a. Let from be ! ToString(๐”ฝ(k)).
896            let from = k;
897            // b. Let to be ! ToString(๐”ฝ(k - 1)).
898            let to = k - 1;
899            // c. Let fromPresent be ? HasProperty(O, from).
900            let from_present = o.has_property(from, context)?;
901            // d. If fromPresent is true, then
902            if from_present {
903                // i. Let fromVal be ? Get(O, from).
904                let from_val = o.get(from, context)?;
905                // ii. Perform ? Set(O, to, fromVal, true).
906                o.set(to, from_val, true, context)?;
907            // e. Else,
908            } else {
909                // i. Assert: fromPresent is false.
910                // ii. Perform ? DeletePropertyOrThrow(O, to).
911                o.delete_property_or_throw(to, context)?;
912            }
913            // f. Set k to k + 1.
914        }
915        // 7. Perform ? DeletePropertyOrThrow(O, ! ToString(๐”ฝ(len - 1))).
916        o.delete_property_or_throw(len - 1, context)?;
917        // 8. Perform ? Set(O, "length", ๐”ฝ(len - 1), true).
918        o.set("length", len - 1, true, context)?;
919        // 9. Return first.
920        Ok(first)
921    }
922
923    /// `Array.prototype.unshift( ...items )`
924    ///
925    /// The arguments are prepended to the start of the array, such that their order
926    /// within the array is the same as the order in which they appear in the
927    /// argument list.
928    ///
929    /// More information:
930    ///  - [ECMAScript reference][spec]
931    ///  - [MDN documentation][mdn]
932    ///
933    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
934    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
935    pub(crate) fn unshift(
936        this: &JsValue,
937        args: &[JsValue],
938        context: &mut Context,
939    ) -> JsResult<JsValue> {
940        // 1. Let O be ? ToObject(this value).
941        let o = this.to_object(context)?;
942        // 2. Let len be ? LengthOfArrayLike(O).
943        let len = o.length_of_array_like(context)? as u64;
944        // 3. Let argCount be the number of elements in items.
945        let arg_count = args.len() as u64;
946        // 4. If argCount > 0, then
947        if arg_count > 0 {
948            // a. If len + argCount > 2^53 - 1, throw a TypeError exception.
949            if len + arg_count > 2u64.pow(53) - 1 {
950                return context.throw_type_error(
951                    "length + number of arguments exceeds the max safe integer limit",
952                );
953            }
954            // b. Let k be len.
955            let mut k = len;
956            // c. Repeat, while k > 0,
957            while k > 0 {
958                // i. Let from be ! ToString(๐”ฝ(k - 1)).
959                let from = k - 1;
960                // ii. Let to be ! ToString(๐”ฝ(k + argCount - 1)).
961                let to = k + arg_count - 1;
962                // iii. Let fromPresent be ? HasProperty(O, from).
963                let from_present = o.has_property(from, context)?;
964                // iv. If fromPresent is true, then
965                if from_present {
966                    // 1. Let fromValue be ? Get(O, from).
967                    let from_value = o.get(from, context)?;
968                    // 2. Perform ? Set(O, to, fromValue, true).
969                    o.set(to, from_value, true, context)?;
970                // v. Else,
971                } else {
972                    // 1. Assert: fromPresent is false.
973                    // 2. Perform ? DeletePropertyOrThrow(O, to).
974                    o.delete_property_or_throw(to, context)?;
975                }
976                // vi. Set k to k - 1.
977                k -= 1;
978            }
979            // d. Let j be +0๐”ฝ.
980            // e. For each element E of items, do
981            for (j, e) in args.iter().enumerate() {
982                // i. Perform ? Set(O, ! ToString(j), E, true).
983                o.set(j, e, true, context)?;
984                // ii. Set j to j + 1๐”ฝ.
985            }
986        }
987        // 5. Perform ? Set(O, "length", ๐”ฝ(len + argCount), true).
988        o.set("length", len + arg_count, true, context)?;
989        // 6. Return ๐”ฝ(len + argCount).
990        Ok((len + arg_count).into())
991    }
992
993    /// `Array.prototype.every( callback, [ thisArg ] )`
994    ///
995    /// The every method executes the provided callback function once for each
996    /// element present in the array until it finds the one where callback returns
997    /// a falsy value. It returns `false` if it finds such element, otherwise it
998    /// returns `true`.
999    ///
1000    /// More information:
1001    ///  - [ECMAScript reference][spec]
1002    ///  - [MDN documentation][mdn]
1003    ///
1004    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every
1005    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
1006    pub(crate) fn every(
1007        this: &JsValue,
1008        args: &[JsValue],
1009        context: &mut Context,
1010    ) -> JsResult<JsValue> {
1011        // 1. Let O be ? ToObject(this value).
1012        let o = this.to_object(context)?;
1013        // 2. Let len be ? LengthOfArrayLike(O).
1014        let len = o.length_of_array_like(context)?;
1015        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1016        let callback = if let Some(arg) = args
1017            .get(0)
1018            .and_then(JsValue::as_object)
1019            .filter(JsObject::is_callable)
1020        {
1021            arg
1022        } else {
1023            return context.throw_type_error("Array.prototype.every: callback is not callable");
1024        };
1025
1026        let this_arg = args.get_or_undefined(1);
1027
1028        // 4. Let k be 0.
1029        // 5. Repeat, while k < len,
1030        for k in 0..len {
1031            // a. Let Pk be ! ToString(๐”ฝ(k)).
1032            // b. Let kPresent be ? HasProperty(O, Pk).
1033            let k_present = o.has_property(k, context)?;
1034            // c. If kPresent is true, then
1035            if k_present {
1036                // i. Let kValue be ? Get(O, Pk).
1037                let k_value = o.get(k, context)?;
1038                // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
1039                let test_result = callback
1040                    .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
1041                    .to_boolean();
1042                // iii. If testResult is false, return false.
1043                if !test_result {
1044                    return Ok(JsValue::new(false));
1045                }
1046            }
1047            // d. Set k to k + 1.
1048        }
1049        // 6. Return true.
1050        Ok(JsValue::new(true))
1051    }
1052
1053    /// `Array.prototype.map( callback, [ thisArg ] )`
1054    ///
1055    /// For each element in the array the callback function is called, and a new
1056    /// array is constructed from the return values of these calls.
1057    ///
1058    /// More information:
1059    ///  - [ECMAScript reference][spec]
1060    ///  - [MDN documentation][mdn]
1061    ///
1062    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map
1063    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
1064    pub(crate) fn map(
1065        this: &JsValue,
1066        args: &[JsValue],
1067        context: &mut Context,
1068    ) -> JsResult<JsValue> {
1069        // 1. Let O be ? ToObject(this value).
1070        let o = this.to_object(context)?;
1071        // 2. Let len be ? LengthOfArrayLike(O).
1072        let len = o.length_of_array_like(context)?;
1073        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1074        let callback = args.get_or_undefined(0);
1075        if !callback.is_function() {
1076            return context.throw_type_error("Array.prototype.map: Callbackfn is not callable");
1077        }
1078
1079        // 4. Let A be ? ArraySpeciesCreate(O, len).
1080        let a = Self::array_species_create(&o, len, context)?;
1081
1082        let this_arg = args.get_or_undefined(1);
1083
1084        // 5. Let k be 0.
1085        // 6. Repeat, while k < len,
1086        for k in 0..len {
1087            // a. Let Pk be ! ToString(๐”ฝ(k)).
1088            // b. Let k_present be ? HasProperty(O, Pk).
1089            let k_present = o.has_property(k, context)?;
1090            // c. If k_present is true, then
1091            if k_present {
1092                // i. Let kValue be ? Get(O, Pk).
1093                let k_value = o.get(k, context)?;
1094                // ii. Let mappedValue be ? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป).
1095                let mapped_value =
1096                    context.call(callback, this_arg, &[k_value, k.into(), this.into()])?;
1097                // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
1098                a.create_data_property_or_throw(k, mapped_value, context)?;
1099            }
1100            // d. Set k to k + 1.
1101        }
1102        // 7. Return A.
1103        Ok(a.into())
1104    }
1105
1106    /// `Array.prototype.indexOf( searchElement[, fromIndex ] )`
1107    ///
1108    /// More information:
1109    ///  - [ECMAScript reference][spec]
1110    ///  - [MDN documentation][mdn]
1111    ///
1112    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof
1113    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
1114    pub(crate) fn index_of(
1115        this: &JsValue,
1116        args: &[JsValue],
1117        context: &mut Context,
1118    ) -> JsResult<JsValue> {
1119        // 1. Let O be ? ToObject(this value).
1120        let o = this.to_object(context)?;
1121
1122        // 2. Let len be ? LengthOfArrayLike(O).
1123        let len = o.length_of_array_like(context)? as i64;
1124
1125        // 3. If len is 0, return -1๐”ฝ.
1126        if len == 0 {
1127            return Ok(JsValue::new(-1));
1128        }
1129
1130        // 4. Let n be ? ToIntegerOrInfinity(fromIndex).
1131        let n = args
1132            .get(1)
1133            .cloned()
1134            .unwrap_or_default()
1135            .to_integer_or_infinity(context)?;
1136        // 5. Assert: If fromIndex is undefined, then n is 0.
1137        let n = match n {
1138            // 6. If n is +โˆž, return -1๐”ฝ.
1139            IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(-1)),
1140            // 7. Else if n is -โˆž, set n to 0.
1141            IntegerOrInfinity::NegativeInfinity => 0,
1142            IntegerOrInfinity::Integer(value) => value,
1143        };
1144
1145        // 8. If n โ‰ฅ 0, then
1146        let mut k;
1147        if n >= 0 {
1148            // a. Let k be n.
1149            k = n
1150        // 9. Else,
1151        } else {
1152            // a. Let k be len + n.
1153            k = len + n;
1154            // b. If k < 0, set k to 0.
1155            if k < 0 {
1156                k = 0;
1157            }
1158        };
1159
1160        let search_element = args.get_or_undefined(0);
1161
1162        // 10. Repeat, while k < len,
1163        while k < len {
1164            // a. Let kPresent be ? HasProperty(O, ! ToString(๐”ฝ(k))).
1165            let k_present = o.has_property(k, context)?;
1166            // b. If kPresent is true, then
1167            if k_present {
1168                // i. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
1169                let element_k = o.get(k, context)?;
1170                // ii. Let same be IsStrictlyEqual(searchElement, elementK).
1171                // iii. If same is true, return ๐”ฝ(k).
1172                if search_element.strict_equals(&element_k) {
1173                    return Ok(JsValue::new(k));
1174                }
1175            }
1176            // c. Set k to k + 1.
1177            k += 1;
1178        }
1179        // 11. Return -1๐”ฝ.
1180        Ok(JsValue::new(-1))
1181    }
1182
1183    /// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )`
1184    ///
1185    ///
1186    /// lastIndexOf compares searchElement to the elements of the array in descending order
1187    /// using the Strict Equality Comparison algorithm, and if found at one or more indices,
1188    /// returns the largest such index; otherwise, -1 is returned.
1189    ///
1190    /// The optional second argument fromIndex defaults to the array's length minus one
1191    /// (i.e. the whole array is searched). If it is greater than or equal to the length of the array,
1192    /// the whole array will be searched. If it is negative, it is used as the offset from the end
1193    /// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned.
1194    ///
1195    /// More information:
1196    ///  - [ECMAScript reference][spec]
1197    ///  - [MDN documentation][mdn]
1198    ///
1199    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof
1200    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf
1201    pub(crate) fn last_index_of(
1202        this: &JsValue,
1203        args: &[JsValue],
1204        context: &mut Context,
1205    ) -> JsResult<JsValue> {
1206        // 1. Let O be ? ToObject(this value).
1207        let o = this.to_object(context)?;
1208
1209        // 2. Let len be ? LengthOfArrayLike(O).
1210        let len = o.length_of_array_like(context)? as i64;
1211
1212        // 3. If len is 0, return -1๐”ฝ.
1213        if len == 0 {
1214            return Ok(JsValue::new(-1));
1215        }
1216
1217        // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1.
1218        let n = if let Some(from_index) = args.get(1) {
1219            from_index.to_integer_or_infinity(context)?
1220        } else {
1221            IntegerOrInfinity::Integer(len - 1)
1222        };
1223
1224        let mut k = match n {
1225            // 5. If n is -โˆž, return -1๐”ฝ.
1226            IntegerOrInfinity::NegativeInfinity => return Ok(JsValue::new(-1)),
1227            // 6. If n โ‰ฅ 0, then
1228            //     a. Let k be min(n, len - 1).
1229            IntegerOrInfinity::Integer(n) if n >= 0 => min(n, len - 1),
1230            IntegerOrInfinity::PositiveInfinity => len - 1,
1231            // 7. Else,
1232            //     a. Let k be len + n.
1233            IntegerOrInfinity::Integer(n) => len + n,
1234        };
1235
1236        let search_element = args.get_or_undefined(0);
1237
1238        // 8. Repeat, while k โ‰ฅ 0,
1239        while k >= 0 {
1240            // a. Let kPresent be ? HasProperty(O, ! ToString(๐”ฝ(k))).
1241            let k_present = o.has_property(k, context)?;
1242            // b. If kPresent is true, then
1243            if k_present {
1244                // i. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
1245                let element_k = o.get(k, context)?;
1246                // ii. Let same be IsStrictlyEqual(searchElement, elementK).
1247                // iii. If same is true, return ๐”ฝ(k).
1248                if JsValue::strict_equals(search_element, &element_k) {
1249                    return Ok(JsValue::new(k));
1250                }
1251            }
1252            // c. Set k to k - 1.
1253            k -= 1;
1254        }
1255        // 9. Return -1๐”ฝ.
1256        Ok(JsValue::new(-1))
1257    }
1258
1259    /// `Array.prototype.find( callback, [thisArg] )`
1260    ///
1261    /// The find method executes the callback function once for each index of the array
1262    /// until the callback returns a truthy value. If so, find immediately returns the value
1263    /// of that element. Otherwise, find returns undefined.
1264    ///
1265    /// More information:
1266    ///  - [ECMAScript reference][spec]
1267    ///  - [MDN documentation][mdn]
1268    ///
1269    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find
1270    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
1271    pub(crate) fn find(
1272        this: &JsValue,
1273        args: &[JsValue],
1274        context: &mut Context,
1275    ) -> JsResult<JsValue> {
1276        // 1. Let O be ? ToObject(this value).
1277        let o = this.to_object(context)?;
1278
1279        // 2. Let len be ? LengthOfArrayLike(O).
1280        let len = o.length_of_array_like(context)?;
1281
1282        // 3. If IsCallable(predicate) is false, throw a TypeError exception.
1283        let predicate = match args.get(0).and_then(JsValue::as_object) {
1284            Some(predicate) if predicate.is_callable() => predicate,
1285            _ => {
1286                return context.throw_type_error("Array.prototype.find: predicate is not callable")
1287            }
1288        };
1289
1290        let this_arg = args.get_or_undefined(1);
1291
1292        // 4. Let k be 0.
1293        let mut k = 0;
1294        // 5. Repeat, while k < len,
1295        while k < len {
1296            // a. Let Pk be ! ToString(๐”ฝ(k)).
1297            let pk = k;
1298            // b. Let kValue be ? Get(O, Pk).
1299            let k_value = o.get(pk, context)?;
1300            // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
1301            let test_result = predicate
1302                .call(
1303                    this_arg,
1304                    &[k_value.clone(), k.into(), o.clone().into()],
1305                    context,
1306                )?
1307                .to_boolean();
1308            // d. If testResult is true, return kValue.
1309            if test_result {
1310                return Ok(k_value);
1311            }
1312            // e. Set k to k + 1.
1313            k += 1;
1314        }
1315        // 6. Return undefined.
1316        Ok(JsValue::undefined())
1317    }
1318
1319    /// `Array.prototype.findIndex( predicate [ , thisArg ] )`
1320    ///
1321    /// This method executes the provided predicate function for each element of the array.
1322    /// If the predicate function returns `true` for an element, this method returns the index of the element.
1323    /// If all elements return `false`, the value `-1` is returned.
1324    ///
1325    /// More information:
1326    ///  - [ECMAScript reference][spec]
1327    ///  - [MDN documentation][mdn]
1328    ///
1329    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex
1330    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
1331    pub(crate) fn find_index(
1332        this: &JsValue,
1333        args: &[JsValue],
1334        context: &mut Context,
1335    ) -> JsResult<JsValue> {
1336        // 1. Let O be ? ToObject(this value).
1337        let o = this.to_object(context)?;
1338
1339        // 2. Let len be ? LengthOfArrayLike(O).
1340        let len = o.length_of_array_like(context)?;
1341
1342        // 3. If IsCallable(predicate) is false, throw a TypeError exception.
1343        let predicate = match args.get(0).and_then(JsValue::as_object) {
1344            Some(predicate) if predicate.is_callable() => predicate,
1345            _ => {
1346                return context
1347                    .throw_type_error("Array.prototype.reduce: predicate is not callable")
1348            }
1349        };
1350
1351        let this_arg = args.get_or_undefined(1);
1352
1353        // 4. Let k be 0.
1354        let mut k = 0;
1355        // 5. Repeat, while k < len,
1356        while k < len {
1357            // a. Let Pk be ! ToString(๐”ฝ(k)).
1358            let pk = k;
1359            // b. Let kValue be ? Get(O, Pk).
1360            let k_value = o.get(pk, context)?;
1361            // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
1362            let test_result = predicate
1363                .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
1364                .to_boolean();
1365            // d. If testResult is true, return ๐”ฝ(k).
1366            if test_result {
1367                return Ok(JsValue::new(k));
1368            }
1369            // e. Set k to k + 1.
1370            k += 1;
1371        }
1372        // 6. Return -1๐”ฝ.
1373        Ok(JsValue::new(-1))
1374    }
1375
1376    /// `Array.prototype.flat( [depth] )`
1377    ///
1378    /// This method creates a new array with all sub-array elements concatenated into it
1379    /// recursively up to the specified depth.
1380    ///
1381    /// More information:
1382    ///  - [ECMAScript reference][spec]
1383    ///  - [MDN documentation][mdn]
1384    ///
1385    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flat
1386    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
1387    pub(crate) fn flat(
1388        this: &JsValue,
1389        args: &[JsValue],
1390        context: &mut Context,
1391    ) -> JsResult<JsValue> {
1392        // 1. Let O be ToObject(this value)
1393        let o = this.to_object(context)?;
1394
1395        // 2. Let sourceLen be LengthOfArrayLike(O)
1396        let source_len = o.length_of_array_like(context)?;
1397
1398        // 3. Let depthNum be 1
1399        let mut depth_num = 1;
1400
1401        // 4. If depth is not undefined, then set depthNum to IntegerOrInfinity(depth)
1402        if let Some(depth) = args.get(0) {
1403            // a. Set depthNum to ? ToIntegerOrInfinity(depth).
1404            // b. If depthNum < 0, set depthNum to 0.
1405            match depth.to_integer_or_infinity(context)? {
1406                IntegerOrInfinity::Integer(value) if value >= 0 => depth_num = value as u64,
1407                IntegerOrInfinity::PositiveInfinity => depth_num = u64::MAX,
1408                _ => depth_num = 0,
1409            }
1410        };
1411
1412        // 5. Let A be ArraySpeciesCreate(O, 0)
1413        let a = Self::array_species_create(&o, 0, context)?;
1414
1415        // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum)
1416        Self::flatten_into_array(
1417            &a,
1418            &o,
1419            source_len as u64,
1420            0,
1421            depth_num,
1422            None,
1423            &JsValue::undefined(),
1424            context,
1425        )?;
1426
1427        Ok(a.into())
1428    }
1429
1430    /// `Array.prototype.flatMap( callback, [ thisArg ] )`
1431    ///
1432    /// This method returns a new array formed by applying a given callback function to
1433    /// each element of the array, and then flattening the result by one level. It is
1434    /// identical to a `map()` followed by a `flat()` of depth 1, but slightly more
1435    /// efficient than calling those two methods separately.
1436    ///
1437    /// More information:
1438    ///  - [ECMAScript reference][spec]
1439    ///  - [MDN documentation][mdn]
1440    ///
1441    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.flatMap
1442    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
1443    pub(crate) fn flat_map(
1444        this: &JsValue,
1445        args: &[JsValue],
1446        context: &mut Context,
1447    ) -> JsResult<JsValue> {
1448        // 1. Let O be ToObject(this value)
1449        let o = this.to_object(context)?;
1450
1451        // 2. Let sourceLen be LengthOfArrayLike(O)
1452        let source_len = o.length_of_array_like(context)?;
1453
1454        // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
1455        let mapper_function = args.get_or_undefined(0);
1456        if !mapper_function.is_function() {
1457            return context.throw_type_error("flatMap mapper function is not callable");
1458        }
1459
1460        // 4. Let A be ? ArraySpeciesCreate(O, 0).
1461        let a = Self::array_species_create(&o, 0, context)?;
1462
1463        // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg).
1464        Self::flatten_into_array(
1465            &a,
1466            &o,
1467            source_len as u64,
1468            0,
1469            1,
1470            Some(mapper_function.as_object().unwrap()),
1471            args.get_or_undefined(1),
1472            context,
1473        )?;
1474
1475        // 6. Return A
1476        Ok(a.into())
1477    }
1478
1479    /// Abstract method `FlattenIntoArray`.
1480    ///
1481    /// More information:
1482    ///  - [ECMAScript reference][spec]
1483    ///
1484    /// [spec]: https://tc39.es/ecma262/#sec-flattenintoarray
1485    #[allow(clippy::too_many_arguments)]
1486    fn flatten_into_array(
1487        target: &JsObject,
1488        source: &JsObject,
1489        source_len: u64,
1490        start: u64,
1491        depth: u64,
1492        mapper_function: Option<JsObject>,
1493        this_arg: &JsValue,
1494        context: &mut Context,
1495    ) -> JsResult<u64> {
1496        // 1. Assert target is Object
1497        // 2. Assert source is Object
1498
1499        // 3. Assert if mapper_function is present, then:
1500        // - IsCallable(mapper_function) is true
1501        // - thisArg is present
1502        // - depth is 1
1503
1504        // 4. Let targetIndex be start
1505        let mut target_index = start;
1506
1507        // 5. Let sourceIndex be 0
1508        let mut source_index = 0;
1509
1510        // 6. Repeat, while R(sourceIndex) < sourceLen
1511        while source_index < source_len {
1512            // a. Let P be ToString(sourceIndex)
1513            let p = source_index;
1514
1515            // b. Let exists be ? HasProperty(source, P).
1516            let exists = source.has_property(p, context)?;
1517            // c. If exists is true, then
1518            if exists {
1519                // i. Let element be Get(source, P)
1520                let mut element = source.get(p, context)?;
1521
1522                // ii. If mapperFunction is present, then
1523                if let Some(ref mapper_function) = mapper_function {
1524                    // 1. Set element to ? Call(mapperFunction, thisArg, <<element, sourceIndex, source>>)
1525                    element = mapper_function.call(
1526                        this_arg,
1527                        &[element, source_index.into(), source.clone().into()],
1528                        context,
1529                    )?;
1530                }
1531
1532                // iii. Let shouldFlatten be false
1533                let mut should_flatten = false;
1534
1535                // iv. If depth > 0, then
1536                if depth > 0 {
1537                    // 1. Set shouldFlatten to ? IsArray(element).
1538                    should_flatten = element.is_array(context)?;
1539                }
1540
1541                // v. If shouldFlatten is true
1542                if should_flatten {
1543                    // For `should_flatten` to be true, element must be an object.
1544                    let element = element.as_object().unwrap();
1545
1546                    // 1. If depth is +Infinity let newDepth be +Infinity
1547                    let new_depth = if depth == u64::MAX {
1548                        u64::MAX
1549                    // 2. Else, let newDepth be depth - 1
1550                    } else {
1551                        depth - 1
1552                    };
1553
1554                    // 3. Let elementLen be ? LengthOfArrayLike(element)
1555                    let element_len = element.length_of_array_like(context)?;
1556
1557                    // 4. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, newDepth)
1558                    target_index = Self::flatten_into_array(
1559                        target,
1560                        &element,
1561                        element_len as u64,
1562                        target_index,
1563                        new_depth,
1564                        None,
1565                        &JsValue::undefined(),
1566                        context,
1567                    )?;
1568
1569                // vi. Else
1570                } else {
1571                    // 1. If targetIndex >= 2^53 - 1, throw a TypeError exception
1572                    if target_index >= Number::MAX_SAFE_INTEGER as u64 {
1573                        return Err(context
1574                            .construct_type_error("Target index exceeded max safe integer value"));
1575                    }
1576
1577                    // 2. Perform ? CreateDataPropertyOrThrow(target, targetIndex, element)
1578                    target.create_data_property_or_throw(target_index, element, context)?;
1579
1580                    // 3. Set targetIndex to targetIndex + 1
1581                    target_index += 1;
1582                }
1583            }
1584            // d. Set sourceIndex to sourceIndex + 1
1585            source_index += 1;
1586        }
1587
1588        // 7. Return targetIndex
1589        Ok(target_index)
1590    }
1591
1592    /// `Array.prototype.fill( value[, start[, end]] )`
1593    ///
1594    /// The method fills (modifies) all the elements of an array from start index (default 0)
1595    /// to an end index (default array length) with a static value. It returns the modified array.
1596    ///
1597    /// More information:
1598    ///  - [ECMAScript reference][spec]
1599    ///  - [MDN documentation][mdn]
1600    ///
1601    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
1602    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
1603    pub(crate) fn fill(
1604        this: &JsValue,
1605        args: &[JsValue],
1606        context: &mut Context,
1607    ) -> JsResult<JsValue> {
1608        // 1. Let O be ? ToObject(this value).
1609        let o = this.to_object(context)?;
1610
1611        // 2. Let len be ? LengthOfArrayLike(O).
1612        let len = o.length_of_array_like(context)?;
1613
1614        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
1615        // 4. If relativeStart is -โˆž, let k be 0.
1616        // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
1617        // 6. Else, let k be min(relativeStart, len).
1618        let mut k = Self::get_relative_start(context, args.get(1), len)?;
1619
1620        // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
1621        // 8. If relativeEnd is -โˆž, let final be 0.
1622        // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
1623        // 10. Else, let final be min(relativeEnd, len).
1624        let final_ = Self::get_relative_end(context, args.get(2), len)?;
1625
1626        let value = args.get_or_undefined(0);
1627
1628        // 11. Repeat, while k < final,
1629        while k < final_ {
1630            // a. Let Pk be ! ToString(๐”ฝ(k)).
1631            let pk = k;
1632            // b. Perform ? Set(O, Pk, value, true).
1633            o.set(pk, value.clone(), true, context)?;
1634            // c. Set k to k + 1.
1635            k += 1;
1636        }
1637        // 12. Return O.
1638        Ok(o.into())
1639    }
1640
1641    /// `Array.prototype.includes( valueToFind [, fromIndex] )`
1642    ///
1643    /// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate.
1644    ///
1645    /// More information:
1646    ///  - [ECMAScript reference][spec]
1647    ///  - [MDN documentation][mdn]
1648    ///
1649    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes
1650    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
1651    pub(crate) fn includes_value(
1652        this: &JsValue,
1653        args: &[JsValue],
1654        context: &mut Context,
1655    ) -> JsResult<JsValue> {
1656        // 1. Let O be ? ToObject(this value).
1657        let o = this.to_object(context)?;
1658
1659        // 2. Let len be ? LengthOfArrayLike(O).
1660        let len = o.length_of_array_like(context)? as i64;
1661
1662        // 3. If len is 0, return false.
1663        if len == 0 {
1664            return Ok(JsValue::new(false));
1665        }
1666
1667        // 4. Let n be ? ToIntegerOrInfinity(fromIndex).
1668        let n = args
1669            .get(1)
1670            .cloned()
1671            .unwrap_or_default()
1672            .to_integer_or_infinity(context)?;
1673        // 5. Assert: If fromIndex is undefined, then n is 0.
1674        // 6. If n is +โˆž, return false.
1675        // 7. Else if n is -โˆž, set n to 0.
1676        let n = match n {
1677            IntegerOrInfinity::PositiveInfinity => return Ok(JsValue::new(false)),
1678            IntegerOrInfinity::NegativeInfinity => 0,
1679            IntegerOrInfinity::Integer(value) => value,
1680        };
1681
1682        // 8. If n โ‰ฅ 0, then
1683        let mut k;
1684        if n >= 0 {
1685            // a. Let k be n.
1686            k = n
1687        // 9. Else,
1688        } else {
1689            // a. Let k be len + n.
1690            k = len + n;
1691            // b. If k < 0, set k to 0.
1692            if k < 0 {
1693                k = 0;
1694            }
1695        }
1696
1697        let search_element = args.get_or_undefined(0);
1698
1699        // 10. Repeat, while k < len,
1700        while k < len {
1701            // a. Let elementK be ? Get(O, ! ToString(๐”ฝ(k))).
1702            let element_k = o.get(k, context)?;
1703            // b. If SameValueZero(searchElement, elementK) is true, return true.
1704            if JsValue::same_value_zero(search_element, &element_k) {
1705                return Ok(JsValue::new(true));
1706            }
1707            // c. Set k to k + 1.
1708            k += 1;
1709        }
1710        // 11. Return false.
1711        Ok(JsValue::new(false))
1712    }
1713
1714    /// `Array.prototype.slice( [begin[, end]] )`
1715    ///
1716    /// The slice method takes two arguments, start and end, and returns an array containing the
1717    /// elements of the array from element start up to, but not including, element end (or through the
1718    /// end of the array if end is undefined). If start is negative, it is treated as length + start
1719    /// where length is the length of the array. If end is negative, it is treated as length + end where
1720    /// length is the length of the array.
1721    ///
1722    /// More information:
1723    ///  - [ECMAScript reference][spec]
1724    ///  - [MDN documentation][mdn]
1725    ///
1726    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice
1727    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
1728    pub(crate) fn slice(
1729        this: &JsValue,
1730        args: &[JsValue],
1731        context: &mut Context,
1732    ) -> JsResult<JsValue> {
1733        // 1. Let O be ? ToObject(this value).
1734        let o = this.to_object(context)?;
1735
1736        // 2. Let len be ? LengthOfArrayLike(O).
1737        let len = o.length_of_array_like(context)?;
1738
1739        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
1740        // 4. If relativeStart is -โˆž, let k be 0.
1741        // 5. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
1742        // 6. Else, let k be min(relativeStart, len).
1743        let mut k = Self::get_relative_start(context, args.get(0), len)?;
1744
1745        // 7. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
1746        // 8. If relativeEnd is -โˆž, let final be 0.
1747        // 9. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
1748        // 10. Else, let final be min(relativeEnd, len).
1749        let final_ = Self::get_relative_end(context, args.get(1), len)?;
1750
1751        // 11. Let count be max(final - k, 0).
1752        let count = final_.saturating_sub(k);
1753
1754        // 12. Let A be ? ArraySpeciesCreate(O, count).
1755        let a = Self::array_species_create(&o, count, context)?;
1756
1757        // 13. Let n be 0.
1758        let mut n: u64 = 0;
1759        // 14. Repeat, while k < final,
1760        while k < final_ {
1761            // a. Let Pk be ! ToString(๐”ฝ(k)).
1762            let pk = k;
1763            // b. Let kPresent be ? HasProperty(O, Pk).
1764            let k_present = o.has_property(pk, context)?;
1765            // c. If kPresent is true, then
1766            if k_present {
1767                // i. Let kValue be ? Get(O, Pk).
1768                let k_value = o.get(pk, context)?;
1769                // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(n)), kValue).
1770                a.create_data_property_or_throw(n, k_value, context)?;
1771            }
1772            // d. Set k to k + 1.
1773            k += 1;
1774            // e. Set n to n + 1.
1775            n += 1;
1776        }
1777
1778        // 15. Perform ? Set(A, "length", ๐”ฝ(n), true).
1779        a.set("length", n, true, context)?;
1780
1781        // 16. Return A.
1782        Ok(a.into())
1783    }
1784
1785    /// `Array.prototype.splice ( start, [deleteCount[, ...items]] )`
1786    ///
1787    /// Splices an array by following
1788    /// The deleteCount elements of the array starting at integer index start are replaced by the elements of items.
1789    /// An Array object containing the deleted elements (if any) is returned.
1790    pub(crate) fn splice(
1791        this: &JsValue,
1792        args: &[JsValue],
1793        context: &mut Context,
1794    ) -> JsResult<JsValue> {
1795        // 1. Let O be ? ToObject(this value).
1796        let o = this.to_object(context)?;
1797        // 2. Let len be ? LengthOfArrayLike(O).
1798        let len = o.length_of_array_like(context)?;
1799
1800        let start = args.get(0);
1801        let delete_count = args.get(1);
1802        let items = args.get(2..).unwrap_or(&[]);
1803        // 3. Let relativeStart be ? ToIntegerOrInfinity(start).
1804        // 4. If relativeStart is -โˆž, let actualStart be 0.
1805        // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, 0).
1806        // 6. Else, let actualStart be min(relativeStart, len).
1807        let actual_start = Self::get_relative_start(context, start, len)?;
1808        // 7. If start is not present, then
1809        let insert_count = if start.is_none() || delete_count.is_none() {
1810            // 7a. Let insertCount be 0.
1811            // 8. Else if deleteCount is not present, then
1812            // a. Let insertCount be 0.
1813            0
1814        // 9. Else,
1815        } else {
1816            // 9a. Let insertCount be the number of elements in items.
1817            items.len()
1818        };
1819        let actual_delete_count = if start.is_none() {
1820            // 7b. Let actualDeleteCount be 0.
1821            0
1822            // 8. Else if deleteCount is not present, then
1823        } else if delete_count.is_none() {
1824            // 8b. Let actualDeleteCount be len - actualStart.
1825            len - actual_start
1826        // 9. Else,
1827        } else {
1828            // b. Let dc be ? ToIntegerOrInfinity(deleteCount).
1829            let dc = delete_count
1830                .cloned()
1831                .unwrap_or_default()
1832                .to_integer_or_infinity(context)?;
1833            // c. Let actualDeleteCount be the result of clamping dc between 0 and len - actualStart.
1834            let max = len - actual_start;
1835            match dc {
1836                IntegerOrInfinity::Integer(i) => (i as usize).clamp(0, max),
1837                IntegerOrInfinity::PositiveInfinity => max,
1838                IntegerOrInfinity::NegativeInfinity => 0,
1839            }
1840        };
1841
1842        // 10. If len + insertCount - actualDeleteCount > 2^53 - 1, throw a TypeError exception.
1843        if len + insert_count - actual_delete_count > Number::MAX_SAFE_INTEGER as usize {
1844            return context.throw_type_error("Target splice exceeded max safe integer value");
1845        }
1846
1847        // 11. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
1848        let arr = Self::array_species_create(&o, actual_delete_count, context)?;
1849        // 12. Let k be 0.
1850        // 13. Repeat, while k < actualDeleteCount,
1851        for k in 0..actual_delete_count {
1852            // a. Let from be ! ToString(๐”ฝ(actualStart + k)).
1853            // b. Let fromPresent be ? HasProperty(O, from).
1854            let from_present = o.has_property(actual_start + k, context)?;
1855            // c. If fromPresent is true, then
1856            if from_present {
1857                // i. Let fromValue be ? Get(O, from).
1858                let from_value = o.get(actual_start + k, context)?;
1859                // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(k)), fromValue).
1860                arr.create_data_property_or_throw(k, from_value, context)?;
1861            }
1862            // d. Set k to k + 1.
1863        }
1864
1865        // 14. Perform ? Set(A, "length", ๐”ฝ(actualDeleteCount), true).
1866        arr.set("length", actual_delete_count, true, context)?;
1867
1868        // 15. Let itemCount be the number of elements in items.
1869        let item_count = items.len();
1870
1871        match item_count.cmp(&actual_delete_count) {
1872            // 16. If itemCount < actualDeleteCount, then
1873            Ordering::Less => {
1874                //     a. Set k to actualStart.
1875                //     b. Repeat, while k < (len - actualDeleteCount),
1876                for k in actual_start..(len - actual_delete_count) {
1877                    // i. Let from be ! ToString(๐”ฝ(k + actualDeleteCount)).
1878                    let from = k + actual_delete_count;
1879                    // ii. Let to be ! ToString(๐”ฝ(k + itemCount)).
1880                    let to = k + item_count;
1881                    // iii. Let fromPresent be ? HasProperty(O, from).
1882                    let from_present = o.has_property(from, context)?;
1883                    // iv. If fromPresent is true, then
1884                    if from_present {
1885                        // 1. Let fromValue be ? Get(O, from).
1886                        let from_value = o.get(from, context)?;
1887                        // 2. Perform ? Set(O, to, fromValue, true).
1888                        o.set(to, from_value, true, context)?;
1889                    // v. Else,
1890                    } else {
1891                        // 1. Assert: fromPresent is false.
1892                        debug_assert!(!from_present);
1893                        // 2. Perform ? DeletePropertyOrThrow(O, to).
1894                        o.delete_property_or_throw(to, context)?;
1895                    }
1896                    // vi. Set k to k + 1.
1897                }
1898                // c. Set k to len.
1899                // d. Repeat, while k > (len - actualDeleteCount + itemCount),
1900                for k in ((len - actual_delete_count + item_count)..len).rev() {
1901                    // i. Perform ? DeletePropertyOrThrow(O, ! ToString(๐”ฝ(k - 1))).
1902                    o.delete_property_or_throw(k, context)?;
1903                    // ii. Set k to k - 1.
1904                }
1905            }
1906            // 17. Else if itemCount > actualDeleteCount, then
1907            Ordering::Greater => {
1908                // a. Set k to (len - actualDeleteCount).
1909                // b. Repeat, while k > actualStart,
1910                for k in (actual_start..len - actual_delete_count).rev() {
1911                    // i. Let from be ! ToString(๐”ฝ(k + actualDeleteCount - 1)).
1912                    let from = k + actual_delete_count;
1913                    // ii. Let to be ! ToString(๐”ฝ(k + itemCount - 1)).
1914                    let to = k + item_count;
1915                    // iii. Let fromPresent be ? HasProperty(O, from).
1916                    let from_present = o.has_property(from, context)?;
1917                    // iv. If fromPresent is true, then
1918                    if from_present {
1919                        // 1. Let fromValue be ? Get(O, from).
1920                        let from_value = o.get(from, context)?;
1921                        // 2. Perform ? Set(O, to, fromValue, true).
1922                        o.set(to, from_value, true, context)?;
1923                    // v. Else,
1924                    } else {
1925                        // 1. Assert: fromPresent is false.
1926                        debug_assert!(!from_present);
1927                        // 2. Perform ? DeletePropertyOrThrow(O, to).
1928                        o.delete_property_or_throw(to, context)?;
1929                    }
1930                    // vi. Set k to k - 1.
1931                }
1932            }
1933            Ordering::Equal => {}
1934        };
1935
1936        // 18. Set k to actualStart.
1937        // 19. For each element E of items, do
1938        if item_count > 0 {
1939            for (k, item) in items
1940                .iter()
1941                .enumerate()
1942                .map(|(i, val)| (i + actual_start, val))
1943            {
1944                // a. Perform ? Set(O, ! ToString(๐”ฝ(k)), E, true).
1945                o.set(k, item, true, context)?;
1946                // b. Set k to k + 1.
1947            }
1948        }
1949
1950        // 20. Perform ? Set(O, "length", ๐”ฝ(len - actualDeleteCount + itemCount), true).
1951        o.set(
1952            "length",
1953            len - actual_delete_count + item_count,
1954            true,
1955            context,
1956        )?;
1957
1958        // 21. Return A.
1959        Ok(JsValue::from(arr))
1960    }
1961
1962    /// `Array.prototype.filter( callback, [ thisArg ] )`
1963    ///
1964    /// For each element in the array the callback function is called, and a new
1965    /// array is constructed for every value whose callback returned a truthy value.
1966    ///
1967    /// More information:
1968    ///  - [ECMAScript reference][spec]
1969    ///  - [MDN documentation][mdn]
1970    ///
1971    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter
1972    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
1973    pub(crate) fn filter(
1974        this: &JsValue,
1975        args: &[JsValue],
1976        context: &mut Context,
1977    ) -> JsResult<JsValue> {
1978        // 1. Let O be ? ToObject(this value).
1979        let o = this.to_object(context)?;
1980
1981        // 2. Let len be ? LengthOfArrayLike(O).
1982        let length = o.length_of_array_like(context)?;
1983
1984        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
1985        let callback = args
1986            .get(0)
1987            .map(|a| a.to_object(context))
1988            .transpose()?
1989            .ok_or_else(|| {
1990                context.construct_type_error(
1991                    "missing argument 0 when calling function Array.prototype.filter",
1992                )
1993            })?;
1994        let this_arg = args.get_or_undefined(1);
1995
1996        if !callback.is_callable() {
1997            return context.throw_type_error("the callback must be callable");
1998        }
1999
2000        // 4. Let A be ? ArraySpeciesCreate(O, 0).
2001        let a = Self::array_species_create(&o, 0, context)?;
2002
2003        // 5. Let k be 0.
2004        // 6. Let to be 0.
2005        let mut to = 0u32;
2006        // 7. Repeat, while k < len,
2007        for idx in 0..length {
2008            // a. Let Pk be ! ToString(๐”ฝ(k)).
2009            // b. Let kPresent be ? HasProperty(O, Pk).
2010            // c. If kPresent is true, then
2011            if o.has_property(idx, context)? {
2012                // i. Let kValue be ? Get(O, Pk).
2013                let element = o.get(idx, context)?;
2014
2015                let args = [element.clone(), JsValue::new(idx), JsValue::new(o.clone())];
2016
2017                // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
2018                let selected = callback.call(this_arg, &args, context)?.to_boolean();
2019
2020                // iii. If selected is true, then
2021                if selected {
2022                    // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(๐”ฝ(to)), kValue).
2023                    a.create_data_property_or_throw(to, element, context)?;
2024                    // 2. Set to to to + 1.
2025                    to += 1;
2026                }
2027            }
2028        }
2029
2030        // 8. Return A.
2031        Ok(a.into())
2032    }
2033
2034    /// Array.prototype.some ( callbackfn [ , thisArg ] )
2035    ///
2036    /// The some method tests whether at least one element in the array passes
2037    /// the test implemented by the provided callback function. It returns a Boolean value,
2038    /// true if the callback function returns a truthy value for at least one element
2039    /// in the array. Otherwise, false.
2040    ///
2041    /// Caution: Calling this method on an empty array returns false for any condition!
2042    ///
2043    /// More information:
2044    ///  - [ECMAScript reference][spec]
2045    ///  - [MDN documentation][mdn]
2046    ///
2047    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some
2048    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
2049    pub(crate) fn some(
2050        this: &JsValue,
2051        args: &[JsValue],
2052        context: &mut Context,
2053    ) -> JsResult<JsValue> {
2054        // 1. Let O be ? ToObject(this value).
2055        let o = this.to_object(context)?;
2056        // 2. Let len be ? LengthOfArrayLike(O).
2057        let len = o.length_of_array_like(context)?;
2058        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2059        let callback = if let Some(arg) = args
2060            .get(0)
2061            .and_then(JsValue::as_object)
2062            .filter(JsObject::is_callable)
2063        {
2064            arg
2065        } else {
2066            return context.throw_type_error("Array.prototype.some: callback is not callable");
2067        };
2068
2069        // 4. Let k be 0.
2070        // 5. Repeat, while k < len,
2071        for k in 0..len {
2072            // a. Let Pk be ! ToString(๐”ฝ(k)).
2073            // b. Let kPresent be ? HasProperty(O, Pk).
2074            let k_present = o.has_property(k, context)?;
2075            // c. If kPresent is true, then
2076            if k_present {
2077                // i. Let kValue be ? Get(O, Pk).
2078                let k_value = o.get(k, context)?;
2079                // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, ยซ kValue, ๐”ฝ(k), O ยป)).
2080                let this_arg = args.get_or_undefined(1);
2081                let test_result = callback
2082                    .call(this_arg, &[k_value, k.into(), o.clone().into()], context)?
2083                    .to_boolean();
2084                // iii. If testResult is true, return true.
2085                if test_result {
2086                    return Ok(JsValue::new(true));
2087                }
2088            }
2089            // d. Set k to k + 1.
2090        }
2091        // 6. Return false.
2092        Ok(JsValue::new(false))
2093    }
2094
2095    /// Array.prototype.sort ( comparefn )
2096    ///
2097    /// The sort method sorts the elements of an array in place and returns the sorted array.
2098    /// The default sort order is ascending, built upon converting the elements into strings,
2099    /// then comparing their sequences of UTF-16 code units values.
2100    ///
2101    /// More information:
2102    ///  - [ECMAScript reference][spec]
2103    ///  - [MDN documentation][mdn]
2104    ///
2105    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.sort
2106    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
2107    pub(crate) fn sort(
2108        this: &JsValue,
2109        args: &[JsValue],
2110        context: &mut Context,
2111    ) -> JsResult<JsValue> {
2112        // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception.
2113        let comparefn = match args.get(0).cloned() {
2114            // todo: change to `is_callable` inside `JsValue`
2115            Some(fun) if fun.is_function() => fun,
2116            None => JsValue::undefined(),
2117            _ => {
2118                return context.throw_type_error(
2119                    "The comparison function must be either a function or undefined",
2120                )
2121            }
2122        };
2123
2124        // Abstract method `SortCompare`.
2125        //
2126        // More information:
2127        //  - [ECMAScript reference][spec]
2128        //
2129        // [spec]: https://tc39.es/ecma262/#sec-sortcompare
2130        let sort_compare =
2131            |x: &JsValue, y: &JsValue, context: &mut Context| -> JsResult<Ordering> {
2132                match (x.is_undefined(), y.is_undefined()) {
2133                    // 1. If x and y are both undefined, return +0๐”ฝ.
2134                    (true, true) => return Ok(Ordering::Equal),
2135                    // 2. If x is undefined, return 1๐”ฝ.
2136                    (true, false) => return Ok(Ordering::Greater),
2137                    // 3. If y is undefined, return -1๐”ฝ.
2138                    (false, true) => return Ok(Ordering::Less),
2139                    _ => {}
2140                }
2141
2142                // 4. If comparefn is not undefined, then
2143                if !comparefn.is_undefined() {
2144                    let args = [x.clone(), y.clone()];
2145                    // a. Let v be ? ToNumber(? Call(comparefn, undefined, ยซ x, y ยป)).
2146                    let v = context
2147                        .call(&comparefn, &JsValue::Undefined, &args)?
2148                        .to_number(context)?;
2149                    // b. If v is NaN, return +0๐”ฝ.
2150                    // c. Return v.
2151                    return Ok(v.partial_cmp(&0.0).unwrap_or(Ordering::Equal));
2152                }
2153                // 5. Let xString be ? ToString(x).
2154                // 6. Let yString be ? ToString(y).
2155                let x_str = x.to_string(context)?;
2156                let y_str = y.to_string(context)?;
2157
2158                // 7. Let xSmaller be IsLessThan(xString, yString, true).
2159                // 8. If xSmaller is true, return -1๐”ฝ.
2160                // 9. Let ySmaller be IsLessThan(yString, xString, true).
2161                // 10. If ySmaller is true, return 1๐”ฝ.
2162                // 11. Return +0๐”ฝ.
2163
2164                // NOTE: skipped IsLessThan because it just makes a lexicographic comparation
2165                // when x and y are strings
2166                Ok(x_str.cmp(&y_str))
2167            };
2168
2169        // 2. Let obj be ? ToObject(this value).
2170        let obj = this.to_object(context)?;
2171
2172        // 3. Let len be ? LengthOfArrayLike(obj).
2173        let length = obj.length_of_array_like(context)?;
2174
2175        // 4. Let items be a new empty List.
2176        let mut items = Vec::with_capacity(length);
2177
2178        // 5. Let k be 0.
2179        // 6. Repeat, while k < len,
2180        for k in 0..length {
2181            // a. Let Pk be ! ToString(๐”ฝ(k)).
2182            // b. Let kPresent be ? HasProperty(obj, Pk).
2183            // c. If kPresent is true, then
2184            if obj.has_property(k, context)? {
2185                // i. Let kValue be ? Get(obj, Pk).
2186                let kval = obj.get(k, context)?;
2187                // ii. Append kValue to items.
2188                items.push(kval);
2189            }
2190            // d. Set k to k + 1.
2191        }
2192
2193        // 7. Let itemCount be the number of elements in items.
2194        let item_count = items.len();
2195
2196        // 8. Sort items using an implementation-defined sequence of calls to SortCompare.
2197        // If any such call returns an abrupt completion, stop before performing any further
2198        // calls to SortCompare or steps in this algorithm and return that completion.
2199        let mut sort_err = Ok(());
2200        items.sort_by(|x, y| {
2201            if sort_err.is_ok() {
2202                sort_compare(x, y, context).unwrap_or_else(|err| {
2203                    sort_err = Err(err);
2204                    Ordering::Equal
2205                })
2206            } else {
2207                Ordering::Equal
2208            }
2209        });
2210        sort_err?;
2211
2212        // 9. Let j be 0.
2213        // 10. Repeat, while j < itemCount,
2214        for (j, item) in items.into_iter().enumerate() {
2215            // a. Perform ? Set(obj, ! ToString(๐”ฝ(j)), items[j], true).
2216            obj.set(j, item, true, context)?;
2217            // b. Set j to j + 1.
2218        }
2219
2220        // 11. Repeat, while j < len,
2221        for j in item_count..length {
2222            // a. Perform ? DeletePropertyOrThrow(obj, ! ToString(๐”ฝ(j))).
2223            obj.delete_property_or_throw(j, context)?;
2224            // b. Set j to j + 1.
2225        }
2226
2227        // 12. Return obj.
2228        Ok(obj.into())
2229    }
2230
2231    /// `Array.prototype.reduce( callbackFn [ , initialValue ] )`
2232    ///
2233    /// More information:
2234    ///  - [ECMAScript reference][spec]
2235    ///  - [MDN documentation][mdn]
2236    ///
2237    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduce
2238    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
2239    pub(crate) fn reduce(
2240        this: &JsValue,
2241        args: &[JsValue],
2242        context: &mut Context,
2243    ) -> JsResult<JsValue> {
2244        // 1. Let O be ? ToObject(this value).
2245        let o = this.to_object(context)?;
2246
2247        // 2. Let len be ? LengthOfArrayLike(O).
2248        let len = o.length_of_array_like(context)?;
2249
2250        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2251        let callback = match args.get(0).and_then(JsValue::as_object) {
2252            Some(callback) if callback.is_callable() => callback,
2253            _ => {
2254                return context
2255                    .throw_type_error("Array.prototype.reduce: callback function is not callable")
2256            }
2257        };
2258
2259        // 4. If len = 0 and initialValue is not present, throw a TypeError exception.
2260        if len == 0 && args.get(1).is_none() {
2261            return context.throw_type_error(
2262                "Array.prototype.reduce: called on an empty array and with no initial value",
2263            );
2264        }
2265
2266        // 5. Let k be 0.
2267        let mut k = 0;
2268        // 6. Let accumulator be undefined.
2269        let mut accumulator = JsValue::undefined();
2270
2271        // 7. If initialValue is present, then
2272        if let Some(initial_value) = args.get(1) {
2273            // a. Set accumulator to initialValue.
2274            accumulator = initial_value.clone();
2275        // 8. Else,
2276        } else {
2277            // a. Let kPresent be false.
2278            let mut k_present = false;
2279            // b. Repeat, while kPresent is false and k < len,
2280            while !k_present && k < len {
2281                // i. Let Pk be ! ToString(๐”ฝ(k)).
2282                let pk = k;
2283                // ii. Set kPresent to ? HasProperty(O, Pk).
2284                k_present = o.has_property(pk, context)?;
2285                // iii. If kPresent is true, then
2286                if k_present {
2287                    // 1. Set accumulator to ? Get(O, Pk).
2288                    accumulator = o.get(pk, context)?;
2289                }
2290                // iv. Set k to k + 1.
2291                k += 1;
2292            }
2293            // c. If kPresent is false, throw a TypeError exception.
2294            if !k_present {
2295                return context.throw_type_error(
2296                    "Array.prototype.reduce: called on an empty array and with no initial value",
2297                );
2298            }
2299        }
2300
2301        // 9. Repeat, while k < len,
2302        while k < len {
2303            // a. Let Pk be ! ToString(๐”ฝ(k)).
2304            let pk = k;
2305            // b. Let kPresent be ? HasProperty(O, Pk).
2306            let k_present = o.has_property(pk, context)?;
2307            // c. If kPresent is true, then
2308            if k_present {
2309                // i. Let kValue be ? Get(O, Pk).
2310                let k_value = o.get(pk, context)?;
2311                // ii. Set accumulator to ? Call(callbackfn, undefined, ยซ accumulator, kValue, ๐”ฝ(k), O ยป).
2312                accumulator = callback.call(
2313                    &JsValue::undefined(),
2314                    &[accumulator, k_value, k.into(), o.clone().into()],
2315                    context,
2316                )?;
2317            }
2318            // d. Set k to k + 1.
2319            k += 1;
2320        }
2321
2322        // 10. Return accumulator.
2323        Ok(accumulator)
2324    }
2325
2326    /// `Array.prototype.reduceRight( callbackFn [ , initialValue ] )`
2327    ///
2328    /// The reduceRight method traverses right to left starting from the last defined value in the array,
2329    /// accumulating a value using a given callback function. It returns the accumulated value.
2330    ///
2331    /// More information:
2332    ///  - [ECMAScript reference][spec]
2333    ///  - [MDN documentation][mdn]
2334    ///
2335    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reduceright
2336    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight
2337    pub(crate) fn reduce_right(
2338        this: &JsValue,
2339        args: &[JsValue],
2340        context: &mut Context,
2341    ) -> JsResult<JsValue> {
2342        // 1. Let O be ? ToObject(this value).
2343        let o = this.to_object(context)?;
2344
2345        // 2. Let len be ? LengthOfArrayLike(O).
2346        let len = o.length_of_array_like(context)?;
2347
2348        // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
2349        let callback = match args.get(0).and_then(JsValue::as_object) {
2350            Some(callback) if callback.is_callable() => callback,
2351            _ => {
2352                return context.throw_type_error(
2353                    "Array.prototype.reduceRight: callback function is not callable",
2354                )
2355            }
2356        };
2357
2358        // 4. If len is 0 and initialValue is not present, throw a TypeError exception.
2359        if len == 0 && args.get(1).is_none() {
2360            return context.throw_type_error(
2361                "Array.prototype.reduceRight: called on an empty array and with no initial value",
2362            );
2363        }
2364
2365        // 5. Let k be len - 1.
2366        let mut k = len as i64 - 1;
2367        // 6. Let accumulator be undefined.
2368        let mut accumulator = JsValue::undefined();
2369        // 7. If initialValue is present, then
2370        if let Some(initial_value) = args.get(1) {
2371            // a. Set accumulator to initialValue.
2372            accumulator = initial_value.clone();
2373        // 8. Else,
2374        } else {
2375            // a. Let kPresent be false.
2376            let mut k_present = false;
2377            // b. Repeat, while kPresent is false and k โ‰ฅ 0,
2378            while !k_present && k >= 0 {
2379                // i. Let Pk be ! ToString(๐”ฝ(k)).
2380                let pk = k;
2381                // ii. Set kPresent to ? HasProperty(O, Pk).
2382                k_present = o.has_property(pk, context)?;
2383                // iii. If kPresent is true, then
2384                if k_present {
2385                    // 1. Set accumulator to ? Get(O, Pk).
2386                    accumulator = o.get(pk, context)?;
2387                }
2388                // iv. Set k to k - 1.
2389                k -= 1;
2390            }
2391            // c. If kPresent is false, throw a TypeError exception.
2392            if !k_present {
2393                return context.throw_type_error(
2394                    "Array.prototype.reduceRight: called on an empty array and with no initial value",
2395                );
2396            }
2397        }
2398
2399        // 9. Repeat, while k โ‰ฅ 0,
2400        while k >= 0 {
2401            // a. Let Pk be ! ToString(๐”ฝ(k)).
2402            let pk = k;
2403            // b. Let kPresent be ? HasProperty(O, Pk).
2404            let k_present = o.has_property(pk, context)?;
2405            // c. If kPresent is true, then
2406            if k_present {
2407                // i. Let kValue be ? Get(O, Pk).
2408                let k_value = o.get(pk, context)?;
2409                // ii. Set accumulator to ? Call(callbackfn, undefined, ยซ accumulator, kValue, ๐”ฝ(k), O ยป).
2410                accumulator = callback.call(
2411                    &JsValue::undefined(),
2412                    &[accumulator.clone(), k_value, k.into(), o.clone().into()],
2413                    context,
2414                )?;
2415            }
2416            // d. Set k to k - 1.
2417            k -= 1;
2418        }
2419
2420        // 10. Return accumulator.
2421        Ok(accumulator)
2422    }
2423
2424    /// `Array.prototype.copyWithin ( target, start [ , end ] )`
2425    ///
2426    /// The copyWithin() method shallow copies part of an array to another location
2427    /// in the same array and returns it without modifying its length.
2428    ///
2429    /// More information:
2430    ///  - [ECMAScript reference][spec]
2431    ///  - [MDN documentation][mdn]
2432    ///
2433    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.copywithin
2434    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
2435    pub(crate) fn copy_within(
2436        this: &JsValue,
2437        args: &[JsValue],
2438        context: &mut Context,
2439    ) -> JsResult<JsValue> {
2440        // 1. Let O be ? ToObject(this value).
2441        let o = this.to_object(context)?;
2442
2443        // 2. Let len be ? LengthOfArrayLike(O).
2444        let len = o.length_of_array_like(context)?;
2445
2446        // 3. Let relativeTarget be ? ToIntegerOrInfinity(target).
2447        // 4. If relativeTarget is -โˆž, let to be 0.
2448        // 5. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0).
2449        // 6. Else, let to be min(relativeTarget, len).
2450        let mut to = Self::get_relative_start(context, args.get(0), len)? as i64;
2451
2452        // 7. Let relativeStart be ? ToIntegerOrInfinity(start).
2453        // 8. If relativeStart is -โˆž, let from be 0.
2454        // 9. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
2455        // 10. Else, let from be min(relativeStart, len).
2456        let mut from = Self::get_relative_start(context, args.get(1), len)? as i64;
2457
2458        // 11. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
2459        // 12. If relativeEnd is -โˆž, let final be 0.
2460        // 13. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
2461        // 14. Else, let final be min(relativeEnd, len).
2462        let final_ = Self::get_relative_end(context, args.get(2), len)? as i64;
2463
2464        // 15. Let count be min(final - from, len - to).
2465        let mut count = min(final_ - from, len as i64 - to);
2466
2467        // 16. If from < to and to < from + count, then
2468        let direction = if from < to && to < from + count {
2469            // b. Set from to from + count - 1.
2470            from = from + count - 1;
2471            // c. Set to to to + count - 1.
2472            to = to + count - 1;
2473
2474            // a. Let direction be -1.
2475            -1
2476        // 17. Else,
2477        } else {
2478            // a. Let direction be 1.
2479            1
2480        };
2481
2482        // 18. Repeat, while count > 0,
2483        while count > 0 {
2484            // a. Let fromKey be ! ToString(๐”ฝ(from)).
2485            let from_key = from;
2486
2487            // b. Let toKey be ! ToString(๐”ฝ(to)).
2488            let to_key = to;
2489
2490            // c. Let fromPresent be ? HasProperty(O, fromKey).
2491            let from_present = o.has_property(from_key, context)?;
2492            // d. If fromPresent is true, then
2493            if from_present {
2494                // i. Let fromVal be ? Get(O, fromKey).
2495                let from_val = o.get(from_key, context)?;
2496                // ii. Perform ? Set(O, toKey, fromVal, true).
2497                o.set(to_key, from_val, true, context)?;
2498            // e. Else,
2499            } else {
2500                // i. Assert: fromPresent is false.
2501                // ii. Perform ? DeletePropertyOrThrow(O, toKey).
2502                o.delete_property_or_throw(to_key, context)?;
2503            }
2504            // f. Set from to from + direction.
2505            from += direction;
2506            // g. Set to to to + direction.
2507            to += direction;
2508            // h. Set count to count - 1.
2509            count -= 1;
2510        }
2511        // 19. Return O.
2512        Ok(o.into())
2513    }
2514
2515    /// `Array.prototype.values( )`
2516    ///
2517    /// The values method returns an iterable that iterates over the values in the array.
2518    ///
2519    /// More information:
2520    ///  - [ECMAScript reference][spec]
2521    ///  - [MDN documentation][mdn]
2522    ///
2523    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
2524    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
2525    pub(crate) fn values(
2526        this: &JsValue,
2527        _: &[JsValue],
2528        context: &mut Context,
2529    ) -> JsResult<JsValue> {
2530        Ok(ArrayIterator::create_array_iterator(
2531            this.clone(),
2532            PropertyNameKind::Value,
2533            context,
2534        ))
2535    }
2536
2537    /// `Array.prototype.keys( )`
2538    ///
2539    /// The keys method returns an iterable that iterates over the indexes in the array.
2540    ///
2541    /// More information:
2542    ///  - [ECMAScript reference][spec]
2543    ///  - [MDN documentation][mdn]
2544    ///
2545    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
2546    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
2547    pub(crate) fn keys(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
2548        Ok(ArrayIterator::create_array_iterator(
2549            this.clone(),
2550            PropertyNameKind::Key,
2551            context,
2552        ))
2553    }
2554
2555    /// `Array.prototype.entries( )`
2556    ///
2557    /// The entries method returns an iterable that iterates over the key-value pairs in the array.
2558    ///
2559    /// More information:
2560    ///  - [ECMAScript reference][spec]
2561    ///  - [MDN documentation][mdn]
2562    ///
2563    /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.values
2564    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values
2565    pub(crate) fn entries(
2566        this: &JsValue,
2567        _: &[JsValue],
2568        context: &mut Context,
2569    ) -> JsResult<JsValue> {
2570        Ok(ArrayIterator::create_array_iterator(
2571            this.clone(),
2572            PropertyNameKind::KeyAndValue,
2573            context,
2574        ))
2575    }
2576
2577    /// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions.
2578    pub(super) fn get_relative_start(
2579        context: &mut Context,
2580        arg: Option<&JsValue>,
2581        len: usize,
2582    ) -> JsResult<usize> {
2583        // 1. Let relativeStart be ? ToIntegerOrInfinity(start).
2584        let relative_start = arg
2585            .cloned()
2586            .unwrap_or_default()
2587            .to_integer_or_infinity(context)?;
2588        match relative_start {
2589            // 2. If relativeStart is -โˆž, let k be 0.
2590            IntegerOrInfinity::NegativeInfinity => Ok(0),
2591            // 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0).
2592            IntegerOrInfinity::Integer(i) if i < 0 => Ok(max(len as i64 + i, 0) as usize),
2593            // Both `as` casts are safe as both variables are non-negative
2594            // 4. Else, let k be min(relativeStart, len).
2595            IntegerOrInfinity::Integer(i) => Ok(min(i, len as i64) as usize),
2596
2597            // Special case - postive infinity. `len` is always smaller than +inf, thus from (4)
2598            IntegerOrInfinity::PositiveInfinity => Ok(len),
2599        }
2600    }
2601
2602    /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions.
2603    pub(super) fn get_relative_end(
2604        context: &mut Context,
2605        arg: Option<&JsValue>,
2606        len: usize,
2607    ) -> JsResult<usize> {
2608        let default_value = JsValue::undefined();
2609        let value = arg.unwrap_or(&default_value);
2610        // 1. If end is undefined, let relativeEnd be len [and return it]
2611        if value.is_undefined() {
2612            Ok(len)
2613        } else {
2614            // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end).
2615            let relative_end = value.to_integer_or_infinity(context)?;
2616            match relative_end {
2617                // 2. If relativeEnd is -โˆž, let final be 0.
2618                IntegerOrInfinity::NegativeInfinity => Ok(0),
2619                // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
2620                IntegerOrInfinity::Integer(i) if i < 0 => Ok(max(len as i64 + i, 0) as usize),
2621                // 4. Else, let final be min(relativeEnd, len).
2622                // Both `as` casts are safe as both variables are non-negative
2623                IntegerOrInfinity::Integer(i) => Ok(min(i, len as i64) as usize),
2624
2625                // Special case - postive infinity. `len` is always smaller than +inf, thus from (4)
2626                IntegerOrInfinity::PositiveInfinity => Ok(len),
2627            }
2628        }
2629    }
2630}