Implement ES2024 grouping and resolver builtins

This commit is contained in:
Basilisk-Dev
2026-05-15 18:17:24 -04:00
committed by roytam1
parent 6f47a2b0da
commit e1b689d34e
11 changed files with 297 additions and 9 deletions
+43
View File
@@ -55,6 +55,49 @@ function MapForEach(callbackfn, thisArg = undefined) {
}
}
// ES2024
// Map.groupBy ( items, callbackfn )
function MapGroupBy(items, callbackfn) {
// Step 1.
RequireObjectCoercible(items);
// Step 2.
if (!IsCallable(callbackfn))
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
// Step 3.
var groups = std_Map_create();
// Step 4.
var k = 0;
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
if (k >= MAX_NUMERIC_INDEX)
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
// Step 6.b.
var key = callContentFunction(callbackfn, undefined, value, k);
// Steps 6.c-d.
var elements;
if (callFunction(std_Map_has, groups, key)) {
elements = callFunction(std_Map_get, groups, key);
callFunction(std_Array_push, elements, value);
} else {
elements = [value];
callFunction(std_Map_set, groups, key, elements);
}
// Step 6.e.
k++;
}
// Step 9.
return groups;
}
var iteratorTemp = { mapIterationResultPair : null };
function MapIteratorNext() {
+15 -4
View File
@@ -332,9 +332,15 @@ const JSPropertySpec MapObject::staticProperties[] = {
JS_PS_END
};
const JSFunctionSpec MapObject::staticMethods[] = {
JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0),
JS_FS_END
};
static JSObject*
InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
const JSPropertySpec* properties, const JSFunctionSpec* methods,
const JSFunctionSpec* staticMethods,
const JSPropertySpec* staticProperties)
{
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
@@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSPro
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
if (!ctor ||
!JS_DefineProperties(cx, ctor, staticProperties) ||
!LinkConstructorAndPrototype(cx, ctor, proto) ||
!JS_DefineProperties(cx, ctor, staticProperties))
{
return nullptr;
}
if (staticMethods && !JS_DefineFunctions(cx, ctor, staticMethods))
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
{
@@ -359,7 +370,7 @@ MapObject::initClass(JSContext* cx, JSObject* obj)
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
staticProperties));
staticMethods, staticProperties));
if (proto) {
// Define the "entries" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
@@ -1084,7 +1095,7 @@ SetObject::initClass(JSContext* cx, JSObject* obj)
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
staticProperties));
nullptr, staticProperties));
if (proto) {
// Define the "values" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
+3 -2
View File
@@ -110,7 +110,9 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
JS::MutableHandle<GCVector<JS::Value>> entries);
static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
// Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
@@ -137,6 +139,7 @@ class MapObject : public NativeObject {
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static const JSFunctionSpec staticMethods[];
static const JSPropertySpec staticProperties[];
ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
static ValueMap& extract(HandleObject o);
@@ -153,10 +156,8 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
+7 -3
View File
@@ -240,12 +240,16 @@ function ObjectGroupBy(items, callbackfn) {
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
var key = callContentFunction(callbackfn, undefined, value, k);
if (k >= MAX_NUMERIC_INDEX)
ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
// Step 6.b.
var key = callContentFunction(callbackfn, undefined, value, k);
// Step 6.c.
key = ToPropertyKey(key);
// Steps 6.c-d.
// Steps 6.d-e.
var elements = groups[key];
if (elements === undefined) {
_DefineDataProperty(groups, key, [value]);
@@ -253,7 +257,7 @@ function ObjectGroupBy(items, callbackfn) {
callFunction(std_Array_push, elements, value);
}
// Step 6.e.
// Step 6.f.
k++;
}
+43
View File
@@ -3722,6 +3722,48 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
return true;
}
// ES2024
// Promise.withResolvers ( )
static bool
Promise_static_withResolvers(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!args.thisv().isObject()) {
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, args.thisv(), nullptr);
return false;
}
RootedObject C(cx, &args.thisv().toObject());
// Step 2.
Rooted<PromiseCapability> capability(cx);
if (!NewPromiseCapability(cx, C, &capability, false))
return false;
// Step 3.
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!obj)
return false;
// Steps 4-7.
RootedValue promise(cx, ObjectValue(*capability.promise()));
if (!::JS_DefineProperty(cx, obj, "promise", promise, JSPROP_ENUMERATE))
return false;
RootedValue resolve(cx, ObjectValue(*capability.resolve()));
if (!::JS_DefineProperty(cx, obj, "resolve", resolve, JSPROP_ENUMERATE))
return false;
RootedValue reject(cx, ObjectValue(*capability.reject()));
if (!::JS_DefineProperty(cx, obj, "reject", reject, JSPROP_ENUMERATE))
return false;
// Step 8.
args.rval().setObject(*obj);
return true;
}
/**
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
*/
@@ -5251,6 +5293,7 @@ static const JSFunctionSpec promise_static_methods[] = {
JS_FN("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0),
JS_FN("withResolvers", Promise_static_withResolvers, 0, 0),
JS_FS_END
};
+76
View File
@@ -654,6 +654,82 @@ function String_repeat(count) {
return T;
}
// ES2024
// String.prototype.isWellFormed ( )
function String_isWellFormed() {
// Steps 1-2.
RequireObjectCoercible(this);
var S = ToString(this);
// Step 3.
var length = S.length;
for (var k = 0; k < length; k++) {
var c = callFunction(std_String_charCodeAt, S, k);
if (c >= 0xD800 && c <= 0xDBFF) {
if (k + 1 >= length)
return false;
var d = callFunction(std_String_charCodeAt, S, k + 1);
if (d < 0xDC00 || d > 0xDFFF)
return false;
k++;
} else if (c >= 0xDC00 && c <= 0xDFFF) {
return false;
}
}
// Step 4.
return true;
}
// ES2024
// String.prototype.toWellFormed ( )
function String_toWellFormed() {
// Steps 1-2.
RequireObjectCoercible(this);
var S = ToString(this);
// Step 3.
var length = S.length;
var result = "";
var copied = 0;
for (var k = 0; k < length; k++) {
var c = callFunction(std_String_charCodeAt, S, k);
var isUnpairedSurrogate = false;
if (c >= 0xD800 && c <= 0xDBFF) {
if (k + 1 < length) {
var d = callFunction(std_String_charCodeAt, S, k + 1);
if (d >= 0xDC00 && d <= 0xDFFF) {
k++;
continue;
}
}
isUnpairedSurrogate = true;
} else if (c >= 0xDC00 && c <= 0xDFFF) {
isUnpairedSurrogate = true;
}
if (isUnpairedSurrogate) {
if (copied < k)
result += callFunction(String_substring, S, copied, k);
result += "\uFFFD";
copied = k + 1;
}
}
if (copied === 0)
return S;
if (copied < length)
result += callFunction(String_substring, S, copied, length);
// Step 4.
return result;
}
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
function String_iterator() {
RequireObjectCoercible(this);
+2
View File
@@ -3305,6 +3305,8 @@ static const JSFunctionSpec string_methods[] = {
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
JS_SELF_HOSTED_FN("isWellFormed", "String_isWellFormed", 0,0),
JS_SELF_HOSTED_FN("toWellFormed", "String_toWellFormed", 0,0),
JS_FN("normalize", str_normalize, 0,0),
/* Perl-ish methods (search is actually Python-esque). */
@@ -0,0 +1,31 @@
// |reftest| skip-if(!Promise.withResolvers)
var desc = Object.getOwnPropertyDescriptor(Promise, "withResolvers");
assertEq(desc.enumerable, false);
assertEq(desc.configurable, true);
assertEq(desc.writable, true);
assertEq(Promise.withResolvers.length, 0);
assertEq(Promise.withResolvers.name, "withResolvers");
var capability = Promise.withResolvers();
assertEq(capability.promise instanceof Promise, true);
assertEq(typeof capability.resolve, "function");
assertEq(typeof capability.reject, "function");
assertEqArray(Object.keys(capability), ["promise", "resolve", "reject"]);
capability.resolve(42);
capability.promise.then(v => assertEq(v, 42));
class MyPromise extends Promise {}
var subCapability = Promise.withResolvers.call(MyPromise);
assertEq(subCapability.promise instanceof MyPromise, true);
subCapability.reject("rejected");
subCapability.promise.then(
() => { throw new Error("expected rejection"); },
reason => assertEq(reason, "rejected")
);
assertThrowsInstanceOf(() => Promise.withResolvers.call({}), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
+26
View File
@@ -0,0 +1,26 @@
// |reftest| skip-if(!String.prototype.isWellFormed||!String.prototype.toWellFormed)
assertEq("".isWellFormed(), true);
assertEq("abc".isWellFormed(), true);
assertEq("\uD83D\uDE00".isWellFormed(), true);
assertEq("\uD800".isWellFormed(), false);
assertEq("\uDC00".isWellFormed(), false);
assertEq("\uD800a".isWellFormed(), false);
assertEq("a\uDC00".isWellFormed(), false);
assertEq("\uD800\uD800\uDC00".isWellFormed(), false);
assertEq("abc".toWellFormed(), "abc");
assertEq("\uD83D\uDE00".toWellFormed(), "\uD83D\uDE00");
assertEq("\uD800".toWellFormed(), "\uFFFD");
assertEq("\uDC00".toWellFormed(), "\uFFFD");
assertEq("\uD800a\uDC00".toWellFormed(), "\uFFFDa\uFFFD");
assertEq("\uD800\uD800\uDC00".toWellFormed(), "\uFFFD\uD800\uDC00");
assertEq(String.prototype.isWellFormed.call(123), true);
assertEq(String.prototype.toWellFormed.call({ toString() { return "\uD800x"; } }), "\uFFFDx");
assertThrowsInstanceOf(() => String.prototype.isWellFormed.call(null), TypeError);
assertThrowsInstanceOf(() => String.prototype.toWellFormed.call(undefined), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
@@ -0,0 +1,34 @@
// |reftest| skip-if(!Object.groupBy||!Map.groupBy)
var objectGroups = Object.groupBy(["a", "bb", "c"], value => value.length);
assertEq(Object.getPrototypeOf(objectGroups), null);
assertEqArray(objectGroups["1"], ["a", "c"]);
assertEqArray(objectGroups["2"], ["bb"]);
var symbol = Symbol();
var symbolGroups = Object.groupBy([1, 2], value => value === 1 ? symbol : "__proto__");
assertEqArray(symbolGroups[symbol], [1]);
assertEqArray(symbolGroups.__proto__, [2]);
var indexes = [];
var mapGroups = Map.groupBy(["a", "bb", "c"], (value, index) => {
indexes.push(index);
return value.length;
});
assertEq(mapGroups instanceof Map, true);
assertEqArray(indexes, [0, 1, 2]);
assertEqArray(mapGroups.get(1), ["a", "c"]);
assertEqArray(mapGroups.get(2), ["bb"]);
var key = {};
var objectKeyGroups = Map.groupBy([1, 2], value => value === 1 ? key : NaN);
assertEqArray(objectKeyGroups.get(key), [1]);
assertEqArray(objectKeyGroups.get(NaN), [2]);
assertThrowsInstanceOf(() => Object.groupBy(null, x => x), TypeError);
assertThrowsInstanceOf(() => Map.groupBy(undefined, x => x), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);
+17
View File
@@ -270,6 +270,20 @@ intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_NewMap(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
Rooted<MapObject*> map(cx, MapObject::create(cx));
if (!map)
return false;
args.rval().setObject(*map);
return true;
}
static bool
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
{
@@ -2255,7 +2269,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
JS_FN("std_Map_create", intrinsic_NewMap, 0,0),
JS_FN("std_Map_get", MapObject::get, 1,0),
JS_FN("std_Map_has", MapObject::has, 1,0),
JS_FN("std_Map_set", MapObject::set, 2,0),
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
JS_FN("std_Number_valueOf", num_valueOf, 0,0),