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