Skip to content

Commit 1ef7ea8

Browse files
committed
[MERGE #5573 @rhuanjl] Implement array.prototype.flat and flatMap
Merge pull request #5573 from rhuanjl:flat This PR implements the stage 3 proposal Array methods: Array.prototype.flat and Array.prototype.flatMap as JsBuiltIns. Draft specification text is here: https://tc39.github.io/proposal-flatMap/ Notes: 1. 3 test262 failures: - length is not configurable (for both methods), this is due to ScriptFunction lengths in CC all not being configurable and should be fixed if #5405 is merged - the case using null as this fails -- EDIT: FIXED 2. I can't find a feasible way to test the length is too big error - not sure if CC can actually handle arrays that big anyway so maybe not worth implementing that case? 3. Should probably have a few more CI tests Fixes #5543
2 parents 08567f7 + 9de922b commit 1ef7ea8

21 files changed

+24393
-22523
lines changed

lib/Common/CommonDefines.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,10 @@
132132
// Language features
133133
#if !defined(CHAKRACORE_LITE) && (defined(_WIN32) || defined(INTL_ICU))
134134
#define ENABLE_INTL_OBJECT // Intl support
135-
#define ENABLE_JS_BUILTINS // Built In functions support
136135
#endif
137136

137+
#define ENABLE_JS_BUILTINS // Built In functions support
138+
138139
#if defined(_WIN32) && !defined(HAS_ICU)
139140
#define INTL_WINGLOB 1
140141
#endif

lib/Parser/rterrors.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ RT_ERROR_MSG(JSERROR_SetPrototypeOf, 5616, "Failed to set prototype", "Failed to
302302
RT_ERROR_MSG(JSERR_ObjectIsNotInitialized, 5617, "%s: Object internal state is not initialized", "Object internal state is not initialized", kjstTypeError, 0)
303303

304304
RT_ERROR_MSG(JSERR_GeneratorAlreadyExecuting, 5618, "%s: Cannot execute generator function because it is currently executing", "", kjstTypeError, 0)
305-
// 5619-5626 Unused
305+
RT_ERROR_MSG(JSERR_LengthIsTooBig, 5619, "Length property would exceed maximum value in output from '%s'", "", kjstTypeError, 0)
306+
// 5620-5626 Unused
306307
RT_ERROR_MSG(JSERR_NeedConstructor, 5627, "'%s' is not a constructor", "Constructor expected", kjstTypeError, 0)
307308

308309
RT_ERROR_MSG(VBSERR_CantDisplayDate, 32812, "", "The specified date is not available in the current locale's calendar", kjstRangeError, 0)

lib/Runtime/Base/JnDirectFields.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ ENTRY(every)
140140
ENTRY(exec)
141141
ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "false_" instead
142142
ENTRY(flags)
143+
ENTRY(flat)
144+
ENTRY(flatMap)
143145
ENTRY(fill)
144146
ENTRY(filter)
145147
ENTRY(finally)
@@ -657,6 +659,7 @@ ENTRY(builtInRegexMatch)
657659
ENTRY(builtInCallInstanceFunction)
658660
ENTRY(raiseInvalidCurrencyCode)
659661
ENTRY(raiseInvalidDate)
662+
ENTRY(raiseLengthIsTooBig)
660663
ENTRY(raiseLocaleNotWellFormed)
661664
ENTRY(raiseMissingCurrencyCode)
662665
ENTRY(raiseNeedObject)

lib/Runtime/Library/EngineInterfaceObjectBuiltIns.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ BuiltInRaiseException1(RangeError, LocaleNotWellFormed)
5858
BuiltInRaiseException1(TypeError, This_NullOrUndefined)
5959
BuiltInRaiseException1(TypeError, NotAConstructor)
6060
BuiltInRaiseException1(TypeError, ObjectIsNonExtensible)
61+
BuiltInRaiseException1(TypeError, LengthIsTooBig)
6162
BuiltInRaiseException2(TypeError, NeedObjectOfType)
6263
BuiltInRaiseException1(RangeError, InvalidCurrencyCode)
6364
BuiltInRaiseException(TypeError, MissingCurrencyCode)

lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Lines changed: 5518 additions & 5518 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Lines changed: 5518 additions & 5517 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Lines changed: 4838 additions & 4838 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Lines changed: 4838 additions & 4837 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/JavascriptLibrary.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,7 @@ namespace Js
16691669

16701670
bool JavascriptLibrary::InitializeArrayPrototype(DynamicObject* arrayPrototype, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode)
16711671
{
1672-
typeHandler->Convert(arrayPrototype, mode, 24);
1672+
typeHandler->Convert(arrayPrototype, mode, 26);
16731673
// Note: Any new function addition/deletion/modification should also be updated in JavascriptLibrary::ProfilerRegisterArray
16741674
// so that the update is in sync with profiler
16751675

lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js

Lines changed: 178 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
ArrayEntries: { className: "Array", methodName: "entries", argumentsCount: 0, forceInline: true /*optional*/ },
1515
ArrayIndexOf: { className: "Array", methodName: "indexOf", argumentsCount: 1, forceInline: true /*optional*/ },
1616
ArrayFilter: { className: "Array", methodName: "filter", argumentsCount: 1, forceInline: true /*optional*/ },
17+
ArrayFlat: { className: "Array", methodName: "flat", argumentsCount: 0, forceInline: true /*optional*/ },
18+
ArrayFlatMap: { className: "Array", methodName: "flatMap", argumentsCount: 1, forceInline: true /*optional*/ },
1719
};
1820

1921
var setPrototype = platform.builtInSetPrototype;
@@ -36,6 +38,7 @@
3638
__chakraLibrary.ArrayIterator.prototype = CreateObject(iteratorPrototype);
3739
__chakraLibrary.raiseNeedObjectOfType = platform.raiseNeedObjectOfType;
3840
__chakraLibrary.raiseThis_NullOrUndefined = platform.raiseThis_NullOrUndefined;
41+
__chakraLibrary.raiseLengthIsTooBig = platform.raiseLengthIsTooBig;
3942
__chakraLibrary.raiseFunctionArgument_NeedFunction = platform.raiseFunctionArgument_NeedFunction;
4043
__chakraLibrary.callInstanceFunc = platform.builtInCallInstanceFunction;
4144
__chakraLibrary.functionBind = platform.builtInJavascriptFunctionEntryBind;
@@ -200,7 +203,7 @@
200203
len = __chakraLibrary.GetLength(o);
201204
}
202205

203-
if (typeof callbackfn != "function") {
206+
if (typeof callbackfn !== "function") {
204207
__chakraLibrary.raiseFunctionArgument_NeedFunction("Array.prototype.filter");
205208
}
206209

@@ -238,5 +241,178 @@
238241

239242
return a;
240243
});
241-
244+
245+
platform.registerChakraLibraryFunction("FlattenIntoArray", function(target, source, sourceLen, start, depth)
246+
{
247+
// this is FlattenIntoArray from the flat/flatMap proposal BUT with no mapperFunction
248+
// a seperate function has been made to handle the case where there is a mapperFunction
249+
"use strict";
250+
//1. Let targetIndex be start.
251+
let targetIndex = start;
252+
//2. Let sourceIndex be 0.
253+
let sourceIndex = 0;
254+
//3. Repeat, while sourceIndex < sourceLen
255+
let element;
256+
while (sourceIndex < sourceLen) {
257+
// a. Let P be ! ToString(sourceIndex).
258+
// b. Let exists be ? HasProperty(source, P).
259+
if (sourceIndex in source) {
260+
// c. If exists is true, then
261+
// i. Let element be ? Get(source, P).
262+
element = source[sourceIndex];
263+
// ii. If mapperFunction is present - skipped see separate function
264+
// iii. Let shouldFlatten be false.
265+
// iv. If depth > 0, then
266+
// 1. Set shouldFlatten to ? IsArray(element).
267+
if (depth > 0 && __chakraLibrary.isArray(element)) {
268+
// v. If shouldFlatten is true, then
269+
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
270+
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1).
271+
targetIndex = __chakraLibrary.FlattenIntoArray(target, element, __chakraLibrary.toLength(element.length), targetIndex, depth - 1);
272+
} else {
273+
// vi. Else,
274+
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
275+
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
276+
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flat");
277+
}
278+
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
279+
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element);
280+
// 3. Increase targetIndex by 1.
281+
++targetIndex;
282+
}
283+
}
284+
// d. Increase sourceIndex by 1.
285+
++sourceIndex;
286+
}
287+
//4. Return targetIndex.
288+
return targetIndex;
289+
});
290+
291+
platform.registerChakraLibraryFunction("FlattenIntoArrayMapped", function(target, source, sourceLen, start, mapperFunction) {
292+
"use strict";
293+
// this is FlattenIntoArray from the flat/flatMap proposal BUT with:
294+
// depth = 1 and the presence of a mapperFunction guaranteed
295+
// both these conditions are always met when this is called from flatMap
296+
// Additionally this is slightly refactored rather than taking a thisArg
297+
// the calling function binds the thisArg if it's required
298+
//1. Let targetIndex be start.
299+
let targetIndex = start;
300+
//2. Let sourceIndex be 0.
301+
let sourceIndex = 0;
302+
//3. Repeat, while sourceIndex < sourceLen
303+
304+
let element, innerLength, innerIndex;
305+
while (sourceIndex < sourceLen) {
306+
// a. Let P be ! ToString(sourceIndex).
307+
// b. Let exists be ? HasProperty(source, P).
308+
if (sourceIndex in source) {
309+
// c. If exists is true, then
310+
// i. Let element be ? Get(source, P).
311+
// ii. If mapperFunction is present, then
312+
// 1. Assert: thisArg is present.
313+
// 2. Set element to ? Call(mapperFunction, thisArg , element, sourceIndex, source).
314+
element = mapperFunction(source[sourceIndex], sourceIndex, source);
315+
// iii. Let shouldFlatten be false.
316+
// iv. If depth > 0, then
317+
// 1. Set shouldFlatten to ? IsArray(element).
318+
// v. If shouldFlatten is true, then
319+
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
320+
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1).
321+
if (__chakraLibrary.isArray(element)) {
322+
// instead of calling FlattenIntoArray use a simple loop here - as depth is always 0
323+
innerLength = __chakraLibrary.toLength(element.length);
324+
innerIndex = 0;
325+
while (innerIndex < innerLength) {
326+
if (innerIndex in element) {
327+
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
328+
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
329+
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flatMap");
330+
}
331+
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
332+
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element[innerIndex]);
333+
// 3. Increase targetIndex by 1.
334+
++targetIndex;
335+
}
336+
++innerIndex;
337+
}
338+
} else {
339+
// vi. Else,
340+
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
341+
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
342+
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flatMap");
343+
}
344+
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
345+
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element);
346+
// 3. Increase targetIndex by 1.
347+
++targetIndex;
348+
}
349+
}
350+
// d. Increase sourceIndex by 1.
351+
++sourceIndex;
352+
}
353+
//4. Return targetIndex.
354+
return targetIndex;
355+
});
356+
357+
platform.registerFunction(FunctionsEnum.ArrayFlat, function (depth) {
358+
"use strict";
359+
//1. Let O be ? ToObject(this value).
360+
//2. Let sourceLen be ? ToLength(? Get(O, "length")).
361+
let o, sourceLen;
362+
363+
if (__chakraLibrary.isArray(this)) {
364+
o = this;
365+
sourceLen = o.length;
366+
} else {
367+
if (this === null || this === undefined) {
368+
__chakraLibrary.raiseThis_NullOrUndefined("Array.prototype.flat");
369+
}
370+
o = __chakraLibrary.Object(this);
371+
sourceLen = __chakraLibrary.GetLength(o);
372+
}
373+
//3. Let depthNum be 1.
374+
//4. If depth is not undefined, then
375+
//5. Set depthNum to ? ToInteger(depth).
376+
const depthNum = depth !== undefined ? __chakraLibrary.toInteger(depth) : 1;
377+
//6. Let A be ? ArraySpeciesCreate(O, 0).
378+
const A = __chakraLibrary.arraySpeciesCreate(o, 0);
379+
//7. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
380+
__chakraLibrary.FlattenIntoArray(A, o, sourceLen, 0, depthNum);
381+
//8. Return A.
382+
return A;
383+
});
384+
385+
platform.registerFunction(FunctionsEnum.ArrayFlatMap, function (mapperFunction, thisArg) {
386+
"use strict";
387+
//1. Let O be ? ToObject(this value).
388+
//2. Let sourceLen be ? ToLength(? Get(O, "length")).
389+
let o, sourceLen;
390+
if (__chakraLibrary.isArray(this)) {
391+
o = this;
392+
sourceLen = o.length;
393+
} else {
394+
if (this === null || this === undefined) {
395+
__chakraLibrary.raiseThis_NullOrUndefined("Array.prototype.flatMap");
396+
}
397+
o = __chakraLibrary.Object(this);
398+
sourceLen = __chakraLibrary.GetLength(o);
399+
}
400+
//3. If IsCallable(mapperFunction) is false throw a TypeError exception
401+
if (typeof mapperFunction !== "function") {
402+
__chakraLibrary.raiseFunctionArgument_NeedFunction("Array.prototype.flatMap");
403+
}
404+
//4. If thisArg is present, let T be thisArg; else let T be undefined
405+
//5. Let A be ? ArraySpeciesCreate(O, 0).
406+
const A = __chakraLibrary.arraySpeciesCreate(o, 0);
407+
//6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
408+
if (thisArg === undefined) {
409+
__chakraLibrary.FlattenIntoArrayMapped(A, o, sourceLen, 0, mapperFunction);
410+
} else {
411+
const func = __chakraLibrary.callInstanceFunc(__chakraLibrary.functionBind, mapperFunction, thisArg);
412+
__chakraLibrary.FlattenIntoArrayMapped(A, o, sourceLen, 0, func);
413+
}
414+
//7. Return A.
415+
return A;
416+
});
417+
242418
});

0 commit comments

Comments
 (0)