Lower static Array fill/copyWithin and position coercion (#457)

* Coerce static Array slice and splice positions

* Lower static Array fill and copyWithin with Test262 assertions
This commit is contained in:
Chris Tate
2026-09-25 23:17:24 -05:00
committed by GitHub
parent baef5fed29
commit 183a0bbcec
28 changed files with 1163 additions and 45 deletions
@@ -0,0 +1,197 @@
import { BOOL, F64, type IrExpr, type IrStmt, type IrType, type SrcLoc } from "../../ir/ir.js";
import { numLit, varRef } from "../../ir/build.js";
import { typeKey } from "../type-mapper.js";
import type { Lowerer } from "./lowerer.js";
import { arrayValueStore } from "./array-values.js";
const add = (left: IrExpr, right: IrExpr, loc: SrcLoc): IrExpr => ({ kind: "bin", op: "+", left, right, type: F64, loc });
const sub = (left: IrExpr, right: IrExpr, loc: SrcLoc): IrExpr => ({ kind: "bin", op: "-", left, right, type: F64, loc });
const lt = (left: IrExpr, right: IrExpr, loc: SrcLoc): IrExpr => ({ kind: "bin", op: "<", left, right, type: BOOL, loc });
const eq = (left: IrExpr, right: IrExpr, loc: SrcLoc): IrExpr => ({ kind: "bin", op: "===", left, right, type: BOOL, loc });
const math = (fn: "min" | "max" | "trunc", args: IrExpr[], loc: SrcLoc): IrExpr => ({ kind: "libCall", fn: `math.${fn}`, args, type: F64, loc });
const lengthOf = (arr: IrExpr, loc: SrcLoc): IrExpr => ({ kind: "arrIntrinsic", method: "length", receiver: arr, args: [], type: F64, loc });
function relativeIndex(position: IrExpr, length: IrExpr, loc: SrcLoc): IrExpr {
const integer: IrExpr = {
kind: "ternary",
cond: { kind: "libCall", fn: "num.isNaN", args: [position], type: BOOL, loc },
then: numLit(0, loc),
else_: math("trunc", [position], loc),
type: F64,
loc,
};
return {
kind: "ternary",
cond: lt(integer, numLit(0, loc), loc),
then: math("max", [add(length, integer, loc), numLit(0, loc)], loc),
else_: math("min", [integer, length], loc),
type: F64,
loc,
};
}
export function lowerArrayFill(
lowerer: Lowerer,
receiver: IrExpr,
value: IrExpr | null,
writeUndefined: boolean,
start: IrExpr,
end: IrExpr,
arrType: IrType & { kind: "array" },
loc: SrcLoc,
): IrExpr {
const valueType = writeUndefined ? F64 : value!.type;
const key = "indexed:fill:" + typeKey(arrType.elem) + ":" + typeKey(valueType) + ":" + writeUndefined;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = "%arr.fill." + lowerer.arrHofHelpers.size;
lowerer.arrHofHelpers.set(key, name);
const a = varRef("a.0", arrType, loc);
const n = varRef("n.0", F64, loc);
const i = varRef("i.0", F64, loc);
const body: IrStmt[] = writeUndefined
? [{ kind: "arraySetUndefined", arr: a, index: i, loc }]
: [arrayValueStore(lowerer, a, i, varRef("v.0", valueType, loc), arrType.elem, loc)];
lowerer.liftedFns.push({
name,
params: [
{ localId: "a.0", name: "a", type: arrType },
{ localId: "v.0", name: "v", type: valueType },
{ localId: "start.0", name: "start", type: F64 },
{ localId: "end.0", name: "end", type: F64 },
],
returnType: arrType,
locals: [
{ id: "a.0", name: "a", type: arrType, mutable: true },
{ id: "v.0", name: "v", type: valueType, mutable: false },
{ id: "start.0", name: "start", type: F64, mutable: false },
{ id: "end.0", name: "end", type: F64, mutable: false },
{ id: "n.0", name: "n", type: F64, mutable: false },
{ id: "from.0", name: "from", type: F64, mutable: false },
{ id: "until.0", name: "until", type: F64, mutable: false },
{ id: "i.0", name: "i", type: F64, mutable: true },
],
body: [
{ kind: "varDecl", localId: "n.0", init: lengthOf(a, loc), loc },
{ kind: "varDecl", localId: "from.0", init: relativeIndex(varRef("start.0", F64, loc), n, loc), loc },
{ kind: "varDecl", localId: "until.0", init: relativeIndex(varRef("end.0", F64, loc), n, loc), loc },
{
kind: "for",
init: { kind: "varDecl", localId: "i.0", init: varRef("from.0", F64, loc), loc },
cond: lt(i, varRef("until.0", F64, loc), loc),
update: { kind: "assign", localId: "i.0", value: add(i, numLit(1, loc), loc), loc },
body,
loc,
},
{ kind: "return", value: a, loc },
],
loc,
});
}
const valueArg: IrExpr = value && writeUndefined
? { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc }], result: numLit(0, loc), type: F64, loc }
: value ?? numLit(0, loc);
return { kind: "call", callee: name, args: [receiver, valueArg, start, end], type: arrType, loc };
}
export function lowerArrayCopyWithin(
lowerer: Lowerer,
receiver: IrExpr,
target: IrExpr,
start: IrExpr,
end: IrExpr,
arrType: IrType & { kind: "array" },
loc: SrcLoc,
): IrExpr {
const key = "indexed:copyWithin:" + typeKey(arrType.elem);
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = "%arr.copyWithin." + lowerer.arrHofHelpers.size;
lowerer.arrHofHelpers.set(key, name);
const a = varRef("a.0", arrType, loc);
const n = varRef("n.0", F64, loc);
const dst = varRef("targetIndex.0", F64, loc);
const src = varRef("startIndex.0", F64, loc);
const last = varRef("endIndex.0", F64, loc);
const remaining = varRef("count.0", F64, loc);
const step = varRef("direction.0", F64, loc);
const from = varRef("from.0", F64, loc);
const to = varRef("to.0", F64, loc);
const state = varRef("state.0", F64, loc);
const backwards: IrExpr = {
kind: "logical", op: "&&", left: lt(src, dst, loc),
right: lt(dst, add(src, remaining, loc), loc), type: BOOL, loc,
};
const offset: IrExpr = {
kind: "ternary", cond: lt(step, numLit(0, loc), loc),
then: sub(remaining, numLit(1, loc), loc), else_: numLit(0, loc), type: F64, loc,
};
lowerer.liftedFns.push({
name,
params: [
{ localId: "a.0", name: "a", type: arrType },
{ localId: "target.0", name: "target", type: F64 },
{ localId: "start.0", name: "start", type: F64 },
{ localId: "end.0", name: "end", type: F64 },
],
returnType: arrType,
locals: [
{ id: "a.0", name: "a", type: arrType, mutable: true },
{ id: "target.0", name: "target", type: F64, mutable: false },
{ id: "start.0", name: "start", type: F64, mutable: false },
{ id: "end.0", name: "end", type: F64, mutable: false },
{ id: "n.0", name: "n", type: F64, mutable: false },
{ id: "targetIndex.0", name: "targetIndex", type: F64, mutable: false },
{ id: "startIndex.0", name: "startIndex", type: F64, mutable: false },
{ id: "endIndex.0", name: "endIndex", type: F64, mutable: false },
{ id: "count.0", name: "count", type: F64, mutable: true },
{ id: "direction.0", name: "direction", type: F64, mutable: false },
{ id: "from.0", name: "from", type: F64, mutable: true },
{ id: "to.0", name: "to", type: F64, mutable: true },
{ id: "state.0", name: "state", type: F64, mutable: false },
{ id: "value.0", name: "value", type: arrType.elem, mutable: false },
],
body: [
{ kind: "varDecl", localId: "n.0", init: lengthOf(a, loc), loc },
{ kind: "varDecl", localId: "targetIndex.0", init: relativeIndex(varRef("target.0", F64, loc), n, loc), loc },
{ kind: "varDecl", localId: "startIndex.0", init: relativeIndex(varRef("start.0", F64, loc), n, loc), loc },
{ kind: "varDecl", localId: "endIndex.0", init: relativeIndex(varRef("end.0", F64, loc), n, loc), loc },
{
kind: "varDecl", localId: "count.0",
init: {
kind: "ternary", cond: eq(src, dst, loc), then: numLit(0, loc),
else_: math("min", [math("max", [sub(last, src, loc), numLit(0, loc)], loc), sub(n, dst, loc)], loc),
type: F64, loc,
}, loc,
},
{ kind: "varDecl", localId: "direction.0", init: { kind: "ternary", cond: backwards, then: numLit(-1, loc), else_: numLit(1, loc), type: F64, loc }, loc },
{ kind: "varDecl", localId: "from.0", init: add(src, offset, loc), loc },
{ kind: "varDecl", localId: "to.0", init: add(dst, offset, loc), loc },
{
kind: "while", cond: lt(numLit(0, loc), remaining, loc),
body: [
{ kind: "varDecl", localId: "state.0", init: { kind: "arrayState", arr: a, index: from, type: F64, loc }, loc },
{
kind: "if", cond: eq(state, numLit(0, loc), loc),
then: [{ kind: "arrayDelete", arr: a, index: to, loc }],
else_: [{
kind: "if", cond: eq(state, numLit(2, loc), loc),
then: [{ kind: "arraySetUndefined", arr: a, index: to, loc }],
else_: [
{ kind: "varDecl", localId: "value.0", init: { kind: "arrayGet", arr: a, index: from, type: arrType.elem, loc }, loc },
{ kind: "arraySet", arr: a, index: to, value: varRef("value.0", arrType.elem, loc), loc },
], loc,
}], loc,
},
{ kind: "assign", localId: "from.0", value: add(from, step, loc), loc },
{ kind: "assign", localId: "to.0", value: add(to, step, loc), loc },
{ kind: "assign", localId: "count.0", value: sub(remaining, numLit(1, loc), loc), loc },
], loc,
},
{ kind: "return", value: a, loc },
],
loc,
});
}
return { kind: "call", callee: name, args: [receiver, target, start, end], type: arrType, loc };
}
@@ -17,7 +17,8 @@ import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, cur
import { typeKey } from "../type-mapper.js";
import { WidthLift } from "./lowerer.js";
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
import { lowerOptionalArgument, lowerPositionArgument, positionNumber } from "./optional-arguments.js";
import { lowerPositionArgument, positionNumber } from "./optional-arguments.js";
import { lowerArrayCopyWithin, lowerArrayFill } from "./array-indexed-mutation.js";
function primitivePositionType(lowerer: Lowerer, type: IrType): boolean {
if (type.kind === "union") {
@@ -27,6 +28,30 @@ function primitivePositionType(lowerer: Lowerer, type: IrType): boolean {
type.kind === "nullT" || type.kind === "undefinedT";
}
function lowerArrayPosition(
lowerer: Lowerer,
node: ts.Expression | undefined,
defaultValue: IrExpr,
subject: string,
): IrExpr {
if (!node) return defaultValue;
if (ts.isSpreadElement(node)) lowerer.noLowering(`${subject} with a spread argument`, node);
const value = lowerPositionArgument(lowerer, node, defaultValue);
if (!primitivePositionType(lowerer, value.type)) {
lowerer.noLowering(`${subject} of '${lowerer.fmt(value.type)}' values`, node);
}
const loc = locOf(node);
const local = lowerer.declareHiddenLocal("%arrayPosition", value.type);
const ref = varRef(local.id, value.type, loc);
return {
kind: "seqExpr",
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
result: positionNumber(lowerer, ref, defaultValue, node, subject),
type: F64,
loc,
};
}
/** Build the argument array for a mutating/copying operation while keeping
* the receiver's scalar payload ABI. A missing read is represented as the
* present UNDEFINED state in this temporary, so push/unshift and toSpliced
@@ -212,6 +237,41 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
loc,
};
}
if (name === "fill") {
if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
lowerer.noLowering(`.fill with ${call.arguments.length} arguments`, call);
}
const value = call.arguments[0] ? lowerer.lowerExpr(call.arguments[0]) : null;
if (value && (value.type.kind === "dyn" || value.type.kind === "jsval") &&
isRefCounted(elem) && elem.kind !== "string" && !typeEquals(value.type, elem)) {
lowerer.noLowering(".fill of a checked-dynamic reference into a static array", call.arguments[0]!);
}
const writeUndefined = value === null || value.type.kind === "undefinedT";
return lowerArrayFill(
lowerer,
lowerer.lowerExpr(access.expression),
value,
writeUndefined,
lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array fill start"),
lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array fill end"),
receiverIr,
loc,
);
}
if (name === "copyWithin") {
if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
lowerer.noLowering(`.copyWithin with ${call.arguments.length} arguments`, call);
}
return lowerArrayCopyWithin(
lowerer,
lowerer.lowerExpr(access.expression),
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array copyWithin target"),
lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array copyWithin start"),
lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array copyWithin end"),
receiverIr,
loc,
);
}
if (name === "with") {
if (call.arguments.length !== 2 || call.arguments.some(ts.isSpreadElement)) {
lowerer.noLowering(`.with with ${call.arguments.length} arguments`, call);
@@ -287,26 +347,13 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
lowerer.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
}
const receiver = lowerer.lowerExpr(access.expression);
const start = call.arguments[0]
? lowerer.lowerExprExpecting(call.arguments[0], F64)
: { kind: "numLit" as const, value: 0, type: F64, loc };
const deleteCountDefault: IrExpr = {
kind: "numLit",
value: NaN,
type: F64,
loc,
};
const deleteCount =
call.arguments.length === 0
? { kind: "numLit" as const, value: 0, type: F64, loc }
: call.arguments[1]
? lowerOptionalArgument(
lowerer,
call.arguments[1],
F64,
deleteCountDefault,
)
: { kind: "numLit" as const, value: Infinity, type: F64, loc };
const start = lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array toSpliced start");
const deleteCount = lowerArrayPosition(
lowerer,
call.arguments[1],
numLit(call.arguments.length === 1 ? Infinity : 0, loc),
"array toSpliced deleteCount",
);
const itemNodes = call.arguments.slice(2);
const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
const statefulItems = itemProbes.some((probe) =>
@@ -339,7 +386,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
const arity = {
push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [1, 1],
concat: [0, Number.MAX_SAFE_INTEGER],
slice: [0, 2], shift: [0, 0], splice: [1, 2], at: [0, 1],
slice: [0, 2], shift: [0, 0], splice: [0, 2], at: [0, 1],
map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
findLast: [1, 1], findLastIndex: [1, 1],
every: [1, 1], flatMap: [1, 1], reduce: [1, 2], reduceRight: [1, 2],
@@ -522,30 +569,19 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
return { kind: "call", callee: helper, args: [receiver, ...args], type: receiverIr, loc };
}
if (name === "slice") {
// `a.slice(start?, end?)` — a fresh shallow copy of the index range,
// JS-exact index handling (ToIntegerOrInfinity, negatives from the
// end, clamping; omitted args are omitted from the IR — the backend
// fills 0 / +Infinity, the string-slice convention). Ref elements
// are RETAINED into the copy: the same references, exactly JS's
// shallow copy. Every element kind slices — the receiver's own type
// is the result type.
const receiver = lowerer.lowerExpr(access.expression);
const args = call.arguments.map((a) => lowerer.lowerExpr(a));
for (let i = 0; i < args.length; i++) {
if (args[i]!.type.kind !== "f64") lowerer.badType(call.arguments[i]!, lowerer.typeOf(call.arguments[i]!));
}
const args = [
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array slice start"),
lowerArrayPosition(lowerer, call.arguments[1], numLit(Infinity, loc), "array slice end"),
];
return { kind: "arrIntrinsic", method: "slice", receiver, args, type: receiverIr, loc };
}
if (name === "splice") {
// The REMOVAL forms: splice(start) and splice(start, deleteCount) —
// Node-exact relative/clamped indices, the removed elements back in
// order (their ownership moves out of the receiver). Insertion
// (3+ args) fenced by arity above.
const receiver = lowerer.lowerExpr(access.expression);
const args = call.arguments.map((a) => lowerer.lowerExpr(a));
for (let i = 0; i < args.length; i++) {
if (args[i]!.type.kind !== "f64") lowerer.badType(call.arguments[i]!, lowerer.typeOf(call.arguments[i]!));
}
const args = [
lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array splice start"),
lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array splice deleteCount"),
];
return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
}
if (name === "shift") {
@@ -300,6 +300,8 @@ export const ARRAY_METHODS = new Set([
"unshift",
"pop",
"reverse",
"fill",
"copyWithin",
"concat",
"map",
"filter",
+12
View File
@@ -2116,12 +2116,24 @@
"name": "Array.prototype.concat",
"status": "static"
},
{
"id": "stdlib.array.copyWithin",
"kind": "stdlib",
"name": "Array.prototype.copyWithin",
"status": "static"
},
{
"id": "stdlib.array.every",
"kind": "stdlib",
"name": "Array.prototype.every",
"status": "static"
},
{
"id": "stdlib.array.fill",
"kind": "stdlib",
"name": "Array.prototype.fill",
"status": "static"
},
{
"id": "stdlib.array.filter",
"kind": "stdlib",
@@ -7057,6 +7057,24 @@
],
"diags": []
},
"<repo>/tests/corpus/3044-array-slice-splice-positions.js": {
"order": [
"<repo>/tests/corpus/3044-array-slice-splice-positions.js"
],
"diags": []
},
"<repo>/tests/corpus/3045-array-indexed-mutation.js": {
"order": [
"<repo>/tests/corpus/3045-array-indexed-mutation.js"
],
"diags": []
},
"<repo>/tests/corpus/3046-array-indexed-ref-identity.ts": {
"order": [
"<repo>/tests/corpus/3046-array-indexed-ref-identity.ts"
],
"diags": []
},
"<repo>/tests/corpus/305-truthiness.ts": {
"order": [
"<repo>/tests/corpus/305-truthiness.ts"
@@ -0,0 +1,45 @@
const source = [10, 20, 30, 40];
console.log("slice", JSON.stringify(source.slice("1", "3")), JSON.stringify(source.slice(true, null)));
console.log("slice-default", JSON.stringify(source.slice()), JSON.stringify(source.slice(undefined, undefined)));
console.log("slice-range", JSON.stringify(source.slice("-2", Infinity)), JSON.stringify(source.slice(NaN, false)));
const sliceOrder = [];
const sliceResult = (() => {
sliceOrder.push("receiver");
return source;
})().slice(void sliceOrder.push("start"), void sliceOrder.push("end"));
console.log("slice-effects", JSON.stringify(sliceResult), sliceOrder.join(","));
const selectedStart = source.length === 4 ? "1" : undefined;
const absentStart = source.length === 3 ? "1" : undefined;
console.log("slice-union", JSON.stringify(source.slice(selectedStart)), JSON.stringify(source.slice(absentStart)));
const spliceSource = [10, 20, 30, 40];
console.log("splice-no-args", JSON.stringify(spliceSource.splice()), JSON.stringify(spliceSource));
console.log("splice-one-arg", JSON.stringify(spliceSource.splice("2")), JSON.stringify(spliceSource));
console.log("splice-undefined-count", JSON.stringify(spliceSource.splice("0", undefined)), JSON.stringify(spliceSource));
console.log("splice-undefined-start", JSON.stringify(spliceSource.splice(undefined)), JSON.stringify(spliceSource));
const spliceMore = [10, 20, 30, 40, 50];
console.log("splice-coerce", JSON.stringify(spliceMore.splice(true, "2")), JSON.stringify(spliceMore));
console.log("splice-null", JSON.stringify(spliceMore.splice(null, false)), JSON.stringify(spliceMore));
const selectedCount = source.length === 4 ? "2" : undefined;
const absentCount = source.length === 3 ? "2" : undefined;
const spliceUnion = [10, 20, 30, 40];
console.log("splice-union", JSON.stringify(spliceUnion.splice(1, selectedCount)), JSON.stringify(spliceUnion));
console.log("splice-union-undefined", JSON.stringify(spliceUnion.splice(0, absentCount)), JSON.stringify(spliceUnion));
const spliceOrder = [];
const spliceResult = spliceMore.splice(void spliceOrder.push("start"), void spliceOrder.push("count"));
console.log("splice-effects", JSON.stringify(spliceResult), JSON.stringify(spliceMore), spliceOrder.join(","));
console.log("toSpliced", JSON.stringify(source.toSpliced("1", "2")), JSON.stringify(source.toSpliced(false, true, 99)));
console.log("toSpliced-default", JSON.stringify(source.toSpliced()), JSON.stringify(source.toSpliced("2")));
console.log("toSpliced-undefined", JSON.stringify(source.toSpliced(undefined, undefined, 99)));
console.log("toSpliced-union", JSON.stringify(source.toSpliced(1, selectedCount)), JSON.stringify(source.toSpliced(1, absentCount)));
const copyOrder = [];
const copied = source.toSpliced(void copyOrder.push("start"), void copyOrder.push("count"), (() => {
copyOrder.push("item");
return 99;
})());
console.log("toSpliced-effects", JSON.stringify(copied), copyOrder.join(","), JSON.stringify(source));
@@ -0,0 +1,53 @@
/** @param {number[]} a */
function describe(a) {
return JSON.stringify(a);
}
const filled = [0, 1, 2, 3];
console.log("fill", filled.fill(9, "1", "3") === filled, describe(filled));
console.log("fill-default", describe([0, 1].fill()), describe([0, 1].fill(8, undefined, undefined)));
console.log("fill-coerce", describe([0, 1, 2, 3].fill(9, true, null)), describe([0, 1, 2, 3].fill(9, NaN, Infinity)));
console.log("fill-negative", describe([0, 1, 2, 3].fill(8, -3, -1)), describe([0, 1].fill(8, -Infinity, 1)));
console.log("fill-empty", describe([0, 1, 2].fill(8, 2, 1)));
/** @type {number[]} */
const sparse = [];
sparse.length = 4;
sparse[3] = 7;
console.log("fill-hole", describe(sparse.fill(5, 1, 3)), 0 in sparse, 1 in sparse, 2 in sparse);
const optional = [1];
const absent = optional[9];
console.log("fill-missing-read", describe([0, 1].fill(absent)));
const forward = [0, 1, 2, 3, 4];
console.log("copy-forward", forward.copyWithin(0, 2) === forward, describe(forward));
const backward = [0, 1, 2, 3, 4];
console.log("copy-backward", backward.copyWithin(1, 0, 4) === backward, describe(backward));
console.log("copy-default", describe([0, 1, 2].copyWithin()), describe([0, 1, 2].copyWithin(undefined, 1, undefined)));
console.log("copy-negative", describe([0, 1, 2, 3].copyWithin(-2, -4, -1)));
console.log("copy-coerce", describe([0, 1, 2, 3].copyWithin("1", true, "3.8")));
console.log("copy-bounds", describe([0, 1, 2].copyWithin(99, 0)), describe([0, 1, 2].copyWithin(0, -Infinity, Infinity)));
/** @type {number[]} */
const holes = [0, , 2, , 4];
console.log("copy-holes-forward", describe(holes.copyWithin(0, 1, 4)), 0 in holes, 1 in holes, 2 in holes, 3 in holes);
/** @type {number[]} */
const holesBack = [, 1, , 3, 4];
console.log("copy-holes-backward", describe(holesBack.copyWithin(1, 0, 4)), 0 in holesBack, 1 in holesBack, 2 in holesBack, 3 in holesBack);
const missing = [10, 20];
missing[1] = missing[9];
console.log("copy-undefined", describe(missing.copyWithin(0, 1)), 0 in missing);
const effects = [];
const effectTarget = [0, 1, 2];
const fillReturn = (() => { effects.push("receiver"); return effectTarget; })().fill(
(() => { effects.push("value"); return 8; })(),
(() => { effects.push("start"); return "1"; })(),
(() => { effects.push("end"); return 3; })(),
);
console.log("fill-order", effects.join(","), fillReturn === effectTarget, describe(effectTarget));
const copyEffects = [];
const copyReturn = (() => { copyEffects.push("receiver"); return effectTarget; })().copyWithin(
(() => { copyEffects.push("target"); return "0"; })(),
(() => { copyEffects.push("start"); return 1; })(),
(() => { copyEffects.push("end"); return "3"; })(),
);
console.log("copy-order", copyEffects.join(","), copyReturn === effectTarget, describe(effectTarget));
@@ -0,0 +1,13 @@
type Item = { label: string };
const fillValue: Item = { label: "same" };
const filled: Item[] = [{ label: "first" }, { label: "second" }, { label: "third" }];
console.log("fill-return", filled.fill(fillValue, 1) === filled);
fillValue.label = "updated";
console.log("fill-shared", JSON.stringify(filled));
const source: Item = { label: "first" };
const copied: Item[] = [source, { label: "second" }, { label: "third" }];
console.log("copy-return", copied.copyWithin(1, 0, 2) === copied);
source.label = "updated";
console.log("copy-shared", JSON.stringify(copied));
+7
View File
@@ -50,6 +50,13 @@ test("unsupported execution requirements and assertion reflection remain exclusi
expect(exclusion(text, metadata(text), "sloppy")).toBe("execution:sloppy");
});
test("the compareArray include only admits the implemented assertion form", () => {
const accepted = source("includes: [compareArray.js]", "assert.compareArray([1, NaN], [1, NaN]);");
expect(exclusion(accepted, metadata(accepted), "strict")).toBeUndefined();
const globalHelper = source("includes: [compareArray.js]", "compareArray([1], [1]);");
expect(exclusion(globalHelper, metadata(globalHelper), "strict")).toBe("harness:compareArray-surface");
});
test("the adapter retains the test body without a function or try/catch wrapper", () => {
const body = source("description: lexical", "const x = 1;\nassert.sameValue(x, 1);");
const result = prepare(body);
+18
View File
@@ -51,6 +51,10 @@ assert.notSameValue(1, "1");
{ name: "wrong SameValue", status: "fail", source: "assert.sameValue(1, 2);" },
{ name: "signed zero mismatch", status: "fail", source: "assert.sameValue(0, -0);" },
{ name: "NaN is the same value", status: "fail", source: "assert.notSameValue(NaN, NaN);" },
{ name: "scalar array contents", status: "pass", source: "assert.compareArray([1, NaN, -0, undefined], [1, NaN, -0, undefined]);" },
{ name: "array length mismatch", status: "fail", source: "assert.compareArray([1], [1, 2]);" },
{ name: "array element mismatch", status: "fail", source: "assert.compareArray([1, 2], [1, 3]);" },
{ name: "array signed zero mismatch", status: "fail", source: "assert.compareArray([0], [-0]);" },
{ name: "assert requires true, not truthiness", status: "fail", source: "assert(1);" },
];
@@ -78,4 +82,18 @@ describe(`Test262 host assertion contract${shardSuffix()}`, () => {
expect(result, JSON.stringify(result)).toMatchObject({ status: "harness-refusal", reason: "reference-assertion" });
});
}
test("array element identity assertions are refused", async () => {
const source = "const value = {}; try { assert.compareArray([value], [value]); } catch { }";
expect(() => runUpstream(source)).not.toThrow();
const result = await runSource(source, { sanitize });
expect(result, JSON.stringify(result)).toMatchObject({ status: "harness-refusal", reason: "reference-assertion" });
});
test("array-like objects remain outside the adapter", async () => {
const source = "try { assert.compareArray({ 0: 1, length: 1 }, [1]); } catch { }";
expect(() => runUpstream(source)).not.toThrow();
const result = await runSource(source, { sanitize });
expect(result, JSON.stringify(result)).toMatchObject({ status: "harness-refusal", reason: "reference-assertion" });
});
});
+2 -2
View File
@@ -30,9 +30,9 @@ Pass the resulting directory as `--root` and use `--filter` to survey an API fam
The current profile is `static-strict-scalar-adapter-v1`. It selects synchronous, positive strict-script variants and adapts each script to a standalone module. Preparation adds a strict directive, imports the host assertions, appends a completion marker, and leaves the upstream test body unchanged. Each program executes in a fresh native process. Conservative syntax checks reject known global-script dependencies, but do not establish that every module adaptation preserves the original script's semantics. Reports are local investigation artifacts, not published compatibility assessments.
The unmodified upstream assertion helper currently reaches scriptc's JavaScript function-expando refusal. Test262 explicitly permits host implementations of harness functions in its [interpretation rules](https://github.com/tc39/test262/blob/7ab7fafa0003f73fc85c1b95d88094d33f7eb8bd/INTERPRETING.md#host-defined-functions). `harness.ts` supplies `assert`, `assert.sameValue`, `assert.notSameValue`, and `Test262Error`. Scalar SameValue uses the statically compiled `node:assert/strict` implementation. Contract tests compare it with the original Test262 helpers under Node using cases for NaN, signed zero, null/undefined, differing primitive types, failed assertions, and premature termination.
The unmodified upstream assertion helper currently reaches scriptc's JavaScript function-expando refusal. Test262 explicitly permits host implementations of harness functions in its [interpretation rules](https://github.com/tc39/test262/blob/7ab7fafa0003f73fc85c1b95d88094d33f7eb8bd/INTERPRETING.md#host-defined-functions). `harness.ts` supplies `assert`, `assert.sameValue`, `assert.notSameValue`, `assert.compareArray`, and `Test262Error`. Scalar SameValue and scalar array-element comparisons use the statically compiled `node:assert/strict` implementation. Contract tests compare successful and failing assertions with the original Test262 helpers under Node, including NaN, signed zero, null/undefined, differing primitive types, and premature termination.
Reference-equality assertions terminate with a distinct harness-refusal result: conversion at a native function boundary can copy references, so this adapter cannot faithfully compare every object, array, or function identity. Tests cannot catch this refusal and accidentally pass. Assertion aliases, mutation, reflection, additional assertion methods, and all `includes` helpers are excluded until explicitly implemented and tested.
Reference-equality assertions terminate with a distinct harness-refusal result: conversion at a native function boundary can copy references, so this adapter cannot faithfully compare every object, array, or function identity. Tests cannot catch this refusal and accidentally pass. Sparse array comparisons are not yet handled faithfully across that boundary. Assertion aliases, mutation, reflection, additional assertion methods, and `includes` helpers other than `compareArray.js` remain excluded.
Sloppy variants, raw tests, modules, asynchronous completion, agents, and negative tests are currently excluded. Parse, resolution, and runtime negative phases retain their distinct reasons; arbitrary compiler errors never satisfy a negative expectation. `$262`, dynamic evaluation, observable script-global state, and other unsupported host requirements are excluded as well. The runtime and compiler continue to enforce their existing refusal boundaries for admitted source.
+20
View File
@@ -39,4 +39,24 @@ export namespace assert {
throw new Test262Error(message);
}
}
export function compareArray(actual: unknown, expected: unknown, message = "Expected matching array contents"): void {
if (!Array.isArray(actual) || !Array.isArray(expected)) {
scalar(actual);
scalar(expected);
throw new Test262Error(message);
}
if (actual.length !== expected.length) throw new Test262Error(message);
for (let i = 0; i < actual.length; i++) {
const left: unknown = i in actual ? actual[i] : undefined;
const right: unknown = i in expected ? expected[i] : undefined;
scalar(left);
scalar(right);
try {
nodeAssert.strictEqual(left, right);
} catch {
throw new Test262Error(message);
}
}
}
}
+6 -2
View File
@@ -91,7 +91,8 @@ export function exclusion(source, meta, variant) {
if (meta.negative) return `negative-phase:${meta.negative.phase}`;
if (meta.flags.includes("async")) return "execution:async";
if (meta.flags.some((flag) => flag.startsWith("CanBlock"))) return "host:agents";
if (meta.includes.length) return `harness-includes:${meta.includes.join(",")}`;
const unsupportedIncludes = meta.includes.filter((name) => name !== "compareArray.js");
if (unsupportedIncludes.length) return `harness-includes:${unsupportedIncludes.join(",")}`;
const sf = ts.createSourceFile("test.js", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
let reason;
const forbidden = new Set(["$262", "$DONE", "$DONOTEVALUATE", "globalThis", "eval", "Function", "print", "process", "require", "arguments"]);
@@ -106,10 +107,13 @@ export function exclusion(source, meta, variant) {
if (ts.isCallExpression(parent) && parent.expression === node) {
// assert(value, message)
} else if (ts.isPropertyAccessExpression(parent) && parent.expression === node &&
["sameValue", "notSameValue"].includes(parent.name.text) &&
["sameValue", "notSameValue", "compareArray"].includes(parent.name.text) &&
ts.isCallExpression(parent.parent) && parent.parent.expression === parent) {
// Supported assertion calls; aliases, mutations, and reflection stay out.
} else reason = "harness:assert-surface";
} else if (meta.includes.includes("compareArray.js") && ts.isIdentifier(node) && node.text === "compareArray" &&
!(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node && ts.isIdentifier(node.parent.expression) && node.parent.expression.text === "assert")) {
reason = "harness:compareArray-surface";
} else if (ts.isIdentifier(node) && node.text === "Test262Error") {
if (!ts.isNewExpression(node.parent) || node.parent.expression !== node) reason = "harness:Test262Error-surface";
}
+28
View File
@@ -10,6 +10,18 @@
"test/built-ins/Array/prototype/at/returns-item.js",
"test/built-ins/Array/prototype/at/returns-undefined-for-holes-in-sparse-arrays.js",
"test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js",
"test/built-ins/Array/prototype/copyWithin/coerced-values-end.js",
"test/built-ins/Array/prototype/copyWithin/coerced-values-start.js",
"test/built-ins/Array/prototype/copyWithin/negative-end.js",
"test/built-ins/Array/prototype/copyWithin/negative-start.js",
"test/built-ins/Array/prototype/copyWithin/negative-target.js",
"test/built-ins/Array/prototype/copyWithin/non-negative-out-of-bounds-target-and-start.js",
"test/built-ins/Array/prototype/copyWithin/non-negative-target-and-start.js",
"test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js",
"test/built-ins/Array/prototype/copyWithin/undefined-end.js",
"test/built-ins/Array/prototype/fill/coerced-indexes.js",
"test/built-ins/Array/prototype/fill/fill-values-relative-end.js",
"test/built-ins/Array/prototype/fill/fill-values-relative-start.js",
"test/built-ins/Array/prototype/includes/no-arg.js",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-4-1.js",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-13.js",
@@ -42,6 +54,8 @@
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-6.js",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js",
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js",
"test/built-ins/Array/prototype/splice/15.4.4.12-9-a-1.js",
"test/built-ins/Array/prototype/splice/called_with_one_argument.js",
"test/built-ins/BigInt/prototype/toString/default-radix.js",
"test/built-ins/Math/abs/S15.8.2.1_A1.js",
"test/built-ins/Math/abs/S15.8.2.1_A2.js",
@@ -163,6 +177,18 @@
"test/built-ins/Array/prototype/at/returns-item.js": "793b0f4a94ccf54a1e43813b3fde39d8e1f216cde9e22be674ecefd6a46e8e63",
"test/built-ins/Array/prototype/at/returns-undefined-for-holes-in-sparse-arrays.js": "32d020ce560af714f8c429f6d168507797304f72ef340f31c725c925e38d3969",
"test/built-ins/Array/prototype/at/returns-undefined-for-out-of-range-index.js": "ad51fad4e5e759ba996c59306e49bfd355f4c9afc6f0ba4bc90a768f57e3ff63",
"test/built-ins/Array/prototype/copyWithin/coerced-values-end.js": "bb14f53816c619c6d21886f99fa64b9afb0223af50e2fb1c7a3cc3bee7708862",
"test/built-ins/Array/prototype/copyWithin/coerced-values-start.js": "82f01c1dedf206d935030520aa5c1ba5a7adc439f86b0e1f1c9ae9cd7bb0577f",
"test/built-ins/Array/prototype/copyWithin/negative-end.js": "bf4eda932254d7bab04164ee0eb26492feadd3fcb74b01bb2baee77cb76ba17c",
"test/built-ins/Array/prototype/copyWithin/negative-start.js": "c2231097edb3ef50da00d9e882ad6b8fb28c41d50985332869315999043a463b",
"test/built-ins/Array/prototype/copyWithin/negative-target.js": "c0716a622c7427667129ddd32b3abc9a42d5975974cc521eb1208cf24e3a4940",
"test/built-ins/Array/prototype/copyWithin/non-negative-out-of-bounds-target-and-start.js": "10aa7748761388033a832d4f93a364cb96df60fa68e1fa0006394b1cd747ae4c",
"test/built-ins/Array/prototype/copyWithin/non-negative-target-and-start.js": "5791c024bbc5de9631a11857822b41bd4be622175fa957523c93bb15068271a7",
"test/built-ins/Array/prototype/copyWithin/non-negative-target-start-and-end.js": "05f846e32ef19b3d36f807b717450a1fdad89193e7a93ea807607c1209f938c2",
"test/built-ins/Array/prototype/copyWithin/undefined-end.js": "88c1354b0cfce33c1f6a9dd5ca3c4186fda612b0b21f6aedbac710f43bdad18b",
"test/built-ins/Array/prototype/fill/coerced-indexes.js": "e5ea8d3c667d1aee2c009ec11bbc5969f81eddffd9b5a95c0ccc4211e94d83bc",
"test/built-ins/Array/prototype/fill/fill-values-relative-end.js": "a99a40ef2166f7cc368c675779edc5b3150c52089857caba3b1151a135c4b9b5",
"test/built-ins/Array/prototype/fill/fill-values-relative-start.js": "68ea96909caf42da6632d84dd8112d44c53a5ff731339ae2c4f8587be8239192",
"test/built-ins/Array/prototype/includes/no-arg.js": "e38e7db529094b08ac13cc8a53cdbf59b5b2537711922af9bf8b1178f615e511",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-4-1.js": "ef762d69cbdf878821a6f998064590c2c19aa9e3db297968a5db02a6f5cd455a",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-5-13.js": "3c1ebd36e0e4657c42abbb76a707546a7b46ad954d3e569a84b796695d10da02",
@@ -195,6 +221,8 @@
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-6.js": "d389d03867aef471519b23d75bf6b24e68038a91de9f4489ac0d08da9f393bbb",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-7.js": "5afb53c527ddb49da7a505af67cd59ad49d8b47d5af9280ed0e5578672a94c40",
"test/built-ins/Array/prototype/lastIndexOf/fromindex-zero-conversion.js": "8231b818adf4123a4e5c9b65206b0b72984894b825319da669b99f820c75f963",
"test/built-ins/Array/prototype/splice/15.4.4.12-9-a-1.js": "6e6cd215339a848aef80163023c1201e9c502af76d4dcc62915bf6f868d9523a",
"test/built-ins/Array/prototype/splice/called_with_one_argument.js": "719fd6f8bf400aacefa5a0499b69495384e831668463d42960ff44d399f9f74e",
"test/built-ins/BigInt/prototype/toString/default-radix.js": "d901ef7e3fab5b8c528cb60a12c80145a6c5b7fbd02f829fe657259eddbe711a",
"test/built-ins/Math/abs/S15.8.2.1_A1.js": "c5b508cdc6130c8670e8ac6d822ca93e5dbead7566543cb65b793602b7203d7c",
"test/built-ins/Math/abs/S15.8.2.1_A2.js": "a625e117f7858319c2ff25b959539266a6f29473925bb47ad5d8bbfe88e7b31e",
@@ -0,0 +1,45 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
end argument is coerced to an integer values.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, null), [0, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(1, 0, null) must return [0, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, NaN), [0, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(1, 0, NaN) must return [0, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, false), [0, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(1, 0, false) must return [0, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, true), [0, 0, 2, 3],
'[0, 1, 2, 3].copyWithin(1, 0, true) must return [0, 0, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, '-2'), [0, 0, 1, 3],
'[0, 1, 2, 3].copyWithin(1, 0, "-2") must return [0, 0, 1, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, -2.5), [0, 0, 1, 3],
'[0, 1, 2, 3].copyWithin(1, 0, -2.5) must return [0, 0, 1, 3]'
);
@@ -0,0 +1,56 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
start argument is coerced to an integer value.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
8. Let relativeStart be ToInteger(start).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, undefined), [0, 0, 1, 2],
'[0, 1, 2, 3].copyWithin(1, undefined) must return [0, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, false), [0, 0, 1, 2],
'[0, 1, 2, 3].copyWithin(1, false) must return [0, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, NaN), [0, 0, 1, 2],
'[0, 1, 2, 3].copyWithin(1, NaN) must return [0, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, null), [0, 0, 1, 2],
'[0, 1, 2, 3].copyWithin(1, null) must return [0, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, true), [1, 2, 3, 3],
'[0, 1, 2, 3].copyWithin(0, true) must return [1, 2, 3, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, '1'), [1, 2, 3, 3],
'[0, 1, 2, 3].copyWithin(0, "1") must return [1, 2, 3, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0.5), [0, 0, 1, 2],
'[0, 1, 2, 3].copyWithin(1, 0.5) must return [0, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 1.5), [1, 2, 3, 3],
'[0, 1, 2, 3].copyWithin(0, 1.5) must return [1, 2, 3, 3]'
);
@@ -0,0 +1,58 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Set values with negative end argument.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
12. ReturnIfAbrupt(relativeEnd).
13. If relativeEnd < 0, let final be max((len + relativeEnd),0); else let
final be min(relativeEnd, len).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 1, -1), [1, 2, 2, 3],
'[0, 1, 2, 3].copyWithin(0, 1, -1) must return [1, 2, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(2, 0, -1), [0, 1, 0, 1, 2],
'[0, 1, 2, 3, 4].copyWithin(2, 0, -1) must return [0, 1, 0, 1, 2]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(1, 2, -2), [0, 2, 2, 3, 4],
'[0, 1, 2, 3, 4].copyWithin(1, 2, -2) must return [0, 2, 2, 3, 4]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, -2, -1), [2, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(0, -2, -1) must return [2, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(2, -2, -1), [0, 1, 3, 3, 4],
'[0, 1, 2, 3, 4].copyWithin(2, -2, -1) must return [0, 1, 3, 3, 4]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(-3, -2, -1), [0, 2, 2, 3],
'[0, 1, 2, 3].copyWithin(-3, -2, -1) must return [0, 2, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(-2, -3, -1), [0, 1, 2, 2, 3],
'[0, 1, 2, 3, 4].copyWithin(-2, -3, -1) must return [0, 1, 2, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(-5, -2, -1), [3, 1, 2, 3, 4],
'[0, 1, 2, 3, 4].copyWithin(-5, -2, -1) must return [3, 1, 2, 3, 4]'
);
@@ -0,0 +1,45 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Set values with negative start argument.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
10. If relativeStart < 0, let from be max((len + relativeStart),0); else let
from be min(relativeStart, len).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, -1), [3, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(0, -1) must return [3, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(2, -2), [0, 1, 3, 4, 4],
'[0, 1, 2, 3, 4].copyWithin(2, -2) must return [0, 1, 3, 4, 4]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(1, -2), [0, 3, 4, 3, 4],
'[0, 1, 2, 3, 4].copyWithin(1, -2) must return [0, 3, 4, 3, 4]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(-1, -2), [0, 1, 2, 2],
'[0, 1, 2, 3].copyWithin(-1, -2) must return [0, 1, 2, 2]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(-2, -3), [0, 1, 2, 2, 3],
'[0, 1, 2, 3, 4].copyWithin(-2, -3) must return [0, 1, 2, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(-5, -2), [3, 4, 2, 3, 4],
'[0, 1, 2, 3, 4].copyWithin(-5, -2) must return [3, 4, 2, 3, 4]'
);
@@ -0,0 +1,30 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Set values with negative target argument.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
7. If relativeTarget < 0, let to be max((len + relativeTarget),0); else let to
be min(relativeTarget, len).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(-1, 0), [0, 1, 2, 0],
'[0, 1, 2, 3].copyWithin(-1, 0) must return [0, 1, 2, 0]'
);
assert.compareArray(
[0, 1, 2, 3, 4].copyWithin(-2, 2), [0, 1, 2, 2, 3],
'[0, 1, 2, 3, 4].copyWithin(-2, 2) must return [0, 1, 2, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(-1, 2), [0, 1, 2, 2],
'[0, 1, 2, 3].copyWithin(-1, 2) must return [0, 1, 2, 2]'
);
@@ -0,0 +1,105 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Max values of target and start positions are this.length.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
7. If relativeTarget < 0, let to be max((len + relativeTarget),0); else let to
be min(relativeTarget, len).
...
10. If relativeStart < 0, let from be max((len + relativeStart),0); else let
from be min(relativeStart, len).
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
...
14. Let count be min(final-from, len-to).
15. If from<to and to<from+count
...
16. Else,
a. Let direction = 1.
17. Repeat, while count > 0
...
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(6, 0), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(6, 0) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(7, 0), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(7, 0) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(Infinity, 0), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(Infinity, 0) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(6, 2), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(6, 2) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(7, 2), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(7, 2) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(Infinity, 2), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(Infinity, 2) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(0, 6), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(0, 6) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(0, 7), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(0, 7) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(0, Infinity), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(0, Infinity) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(2, 6), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(2, 6) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(1, 7), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(1, 7) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(3, Infinity), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(3, Infinity) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(6, 6), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(6, 6) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(10, 10), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(10, 10) must return [0, 1, 2, 3, 4, 5]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(Infinity, Infinity), [0, 1, 2, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(Infinity, Infinity) must return [0, 1, 2, 3, 4, 5]'
);
@@ -0,0 +1,52 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Copy values with non-negative target and start positions.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
7. If relativeTarget < 0, let to be max((len + relativeTarget),0); else let to
be min(relativeTarget, len).
...
10. If relativeStart < 0, let from be max((len + relativeStart),0); else let
from be min(relativeStart, len).
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
...
14. Let count be min(final-from, len-to).
15. If from<to and to<from+count
...
16. Else,
a. Let direction = 1.
17. Repeat, while count > 0
...
a. If fromPresent is true, then
i. Let fromVal be Get(O, fromKey).
...
iii. Let setStatus be Set(O, toKey, fromVal, true).
...
includes: [compareArray.js]
---*/
assert.compareArray(
['a', 'b', 'c', 'd', 'e', 'f'].copyWithin(0, 0),
['a', 'b', 'c', 'd', 'e', 'f']
);
assert.compareArray(
['a', 'b', 'c', 'd', 'e', 'f'].copyWithin(0, 2),
['c', 'd', 'e', 'f', 'e', 'f']
);
assert.compareArray(
['a', 'b', 'c', 'd', 'e', 'f'].copyWithin(3, 0),
['a', 'b', 'c', 'a', 'b', 'c']
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(1, 4),
[0, 4, 5, 3, 4, 5]
);
@@ -0,0 +1,70 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
Copy values with non-negative target, start and end positions.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
7. If relativeTarget < 0, let to be max((len + relativeTarget),0); else let to
be min(relativeTarget, len).
...
10. If relativeStart < 0, let from be max((len + relativeStart),0); else let
from be min(relativeStart, len).
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
...
14. Let count be min(final-from, len-to).
15. If from<to and to<from+count
a. Let direction be -1.
b. Let from be from + count -1.
c. Let to be to + count -1.
16. Else,
a. Let direction = 1.
17. Repeat, while count > 0
...
a. If fromPresent is true, then
i. Let fromVal be Get(O, fromKey).
...
iii. Let setStatus be Set(O, toKey, fromVal, true).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 0, 0), [0, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(0, 0, 0) must return [0, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 0, 2), [0, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(0, 0, 2) must return [0, 1, 2, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 1, 2), [1, 1, 2, 3],
'[0, 1, 2, 3].copyWithin(0, 1, 2) must return [1, 1, 2, 3]'
);
/*
* 15. If from<to and to<from+count
* a. Let direction be -1.
* b. Let from be from + count -1.
* c. Let to be to + count -1.
*
* 0 < 1, 1 < 0 + 2
* direction = -1
* from = 0 + 2 - 1
* to = 1 + 2 - 1
*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(1, 0, 2), [0, 0, 1, 3],
'[0, 1, 2, 3].copyWithin(1, 0, 2) must return [0, 0, 1, 3]'
);
assert.compareArray(
[0, 1, 2, 3, 4, 5].copyWithin(1, 3, 5), [0, 3, 4, 3, 4, 5],
'[0, 1, 2, 3, 4, 5].copyWithin(1, 3, 5) must return [0, 3, 4, 3, 4, 5]'
);
@@ -0,0 +1,25 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.copywithin
description: >
If `end` is undefined, set final position to `this.length`.
info: |
22.1.3.3 Array.prototype.copyWithin (target, start [ , end ] )
...
11. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
...
includes: [compareArray.js]
---*/
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 1, undefined), [1, 2, 3, 3],
'[0, 1, 2, 3].copyWithin(0, 1, undefined) must return [1, 2, 3, 3]'
);
assert.compareArray(
[0, 1, 2, 3].copyWithin(0, 1), [1, 2, 3, 3],
'[0, 1, 2, 3].copyWithin(0, 1) must return [1, 2, 3, 3]'
);
@@ -0,0 +1,82 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.fill
description: >
Fills elements from coerced to Integer `start` and `end` values
info: |
Array.prototype.fill ( _value_ [ , _start_ [ , _end_ ] ] )
3. Let _relativeStart_ be ? ToIntegerOrInfinity(_start_).
4. If _relativeStart_ = -∞, let _k_ be 0.
5. Else if _relativeStart_ &lt; 0, let _k_ be max(_len_ + _relativeStart_, 0).
7. If _end_ is *undefined*, let _relativeEnd_ be _len_; else let _relativeEnd_ be ? ToIntegerOrInfinity(_end_).
8. If _relativeEnd_ = -∞, let _final_ be 0.
includes: [compareArray.js]
---*/
assert.compareArray([0, 0].fill(1, undefined), [1, 1],
'[0, 0].fill(1, undefined) must return [1, 1]'
);
assert.compareArray([0, 0].fill(1, 0, undefined), [1, 1],
'[0, 0].fill(1, 0, undefined) must return [1, 1]'
);
assert.compareArray([0, 0].fill(1, null), [1, 1],
'[0, 0].fill(1, null) must return [1, 1]'
);
assert.compareArray([0, 0].fill(1, 0, null), [0, 0],
'[0, 0].fill(1, 0, null) must return [0, 0]'
);
assert.compareArray([0, 0].fill(1, true), [0, 1],
'[0, 0].fill(1, true) must return [0, 1]'
);
assert.compareArray([0, 0].fill(1, 0, true), [1, 0],
'[0, 0].fill(1, 0, true) must return [1, 0]'
);
assert.compareArray([0, 0].fill(1, false), [1, 1],
'[0, 0].fill(1, false) must return [1, 1]'
);
assert.compareArray([0, 0].fill(1, 0, false), [0, 0],
'[0, 0].fill(1, 0, false) must return [0, 0]'
);
assert.compareArray([0, 0].fill(1, NaN), [1, 1],
'[0, 0].fill(1, NaN) must return [1, 1]'
);
assert.compareArray([0, 0].fill(1, 0, NaN), [0, 0],
'[0, 0].fill(1, 0, NaN) must return [0, 0]'
);
assert.compareArray([0, 0].fill(1, '1'), [0, 1],
'[0, 0].fill(1, "1") must return [0, 1]'
);
assert.compareArray([0, 0].fill(1, 0, '1'), [1, 0],
'[0, 0].fill(1, 0, "1") must return [1, 0]'
);
assert.compareArray([0, 0].fill(1, 1.5), [0, 1],
'[0, 0].fill(1, 1.5) must return [0, 1]'
);
assert.compareArray([0, 0].fill(1, 0, 1.5), [1, 0],
'[0, 0].fill(1, 0, 1.5) must return [1, 0]'
);
assert.compareArray([0, 0].fill(1, Number.NEGATIVE_INFINITY, 1), [1, 0],
'[0, 0].fill(1, Number.NEGATIVE_INFINITY, 1) must return [1, 0]'
);
assert.compareArray([0, 0].fill(1, 0, Number.NEGATIVE_INFINITY), [0, 0],
'[0, 0].fill(1, 0, Number.NEGATIVE_INFINITY) must return [0, 0]'
);
@@ -0,0 +1,30 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.fill
description: >
Fills all the elements from a with a custom start index.
info: |
22.1.3.6 Array.prototype.fill (value [ , start [ , end ] ] )
...
8. If end is undefined, let relativeEnd be len; else let relativeEnd be
ToInteger(end).
9. ReturnIfAbrupt(relativeEnd).
10. If relativeEnd < 0, let final be max((len + relativeEnd),0); else let
final be min(relativeEnd, len).
...
includes: [compareArray.js]
---*/
assert.compareArray([0, 0, 0].fill(8, 0, 1), [8, 0, 0],
'[0, 0, 0].fill(8, 0, 1) must return [8, 0, 0]'
);
assert.compareArray([0, 0, 0].fill(8, 0, -1), [8, 8, 0],
'[0, 0, 0].fill(8, 0, -1) must return [8, 8, 0]'
);
assert.compareArray([0, 0, 0].fill(8, 0, 5), [8, 8, 8],
'[0, 0, 0].fill(8, 0, 5) must return [8, 8, 8]'
);
@@ -0,0 +1,27 @@
// Copyright (C) 2015 the V8 project authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.fill
description: >
Fills all the elements from a with a custom start index.
info: |
22.1.3.6 Array.prototype.fill (value [ , start [ , end ] ] )
...
7. If relativeStart < 0, let k be max((len + relativeStart),0); else let k be
min(relativeStart, len).
...
includes: [compareArray.js]
---*/
assert.compareArray([0, 0, 0].fill(8, 1), [0, 8, 8],
'[0, 0, 0].fill(8, 1) must return [0, 8, 8]'
);
assert.compareArray([0, 0, 0].fill(8, 4), [0, 0, 0],
'[0, 0, 0].fill(8, 4) must return [0, 0, 0]'
);
assert.compareArray([0, 0, 0].fill(8, -1), [0, 0, 8],
'[0, 0, 0].fill(8, -1) must return [0, 0, 8]'
);
@@ -0,0 +1,15 @@
// Copyright (c) 2012 Ecma International. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.prototype.splice
description: >
Array.prototype.splice - 'from' is the result of
ToString(actualStart+k) in an Array
---*/
var arrObj = [1, 2, 3];
var newArrObj = arrObj.splice(-2, 1);
assert.sameValue(newArrObj.length, 1, 'newArrObj.length');
assert.sameValue(newArrObj[0], 2, 'newArrObj[0]');
@@ -0,0 +1,25 @@
// Copyright (C) 2015 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: Array.prototype.splice deletes length-start elements when called with one argument
info: |
22.1.3.25 Array.prototype.splice (start, deleteCount , ...items )
...
9. Else if the number of actual arguments is 1, then
a. Let insertCount be 0.
b. Let actualDeleteCount be len – actualStart.
esid: sec-array.prototype.splice
---*/
var array = ["first", "second", "third"];
var result = array.splice(1);
assert.sameValue(array.length, 1, "array length updated");
assert.sameValue(array[0], "first", "array[0] unchanged");
assert.sameValue(result.length, 2, "result array length correct");
assert.sameValue(result[0], "second", "result[0] correct");
assert.sameValue(result[1], "third", "result[1] correct");