From 3d131186be4807e011e1854302414bcdde2e1cc2 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Mon, 23 Mar 2026 08:50:01 -0400 Subject: [PATCH] Issue #2551 - implement TypedArray.prototype.with --- js/src/builtin/TypedArray.js | 59 +++++++++++++ .../detached-array-buffer-checks.js | 4 + js/src/tests/ecma_6/TypedArray/with.js | 84 +++++++++++++++++++ js/src/vm/TypedArrayObject.cpp | 1 + 4 files changed, 148 insertions(+) create mode 100644 js/src/tests/ecma_6/TypedArray/with.js diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 237b47ad8c..9c22c51f2c 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -37,6 +37,10 @@ function TypedArrayLengthMethod() { return TypedArrayLength(this); } +function TypedArrayContentTypeIsBigIntMethod() { + return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this); +} + function GetAttachedArrayBuffer(tarray) { var buffer = ViewedArrayBufferIfReified(tarray); if (IsDetachedBuffer(buffer)) @@ -895,6 +899,61 @@ function TypedArrayToReversed() { return A; } +// ES2023 23.2.3.36 %TypedArray%.prototype.with ( index, value ) +function TypedArrayWith(index, value) { + // Step 1. + var O = this; + + // Step 2. + // This function is not generic. + // We want to make sure that we have an attached buffer, per spec prose. + var isTypedArray = IsTypedArrayEnsuringArrayBuffer(O); + + // If we got here, `this` is either a typed array or a wrapper for one. + + // Step 3. + var len; + if (isTypedArray) + len = TypedArrayLength(O); + else + len = callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayLengthMethod"); + + // Steps 4-6. + var relativeIndex = ToInteger(index); + var actualIndex = relativeIndex >= 0 ? relativeIndex : len + relativeIndex; + + // Steps 7-8. + var isBigIntContentType; + if (isTypedArray) { + isBigIntContentType = callFunction(TypedArrayContentTypeIsBigIntMethod, O); + } else { + isBigIntContentType = callFunction(CallTypedArrayMethodIfWrapped, O, + "TypedArrayContentTypeIsBigIntMethod"); + } + var numericValue = isBigIntContentType ? ToBigInt(value) : ToNumber(value); + + // Step 9. + if (actualIndex < 0 || actualIndex >= len) + ThrowRangeError(JSMSG_BAD_INDEX); + + // Step 10. + var A = TypedArrayCreateSameType(O, len); + + // Steps 11-12. + for (var k = 0; k < len; k++) { + var fromValue; + if (k === actualIndex) { + fromValue = numericValue; + } else { + fromValue = O[k]; + } + A[k] = fromValue; + } + + // Step 13. + return A; +} + // ES6 draft 20150220 22.2.3.22.1 %TypedArray%.prototype.set(array [, offset]) function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetBuffer) { assert(!IsPossiblyWrappedTypedArray(array), diff --git a/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js b/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js index 76a00d886d..c5b58db328 100644 --- a/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js +++ b/js/src/tests/ecma_6/TypedArray/detached-array-buffer-checks.js @@ -97,6 +97,10 @@ assertThrowsInstanceOf(() => { array.values(); }, TypeError); +assertThrowsInstanceOf(() => { + array.with(POISON, POISON); +}, TypeError); + assertThrowsInstanceOf(() => { array.every(POISON); }, TypeError); diff --git a/js/src/tests/ecma_6/TypedArray/with.js b/js/src/tests/ecma_6/TypedArray/with.js new file mode 100644 index 0000000000..41ce1e706f --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/with.js @@ -0,0 +1,84 @@ +for (var constructor of anyTypedArrayConstructors) { + assertEq(constructor.prototype.with.length, 2); + + var original = new constructor([1, 2, 3, 4]); + var updated = original.with(1, 9); + assertDeepEq(updated, new constructor([1, 9, 3, 4])); + assertDeepEq(original, new constructor([1, 2, 3, 4])); + assertEq(updated === original, false); + assertEq(updated.constructor, constructor); + + assertDeepEq(new constructor([1, 2, 3]).with(-1, 7), + new constructor([1, 2, 7])); + assertDeepEq(new constructor([1, 2, 3]).with(-0, 7), + new constructor([7, 2, 3])); + + assertThrowsInstanceOf(() => { + new constructor([1, 2, 3]).with(3, 9); + }, RangeError); + assertThrowsInstanceOf(() => { + new constructor([1, 2, 3]).with(-4, 9); + }, RangeError); + + var valueOrder = []; + var value = { + valueOf() { + valueOrder.push("valueOf"); + return 9; + } + }; + assertThrowsInstanceOf(() => { + new constructor([1, 2, 3]).with(9, value); + }, RangeError); + assertEq(valueOrder.join(","), "valueOf"); + + var ctorIgnored = new constructor([5, 6, 7]); + Object.defineProperty(ctorIgnored, "constructor", { + get() { + throw new Error("constructor accessor called"); + } + }); + assertDeepEq(ctorIgnored.with(0, 4), new constructor([4, 6, 7])); + + if (constructor === Uint8ClampedArray || + (typeof isSharedConstructor === "function" && isSharedConstructor(constructor) && + constructor.name === Uint8ClampedArray.name)) + { + assertDeepEq(new constructor([0, 1, 2]).with(1, 2.6), + new constructor([0, 3, 2])); + } + + var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./, + new Proxy(new constructor(), {})]; + invalidReceivers.forEach(invalidReceiver => { + assertThrowsInstanceOf(() => { + constructor.prototype.with.call(invalidReceiver, 0, 1); + }, TypeError, + "Assert that with fails if this value is not a TypedArray"); + }); +} + +for (var constructor of typedArrayConstructors) { + if (typeof newGlobal === "function") { + var withFn = newGlobal()[constructor.name].prototype.with; + var original = new constructor([3, 2, 1]); + var updated = withFn.call(original, 1, 8); + + assertDeepEq(updated, new constructor([3, 8, 1])); + assertDeepEq(original, new constructor([3, 2, 1])); + } +} + +if (typeof BigInt64Array === "function" && typeof BigUint64Array === "function") { + var bigIntArray = new BigInt64Array([1n, 2n, 3n]); + assertEq(BigInt64Array.prototype.with.length, 2); + assertDeepEq(bigIntArray.with(1, 9n), new BigInt64Array([1n, 9n, 3n])); + assertThrowsInstanceOf(() => bigIntArray.with(1, 9), TypeError); + + var bigUintArray = new BigUint64Array([1n, 2n, 3n]); + assertDeepEq(bigUintArray.with(2, 4n), new BigUint64Array([1n, 2n, 4n])); + assertThrowsInstanceOf(() => bigUintArray.with(9, 1), TypeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 068d4ef4a6..fd687946ca 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1629,6 +1629,7 @@ TypedArrayObject::protoFunctions[] = { JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0), JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0), JS_SELF_HOSTED_FN("toReversed", "TypedArrayToReversed", 0, 0), + JS_SELF_HOSTED_FN("with", "TypedArrayWith", 2, 0), JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0), JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0), JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),