a3c17ce262f0b80fdaaeb9ed20f4d5a61f4e0bd5
[WebKit-https.git] / Source / JavaScriptCore / builtins / ArrayPrototype.js
1 /*
2  * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 @constructor
28 @globalPrivate
29 function createArrayIterator(iteratedObject, kind, iterationFunction)
30 {
31     "use strict";
32
33     @putByIdDirectPrivate(this, "iteratedObject", iteratedObject);
34     @putByIdDirectPrivate(this, "arrayIteratorKind", kind);
35     @putByIdDirectPrivate(this, "arrayIteratorNextIndex", 0);
36     @putByIdDirectPrivate(this, "arrayIteratorNext", iterationFunction);
37     @putByIdDirectPrivate(this, "arrayIteratorIsDone", false);
38 }
39
40 function values()
41 {
42     "use strict";
43
44     return new @createArrayIterator(@toObject(this, "Array.prototype.values requires that |this| not be null or undefined"), "value", @arrayIteratorValueNext);
45 }
46
47 function keys()
48 {
49     "use strict";
50
51     return new @createArrayIterator(@toObject(this, "Array.prototype.keys requires that |this| not be null or undefined"), "key", @arrayIteratorKeyNext);
52 }
53
54 function entries()
55 {
56     "use strict";
57
58     return new @createArrayIterator(@toObject(this, "Array.prototype.entries requires that |this| not be null or undefined"), "key+value", @arrayIteratorKeyValueNext);
59 }
60
61 function reduce(callback /*, initialValue */)
62 {
63     "use strict";
64
65     var array = @toObject(this, "Array.prototype.reduce requires that |this| not be null or undefined");
66     var length = @toLength(array.length);
67
68     if (typeof callback !== "function")
69         @throwTypeError("Array.prototype.reduce callback must be a function");
70
71     var argumentCount = @argumentCount();
72     if (length === 0 && argumentCount < 2)
73         @throwTypeError("reduce of empty array with no initial value");
74
75     var accumulator, k = 0;
76     if (argumentCount > 1)
77         accumulator = @argument(1);
78     else {
79         while (k < length && !(k in array))
80             k += 1;
81         if (k >= length)
82             @throwTypeError("reduce of empty array with no initial value");
83         accumulator = array[k++];
84     }
85
86     while (k < length) {
87         if (k in array)
88             accumulator = callback.@call(@undefined, accumulator, array[k], k, array);
89         k += 1;
90     }
91     return accumulator;
92 }
93
94 function reduceRight(callback /*, initialValue */)
95 {
96     "use strict";
97
98     var array = @toObject(this, "Array.prototype.reduceRight requires that |this| not be null or undefined");
99     var length = @toLength(array.length);
100
101     if (typeof callback !== "function")
102         @throwTypeError("Array.prototype.reduceRight callback must be a function");
103
104     var argumentCount = @argumentCount();
105     if (length === 0 && argumentCount < 2)
106         @throwTypeError("reduceRight of empty array with no initial value");
107
108     var accumulator, k = length - 1;
109     if (argumentCount > 1)
110         accumulator = @argument(1);
111     else {
112         while (k >= 0 && !(k in array))
113             k -= 1;
114         if (k < 0)
115             @throwTypeError("reduceRight of empty array with no initial value");
116         accumulator = array[k--];
117     }
118
119     while (k >= 0) {
120         if (k in array)
121             accumulator = callback.@call(@undefined, accumulator, array[k], k, array);
122         k -= 1;
123     }
124     return accumulator;
125 }
126
127 function every(callback /*, thisArg */)
128 {
129     "use strict";
130
131     var array = @toObject(this, "Array.prototype.every requires that |this| not be null or undefined");
132     var length = @toLength(array.length);
133
134     if (typeof callback !== "function")
135         @throwTypeError("Array.prototype.every callback must be a function");
136     
137     var thisArg = @argument(1);
138     
139     for (var i = 0; i < length; i++) {
140         if (!(i in array))
141             continue;
142         if (!callback.@call(thisArg, array[i], i, array))
143             return false;
144     }
145     
146     return true;
147 }
148
149 function forEach(callback /*, thisArg */)
150 {
151     "use strict";
152
153     var array = @toObject(this, "Array.prototype.forEach requires that |this| not be null or undefined");
154     var length = @toLength(array.length);
155
156     if (typeof callback !== "function")
157         @throwTypeError("Array.prototype.forEach callback must be a function");
158     
159     var thisArg = @argument(1);
160     
161     for (var i = 0; i < length; i++) {
162         if (i in array)
163             callback.@call(thisArg, array[i], i, array);
164     }
165 }
166
167 function filter(callback /*, thisArg */)
168 {
169     "use strict";
170
171     var array = @toObject(this, "Array.prototype.filter requires that |this| not be null or undefined");
172     var length = @toLength(array.length);
173
174     if (typeof callback !== "function")
175         @throwTypeError("Array.prototype.filter callback must be a function");
176     
177     var thisArg = @argument(1);
178
179     // Do 9.4.2.3 ArraySpeciesCreate
180     var result;
181     var constructor;
182     if (@isArray(array)) {
183         constructor = array.constructor;
184         // We have this check so that if some array from a different global object
185         // calls this map they don't get an array with the Array.prototype of the
186         // other global object.
187         if (@Array !== constructor && @isArrayConstructor(constructor))
188             constructor = @undefined;
189         if (@isObject(constructor)) {
190             constructor = constructor.@speciesSymbol;
191             if (constructor === null)
192                 constructor = @undefined;
193         }
194     }
195     if (constructor === @Array || constructor === @undefined)
196         result = @newArrayWithSize(0);
197     else
198         result = new constructor(0);
199
200     var nextIndex = 0;
201     for (var i = 0; i < length; i++) {
202         if (!(i in array))
203             continue;
204         var current = array[i]
205         if (callback.@call(thisArg, current, i, array)) {
206             @putByValDirect(result, nextIndex, current);
207             ++nextIndex;
208         }
209     }
210     return result;
211 }
212
213 function map(callback /*, thisArg */)
214 {
215     "use strict";
216
217     var array = @toObject(this, "Array.prototype.map requires that |this| not be null or undefined");
218     var length = @toLength(array.length);
219
220     if (typeof callback !== "function")
221         @throwTypeError("Array.prototype.map callback must be a function");
222     
223     var thisArg = @argument(1);
224
225     // Do 9.4.2.3 ArraySpeciesCreate
226     var result;
227     var constructor;
228     if (@isArray(array)) {
229         constructor = array.constructor;
230         // We have this check so that if some array from a different global object
231         // calls this map they don't get an array with the Array.prototype of the
232         // other global object.
233         if (@Array !== constructor && @isArrayConstructor(constructor))
234             constructor = @undefined;
235         if (@isObject(constructor)) {
236             constructor = constructor.@speciesSymbol;
237             if (constructor === null)
238                 constructor = @undefined;
239         }
240     }
241     if (constructor === @Array || constructor === @undefined)
242         result = @newArrayWithSize(length);
243     else
244         result = new constructor(length);
245
246     for (var i = 0; i < length; i++) {
247         if (!(i in array))
248             continue;
249         var mappedValue = callback.@call(thisArg, array[i], i, array);
250         @putByValDirect(result, i, mappedValue);
251     }
252     return result;
253 }
254
255 function some(callback /*, thisArg */)
256 {
257     "use strict";
258
259     var array = @toObject(this, "Array.prototype.some requires that |this| not be null or undefined");
260     var length = @toLength(array.length);
261
262     if (typeof callback !== "function")
263         @throwTypeError("Array.prototype.some callback must be a function");
264     
265     var thisArg = @argument(1);
266     for (var i = 0; i < length; i++) {
267         if (!(i in array))
268             continue;
269         if (callback.@call(thisArg, array[i], i, array))
270             return true;
271     }
272     return false;
273 }
274
275 function fill(value /* [, start [, end]] */)
276 {
277     "use strict";
278
279     var array = @toObject(this, "Array.prototype.fill requires that |this| not be null or undefined");
280     var length = @toLength(array.length);
281
282     var relativeStart = @toInteger(@argument(1));
283     var k = 0;
284     if (relativeStart < 0) {
285         k = length + relativeStart;
286         if (k < 0)
287             k = 0;
288     } else {
289         k = relativeStart;
290         if (k > length)
291             k = length;
292     }
293     var relativeEnd = length;
294     var end = @argument(2);
295     if (end !== @undefined)
296         relativeEnd = @toInteger(end);
297     var final = 0;
298     if (relativeEnd < 0) {
299         final = length + relativeEnd;
300         if (final < 0)
301             final = 0;
302     } else {
303         final = relativeEnd;
304         if (final > length)
305             final = length;
306     }
307     for (; k < final; k++)
308         array[k] = value;
309     return array;
310 }
311
312 function find(callback /*, thisArg */)
313 {
314     "use strict";
315
316     var array = @toObject(this, "Array.prototype.find requires that |this| not be null or undefined");
317     var length = @toLength(array.length);
318
319     if (typeof callback !== "function")
320         @throwTypeError("Array.prototype.find callback must be a function");
321     
322     var thisArg = @argument(1);
323     for (var i = 0; i < length; i++) {
324         var kValue = array[i];
325         if (callback.@call(thisArg, kValue, i, array))
326             return kValue;
327     }
328     return @undefined;
329 }
330
331 function findIndex(callback /*, thisArg */)
332 {
333     "use strict";
334
335     var array = @toObject(this, "Array.prototype.findIndex requires that |this| not be null or undefined");
336     var length = @toLength(array.length);
337
338     if (typeof callback !== "function")
339         @throwTypeError("Array.prototype.findIndex callback must be a function");
340     
341     var thisArg = @argument(1);
342     for (var i = 0; i < length; i++) {
343         if (callback.@call(thisArg, array[i], i, array))
344             return i;
345     }
346     return -1;
347 }
348
349 function includes(searchElement /*, fromIndex*/)
350 {
351     "use strict";
352
353     var array = @toObject(this, "Array.prototype.includes requires that |this| not be null or undefined");
354     var length = @toLength(array.length);
355
356     if (length === 0)
357         return false;
358
359     var fromIndex = 0;
360     var from = @argument(1);
361     if (from !== @undefined)
362         fromIndex = @toInteger(from);
363
364     var index;
365     if (fromIndex >= 0)
366         index = fromIndex;
367     else
368         index = length + fromIndex;
369
370     if (index < 0)
371         index = 0;
372
373     var currentElement;
374     for (; index < length; ++index) {
375         currentElement = array[index];
376         // Use SameValueZero comparison, rather than just StrictEquals.
377         if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement))
378             return true;
379     }
380     return false;
381 }
382
383 function sort(comparator)
384 {
385     "use strict";
386
387     function min(a, b)
388     {
389         return a < b ? a : b;
390     }
391
392     function stringComparator(a, b)
393     {
394         var aString = a.string;
395         var bString = b.string;
396
397         var aLength = aString.length;
398         var bLength = bString.length;
399         var length = min(aLength, bLength);
400
401         for (var i = 0; i < length; ++i) {
402             var aCharCode = aString.@charCodeAt(i);
403             var bCharCode = bString.@charCodeAt(i);
404
405             if (aCharCode == bCharCode)
406                 continue;
407
408             return aCharCode - bCharCode;
409         }
410
411         return aLength - bLength;
412     }
413
414     // Move undefineds and holes to the end of a sparse array. Result is [values..., undefineds..., holes...].
415     function compactSparse(array, dst, src, length)
416     {
417         var values = [ ];
418         var seen = { };
419         var valueCount = 0;
420         var undefinedCount = 0;
421
422         // Clean up after the in-progress non-sparse compaction that failed.
423         for (var i = dst; i < src; ++i)
424             delete array[i];
425
426         for (var object = array; object; object = @Object.@getPrototypeOf(object)) {
427             var propertyNames = @Object.@getOwnPropertyNames(object);
428             for (var i = 0; i < propertyNames.length; ++i) {
429                 var index = propertyNames[i];
430                 if (index < length) { // Exclude non-numeric properties and properties past length.
431                     if (seen[index]) // Exclude duplicates.
432                         continue;
433                     seen[index] = 1;
434
435                     var value = array[index];
436                     delete array[index];
437
438                     if (value === @undefined) {
439                         ++undefinedCount;
440                         continue;
441                     }
442
443                     array[valueCount++] = value;
444                 }
445             }
446         }
447
448         for (var i = valueCount; i < valueCount + undefinedCount; ++i)
449             array[i] = @undefined;
450
451         return valueCount;
452     }
453
454     function compactSlow(array, length)
455     {
456         var holeCount = 0;
457
458         for (var dst = 0, src = 0; src < length; ++src) {
459             if (!(src in array)) {
460                 ++holeCount;
461                 if (holeCount < 256)
462                     continue;
463                 return compactSparse(array, dst, src, length);
464             }
465
466             var value = array[src];
467             if (value === @undefined)
468                 continue;
469
470             array[dst++] = value;
471         }
472
473         var valueCount = dst;
474         var undefinedCount = length - valueCount - holeCount;
475
476         for (var i = valueCount; i < valueCount + undefinedCount; ++i)
477             array[i] = @undefined;
478
479         for (var i = valueCount + undefinedCount; i < length; ++i)
480             delete array[i];
481
482         return valueCount;
483     }
484
485     // Move undefineds and holes to the end of an array. Result is [values..., undefineds..., holes...].
486     function compact(array, length)
487     {
488         for (var i = 0; i < array.length; ++i) {
489             if (array[i] === @undefined)
490                 return compactSlow(array, length);
491         }
492
493         return length;
494     }
495
496     function merge(dst, src, srcIndex, srcEnd, width, comparator)
497     {
498         var left = srcIndex;
499         var leftEnd = min(left + width, srcEnd);
500         var right = leftEnd;
501         var rightEnd = min(right + width, srcEnd);
502
503         for (var dstIndex = left; dstIndex < rightEnd; ++dstIndex) {
504             if (right < rightEnd) {
505                 if (left >= leftEnd) {
506                     dst[dstIndex] = src[right++];
507                     continue;
508                 }
509
510                 let comparisonResult = comparator(src[right], src[left]);
511                 if ((typeof comparisonResult === "boolean" && !comparisonResult) || comparisonResult < 0) {
512                     dst[dstIndex] = src[right++];
513                     continue;
514                 }
515
516             }
517
518             dst[dstIndex] = src[left++];
519         }
520     }
521
522     function mergeSort(array, valueCount, comparator)
523     {
524         var buffer = [ ];
525         buffer.length = valueCount;
526
527         var dst = buffer;
528         var src = array;
529         for (var width = 1; width < valueCount; width *= 2) {
530             for (var srcIndex = 0; srcIndex < valueCount; srcIndex += 2 * width)
531                 merge(dst, src, srcIndex, valueCount, width, comparator);
532
533             var tmp = src;
534             src = dst;
535             dst = tmp;
536         }
537
538         if (src != array) {
539             for(var i = 0; i < valueCount; i++)
540                 array[i] = src[i];
541         }
542     }
543
544     function bucketSort(array, dst, bucket, depth)
545     {
546         if (bucket.length < 32 || depth > 32) {
547             mergeSort(bucket, bucket.length, stringComparator);
548             for (var i = 0; i < bucket.length; ++i)
549                 array[dst++] = bucket[i].value;
550             return dst;
551         }
552
553         var buckets = [ ];
554         for (var i = 0; i < bucket.length; ++i) {
555             var entry = bucket[i];
556             var string = entry.string;
557             if (string.length == depth) {
558                 array[dst++] = entry.value;
559                 continue;
560             }
561
562             var c = string.@charCodeAt(depth);
563             if (!buckets[c])
564                 buckets[c] = [ ];
565             buckets[c][buckets[c].length] = entry;
566         }
567
568         for (var i = 0; i < buckets.length; ++i) {
569             if (!buckets[i])
570                 continue;
571             dst = bucketSort(array, dst, buckets[i], depth + 1);
572         }
573
574         return dst;
575     }
576
577     function comparatorSort(array, length, comparator)
578     {
579         var valueCount = compact(array, length);
580         mergeSort(array, valueCount, comparator);
581     }
582
583     function stringSort(array, length)
584     {
585         var valueCount = compact(array, length);
586
587         var strings = @newArrayWithSize(valueCount);
588         for (var i = 0; i < valueCount; ++i)
589             strings[i] = { string: @toString(array[i]), value: array[i] };
590
591         bucketSort(array, 0, strings, 0);
592     }
593
594     var array = @toObject(this, "Array.prototype.sort requires that |this| not be null or undefined");
595
596     var length = array.length >>> 0;
597
598     // For compatibility with Firefox and Chrome, do nothing observable
599     // to the target array if it has 0 or 1 sortable properties.
600     if (length < 2)
601         return array;
602
603     if (typeof comparator == "function")
604         comparatorSort(array, length, comparator);
605     else if (comparator === null || comparator === @undefined)
606         stringSort(array, length);
607     else
608         @throwTypeError("Array.prototype.sort requires the comparsion function be a function or undefined");
609
610     return array;
611 }
612
613 @globalPrivate
614 function concatSlowPath()
615 {
616     "use strict";
617
618     var currentElement = @toObject(this, "Array.prototype.concat requires that |this| not be null or undefined");
619
620     var constructor;
621     if (@isArray(currentElement)) {
622         constructor = currentElement.constructor;
623         // We have this check so that if some array from a different global object
624         // calls this map they don't get an array with the Array.prototype of the
625         // other global object.
626         if (@Array !== constructor && @isArrayConstructor(constructor))
627             constructor = @undefined;
628         else if (@isObject(constructor)) {
629             constructor = constructor.@speciesSymbol;
630             if (constructor === null)
631                 constructor = @Array;
632         }
633     }
634
635     var argCount = arguments.length;
636     var result;
637     if (constructor === @Array || constructor === @undefined)
638         result = @newArrayWithSize(0);
639     else
640         result = new constructor(0);
641     var resultIsArray = @isJSArray(result);
642
643     var resultIndex = 0;
644     var argIndex = 0;
645
646     do {
647         let spreadable = @isObject(currentElement) && currentElement.@isConcatSpreadableSymbol;
648         if ((spreadable === @undefined && @isArray(currentElement)) || spreadable) {
649             let length = @toLength(currentElement.length);
650             if (length + resultIndex > @MAX_ARRAY_INDEX)
651                 @throwRangeError("Length exceeded the maximum array length");
652             if (resultIsArray && @isJSArray(currentElement)) {
653                 @appendMemcpy(result, currentElement, resultIndex);
654                 resultIndex += length;
655             } else {
656                 for (var i = 0; i < length; i++) {
657                     if (i in currentElement)
658                         @putByValDirect(result, resultIndex, currentElement[i]);
659                     resultIndex++;
660                 }
661             }
662         } else {
663             if (resultIndex >= @MAX_ARRAY_INDEX)
664                 @throwRangeError("Length exceeded the maximum array length");
665             @putByValDirect(result, resultIndex++, currentElement);
666         }
667         currentElement = arguments[argIndex];
668     } while (argIndex++ < argCount);
669
670     result.length = resultIndex;
671     return result;
672 }
673
674 function concat(first)
675 {
676     "use strict";
677
678     if (@argumentCount() === 1
679         && @isJSArray(this)
680         && this.@isConcatSpreadableSymbol === @undefined
681         && (!@isObject(first) || first.@isConcatSpreadableSymbol === @undefined)) {
682
683         let result = @concatMemcpy(this, first);
684         if (result !== null)
685             return result;
686     }
687
688     return @tailCallForwardArguments(@concatSlowPath, this);
689 }
690
691 function copyWithin(target, start /*, end */)
692 {
693     "use strict";
694
695     function maxWithPositives(a, b)
696     {
697         return (a < b) ? b : a;
698     }
699
700     function minWithMaybeNegativeZeroAndPositive(maybeNegativeZero, positive)
701     {
702         return (maybeNegativeZero < positive) ? maybeNegativeZero : positive;
703     }
704
705     var array = @toObject(this, "Array.prototype.copyWithin requires that |this| not be null or undefined");
706     var length = @toLength(array.length);
707
708     var relativeTarget = @toInteger(target);
709     var to = (relativeTarget < 0) ? maxWithPositives(length + relativeTarget, 0) : minWithMaybeNegativeZeroAndPositive(relativeTarget, length);
710
711     var relativeStart = @toInteger(start);
712     var from = (relativeStart < 0) ? maxWithPositives(length + relativeStart, 0) : minWithMaybeNegativeZeroAndPositive(relativeStart, length);
713
714     var relativeEnd;
715     var end = @argument(2);
716     if (end === @undefined)
717         relativeEnd = length;
718     else
719         relativeEnd = @toInteger(end);
720
721     var finalValue = (relativeEnd < 0) ? maxWithPositives(length + relativeEnd, 0) : minWithMaybeNegativeZeroAndPositive(relativeEnd, length);
722
723     var count = minWithMaybeNegativeZeroAndPositive(finalValue - from, length - to);
724
725     var direction = 1;
726     if (from < to && to < from + count) {
727         direction = -1;
728         from = from + count - 1;
729         to = to + count - 1;
730     }
731
732     for (var i = 0; i < count; ++i, from += direction, to += direction) {
733         if (from in array)
734             array[to] = array[from];
735         else
736             delete array[to];
737     }
738
739     return array;
740 }
741
742 @globalPrivate
743 function arraySpeciesCreate(array, length)
744 {
745     "use strict";
746
747     if (!@isArray(array))
748         return @newArrayWithSize(length);
749
750     var constructor = array.constructor;
751     var arrayConstructorInRealm = @Array;
752     // We have this check so that if some array from a different global object
753     // calls this map they don't get an array with the Array.prototype of the
754     // other global object.
755     if (arrayConstructorInRealm !== constructor && @isArrayConstructor(constructor))
756         return @newArrayWithSize(length);
757
758     if (@isObject(constructor)) {
759         constructor = constructor.@speciesSymbol;
760         if (constructor == null)
761             return @newArrayWithSize(length);
762     }
763
764     if (constructor === arrayConstructorInRealm || constructor === @undefined)
765         return @newArrayWithSize(length);
766     return new constructor(length);
767 }
768
769 @globalPrivate
770 function flattenIntoArray(target, source, sourceLength, targetIndex, depth)
771 {
772     "use strict";
773
774     for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) {
775         if (sourceIndex in source) {
776             var element = source[sourceIndex];
777             if (depth > 0 && @isArray(element))
778                 targetIndex = @flattenIntoArray(target, element, @toLength(element.length), targetIndex, depth - 1);
779             else {
780                 if (targetIndex >= @MAX_SAFE_INTEGER)
781                     @throwTypeError("flatten array exceeds 2**53 - 1");
782                 @putByValDirect(target, targetIndex, element);
783                 ++targetIndex;
784             }
785         }
786     }
787     return targetIndex;
788 }
789
790 function flatten()
791 {
792     "use strict";
793
794     var array = @toObject(this, "Array.prototype.flatten requires that |this| not be null or undefined");
795     var length = @toLength(array.length);
796
797     var depthNum = 1;
798     var depth = @argument(0);
799     if (depth !== @undefined)
800         depthNum = @toInteger(depth);
801
802     var result = @arraySpeciesCreate(array, 0);
803
804     @flattenIntoArray(result, array, length, 0, depthNum);
805     return result;
806 }
807
808 @globalPrivate
809 function flattenIntoArrayWithCallback(target, source, sourceLength, targetIndex, callback, thisArg)
810 {
811     "use strict";
812
813     for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) {
814         if (sourceIndex in source) {
815             var element = callback.@call(thisArg, source[sourceIndex], sourceIndex, source);
816             if (@isArray(element))
817                 targetIndex = @flattenIntoArray(target, element, @toLength(element.length), targetIndex, 0);
818             else {
819                 if (targetIndex >= @MAX_SAFE_INTEGER)
820                     @throwTypeError("flatten array exceeds 2**53 - 1");
821                 @putByValDirect(target, targetIndex, element);
822                 ++targetIndex;
823             }
824         }
825     }
826     return target;
827 }
828
829 function flatMap(callback)
830 {
831     "use strict";
832
833     var array = @toObject(this, "Array.prototype.flatMap requires that |this| not be null or undefined");
834     var length = @toLength(array.length);
835
836     if (typeof callback !== "function")
837         @throwTypeError("Array.prototype.flatMap callback must be a function");
838
839     var thisArg = @argument(1);
840
841     var result = @arraySpeciesCreate(array, 0);
842
843     return @flattenIntoArrayWithCallback(result, array, length, 0, callback, thisArg);
844 }