Issue #2990 - implement object.GroupBy

This commit is contained in:
Basilisk-Dev 2026-03-10 10:45:22 -04:00 • committed by OwnedByWuigi
commit 1489a6f8b5
3 changed files with 275 additions and 145 deletions

View file

@ -1247,6 +1247,7 @@ static const JSFunctionSpec object_static_methods[] = {
JS_FN("seal", obj_seal, 1, 0), JS_FN("seal", obj_seal, 1, 0),
JS_FN("isSealed", obj_isSealed, 1, 0), JS_FN("isSealed", obj_isSealed, 1, 0),
JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 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_SELF_HOSTED_FN("hasOwn", "ObjectHasOwn", 2, 0),
JS_FS_END JS_FS_END
}; };

View file

@ -4,228 +4,268 @@
// ES6 draft rev36 2015-03-17 19.1.2.1 // ES6 draft rev36 2015-03-17 19.1.2.1
function ObjectStaticAssign(target, firstSource) { function ObjectStaticAssign(target, firstSource) {
// Steps 1-2. // Steps 1-2.
var to = ToObject(target); var to = ToObject(target);
// Step 3. // Step 3.
if (arguments.length < 2) if (arguments.length < 2) return to;
return to;
// Steps 4-5. // Steps 4-5.
for (var i = 1; i < arguments.length; i++) { for (var i = 1; i < arguments.length; i++) {
// Step 5.a. // Step 5.a.
var nextSource = arguments[i]; var nextSource = arguments[i];
if (nextSource === null || nextSource === undefined) if (nextSource === null || nextSource === undefined) continue;
continue;
// Steps 5.b.i-ii. // Steps 5.b.i-ii.
var from = ToObject(nextSource); var from = ToObject(nextSource);
// Steps 5.b.iii-iv. // Steps 5.b.iii-iv.
var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS); var keys = OwnPropertyKeys(
from,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
);
// Step 5.c. // Step 5.c.
for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) { for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) {
var nextKey = keys[nextIndex]; var nextKey = keys[nextIndex];
// Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable // Steps 5.c.i-iii. We abbreviate this by calling propertyIsEnumerable
// which is faster and returns false for not defined properties. // which is faster and returns false for not defined properties.
if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) { if (callFunction(std_Object_propertyIsEnumerable, from, nextKey)) {
// Steps 5.c.iii.1-4. // Steps 5.c.iii.1-4.
to[nextKey] = from[nextKey]; to[nextKey] = from[nextKey];
} }
}
} }
}
// Step 6. // Step 6.
return to; return to;
} }
// ES stage 4 proposal // ES stage 4 proposal
function ObjectGetOwnPropertyDescriptors(O) { function ObjectGetOwnPropertyDescriptors(O) {
// Step 1. // Step 1.
var obj = ToObject(O); var obj = ToObject(O);
// Step 2. // Step 2.
var keys = OwnPropertyKeys(obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS); var keys = OwnPropertyKeys(
obj,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
);
// Step 3. // Step 3.
var descriptors = {}; var descriptors = {};
// Step 4. // Step 4.
for (var index = 0, len = keys.length; index < len; index++) { for (var index = 0, len = keys.length; index < len; index++) {
var key = keys[index]; var key = keys[index];
// Steps 4.a-b. // Steps 4.a-b.
var desc = std_Object_getOwnPropertyDescriptor(obj, key); var desc = std_Object_getOwnPropertyDescriptor(obj, key);
// Step 4.c. // Step 4.c.
if (typeof desc !== "undefined") if (typeof desc !== "undefined")
_DefineDataProperty(descriptors, key, desc); _DefineDataProperty(descriptors, key, desc);
} }
// Step 5. // Step 5.
return descriptors; return descriptors;
} }
/* ES6 draft rev 32 (2015 Feb 2) 19.1.2.9. */ /* ES6 draft rev 32 (2015 Feb 2) 19.1.2.9. */
function ObjectGetPrototypeOf(obj) { 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. */ /* ES6 draft rev 32 (2015 Feb 2) 19.1.2.11. */
function ObjectIsExtensible(obj) { 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 */ /* ES2015 19.1.3.5 Object.prototype.toLocaleString */
function Object_toLocaleString() { function Object_toLocaleString() {
// Step 1. // Step 1.
var O = this; var O = this;
// Step 2. // Step 2.
return callContentFunction(O.toString, O); return callContentFunction(O.toString, O);
} }
// ES 2017 draft bb96899bb0d9ef9be08164a26efae2ee5f25e875 19.1.3.7 // ES 2017 draft bb96899bb0d9ef9be08164a26efae2ee5f25e875 19.1.3.7
function Object_valueOf() { function Object_valueOf() {
// Step 1. // Step 1.
return ToObject(this); return ToObject(this);
} }
// ES7 draft (2016 March 8) B.2.2.3 // ES7 draft (2016 March 8) B.2.2.3
function ObjectDefineSetter(name, setter) { function ObjectDefineSetter(name, setter) {
// Step 1. // Step 1.
var object = ToObject(this); var object = ToObject(this);
// Step 2. // Step 2.
if (!IsCallable(setter)) if (!IsCallable(setter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
// Step 3. // Step 3.
var desc = { var desc = {
__proto__: null, __proto__: null,
enumerable: true, enumerable: true,
configurable: true, configurable: true,
set: setter set: setter,
}; };
// Step 4. // Step 4.
var key = ToPropertyKey(name); var key = ToPropertyKey(name);
// Step 5. // Step 5.
std_Object_defineProperty(object, key, desc); std_Object_defineProperty(object, key, desc);
// Step 6. (implicit) // Step 6. (implicit)
} }
// ES7 draft (2016 March 8) B.2.2.2 // ES7 draft (2016 March 8) B.2.2.2
function ObjectDefineGetter(name, getter) { function ObjectDefineGetter(name, getter) {
// Step 1. // Step 1.
var object = ToObject(this); var object = ToObject(this);
// Step 2. // Step 2.
if (!IsCallable(getter)) if (!IsCallable(getter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
// Step 3. // Step 3.
var desc = { var desc = {
__proto__: null, __proto__: null,
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: getter get: getter,
}; };
// Step 4. // Step 4.
var key = ToPropertyKey(name); var key = ToPropertyKey(name);
// Step 5. // Step 5.
std_Object_defineProperty(object, key, desc); std_Object_defineProperty(object, key, desc);
// Step 6. (implicit) // Step 6. (implicit)
} }
// ES7 draft (2016 March 8) B.2.2.5 // ES7 draft (2016 March 8) B.2.2.5
function ObjectLookupSetter(name) { function ObjectLookupSetter(name) {
// Step 1. // Step 1.
var object = ToObject(this); var object = ToObject(this);
// Step 2. // Step 2.
var key = ToPropertyKey(name) var key = ToPropertyKey(name);
do { do {
// Step 3.a. // Step 3.a.
var desc = std_Object_getOwnPropertyDescriptor(object, key); var desc = std_Object_getOwnPropertyDescriptor(object, key);
// Step 3.b. // Step 3.b.
if (desc) { if (desc) {
// Step.b.i. // Step.b.i.
if (callFunction(std_Object_hasOwnProperty, desc, "set")) if (callFunction(std_Object_hasOwnProperty, desc, "set")) return desc.set;
return desc.set;
// Step.b.ii. // Step.b.ii.
return undefined; return undefined;
} }
// Step 3.c. // Step 3.c.
object = std_Reflect_getPrototypeOf(object); object = std_Reflect_getPrototypeOf(object);
} while (object !== null); } while (object !== null);
// Step 3.d. (implicit) // Step 3.d. (implicit)
} }
// ES7 draft (2016 March 8) B.2.2.4 // ES7 draft (2016 March 8) B.2.2.4
function ObjectLookupGetter(name) { function ObjectLookupGetter(name) {
// Step 1. // Step 1.
var object = ToObject(this); var object = ToObject(this);
// Step 2. // Step 2.
var key = ToPropertyKey(name) var key = ToPropertyKey(name);
do { do {
// Step 3.a. // Step 3.a.
var desc = std_Object_getOwnPropertyDescriptor(object, key); var desc = std_Object_getOwnPropertyDescriptor(object, key);
// Step 3.b. // Step 3.b.
if (desc) { if (desc) {
// Step.b.i. // Step.b.i.
if (callFunction(std_Object_hasOwnProperty, desc, "get")) if (callFunction(std_Object_hasOwnProperty, desc, "get")) return desc.get;
return desc.get;
// Step.b.ii. // Step.b.ii.
return undefined; return undefined;
} }
// Step 3.c. // Step 3.c.
object = std_Reflect_getPrototypeOf(object); object = std_Reflect_getPrototypeOf(object);
} while (object !== null); } 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/ // Stage 4 draft 2020-09-06 https://tc39.github.io/proposal-object-from-entries/
// Object.fromEntries (iterable) // Object.fromEntries (iterable)
function ObjectFromEntries(iter) { function ObjectFromEntries(iter) {
// We omit the usual step number comments here because they don't help. // We omit the usual step number comments here because they don't help.
// This implementation inlines AddEntriesFromIterator and // This implementation inlines AddEntriesFromIterator and
// CreateDataPropertyOnObject, so it looks more like the polyfill // CreateDataPropertyOnObject, so it looks more like the polyfill
// than the step-by-step spec algorithm. // than the step-by-step spec algorithm.
const obj = {}; const obj = {};
for (const pair of allowContentIter(iter)) { for (const pair of allowContentIter(iter)) {
if (!IsObject(pair)) if (!IsObject(pair))
ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Object.fromEntries"); ThrowTypeError(JSMSG_INVALID_MAP_ITERABLE, "Object.fromEntries");
_DefineDataProperty(obj, pair[0], pair[1]); _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 // Proposal https://github.com/tc39/proposal-accessible-object-hasownproperty
// Object.hasOwn (Object, Property) // Object.hasOwn (Object, Property)
function ObjectHasOwn(O, P) { function ObjectHasOwn(O, P) {
// Step 1. // Step 1.
var obj = ToObject(O); var obj = ToObject(O);
// Step 2-3. // Step 2-3.
return callFunction(std_Object_hasOwnProperty, obj, P); return callFunction(std_Object_hasOwnProperty, obj, P);
} }

View file

@ -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);