Skip to content

Commit

Permalink
[MERGE #5573 @rhuanjl] Implement array.prototype.flat and flatMap
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sigatrev committed Aug 17, 2018
2 parents 08567f7 + 9de922b commit 1ef7ea8
Show file tree
Hide file tree
Showing 21 changed files with 24,393 additions and 22,523 deletions.
3 changes: 2 additions & 1 deletion lib/Common/CommonDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@
// Language features
#if !defined(CHAKRACORE_LITE) && (defined(_WIN32) || defined(INTL_ICU))
#define ENABLE_INTL_OBJECT // Intl support
#define ENABLE_JS_BUILTINS // Built In functions support
#endif

#define ENABLE_JS_BUILTINS // Built In functions support

#if defined(_WIN32) && !defined(HAS_ICU)
#define INTL_WINGLOB 1
#endif
Expand Down
3 changes: 2 additions & 1 deletion lib/Parser/rterrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,8 @@ RT_ERROR_MSG(JSERROR_SetPrototypeOf, 5616, "Failed to set prototype", "Failed to
RT_ERROR_MSG(JSERR_ObjectIsNotInitialized, 5617, "%s: Object internal state is not initialized", "Object internal state is not initialized", kjstTypeError, 0)

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

RT_ERROR_MSG(VBSERR_CantDisplayDate, 32812, "", "The specified date is not available in the current locale's calendar", kjstRangeError, 0)
Expand Down
3 changes: 3 additions & 0 deletions lib/Runtime/Base/JnDirectFields.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ ENTRY(every)
ENTRY(exec)
ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "false_" instead
ENTRY(flags)
ENTRY(flat)
ENTRY(flatMap)
ENTRY(fill)
ENTRY(filter)
ENTRY(finally)
Expand Down Expand Up @@ -657,6 +659,7 @@ ENTRY(builtInRegexMatch)
ENTRY(builtInCallInstanceFunction)
ENTRY(raiseInvalidCurrencyCode)
ENTRY(raiseInvalidDate)
ENTRY(raiseLengthIsTooBig)
ENTRY(raiseLocaleNotWellFormed)
ENTRY(raiseMissingCurrencyCode)
ENTRY(raiseNeedObject)
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Library/EngineInterfaceObjectBuiltIns.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ BuiltInRaiseException1(RangeError, LocaleNotWellFormed)
BuiltInRaiseException1(TypeError, This_NullOrUndefined)
BuiltInRaiseException1(TypeError, NotAConstructor)
BuiltInRaiseException1(TypeError, ObjectIsNonExtensible)
BuiltInRaiseException1(TypeError, LengthIsTooBig)
BuiltInRaiseException2(TypeError, NeedObjectOfType)
BuiltInRaiseException1(RangeError, InvalidCurrencyCode)
BuiltInRaiseException(TypeError, MissingCurrencyCode)
Expand Down
11,036 changes: 5,518 additions & 5,518 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Large diffs are not rendered by default.

11,035 changes: 5,518 additions & 5,517 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Large diffs are not rendered by default.

9,676 changes: 4,838 additions & 4,838 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Large diffs are not rendered by default.

9,675 changes: 4,838 additions & 4,837 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@ namespace Js

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

Expand Down
180 changes: 178 additions & 2 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
ArrayEntries: { className: "Array", methodName: "entries", argumentsCount: 0, forceInline: true /*optional*/ },
ArrayIndexOf: { className: "Array", methodName: "indexOf", argumentsCount: 1, forceInline: true /*optional*/ },
ArrayFilter: { className: "Array", methodName: "filter", argumentsCount: 1, forceInline: true /*optional*/ },
ArrayFlat: { className: "Array", methodName: "flat", argumentsCount: 0, forceInline: true /*optional*/ },
ArrayFlatMap: { className: "Array", methodName: "flatMap", argumentsCount: 1, forceInline: true /*optional*/ },
};

var setPrototype = platform.builtInSetPrototype;
Expand All @@ -36,6 +38,7 @@
__chakraLibrary.ArrayIterator.prototype = CreateObject(iteratorPrototype);
__chakraLibrary.raiseNeedObjectOfType = platform.raiseNeedObjectOfType;
__chakraLibrary.raiseThis_NullOrUndefined = platform.raiseThis_NullOrUndefined;
__chakraLibrary.raiseLengthIsTooBig = platform.raiseLengthIsTooBig;
__chakraLibrary.raiseFunctionArgument_NeedFunction = platform.raiseFunctionArgument_NeedFunction;
__chakraLibrary.callInstanceFunc = platform.builtInCallInstanceFunction;
__chakraLibrary.functionBind = platform.builtInJavascriptFunctionEntryBind;
Expand Down Expand Up @@ -200,7 +203,7 @@
len = __chakraLibrary.GetLength(o);
}

if (typeof callbackfn != "function") {
if (typeof callbackfn !== "function") {
__chakraLibrary.raiseFunctionArgument_NeedFunction("Array.prototype.filter");
}

Expand Down Expand Up @@ -238,5 +241,178 @@

return a;
});


platform.registerChakraLibraryFunction("FlattenIntoArray", function(target, source, sourceLen, start, depth)
{
// this is FlattenIntoArray from the flat/flatMap proposal BUT with no mapperFunction
// a seperate function has been made to handle the case where there is a mapperFunction
"use strict";
//1. Let targetIndex be start.
let targetIndex = start;
//2. Let sourceIndex be 0.
let sourceIndex = 0;
//3. Repeat, while sourceIndex < sourceLen
let element;
while (sourceIndex < sourceLen) {
// a. Let P be ! ToString(sourceIndex).
// b. Let exists be ? HasProperty(source, P).
if (sourceIndex in source) {
// c. If exists is true, then
// i. Let element be ? Get(source, P).
element = source[sourceIndex];
// ii. If mapperFunction is present - skipped see separate function
// iii. Let shouldFlatten be false.
// iv. If depth > 0, then
// 1. Set shouldFlatten to ? IsArray(element).
if (depth > 0 && __chakraLibrary.isArray(element)) {
// v. If shouldFlatten is true, then
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1).
targetIndex = __chakraLibrary.FlattenIntoArray(target, element, __chakraLibrary.toLength(element.length), targetIndex, depth - 1);
} else {
// vi. Else,
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flat");
}
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element);
// 3. Increase targetIndex by 1.
++targetIndex;
}
}
// d. Increase sourceIndex by 1.
++sourceIndex;
}
//4. Return targetIndex.
return targetIndex;
});

platform.registerChakraLibraryFunction("FlattenIntoArrayMapped", function(target, source, sourceLen, start, mapperFunction) {
"use strict";
// this is FlattenIntoArray from the flat/flatMap proposal BUT with:
// depth = 1 and the presence of a mapperFunction guaranteed
// both these conditions are always met when this is called from flatMap
// Additionally this is slightly refactored rather than taking a thisArg
// the calling function binds the thisArg if it's required
//1. Let targetIndex be start.
let targetIndex = start;
//2. Let sourceIndex be 0.
let sourceIndex = 0;
//3. Repeat, while sourceIndex < sourceLen

let element, innerLength, innerIndex;
while (sourceIndex < sourceLen) {
// a. Let P be ! ToString(sourceIndex).
// b. Let exists be ? HasProperty(source, P).
if (sourceIndex in source) {
// c. If exists is true, then
// i. Let element be ? Get(source, P).
// ii. If mapperFunction is present, then
// 1. Assert: thisArg is present.
// 2. Set element to ? Call(mapperFunction, thisArg , element, sourceIndex, source).
element = mapperFunction(source[sourceIndex], sourceIndex, source);
// iii. Let shouldFlatten be false.
// iv. If depth > 0, then
// 1. Set shouldFlatten to ? IsArray(element).
// v. If shouldFlatten is true, then
// 1. Let elementLen be ? ToLength(? Get(element, "length")).
// 2. Set targetIndex to ? FlattenIntoArray(target, element, elementLen, targetIndex, depth - 1).
if (__chakraLibrary.isArray(element)) {
// instead of calling FlattenIntoArray use a simple loop here - as depth is always 0
innerLength = __chakraLibrary.toLength(element.length);
innerIndex = 0;
while (innerIndex < innerLength) {
if (innerIndex in element) {
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flatMap");
}
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element[innerIndex]);
// 3. Increase targetIndex by 1.
++targetIndex;
}
++innerIndex;
}
} else {
// vi. Else,
// 1. If targetIndex >= 2^53-1, throw a TypeError exception.
if (targetIndex >= 9007199254740991 /* 2^53-1 */) {
__chakraLibrary.raiseLengthIsTooBig("Array.prototype.flatMap");
}
// 2. Perform ? CreateDataPropertyOrThrow(target, ! ToString(targetIndex), element).
__chakraLibrary.arrayCreateDataPropertyOrThrow(target, targetIndex, element);
// 3. Increase targetIndex by 1.
++targetIndex;
}
}
// d. Increase sourceIndex by 1.
++sourceIndex;
}
//4. Return targetIndex.
return targetIndex;
});

platform.registerFunction(FunctionsEnum.ArrayFlat, function (depth) {
"use strict";
//1. Let O be ? ToObject(this value).
//2. Let sourceLen be ? ToLength(? Get(O, "length")).
let o, sourceLen;

if (__chakraLibrary.isArray(this)) {
o = this;
sourceLen = o.length;
} else {
if (this === null || this === undefined) {
__chakraLibrary.raiseThis_NullOrUndefined("Array.prototype.flat");
}
o = __chakraLibrary.Object(this);
sourceLen = __chakraLibrary.GetLength(o);
}
//3. Let depthNum be 1.
//4. If depth is not undefined, then
//5. Set depthNum to ? ToInteger(depth).
const depthNum = depth !== undefined ? __chakraLibrary.toInteger(depth) : 1;
//6. Let A be ? ArraySpeciesCreate(O, 0).
const A = __chakraLibrary.arraySpeciesCreate(o, 0);
//7. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
__chakraLibrary.FlattenIntoArray(A, o, sourceLen, 0, depthNum);
//8. Return A.
return A;
});

platform.registerFunction(FunctionsEnum.ArrayFlatMap, function (mapperFunction, thisArg) {
"use strict";
//1. Let O be ? ToObject(this value).
//2. Let sourceLen be ? ToLength(? Get(O, "length")).
let o, sourceLen;
if (__chakraLibrary.isArray(this)) {
o = this;
sourceLen = o.length;
} else {
if (this === null || this === undefined) {
__chakraLibrary.raiseThis_NullOrUndefined("Array.prototype.flatMap");
}
o = __chakraLibrary.Object(this);
sourceLen = __chakraLibrary.GetLength(o);
}
//3. If IsCallable(mapperFunction) is false throw a TypeError exception
if (typeof mapperFunction !== "function") {
__chakraLibrary.raiseFunctionArgument_NeedFunction("Array.prototype.flatMap");
}
//4. If thisArg is present, let T be thisArg; else let T be undefined
//5. Let A be ? ArraySpeciesCreate(O, 0).
const A = __chakraLibrary.arraySpeciesCreate(o, 0);
//6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum).
if (thisArg === undefined) {
__chakraLibrary.FlattenIntoArrayMapped(A, o, sourceLen, 0, mapperFunction);
} else {
const func = __chakraLibrary.callInstanceFunc(__chakraLibrary.functionBind, mapperFunction, thisArg);
__chakraLibrary.FlattenIntoArrayMapped(A, o, sourceLen, 0, func);
}
//7. Return A.
return A;
});

});
Loading

0 comments on commit 1ef7ea8

Please sign in to comment.