From 1489a6f8b5c72763099430d4a1ff306fb5600595 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Tue, 10 Mar 2026 10:45:22 -0400 Subject: [PATCH] Issue #2990 - implement object.GroupBy --- js/src/builtin/Object.cpp | 1 + js/src/builtin/Object.js | 316 +++++++++++++++----------- js/src/tests/ecma_6/Object/groupBy.js | 89 ++++++++ 3 files changed, 268 insertions(+), 138 deletions(-) create mode 100644 js/src/tests/ecma_6/Object/groupBy.js diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index f4353480ba..547c59d417 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -1247,6 +1247,7 @@ static const JSFunctionSpec object_static_methods[] = { JS_FN("seal", obj_seal, 1, 0), JS_FN("isSealed", obj_isSealed, 1, 0), JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 1, 0), + JS_SELF_HOSTED_FN("groupBy", "ObjectGroupBy", 2, 0), JS_SELF_HOSTED_FN("hasOwn", "ObjectHasOwn", 2, 0), JS_FS_END }; diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index 59d4807317..6627aea593 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -4,228 +4,268 @@ // ES6 draft rev36 2015-03-17 19.1.2.1 function ObjectStaticAssign(target, firstSource) { - // Steps 1-2. - var to = ToObject(target); + // Steps 1-2. + var to = ToObject(target); - // Step 3. - if (arguments.length < 2) - return to; + // Step 3. + if (arguments.length < 2) return to; - // Steps 4-5. - for (var i = 1; i < arguments.length; i++) { - // Step 5.a. - var nextSource = arguments[i]; - if (nextSource === null || nextSource === undefined) - continue; + // Steps 4-5. + for (var i = 1; i < arguments.length; i++) { + // Step 5.a. + var nextSource = arguments[i]; + if (nextSource === null || nextSource === undefined) continue; - // Steps 5.b.i-ii. - var from = ToObject(nextSource); + // Steps 5.b.i-ii. + var from = ToObject(nextSource); - // Steps 5.b.iii-iv. - var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS); + // Steps 5.b.iii-iv. + var keys = OwnPropertyKeys( + from, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, + ); - // Step 5.c. - for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) { - var nextKey = keys[nextIndex]; + // Step 5.c. + for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) { + var nextKey = keys[nextIndex]; - // Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable - // which is faster and returns false for not defined properties. - if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) { - // Steps 5.c.iii.1-4. - to[nextKey] = from[nextKey]; - } - } + // Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable + // which is faster and returns false for not defined properties. + if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) { + // Steps 5.c.iii.1-4. + to[nextKey] = from[nextKey]; + } } + } - // Step 6. - return to; + // Step 6. + return to; } // ES stage 4 proposal function ObjectGetOwnPropertyDescriptors(O) { - // Step 1. - var obj = ToObject(O); + // Step 1. + var obj = ToObject(O); - // Step 2. - var keys = OwnPropertyKeys(obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS); + // Step 2. + var keys = OwnPropertyKeys( + obj, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, + ); - // Step 3. - var descriptors = {}; + // Step 3. + var descriptors = {}; - // Step 4. - for (var index = 0, len = keys.length; index < len; index++) { - var key = keys[index]; + // Step 4. + for (var index = 0, len = keys.length; index < len; index++) { + var key = keys[index]; - // Steps 4.a-b. - var desc = std_Object_getOwnPropertyDescriptor(obj, key); + // Steps 4.a-b. + var desc = std_Object_getOwnPropertyDescriptor(obj, key); - // Step 4.c. - if (typeof desc !== "undefined") - _DefineDataProperty(descriptors, key, desc); - } + // Step 4.c. + if (typeof desc !== "undefined") + _DefineDataProperty(descriptors, key, desc); + } - // Step 5. - return descriptors; + // Step 5. + return descriptors; } /* ES6 draft rev 32 (2015 Feb 2) 19.1.2.9. */ function ObjectGetPrototypeOf(obj) { - return std_Reflect_getPrototypeOf(ToObject(obj)); + return std_Reflect_getPrototypeOf(ToObject(obj)); } /* ES6 draft rev 32 (2015 Feb 2) 19.1.2.11. */ function ObjectIsExtensible(obj) { - return IsObject(obj) && std_Reflect_isExtensible(obj); + return IsObject(obj) && std_Reflect_isExtensible(obj); } /* ES2015 19.1.3.5 Object.prototype.toLocaleString */ function Object_toLocaleString() { - // Step 1. - var O = this; + // Step 1. + var O = this; - // Step 2. - return callContentFunction(O.toString, O); + // Step 2. + return callContentFunction(O.toString, O); } // ES 2017 draft bb96899bb0d9ef9be08164a26efae2ee5f25e875 19.1.3.7 function Object_valueOf() { - // Step 1. - return ToObject(this); + // Step 1. + return ToObject(this); } // ES7 draft (2016 March 8) B.2.2.3 function ObjectDefineSetter(name, setter) { - // Step 1. - var object = ToObject(this); + // Step 1. + var object = ToObject(this); - // Step 2. - if (!IsCallable(setter)) - ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter"); + // Step 2. + if (!IsCallable(setter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter"); - // Step 3. - var desc = { - __proto__: null, - enumerable: true, - configurable: true, - set: setter - }; + // Step 3. + var desc = { + __proto__: null, + enumerable: true, + configurable: true, + set: setter, + }; - // Step 4. - var key = ToPropertyKey(name); + // Step 4. + var key = ToPropertyKey(name); - // Step 5. - std_Object_defineProperty(object, key, desc); + // Step 5. + std_Object_defineProperty(object, key, desc); - // Step 6. (implicit) + // Step 6. (implicit) } // ES7 draft (2016 March 8) B.2.2.2 function ObjectDefineGetter(name, getter) { - // Step 1. - var object = ToObject(this); + // Step 1. + var object = ToObject(this); - // Step 2. - if (!IsCallable(getter)) - ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter"); + // Step 2. + if (!IsCallable(getter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter"); - // Step 3. - var desc = { - __proto__: null, - enumerable: true, - configurable: true, - get: getter - }; + // Step 3. + var desc = { + __proto__: null, + enumerable: true, + configurable: true, + get: getter, + }; - // Step 4. - var key = ToPropertyKey(name); + // Step 4. + var key = ToPropertyKey(name); - // Step 5. - std_Object_defineProperty(object, key, desc); + // Step 5. + std_Object_defineProperty(object, key, desc); - // Step 6. (implicit) + // Step 6. (implicit) } // ES7 draft (2016 March 8) B.2.2.5 function ObjectLookupSetter(name) { - // Step 1. - var object = ToObject(this); + // Step 1. + var object = ToObject(this); - // Step 2. - var key = ToPropertyKey(name) + // Step 2. + var key = ToPropertyKey(name); - do { - // Step 3.a. - var desc = std_Object_getOwnPropertyDescriptor(object, key); + do { + // Step 3.a. + var desc = std_Object_getOwnPropertyDescriptor(object, key); - // Step 3.b. - if (desc) { - // Step.b.i. - if (callFunction(std_Object_hasOwnProperty, desc, "set")) - return desc.set; + // Step 3.b. + if (desc) { + // Step.b.i. + if (callFunction(std_Object_hasOwnProperty, desc, "set")) return desc.set; - // Step.b.ii. - return undefined; - } + // Step.b.ii. + return undefined; + } - // Step 3.c. - object = std_Reflect_getPrototypeOf(object); - } while (object !== null); + // Step 3.c. + object = std_Reflect_getPrototypeOf(object); + } while (object !== null); - // Step 3.d. (implicit) + // Step 3.d. (implicit) } // ES7 draft (2016 March 8) B.2.2.4 function ObjectLookupGetter(name) { - // Step 1. - var object = ToObject(this); + // Step 1. + var object = ToObject(this); - // Step 2. - var key = ToPropertyKey(name) + // Step 2. + var key = ToPropertyKey(name); - do { - // Step 3.a. - var desc = std_Object_getOwnPropertyDescriptor(object, key); + do { + // Step 3.a. + var desc = std_Object_getOwnPropertyDescriptor(object, key); - // Step 3.b. - if (desc) { - // Step.b.i. - if (callFunction(std_Object_hasOwnProperty, desc, "get")) - return desc.get; + // Step 3.b. + if (desc) { + // Step.b.i. + if (callFunction(std_Object_hasOwnProperty, desc, "get")) return desc.get; - // Step.b.ii. - return undefined; - } + // Step.b.ii. + return undefined; + } - // Step 3.c. - object = std_Reflect_getPrototypeOf(object); - } while (object !== null); + // Step 3.c. + object = std_Reflect_getPrototypeOf(object); + } while (object !== null); - // Step 3.d. (implicit) + // Step 3.d. (implicit) } // Stage 4 draft 2020-09-06 https://tc39.github.io/proposal-object-from-entries/ // Object.fromEntries (iterable) function ObjectFromEntries(iter) { - // We omit the usual step number comments here because they don't help. - // This implementation inlines AddEntriesFromIterator and - // CreateDataPropertyOnObject, so it looks more like the polyfill - // than the step-by-step spec algorithm. - const obj = {}; + // We omit the usual step number comments here because they don't help. + // This implementation inlines AddEntriesFromIterator and + // CreateDataPropertyOnObject, so it looks more like the polyfill + // than the step-by-step spec algorithm. + const obj = {}; - for (const pair of allowContentIter(iter)) { - if (!IsObject(pair)) - ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Object.fromEntries"); - _DefineDataProperty(obj, pair[0], pair[1]); + for (const pair of allowContentIter(iter)) { + if (!IsObject(pair)) + ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Object.fromEntries"); + _DefineDataProperty(obj, pair[0], pair[1]); + } + + return obj; +} + +// ES2024 +// Object.groupBy ( items, callbackfn ) +function ObjectGroupBy(items, callbackfn) { + // Step 1. + RequireObjectCoercible(items); + + // Step 2. + if (!IsCallable(callbackfn)) + ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn)); + + // Step 3. + var groups = std_Object_create(null); + + // Step 4. + var k = 0; + + // Steps 5-8. + for (var value of allowContentIter(items)) { + // Step 6.a. + var key = callContentFunction(callbackfn, undefined, value, k); + + // Step 6.b. + key = ToPropertyKey(key); + + // Steps 6.c-d. + var elements = groups[key]; + if (elements === undefined) { + _DefineDataProperty(groups, key, [value]); + } else { + callFunction(std_Array_push, elements, value); } - return obj; + // Step 6.e. + k++; + } + + // Step 9. + return groups; } // Proposal https://github.com/tc39/proposal-accessible-object-hasownproperty // Object.hasOwn (Object, Property) function ObjectHasOwn(O, P) { - // Step 1. - var obj = ToObject(O); - // Step 2-3. - return callFunction(std_Object_hasOwnProperty, obj, P); + // Step 1. + var obj = ToObject(O); + // Step 2-3. + return callFunction(std_Object_hasOwnProperty, obj, P); } diff --git a/js/src/tests/ecma_6/Object/groupBy.js b/js/src/tests/ecma_6/Object/groupBy.js new file mode 100644 index 0000000000..e46fc8d509 --- /dev/null +++ b/js/src/tests/ecma_6/Object/groupBy.js @@ -0,0 +1,89 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +assertEq(typeof Object.groupBy, "function"); +assertEq(Object.groupBy.length, 2); + +let desc = Object.getOwnPropertyDescriptor(Object, "groupBy"); +assertEq(desc.enumerable, false); +assertEq(desc.writable, true); +assertEq(desc.configurable, true); + +{ + let values = [1, 2, 3, 4, 5]; + let calls = []; + + let grouped = Object.groupBy(values, function (value, index) { + "use strict"; + assertEq(this, undefined); + calls.push([value, index]); + return value % 2 ? "odd" : "even"; + }); + + assertEq(Object.getPrototypeOf(grouped), null); + assertDeepEq(Object.keys(grouped), ["odd", "even"]); + assertDeepEq(grouped.odd, [1, 3, 5]); + assertDeepEq(grouped.even, [2, 4]); + assertDeepEq(calls, [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]); +} + +{ + let sym = Symbol("key"); + let grouped = Object.groupBy([10, 11], value => value === 10 ? sym : 0); + + assertDeepEq(Object.keys(grouped), ["0"]); + assertDeepEq(grouped[0], [11]); + + let symbols = Object.getOwnPropertySymbols(grouped); + assertEq(symbols.length, 1); + assertEq(symbols[0], sym); + assertDeepEq(grouped[sym], [10]); +} + +{ + let grouped = Object.groupBy([1, 2], () => "__proto__"); + assertEq(Object.getPrototypeOf(grouped), null); + assertDeepEq(grouped["__proto__"], [1, 2]); + + let protoKeyDesc = Object.getOwnPropertyDescriptor(grouped, "__proto__"); + assertEq(protoKeyDesc.enumerable, true); + assertEq(protoKeyDesc.writable, true); + assertEq(protoKeyDesc.configurable, true); +} + +{ + let closed = false; + let iterable = { + [Symbol.iterator]() { + let i = 0; + return { + next() { + i++; + if (i <= 3) + return { value: i, done: false }; + return { done: true }; + }, + return() { + closed = true; + return { done: true }; + } + }; + } + }; + + assertThrowsValue(() => Object.groupBy(iterable, value => { + if (value === 2) + throw 42; + return "x"; + }), 42); + assertEq(closed, true); +} + +assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError); +assertThrowsInstanceOf(() => Object.groupBy(null, null), TypeError); +assertThrowsInstanceOf(() => Object.groupBy({}, x => x), TypeError); + +if (typeof reportCompare === "function") + reportCompare(true, true);