diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js index 434cd6529b..bcf0ded02e 100644 --- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -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() { diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index fe748a6bde..daeb930c86 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -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 global, const Class* clasp, JSProtoKey key, Native construct, const JSPropertySpec* properties, const JSFunctionSpec* methods, + const JSFunctionSpec* staticMethods, const JSPropertySpec* staticProperties) { RootedPlainObject proto(cx, NewBuiltinClassInstance(cx)); @@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle global, const Class* clasp, JSPro Rooted 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 global(cx, &obj->as()); 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 global(cx, &obj->as()); 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); diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h index a9f685ea00..4d58988e3e 100644 --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -110,7 +110,9 @@ class MapObject : public NativeObject { static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj, JS::MutableHandle> 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(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); diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index 6627aea593..01442144cc 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -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++; } diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 864d872fb5..231f915ab4 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -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 capability(cx); + if (!NewPromiseCapability(cx, C, &capability, false)) + return false; + + // Step 3. + RootedPlainObject obj(cx, NewBuiltinClassInstance(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 }; diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index e1c32482ae..a83a06ad2b 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -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); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 593cf4d708..ee8be97801 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -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). */ diff --git a/js/src/tests/non262/Promise/with-resolvers.js b/js/src/tests/non262/Promise/with-resolvers.js new file mode 100644 index 0000000000..985d458622 --- /dev/null +++ b/js/src/tests/non262/Promise/with-resolvers.js @@ -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); diff --git a/js/src/tests/non262/String/well-formed.js b/js/src/tests/non262/String/well-formed.js new file mode 100644 index 0000000000..7472f6afa8 --- /dev/null +++ b/js/src/tests/non262/String/well-formed.js @@ -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); diff --git a/js/src/tests/non262/collections/groupBy.js b/js/src/tests/non262/collections/groupBy.js new file mode 100644 index 0000000000..121a899ede --- /dev/null +++ b/js/src/tests/non262/collections/groupBy.js @@ -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); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index c9f290463e..2b2418bbdb 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -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 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),