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}