Title: [287136] trunk
Revision
287136
Author
[email protected]
Date
2021-12-16 08:10:09 -0800 (Thu, 16 Dec 2021)

Log Message

Implement Array.prototype.groupBy and Array.prototype.groupByToMap
https://bugs.webkit.org/show_bug.cgi?id=234327

Reviewed by Yusuke Suzuki.

JSTests:

* stress/array-groupBy.js: Added.
(shouldBe):
(shouldBeObject):
(shouldBeObject.replacer):
(notReached):
(toObject):
(reverseInsertionOrder):
* stress/array-groupByToMap.js: Added.
(shouldBe):
(shouldBeObject):
(shouldBeObject.replacer):
(shouldBeMap):
(notReached):
(toObject):
(reverseInsertionOrder):

Source/_javascript_Core:

Implement new Array Grouping proposal <https://tc39.es/proposal-array-grouping/>, which just
reached Stage 3.

`Array.prototype.groupBy`/`Array.prototype.groupByToMap` will return a `{}`/`Map` where each
value in the array is put into a "bucket" keyed by the return value of the provoded callback.

```js
const array = [1, 2, 3, 4];

array.groupBy(n => n % 2 ? "odd" : "even") // { odd: [1, 3], even: [2, 4] }
array.groupByToMap(n => n % 2 ? "odd" : "even") // new Map([["odd", [1, 3]], ["even", [2, 4]])
```

* builtins/ArrayPrototype.js:
(groupBy): Added.
(groupByToMap): Added.
* runtime/ArrayPrototype.cpp:
(JSC::ArrayPrototype::finishCreation):

* bytecode/BytecodeIntrinsicRegistry.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_toPropertyKey): Added.
Allow `@toPropertyKey` to be used in builtins to convert a value to a property key. This is
used to avoid converting the return value of the callback given to `groupBy` more than once.

* builtins/BuiltinNames.h:
* bytecode/LinkTimeConstant.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
Allow `@Map` to be used in builtins to create a primordial `Map` instance. This is used to
avoid side effects when creating and populating the `Map` returned by `groupByToMap`.

* runtime/OptionsList.h:
Add `useArrayGroupByMethod` option.

Source/WebInspectorUI:

* UserInterface/Models/NativeFunctionParameters.js:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (287135 => 287136)


--- trunk/JSTests/ChangeLog	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/JSTests/ChangeLog	2021-12-16 16:10:09 UTC (rev 287136)
@@ -1,3 +1,26 @@
+2021-12-16  Devin Rousso  <[email protected]>
+
+        Implement Array.prototype.groupBy and Array.prototype.groupByToMap
+        https://bugs.webkit.org/show_bug.cgi?id=234327
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/array-groupBy.js: Added.
+        (shouldBe):
+        (shouldBeObject):
+        (shouldBeObject.replacer):
+        (notReached):
+        (toObject):
+        (reverseInsertionOrder):
+        * stress/array-groupByToMap.js: Added.
+        (shouldBe):
+        (shouldBeObject):
+        (shouldBeObject.replacer):
+        (shouldBeMap):
+        (notReached):
+        (toObject):
+        (reverseInsertionOrder):
+
 2021-12-15  Joseph Griego  <[email protected]>
 
         [Shadow Realms] Wrapped functions must only throw TypeError from calling realm

Added: trunk/JSTests/stress/array-groupBy.js (0 => 287136)


--- trunk/JSTests/stress/array-groupBy.js	                        (rev 0)
+++ trunk/JSTests/stress/array-groupBy.js	2021-12-16 16:10:09 UTC (rev 287136)
@@ -0,0 +1,162 @@
+//@ requireOptions("--useArrayGroupByMethod=1")
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`FAIL: expected '${expected}' actual '${actual}'`);
+}
+
+function shouldBeObject(actual, expected) {
+    function replacer(key, value) {
+        if (value === undefined)
+            return "%undefined%";
+        return value;
+    }
+
+    let actualJSON = JSON.stringify(actual, replacer).replaceAll("\"%undefined%\"", "undefined");
+    let expectedJSON = JSON.stringify(expected, replacer).replaceAll("\"%undefined%\"", "undefined");
+    shouldBe(actualJSON, expectedJSON);
+}
+
+function notReached() {
+    throw new Error("should not reach here");
+}
+
+
+// Helper variables
+
+Array.prototype.__test = 42;
+
+let symbol = Symbol("symbol");
+
+let sparseArrayLength = 6;
+let mixPartialAndFast = new Array(sparseArrayLength);
+mixPartialAndFast[sparseArrayLength - 1] = sparseArrayLength - 1;
+for(let i = 0; i < 3; ++i)
+    mixPartialAndFast[i] = i;
+
+function toObject(array) {
+    let result = {};
+    result.length = array.length;
+    for (let i in array)
+        result[i] = array[i];
+    result.groupBy = Array.prototype.groupBy;
+    return result;
+}
+
+function reverseInsertionOrder(array) {
+    let obj = toObject(array);
+    let props = [];
+    for (let i in obj)
+        props.push(i);
+    let result = {};
+    for (let i = props.length - 1; i >= 0; i--)
+        result[props[i]] = obj[props[i]];
+    result.groupBy = Array.prototype.groupBy;
+    return result;
+}
+
+let objectWithToStringThatThrows = {
+    toString: notReached,
+};
+
+let objectWithValueOfThatThrows = {
+    valueOf: notReached,
+};
+
+
+// Basic
+
+shouldBe(Array.prototype.groupBy.length, 1);
+shouldBe(Array.prototype.groupBy.name, "groupBy");
+
+shouldBeObject([undefined].groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined]});
+shouldBeObject([undefined].groupBy((x) => x === undefined), {"true": [undefined]});
+
+shouldBeObject((new Array(4)).groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined, undefined, undefined, undefined]});
+shouldBeObject((new Array(4)).groupBy((x) => x === undefined), {"true": [undefined, undefined, undefined, undefined]});
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => !(x & 1) ? "a" : "b"), {"a": [0, 2], "b": [1, 3]});
+shouldBeObject([0, 1, 2, 3].groupBy((x) => !(x & 1)), {"true": [0, 2], "false": [1, 3]});
+
+shouldBeObject([0, 1, 2, 3].groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, 3]});
+shouldBeObject([0, 1, 2, 3].groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, 3]});
+
+shouldBeObject(mixPartialAndFast.groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, undefined, undefined, sparseArrayLength - 1]});
+shouldBeObject(mixPartialAndFast.groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, undefined, undefined, sparseArrayLength - 1]});
+
+
+// Generic Object
+
+shouldBeObject(toObject([undefined]).groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined]});
+shouldBeObject(toObject([undefined]).groupBy((x) => x === undefined), {"true": [undefined]});
+
+shouldBeObject(toObject(new Array(4)).groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined, undefined, undefined, undefined]});
+shouldBeObject(toObject(new Array(4)).groupBy((x) => x === undefined), {"true": [undefined, undefined, undefined, undefined]});
+
+shouldBeObject(toObject([0, 1, 2, 3]).groupBy((x) => !(x & 1) ? "a" : "b"), {"a": [0, 2], "b": [1, 3]});
+shouldBeObject(toObject([0, 1, 2, 3]).groupBy((x) => !(x & 1)), {"true": [0, 2], "false": [1, 3]});
+
+shouldBeObject(toObject([0, 1, 2, 3]).groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, 3]});
+shouldBeObject(toObject([0, 1, 2, 3]).groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, 3]});
+
+shouldBeObject(toObject(mixPartialAndFast).groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, undefined, undefined, sparseArrayLength - 1]});
+shouldBeObject(toObject(mixPartialAndFast).groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, undefined, undefined, sparseArrayLength - 1]});
+
+
+// Array-like object with invalid lengths
+
+shouldBeObject(Array.prototype.groupBy.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: 0 }, notReached), {});
+shouldBeObject(Array.prototype.groupBy.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: -0 }, notReached), {});
+shouldBeObject(Array.prototype.groupBy.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: -4 }, notReached), {});
+
+
+// Reversed generic Object
+
+shouldBeObject(reverseInsertionOrder([undefined]).groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined]});
+shouldBeObject(reverseInsertionOrder([undefined]).groupBy((x) => x === undefined), {"true": [undefined]});
+
+shouldBeObject(reverseInsertionOrder(new Array(4)).groupBy((x) => x === undefined ? "a" : "b"), {"a": [undefined, undefined, undefined, undefined]});
+shouldBeObject(reverseInsertionOrder(new Array(4)).groupBy((x) => x === undefined), {"true": [undefined, undefined, undefined, undefined]});
+
+shouldBeObject(reverseInsertionOrder([0, 1, 2, 3]).groupBy((x) => !(x & 1) ? "a" : "b"), {"a": [0, 2], "b": [1, 3]});
+shouldBeObject(reverseInsertionOrder([0, 1, 2, 3]).groupBy((x) => !(x & 1)), {"true": [0, 2], "false": [1, 3]});
+
+shouldBeObject(reverseInsertionOrder([0, 1, 2, 3]).groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, 3]});
+shouldBeObject(reverseInsertionOrder([0, 1, 2, 3]).groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, 3]});
+
+shouldBeObject(reverseInsertionOrder(mixPartialAndFast).groupBy((x, i) => i >= 2 ? "a" : "b"), {"b": [0, 1], "a": [2, undefined, undefined, sparseArrayLength - 1]});
+shouldBeObject(reverseInsertionOrder(mixPartialAndFast).groupBy((x, i) => i >= 2), {"false": [0, 1], "true": [2, undefined, undefined, sparseArrayLength - 1]});
+
+
+// Extra callback parameters
+
+shouldBeObject([0, 1, 2, 3].groupBy((i, j, k, l, m) => m = !m), {"true": [0, 1, 2, 3]});
+
+
+// Special keys
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => "constructor").constructor, [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => "prototype").prototype, [0, 1, 2, 3]);
+shouldBeObject([0, 1, 2, 3].groupBy((x) => "__proto__").__proto__, [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => -0)[0], [0, 1, 2, 3]);
+shouldBeObject([0, 1, 2, 3].groupBy((x) => 0)[0], [0, 1, 2, 3]);
+
+let objectWithToStringCounter = {
+    counter: 0,
+    toString() { return this.counter++; },
+};
+shouldBeObject([0, 1, 2, 3].groupBy((x) => objectWithToStringCounter), {"0": [0], "1": [1], "2": [2], "3": [3]});
+shouldBe(objectWithToStringCounter.counter, 4);
+
+try {
+    shouldBeObject([0, 1, 2, 3].groupBy((x) => objectWithToStringThatThrows), {});
+    notReached();
+} catch (e) {
+    shouldBe(e.message, "should not reach here");
+}
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => objectWithValueOfThatThrows)["[object Object]"], [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupBy((x) => symbol)[symbol], [0, 1, 2, 3]);

Added: trunk/JSTests/stress/array-groupByToMap.js (0 => 287136)


--- trunk/JSTests/stress/array-groupByToMap.js	                        (rev 0)
+++ trunk/JSTests/stress/array-groupByToMap.js	2021-12-16 16:10:09 UTC (rev 287136)
@@ -0,0 +1,161 @@
+//@ requireOptions("--useArrayGroupByMethod=1")
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error(`FAIL: expected '${expected}' actual '${actual}'`);
+}
+
+function shouldBeObject(actual, expected) {
+    function replacer(key, value) {
+        if (value === undefined)
+            return "%undefined%";
+        return value;
+    }
+
+    let actualJSON = JSON.stringify(actual, replacer).replaceAll("\"%undefined%\"", "undefined");
+    let expectedJSON = JSON.stringify(expected, replacer).replaceAll("\"%undefined%\"", "undefined");
+    shouldBe(actualJSON, expectedJSON);
+}
+
+function shouldBeMap(a, b) {
+    shouldBeObject(Array.from(a), b);
+}
+
+function notReached() {
+    throw new Error("should not reach here");
+}
+
+
+// Helper variables
+
+Array.prototype.__test = 42;
+
+let symbol = Symbol("symbol");
+
+let sparseArrayLength = 6;
+let mixPartialAndFast = new Array(sparseArrayLength);
+mixPartialAndFast[sparseArrayLength - 1] = sparseArrayLength - 1;
+for(let i = 0; i < 3; ++i)
+    mixPartialAndFast[i] = i;
+
+function toObject(array) {
+    let result = {};
+    result.length = array.length;
+    for (let i in array)
+        result[i] = array[i];
+    result.groupByToMap = Array.prototype.groupByToMap;
+    return result;
+}
+
+function reverseInsertionOrder(array) {
+    let obj = toObject(array);
+    let props = [];
+    for (let i in obj)
+        props.push(i);
+    let result = {};
+    for (let i = props.length - 1; i >= 0; i--)
+        result[props[i]] = obj[props[i]];
+    result.groupByToMap = Array.prototype.groupByToMap;
+    return result;
+}
+
+let objectWithToStringThatThrows = {
+    toString: notReached,
+};
+
+let objectWithValueOfThatThrows = {
+    valueOf: notReached,
+};
+
+
+// Basic
+
+shouldBe(Array.prototype.groupByToMap.length, 1);
+shouldBe(Array.prototype.groupByToMap.name, "groupByToMap");
+
+shouldBeMap([undefined].groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined]]]);
+shouldBeMap([undefined].groupByToMap((x) => x === undefined), [[true, [undefined]]]);
+
+shouldBeMap((new Array(4)).groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined, undefined, undefined, undefined]]]);
+shouldBeMap((new Array(4)).groupByToMap((x) => x === undefined), [[true, [undefined, undefined, undefined, undefined]]]);
+
+shouldBeMap([0, 1, 2, 3].groupByToMap((x) => !(x & 1) ? "a" : "b"), [["a", [0, 2]], ["b", [1, 3]]]);
+shouldBeMap([0, 1, 2, 3].groupByToMap((x) => !(x & 1)), [[true, [0, 2]], [false, [1, 3]]]);
+
+shouldBeMap([0, 1, 2, 3].groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, 3]]]);
+shouldBeMap([0, 1, 2, 3].groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, 3]]]);
+
+shouldBeMap(mixPartialAndFast.groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, undefined, undefined, sparseArrayLength - 1]]]);
+shouldBeMap(mixPartialAndFast.groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, undefined, undefined, sparseArrayLength - 1]]]);
+
+
+// Generic Object
+
+shouldBeMap(toObject([undefined]).groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined]]]);
+shouldBeMap(toObject([undefined]).groupByToMap((x) => x === undefined), [[true, [undefined]]]);
+
+shouldBeMap(toObject(new Array(4)).groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined, undefined, undefined, undefined]]]);
+shouldBeMap(toObject(new Array(4)).groupByToMap((x) => x === undefined), [[true, [undefined, undefined, undefined, undefined]]]);
+
+shouldBeMap(toObject([0, 1, 2, 3]).groupByToMap((x) => !(x & 1) ? "a" : "b"), [["a", [0, 2]], ["b", [1, 3]]]);
+shouldBeMap(toObject([0, 1, 2, 3]).groupByToMap((x) => !(x & 1)), [[true, [0, 2]], [false, [1, 3]]]);
+
+shouldBeMap(toObject([0, 1, 2, 3]).groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, 3]]]);
+shouldBeMap(toObject([0, 1, 2, 3]).groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, 3]]]);
+
+shouldBeMap(toObject(mixPartialAndFast).groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, undefined, undefined, sparseArrayLength - 1]]]);
+shouldBeMap(toObject(mixPartialAndFast).groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, undefined, undefined, sparseArrayLength - 1]]]);
+
+
+// Array-like object with invalid lengths
+
+shouldBeMap(Array.prototype.groupByToMap.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: 0 }, notReached), []);
+shouldBeMap(Array.prototype.groupByToMap.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: -0 }, notReached), []);
+shouldBeMap(Array.prototype.groupByToMap.call({ 0: 0, 1: 1, 2: 2, 3: 3, length: -4 }, notReached), []);
+
+
+// Reversed generic Object
+
+shouldBeMap(reverseInsertionOrder([undefined]).groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined]]]);
+shouldBeMap(reverseInsertionOrder([undefined]).groupByToMap((x) => x === undefined), [[true, [undefined]]]);
+
+shouldBeMap(reverseInsertionOrder(new Array(4)).groupByToMap((x) => x === undefined ? "a" : "b"), [["a", [undefined, undefined, undefined, undefined]]]);
+shouldBeMap(reverseInsertionOrder(new Array(4)).groupByToMap((x) => x === undefined), [[true, [undefined, undefined, undefined, undefined]]]);
+
+shouldBeMap(reverseInsertionOrder([0, 1, 2, 3]).groupByToMap((x) => !(x & 1) ? "a" : "b"), [["a", [0, 2]], ["b", [1, 3]]]);
+shouldBeMap(reverseInsertionOrder([0, 1, 2, 3]).groupByToMap((x) => !(x & 1)), [[true, [0, 2]], [false, [1, 3]]]);
+
+shouldBeMap(reverseInsertionOrder([0, 1, 2, 3]).groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, 3]]]);
+shouldBeMap(reverseInsertionOrder([0, 1, 2, 3]).groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, 3]]]);
+
+shouldBeMap(reverseInsertionOrder(mixPartialAndFast).groupByToMap((x, i) => i >= 2 ? "a" : "b"), [["b", [0, 1]], ["a", [2, undefined, undefined, sparseArrayLength - 1]]]);
+shouldBeMap(reverseInsertionOrder(mixPartialAndFast).groupByToMap((x, i) => i >= 2), [[false, [0, 1]], [true, [2, undefined, undefined, sparseArrayLength - 1]]]);
+
+
+// Extra callback parameters
+
+shouldBeMap([0, 1, 2, 3].groupByToMap((i, j, k, l, m) => m = !m), [[true, [0, 1, 2, 3]]]);
+
+
+// Special keys
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => "constructor").get("constructor"), [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => "prototype").get("prototype"), [0, 1, 2, 3]);
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => "__proto__").get("__proto__"), [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => -0).get(0), [0, 1, 2, 3]);
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => 0).get(0), [0, 1, 2, 3]);
+
+let objectWithToStringCounter = {
+    counter: 0,
+    toString() { return this.counter++; },
+};
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => objectWithToStringCounter).get(objectWithToStringCounter), [0, 1, 2, 3]);
+shouldBe(objectWithToStringCounter.counter, 0);
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => objectWithToStringThatThrows).get(objectWithToStringThatThrows), [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => objectWithValueOfThatThrows).get(objectWithValueOfThatThrows), [0, 1, 2, 3]);
+
+shouldBeObject([0, 1, 2, 3].groupByToMap((x) => symbol).get(symbol), [0, 1, 2, 3]);

Modified: trunk/Source/_javascript_Core/ChangeLog (287135 => 287136)


--- trunk/Source/_javascript_Core/ChangeLog	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-12-16 16:10:09 UTC (rev 287136)
@@ -1,3 +1,45 @@
+2021-12-16  Devin Rousso  <[email protected]>
+
+        Implement Array.prototype.groupBy and Array.prototype.groupByToMap
+        https://bugs.webkit.org/show_bug.cgi?id=234327
+
+        Reviewed by Yusuke Suzuki.
+
+        Implement new Array Grouping proposal <https://tc39.es/proposal-array-grouping/>, which just
+        reached Stage 3.
+
+        `Array.prototype.groupBy`/`Array.prototype.groupByToMap` will return a `{}`/`Map` where each
+        value in the array is put into a "bucket" keyed by the return value of the provoded callback.
+
+        ```js
+        const array = [1, 2, 3, 4];
+
+        array.groupBy(n => n % 2 ? "odd" : "even") // { odd: [1, 3], even: [2, 4] }
+        array.groupByToMap(n => n % 2 ? "odd" : "even") // new Map([["odd", [1, 3]], ["even", [2, 4]])
+        ```
+
+        * builtins/ArrayPrototype.js:
+        (groupBy): Added.
+        (groupByToMap): Added.
+        * runtime/ArrayPrototype.cpp:
+        (JSC::ArrayPrototype::finishCreation):
+
+        * bytecode/BytecodeIntrinsicRegistry.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::BytecodeIntrinsicNode::emit_intrinsic_toPropertyKey): Added.
+        Allow `@toPropertyKey` to be used in builtins to convert a value to a property key. This is
+        used to avoid converting the return value of the callback given to `groupBy` more than once.
+
+        * builtins/BuiltinNames.h:
+        * bytecode/LinkTimeConstant.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        Allow `@Map` to be used in builtins to create a primordial `Map` instance. This is used to
+        avoid side effects when creating and populating the `Map` returned by `groupByToMap`.
+
+        * runtime/OptionsList.h:
+        Add `useArrayGroupByMethod` option.
+
 2021-12-15  Yusuke Suzuki  <[email protected]>
 
         Rename Wasm::CodeBlock to Wasm::CalleeGroup

Modified: trunk/Source/_javascript_Core/builtins/ArrayPrototype.js (287135 => 287136)


--- trunk/Source/_javascript_Core/builtins/ArrayPrototype.js	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/builtins/ArrayPrototype.js	2021-12-16 16:10:09 UTC (rev 287136)
@@ -156,6 +156,54 @@
     return result;
 }
 
+function groupBy(callback /*, thisArg */)
+{
+    var array = @toObject(this, "Array.prototype.groupBy requires that |this| not be null or undefined");
+    var length = @toLength(array.length);
+
+    if (!@isCallable(callback))
+        @throwTypeError("Array.prototype.groupBy callback must be a function");
+
+    var thisArg = @argument(1);
+
+    var groups = @Object.@create(null);
+    for (var i = 0; i < length; ++i) {
+        var value = array[i];
+        var key = @toPropertyKey(callback.@call(thisArg, value, i, array));
+        var group = groups[key];
+        if (!group) {
+            group = [];
+            @putByValDirect(groups, key, group);
+        }
+        @putByValDirect(group, group.length, value);
+    }
+    return groups;
+}
+
+function groupByToMap(callback /*, thisArg */)
+{
+    var array = @toObject(this, "Array.prototype.groupByToMap requires that |this| not be null or undefined");
+    var length = @toLength(array.length);
+
+    if (!@isCallable(callback))
+        @throwTypeError("Array.prototype.groupByToMap callback must be a function");
+
+    var thisArg = @argument(1);
+
+    var groups = new @Map;
+    for (var i = 0; i < length; ++i) {
+        var value = array[i];
+        var key = callback.@call(thisArg, value, i, array);
+        var group = groups.@get(key);
+        if (!group) {
+            group = [];
+            groups.@set(key, group);
+        }
+        @putByValDirect(group, group.length, value);
+    }
+    return groups;
+}
+
 function map(callback /*, thisArg */)
 {
     "use strict";

Modified: trunk/Source/_javascript_Core/builtins/BuiltinNames.h (287135 => 287136)


--- trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/builtins/BuiltinNames.h	2021-12-16 16:10:09 UTC (rev 287136)
@@ -70,6 +70,7 @@
     macro(defineProperty) \
     macro(defaultPromiseThen) \
     macro(Set) \
+    macro(Map) \
     macro(throwTypeErrorFunction) \
     macro(typedArrayLength) \
     macro(typedArrayContentType) \

Modified: trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h (287135 => 287136)


--- trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/bytecode/BytecodeIntrinsicRegistry.h	2021-12-16 16:10:09 UTC (rev 287136)
@@ -90,6 +90,7 @@
     macro(putSetIteratorInternalField) \
     macro(toNumber) \
     macro(toString) \
+    macro(toPropertyKey) \
     macro(toObject) \
     macro(newArrayWithSize) \
     macro(newPromise) \

Modified: trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h (287135 => 287136)


--- trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/bytecode/LinkTimeConstant.h	2021-12-16 16:10:09 UTC (rev 287136)
@@ -77,6 +77,7 @@
     v(appendMemcpy, nullptr) \
     v(hostPromiseRejectionTracker, nullptr) \
     v(Set, nullptr) \
+    v(Map, nullptr) \
     v(thisTimeValue, nullptr) \
     v(importInRealm, nullptr) \
     v(evalInRealm, nullptr) \

Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (287135 => 287136)


--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2021-12-16 16:10:09 UTC (rev 287136)
@@ -1841,6 +1841,15 @@
     return generator.move(dst, generator.emitToString(generator.tempDestination(dst), src.get()));
 }
 
+RegisterID* BytecodeIntrinsicNode::emit_intrinsic_toPropertyKey(BytecodeGenerator& generator, RegisterID* dst)
+{
+    ArgumentListNode* node = m_args->m_listNode;
+    RefPtr<RegisterID> src = ""
+    ASSERT(!node->m_next);
+
+    return generator.move(dst, generator.emitToPropertyKey(generator.tempDestination(dst), src.get()));
+}
+
 RegisterID* BytecodeIntrinsicNode::emit_intrinsic_toObject(BytecodeGenerator& generator, RegisterID* dst)
 {
     ArgumentListNode* node = m_args->m_listNode;

Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp (287135 => 287136)


--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp	2021-12-16 16:10:09 UTC (rev 287136)
@@ -100,6 +100,10 @@
     JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("indexOf", arrayProtoFuncIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1, ArrayIndexOfIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("lastIndexOf", arrayProtoFuncLastIndexOf, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().filterPublicName(), arrayPrototypeFilterCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
+    if (Options::useArrayGroupByMethod()) {
+        JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().groupByPublicName(), arrayPrototypeGroupByCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
+        JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().groupByToMapPublicName(), arrayPrototypeGroupByToMapCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
+    }
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().flatPublicName(), arrayPrototypeFlatCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().flatMapPublicName(), arrayPrototypeFlatMapCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().reducePublicName(), arrayPrototypeReduceCodeGenerator, static_cast<unsigned>(PropertyAttribute::DontEnum));
@@ -137,6 +141,8 @@
         Options::useArrayFindLastMethod() ? &vm.propertyNames->builtinNames().findLastIndexPublicName() : nullptr,
         &vm.propertyNames->builtinNames().flatPublicName(),
         &vm.propertyNames->builtinNames().flatMapPublicName(),
+        Options::useArrayGroupByMethod() ? &vm.propertyNames->builtinNames().groupByPublicName() : nullptr,
+        Options::useArrayGroupByMethod() ? &vm.propertyNames->builtinNames().groupByToMapPublicName() : nullptr,
         &vm.propertyNames->builtinNames().includesPublicName(),
         &vm.propertyNames->builtinNames().keysPublicName(),
         &vm.propertyNames->builtinNames().valuesPublicName()

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (287135 => 287136)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2021-12-16 16:10:09 UTC (rev 287136)
@@ -1350,6 +1350,9 @@
     m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::Set)].initLater([] (const Initializer<JSCell>& init) {
             init.set(jsCast<JSGlobalObject*>(init.owner)->setConstructor());
         });
+    m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::Map)].initLater([] (const Initializer<JSCell>& init) {
+            init.set(jsCast<JSGlobalObject*>(init.owner)->mapConstructor());
+        });
     m_linkTimeConstants[static_cast<unsigned>(LinkTimeConstant::mapBucketHead)].initLater([] (const Initializer<JSCell>& init) {
             init.set(JSFunction::create(init.vm, jsCast<JSGlobalObject*>(init.owner), 0, String(), mapPrivateFuncMapBucketHead, JSMapBucketHeadIntrinsic));
         });

Modified: trunk/Source/_javascript_Core/runtime/OptionsList.h (287135 => 287136)


--- trunk/Source/_javascript_Core/runtime/OptionsList.h	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/_javascript_Core/runtime/OptionsList.h	2021-12-16 16:10:09 UTC (rev 287136)
@@ -538,6 +538,7 @@
     /* Feature Flags */\
     \
     v(Bool, useArrayFindLastMethod, true, Normal, "Expose the findLast() and findLastIndex() methods on Array and %TypedArray%.") \
+    v(Bool, useArrayGroupByMethod, false, Normal, "Expose the groupBy() and groupByToMap() methods on Array.") \
     v(Bool, useAtMethod, true, Normal, "Expose the at() method on Array, %TypedArray%, and String.") \
     v(Bool, useHasOwn, true, Normal, "Expose the Object.hasOwn method") \
     v(Bool, useIntlEnumeration, true, Normal, "Expose the Intl enumeration APIs.") \

Modified: trunk/Source/WebInspectorUI/ChangeLog (287135 => 287136)


--- trunk/Source/WebInspectorUI/ChangeLog	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/WebInspectorUI/ChangeLog	2021-12-16 16:10:09 UTC (rev 287136)
@@ -1,3 +1,12 @@
+2021-12-16  Devin Rousso  <[email protected]>
+
+        Implement Array.prototype.groupBy and Array.prototype.groupByToMap
+        https://bugs.webkit.org/show_bug.cgi?id=234327
+
+        Reviewed by Yusuke Suzuki.
+
+        * UserInterface/Models/NativeFunctionParameters.js:
+
 2021-12-14  Razvan Caliman  <[email protected]>
 
         Web Inspector: Computed Panel: Adjust color of CSS Variables grouping mode control

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js (287135 => 287136)


--- trunk/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js	2021-12-16 15:49:37 UTC (rev 287135)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js	2021-12-16 16:10:09 UTC (rev 287136)
@@ -252,6 +252,8 @@
         findLast: "callback, [thisArg]",
         findLastIndex: "callback, [thisArg]",
         forEach: "callback, [thisArg]",
+        groupBy: "callback, [thisArg]",
+        groupByToMap: "callback, [thisArg]",
         includes: "searchValue, [startIndex=0]",
         indexOf: "searchValue, [startIndex=0]",
         join: "[separator=\",\"]",
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to