Issue #2551 - implement array.prototype.with

This commit is contained in:
Basilisk-Dev
2026-03-16 14:27:02 -04:00
committed by roytam1
parent 11b0b680e1
commit d5566bbb0f
4 changed files with 1247 additions and 1049 deletions
+1078 -1048
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -3210,6 +3210,7 @@ static const JSFunctionSpec array_methods[] = {
JS_SELF_HOSTED_FN("findLastIndex", "ArrayFindLastIndex", 1,0),
JS_SELF_HOSTED_FN("toReversed", "ArrayToReversed", 0,0),
JS_SELF_HOSTED_FN("toSorted", "ArrayToSorted", 1,0),
JS_SELF_HOSTED_FN("with", "ArrayWith", 2,0),
JS_FS_END
};
@@ -3383,7 +3384,8 @@ array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
!DefineProperty(cx, unscopables, cx->names().keys, value) ||
!DefineProperty(cx, unscopables, cx->names().toReversed, value) ||
!DefineProperty(cx, unscopables, cx->names().values, value) ||
!DefineProperty(cx, unscopables, cx->names().toSorted, value))
!DefineProperty(cx, unscopables, cx->names().toSorted, value) ||
!DefineProperty(cx, unscopables, cx->names().with, value))
{
return false;
}
+59
View File
@@ -0,0 +1,59 @@
<!doctype html>
<meta charset="utf-8">
<title>Array.prototype.with test</title>
<style>
body { font: 14px/1.4 sans-serif; padding: 16px; }
.pass { color: #0a0; }
.fail { color: #c00; }
pre { white-space: pre-wrap; }
</style>
<pre id="log"></pre>
<script>
(function() {
const log = document.getElementById("log");
function write(msg, cls) {
const line = document.createElement("div");
if (cls) line.className = cls;
line.textContent = msg;
log.appendChild(line);
}
window.assertEq = function(actual, expected, msg) {
if (actual !== expected) {
throw new Error((msg ? msg + ": " : "") +
"expected " + expected + ", got " + actual);
}
};
window.assertThrowsInstanceOf = function(fn, ctor, msg) {
let threw = false;
try {
fn();
} catch (e) {
if (e instanceof ctor) {
threw = true;
} else {
throw new Error((msg ? msg + ": " : "") +
"threw " + e + ", expected " + ctor.name);
}
}
if (!threw) {
throw new Error((msg ? msg + ": " : "") + "did not throw");
}
};
window.reportCompare = function() {};
let hadError = false;
window.addEventListener("error", function(e) {
hadError = true;
write("FAIL: " + e.message, "fail");
});
window.addEventListener("load", function() {
write("PASS: with.js loaded", "pass");
if (!hadError)
write("PASS: all with tests passed", "pass");
});
})();
</script>
<script src="with.js"></script>
+107
View File
@@ -0,0 +1,107 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
assertEq(typeof Array.prototype.with, "function");
assertEq(Array.prototype.with.length, 2);
let desc = Object.getOwnPropertyDescriptor(Array.prototype, "with");
assertEq(desc.writable, true);
assertEq(desc.enumerable, false);
assertEq(desc.configurable, true);
assertThrowsInstanceOf(function () {
Array.prototype.with.call(null, 0, 1);
}, TypeError);
assertThrowsInstanceOf(function () {
Array.prototype.with.call(undefined, 0, 1);
}, TypeError);
// Non-mutating behavior.
let original = [1, 2, 3];
let updated = original.with(1, 9);
assertEq(original !== updated, true);
assertEq(original.join(","), "1,2,3");
assertEq(updated.join(","), "1,9,3");
// Negative and -0 indices.
assertEq([1, 2, 3].with(-1, 7).join(","), "1,2,7");
assertEq([1, 2, 3].with(-0, 7).join(","), "7,2,3");
// Out-of-range index throws.
assertThrowsInstanceOf(function () {
[1, 2, 3].with(3, 9);
}, RangeError);
assertThrowsInstanceOf(function () {
[1, 2, 3].with(-4, 9);
}, RangeError);
// Holes are materialized as undefined in the result.
let sparse = [1, , 3];
let sparseResult = sparse.with(0, 9);
assertEq(sparseResult.length, 3);
assertEq(sparseResult[0], 9);
assertEq(sparseResult[1], undefined);
assertEq(sparseResult[2], 3);
assertEq(1 in sparseResult, true);
// Generic behavior on array-like values.
let arrayLike = { 0: "a", 2: "c", length: 3 };
let arrayLikeResult = Array.prototype.with.call(arrayLike, 1, "b");
assertEq(Array.isArray(arrayLikeResult), true);
assertEq(arrayLikeResult.length, 3);
assertEq(arrayLikeResult.join(","), "a,b,c");
// Reads occur in ascending index order, except replaced index isn't read.
let accessLog = [];
let getterArr = {
length: 3,
get 0() {
accessLog.push(0);
return "zero";
},
get 1() {
accessLog.push(1);
return "one";
},
get 2() {
accessLog.push(2);
return "two";
},
};
let getterResult = Array.prototype.with.call(getterArr, 1, "X");
assertEq(accessLog.join(","), "0,2");
assertEq(getterResult.join(","), "zero,X,two");
// constructor / Symbol.species is ignored and constructor is not read.
let constructorAccessed = false;
let speciesIgnored = [1, 2];
Object.defineProperty(speciesIgnored, "constructor", {
get: function () {
constructorAccessed = true;
throw new Error("constructor should not be read");
},
configurable: true,
});
let speciesIgnoredResult = speciesIgnored.with(0, 7);
assertEq(constructorAccessed, false);
assertEq(Array.isArray(speciesIgnoredResult), true);
assertEq(speciesIgnoredResult.join(","), "7,2");
// Length limit check happens before indexed reads.
let indexedRead = false;
let tooLong = {
length: 4294967296,
get 0() {
indexedRead = true;
throw new Error("index getter should not run");
},
};
assertThrowsInstanceOf(function () {
Array.prototype.with.call(tooLong, 0, 1);
}, RangeError);
assertEq(indexedRead, false);
// @@unscopables must include with.
assertEq(Array.prototype[Symbol.unscopables].with, true);
if (typeof reportCompare === "function") reportCompare(0, 0);