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