Expand static Array flat, splice, and join lowering (#460)

* Expand static Array flattening, splice insertion, and join coercion

* Refresh Array diagnostic and TypeScript 7 baselines
This commit is contained in:
Chris Tate
2026-09-26 08:41:06 -05:00
committed by GitHub
parent 46d30c8671
commit 1f23c28bb0
19 changed files with 463 additions and 33 deletions
+11
View File
@@ -1901,6 +1901,17 @@ function emitContainerExpr(
const cnt = e.args[1] ? emitter.emitExpr(e.args[1]).name : "INFINITY";
return emitter.newTemp(e.type, `scr_arr_splice(${r.name}, ${start.name}, ${cnt})`);
}
case "spliceInsert": {
const start = emitter.emitExpr(e.args[0]!);
const count = emitter.emitExpr(e.args[1]!);
const items = emitter.emitExpr(e.args[2]!);
return emitter.newTemp(e.type, `scr_arr_splice_insert(${r.name}, ${start.name}, ${count.name}, ${items.name})`);
}
case "flatCopy":
case "flatOne": {
const out = emitter.emitExpr(e.args[0]!);
return emitter.newTemp(e.type, `scr_arr_flat_copy(${r.name}, ${out.name}, ${method === "flatOne" ? "true" : "false"})`);
}
default: {
const _exhaustive: never = method;
void _exhaustive;
@@ -443,6 +443,23 @@ export function emitArrIntrinsic(host: LlvmEmitterContext, e: IrExpr & { kind: "
B.line(`${t} = call ptr @scr_arr_splice(ptr ${r.name}, double ${start.name}, double ${cnt})`);
return host.own({ name: t, type: e.type });
}
case "spliceInsert": {
const start = host.emitExpr(e.args[0]!);
const count = host.emitExpr(e.args[1]!);
const items = host.emitExpr(e.args[2]!);
host.declare(`declare ptr @scr_arr_splice_insert(ptr, double, double, ptr)`);
const t = B.tmp();
B.line(`${t} = call ptr @scr_arr_splice_insert(ptr ${r.name}, double ${start.name}, double ${count.name}, ptr ${items.name})`);
return host.own({ name: t, type: e.type });
}
case "flatCopy":
case "flatOne": {
const out = host.emitExpr(e.args[0]!);
host.declare(`declare ptr @scr_arr_flat_copy(ptr, ptr, i1)`);
const t = B.tmp();
B.line(`${t} = call ptr @scr_arr_flat_copy(ptr ${r.name}, ptr ${out.name}, i1 ${method === "flatOne" ? 1 : 0})`);
return host.own({ name: t, type: e.type });
}
default: {
const _exhaustive: never = method;
void _exhaustive;
@@ -16,8 +16,8 @@ import { buildArraySortFn } from "./lower-array-sort.js";
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
import { typeKey } from "../type-mapper.js";
import { WidthLift } from "./lowerer.js";
import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
import { lowerPositionArgument, positionNumber } from "./optional-arguments.js";
import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
import { defaultAfterUndefined, lowerPositionArgument, lowerStaticallyUndefinedArgument, positionNumber } from "./optional-arguments.js";
import { lowerArrayCopyWithin, lowerArrayFill } from "./array-indexed-mutation.js";
function primitivePositionType(lowerer: Lowerer, type: IrType): boolean {
@@ -85,6 +85,72 @@ function lowerArrayValueItems(
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
}
function lowerArraySpreadItems(
lowerer: Lowerer,
nodes: readonly ts.Expression[],
elem: IrType,
arrType: IrType & { kind: "array" },
loc: SrcLoc,
): IrExpr {
const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
const outRef = varRef(out.id, arrType, loc);
const length = (): IrExpr => ({ kind: "arrIntrinsic", method: "length", receiver: outRef, args: [], type: F64, loc });
const body: IrStmt[] = [
{ kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
];
for (const node of nodes) {
if (ts.isSpreadElement(node)) {
const source = lowerer.lowerExpr(node.expression);
if (!typeEquals(source.type, arrType)) {
lowerer.noLowering(`Array insertion spread from '${lowerer.fmt(source.type)}'`, node);
}
body.push({
kind: "exprStmt",
expr: { kind: "arrIntrinsic", method: "pushSpread", receiver: outRef, args: [source], type: F64, loc },
loc,
});
} else {
const value = lowerer.lowerExpr(node);
const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
? value : lowerer.coerceInto(node, value, elem);
body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
}
}
return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
}
function lowerArrayJoinSeparator(lowerer: Lowerer, node: ts.Expression | undefined, loc: SrcLoc): IrExpr {
const comma = strLit(",", loc);
if (!node) return comma;
const undefinedValue = lowerStaticallyUndefinedArgument(lowerer, node);
if (undefinedValue) return defaultAfterUndefined(undefinedValue, comma);
const value = lowerer.lowerExpr(node);
if (value.type.kind === "nullT") return defaultAfterUndefined(value, strLit("null", loc));
if (value.type.kind === "union") {
const undefinedTag = lowerer.armTag(value.type.unionId, UNDEFINED_T);
if (undefinedTag >= 0) {
const local = lowerer.declareHiddenLocal("%joinSeparator", value.type);
const ref = varRef(local.id, value.type, loc);
return {
kind: "seqExpr",
stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
result: {
kind: "ternary",
cond: { kind: "unionIsTag", unionId: value.type.unionId, tag: undefinedTag, value: ref, negated: false, type: BOOL, loc },
then: comma,
else_: lowerer.ensureString(ref, node),
type: STRING, loc,
},
type: STRING, loc,
};
}
}
if (value.type.kind === "dyn" || value.type.kind === "jsval") {
lowerer.noLowering(".join separator with a runtime-dependent undefined value", node);
}
return lowerer.ensureString(value, node);
}
/** Callback-driven array producers bypass mapType's ordinary T[] mapping:
* the callback is lowered first, then the helper's result array is built
* directly from its IR return type. Fence element kinds ScrArr cannot hold
@@ -343,9 +409,6 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
};
}
if (name === "toSpliced") {
if (call.arguments.some(ts.isSpreadElement)) {
lowerer.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
}
const receiver = lowerer.lowerExpr(access.expression);
const start = lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array toSpliced start");
const deleteCount = lowerArrayPosition(
@@ -355,12 +418,15 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
"array toSpliced deleteCount",
);
const itemNodes = call.arguments.slice(2);
const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
const hasSpread = itemNodes.some(ts.isSpreadElement);
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
const statefulItems = itemProbes.some((probe) =>
probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
const items: IrExpr = statefulItems
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
: {
const items: IrExpr = hasSpread
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
: statefulItems
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
: {
kind: "arrayLit",
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
type: receiverIr,
@@ -375,18 +441,18 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
loc,
};
}
if (name === "flat") return lowerArrayFlatCall(lowerer, call, access, receiverIr);
// The lib declares wider call forms than the lowered surface —
// join's separator is optional, and the predicate/mapping HOFs take
// a thisArg. Each unlowered form is fenced
// The lib declares wider call forms than the lowered surface — the
// predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
// per site (SC2020), never silently truncated to the supported
// arguments. push/unshift lower every declared form (variadic, 0 args
// included — Node returns the unchanged length); reduce/reduceRight
// lower both declared forms (with and without an initial value).
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],
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: [0, 1],
concat: [0, Number.MAX_SAFE_INTEGER],
slice: [0, 2], shift: [0, 0], splice: [0, 2], at: [0, 1],
slice: [0, 2], shift: [0, 0], splice: [0, Number.MAX_SAFE_INTEGER], 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],
@@ -397,11 +463,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
];
if (call.arguments.length < arity[0]! || call.arguments.length > arity[1]!) {
const hint =
name === "splice"
? "the removal forms lower — splice(start, deleteCount?); to insert, build a new array with slice and push"
: name === "join"
? 'pass the separator explicitly: join(",")'
: name === "map" || name === "filter" || name === "forEach" ||
name === "map" || name === "filter" || name === "forEach" ||
name === "find" || name === "some" || name === "every" || name === "flatMap"
? "the thisArg parameter has no lowering — use an arrow function"
: undefined;
@@ -578,11 +640,29 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
}
if (name === "splice") {
const receiver = lowerer.lowerExpr(access.expression);
const args = [
const args: IrExpr[] = [
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 (call.arguments.length <= 2) {
return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
}
const itemNodes = call.arguments.slice(2);
const hasSpread = itemNodes.some(ts.isSpreadElement);
const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
const statefulItems = itemProbes.some((probe) =>
probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
const items: IrExpr = hasSpread
? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
: statefulItems
? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
: {
kind: "arrayLit",
elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
type: receiverIr,
loc,
};
return { kind: "arrIntrinsic", method: "spliceInsert", receiver, args: [...args, items], type: receiverIr, loc };
}
if (name === "shift") {
// JS shift exactly: undefined on an empty array, else the first
@@ -626,7 +706,7 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
);
}
const receiver = lowerer.lowerExpr(access.expression);
const sep = lowerer.lowerExpr(call.arguments[0]!);
const sep = lowerArrayJoinSeparator(lowerer, call.arguments[0], loc);
return { kind: "arrIntrinsic", method: "join", receiver, args: [sep], type: STRING, loc };
}
if (name === "map" || name === "filter" || name === "forEach") {
@@ -644,6 +724,82 @@ function fenceProducedArrayElem(lowerer: Lowerer, node: ts.Node, producer: strin
return lowerArrayReduceCall(lowerer, call, access, name as "reduce" | "reduceRight", elem);
}
function literalFlatDepth(value: IrExpr): number | null {
if (value.kind === "numLit") return value.value;
if (value.kind === "strLit") return Number(value.value);
if (value.kind === "boolLit") return value.value ? 1 : 0;
if (value.kind === "unitLit") return value.unit === "undefined" ? 1 : 0;
if (value.kind === "unary" && value.op === "-") {
const operand = literalFlatDepth(value.operand);
return operand === null ? null : -operand;
}
return null;
}
function lowerArrayFlatCall(
lowerer: Lowerer,
call: ts.CallExpression,
access: ts.PropertyAccessExpression,
receiverType: IrType & { kind: "array" },
): IrExpr {
const loc = locOf(call);
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
lowerer.noLowering(`.flat with ${call.arguments.length} arguments`, call);
}
const elemCouldNest = (elem: IrType): boolean =>
elem.kind === "array" || elem.kind === "jsval" || elem.kind === "dyn" ||
(elem.kind === "union" && (lowerer.unions.get(elem.unionId)?.arms.some((arm) =>
arm.kind === "array" || arm.kind === "jsval" || arm.kind === "dyn") ?? true));
if (!elemCouldNest(receiverType.elem) && call.arguments[0]) {
const receiver = lowerer.lowerExpr(access.expression);
const depth = lowerArrayPosition(lowerer, call.arguments[0], numLit(1, loc), "array flat depth");
const local = lowerer.declareHiddenLocal("%flatReceiver", receiverType);
const ref = varRef(local.id, receiverType, loc);
return {
kind: "seqExpr",
stmts: [
{ kind: "varDecl", localId: local.id, init: receiver, loc },
{ kind: "exprStmt", expr: depth, loc },
],
result: {
kind: "arrIntrinsic", method: "flatCopy", receiver: ref,
args: [{ kind: "arrayLit", elems: [], type: receiverType, loc }],
type: receiverType, loc,
},
type: receiverType, loc,
};
}
const depth = call.arguments[0] ? literalFlatDepth(lowerer.lowerExpr(call.arguments[0])) : 1;
if (depth === null) lowerer.noLowering(".flat with a nonconstant depth", call);
let remaining = Number.isNaN(depth) || depth <= 0 ? 0 : Math.trunc(depth);
let result: IrExpr = lowerer.lowerExpr(access.expression);
let currentType = receiverType;
let flattened = false;
while (remaining > 0) {
const elem = currentType.elem;
if (elem.kind !== "array" && elemCouldNest(elem)) {
lowerer.noLowering(".flat over elements with runtime-dependent array shape", call);
}
if (elem.kind !== "array") break;
result = {
kind: "arrIntrinsic", method: "flatOne", receiver: result,
args: [{ kind: "arrayLit", elems: [], type: elem, loc }],
type: elem, loc,
};
currentType = elem;
remaining--;
flattened = true;
}
if (!flattened) {
result = {
kind: "arrIntrinsic", method: "flatCopy", receiver: result,
args: [{ kind: "arrayLit", elems: [], type: currentType, loc }],
type: currentType, loc,
};
}
return result;
}
/** `indexOf` and `includes` normally use the runtime's typed search helpers,
* but an indexed array read is a `T | undefined` value. Keep that union
* through a small lowered loop so the needle's missing state is observable:
@@ -314,6 +314,7 @@ export const ARRAY_METHODS = new Set([
"every",
"at",
"flatMap",
"flat",
"reduce",
"reduceRight",
"indexOf",
+6
View File
@@ -1592,6 +1592,12 @@ export type IrArrIntrinsicMethod =
| "slice"
| "shift"
| "splice"
/** One dense copy pass, or one level of flattening into an empty typed
* result array. The supplied result is borrowed and returned retained. */
| "flatCopy"
| "flatOne"
/** Mutating splice with evaluated insertion items; returns removed slots. */
| "spliceInsert"
| "reverse"
/** ES2023 copying methods. `toSpliced` receives [start, deleteCount,
* itemsArray], with omitted arguments completed by the frontend;
+10
View File
@@ -2798,12 +2798,22 @@ function validateFunction(
? { argTypes: [F64], result: e.receiver.type }
: e.method === "splice"
? { argTypes: [F64, F64], result: e.receiver.type }
: e.method === "spliceInsert"
? { argTypes: [F64, F64, e.receiver.type], result: e.receiver.type }
: e.method === "flatCopy" || e.method === "flatOne"
? { argTypes: [e.type], result: e.type }
: e.method === "shift"
? { argTypes: [], result: e.type } // union-checked below
: { argTypes: [], result: F64 }; // length
if (e.method === "getNumber" && elem.kind !== "f64") {
err(`arrIntrinsic getNumber requires f64 elements, got ${elem.kind}`, e.loc);
}
if (e.method === "flatCopy" && !typeEquals(e.type, e.receiver.type)) {
err("arrIntrinsic flatCopy result must match its receiver", e.loc);
}
if (e.method === "flatOne" && (elem.kind !== "array" || !typeEquals(e.type, elem))) {
err("arrIntrinsic flatOne result must match the nested array type", e.loc);
}
if (
e.method === "join" &&
elem.kind !== "f64" && elem.kind !== "string" && elem.kind !== "bool" &&
+6
View File
@@ -2164,6 +2164,12 @@
"name": "Array.prototype.findLastIndex",
"status": "static"
},
{
"id": "stdlib.array.flat",
"kind": "stdlib",
"name": "Array.prototype.flat",
"status": "static"
},
{
"id": "stdlib.array.flatMap",
"kind": "stdlib",
@@ -7087,12 +7087,30 @@
],
"diags": []
},
"<repo>/tests/corpus/3049-array-flat-depth.ts": {
"order": [
"<repo>/tests/corpus/3049-array-flat-depth.ts"
],
"diags": []
},
"<repo>/tests/corpus/305-truthiness.ts": {
"order": [
"<repo>/tests/corpus/305-truthiness.ts"
],
"diags": []
},
"<repo>/tests/corpus/3050-array-splice-insert.ts": {
"order": [
"<repo>/tests/corpus/3050-array-splice-insert.ts"
],
"diags": []
},
"<repo>/tests/corpus/3051-array-join-default.ts": {
"order": [
"<repo>/tests/corpus/3051-array-join-default.ts"
],
"diags": []
},
"<repo>/tests/corpus/400-fib.ts": {
"order": [
"<repo>/tests/corpus/400-fib.ts"
+48
View File
@@ -1142,6 +1142,54 @@ ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount) {
return out;
}
/* Insert the evaluated arguments at the original start position. The first
* splice moves removed slots out, and the second moves the remaining tail
* out. Appending items follows array iteration (a hole becomes undefined);
* appending the tail follows indexed copying (holes stay holes). */
ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
const ScrArr *items) {
double len = (double)a->len;
double s0 = isnan(start) ? 0 : trunc(start);
if (s0 < 0) s0 += len;
size_t from = s0 <= 0 ? 0 : s0 >= len ? a->len : (size_t)s0;
double avail = len - (double)from;
double d0 = isnan(deleteCount) ? 0 : trunc(deleteCount);
size_t n = d0 <= 0 ? 0 : d0 >= avail ? (size_t)avail : (size_t)d0;
if (items->len > SCR_ARR_MAX_LENGTH - (a->len - n)) scr_arr_oom();
ScrArr *removed = scr_arr_splice(a, start, deleteCount);
ScrArr *tail = scr_arr_splice(a, (double)from, INFINITY);
scr_arr_push_spread(a, items);
scr_arr_concat_copy(a, tail);
scr_arr_release(tail);
return removed;
}
/* FlattenIntoArray for static arrays: a dense copy with depth zero, or one
* level over array elements. The frontend supplies an empty result of the
* correct element kind; inner holes are skipped and present undefined stays
* present. This function borrows both inputs and returns a retained result. */
ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten) {
for (size_t i = 0; i < a->len; i++) {
uint64_t slot;
uint8_t state = scr_arr_state_at(a, i, &slot);
if (state == SCR_ARR_HOLE) continue;
if (flatten && state == SCR_ARR_VALUE) {
const ScrArr *inner = (const ScrArr *)scr_slot_to_ptr(slot);
for (size_t j = 0; j < inner->len; j++) {
if (scr_arr_state_at(inner, j, NULL) == SCR_ARR_HOLE) continue;
if (out->len == SCR_ARR_MAX_LENGTH) scr_arr_oom();
size_t at = out->len++;
scr_arr_copy_index(out, at, inner, j);
}
} else {
if (out->len == SCR_ARR_MAX_LENGTH) scr_arr_oom();
size_t at = out->len++;
scr_arr_copy_index(out, at, a, i);
}
}
return scr_arr_retain(out);
}
/* ── indexOf / includes ────────────────────────────────────────────────
* indexOf uses JS strict equality (===): NaN never matches (NaN !== NaN),
* -0 matches 0 (C == agrees on both). includes uses SameValueZero: the one
+3
View File
@@ -1087,6 +1087,9 @@ uint8_t scr_arr_shift_state(ScrArr *a, uint64_t *slot_out);
* to the end). Returns the removed elements in order as a fresh +1 array,
* ownership MOVED out of the receiver. Borrows a. */
ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount);
ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
const ScrArr *items);
ScrArr *scr_arr_flat_copy(const ScrArr *a, ScrArr *out, bool flatten);
/* indexOf: first index whose element strictly equals (JS ===) the needle,
* or -1. Per element kind: f64 by value (NaN never matches — NaN !== NaN;
+28
View File
@@ -0,0 +1,28 @@
const sparse = [1, , 3];
const copied = sparse.flat(0);
console.log(JSON.stringify(copied), copied.length, 1 in copied, 1 in sparse);
const rows = [[1, , 3], , [4, 5]] as number[][];
const one = rows.flat();
console.log(JSON.stringify(one), one.length, 1 in one);
const nested = [[[1], [2, , 3]], [[4]]];
console.log(JSON.stringify(nested.flat()));
console.log(JSON.stringify(nested.flat(2)));
console.log(JSON.stringify(nested.flat(Infinity)));
console.log(JSON.stringify(nested.flat(0)));
console.log(JSON.stringify(nested.flat(-2)));
const first = { value: 1 };
const second = { value: 2 };
const records = [[first], [second]];
const flattened = records.flat();
console.log(flattened[0] === first, flattened[1] === second, records[0]![0] === first);
const words = [["a", "b"], ["c"]];
console.log(words.flat().join("|"), words.flat(0).length);
console.log(JSON.stringify([7, , 9].flat()), JSON.stringify([7, , 9]));
const scalar = [1, , 3] as number[];
function depthArg(value: number): number { console.log("depth", value); return value; }
console.log(JSON.stringify(scalar.flat(depthArg(5))), JSON.stringify(scalar.flat(depthArg(-1))));
+36
View File
@@ -0,0 +1,36 @@
const values = [0, 1, 2, 3];
console.log(JSON.stringify(values.splice(1, 0, 8, 9)), JSON.stringify(values));
console.log(JSON.stringify(values.splice(-2, 1, 6)), JSON.stringify(values));
console.log(JSON.stringify(values.splice(99, 4, 10)), JSON.stringify(values));
console.log(JSON.stringify(values.splice(-99, 99, 11, 12)), JSON.stringify(values));
const sparse = [1, , 3, 4];
const removed = sparse.splice(1, 1, 9, 10);
console.log(JSON.stringify(removed), removed.length, 0 in removed, JSON.stringify(sparse), 3 in sparse);
const more = [1, , 3, 4];
console.log(JSON.stringify(more.splice(1, 0, 8)), JSON.stringify(more), 2 in more);
const nullable: (number | undefined)[] = [1, 2];
nullable.splice(1, 0, undefined);
console.log(JSON.stringify(nullable), 1 in nullable, nullable[1] === undefined);
const first = { value: 1 };
const second = { value: 2 };
const records = [first, second];
const old = records.splice(1, 1, first, second);
console.log(old[0] === second, records[0] === first, records[1] === first, records[2] === second);
const events: string[] = [];
const ordered = [1, 2, 3];
function receiver(): number[] { events.push("receiver"); return ordered; }
function numberArg(label: string, value: number): number { events.push(label); return value; }
const taken = receiver().splice(numberArg("start", 1), numberArg("count", 1), numberArg("first", 7), numberArg("last", 8));
console.log(events.join("|"), JSON.stringify(taken), JSON.stringify(ordered));
const spread = [7, , 9] as number[];
const destination = [1, 2, 3];
console.log(JSON.stringify(destination.splice(1, 1, 6, ...spread, 10)), JSON.stringify(destination), 3 in destination);
const self = [1, 2, 3];
console.log(JSON.stringify(self.splice(1, 0, ...self)), JSON.stringify(self));
const original = [1, 2, 3];
console.log(JSON.stringify(original.toSpliced(1, 1, 6, ...spread, 10)), JSON.stringify(original));
+17
View File
@@ -0,0 +1,17 @@
console.log([1, 2, 3].join(), ["a", "b"].join(), [true, false].join());
console.log([1, 2].join(undefined), [1, 2].join(void 0));
console.log([1, 2].join("|"), [1].join(), ([] as number[]).join());
let calls = 0;
function missing(): undefined { calls++; return undefined; }
console.log([5, 6].join(missing()), calls);
// @ts-ignore JavaScript accepts a numeric separator.
console.log([1, 2, 3].join(0));
// @ts-ignore JavaScript accepts a boolean separator.
console.log([1, 2].join(false));
// @ts-ignore JavaScript accepts a null separator.
console.log([1, 2].join(null));
function optionalSeparator(usePipe: boolean): string | undefined { return usePipe ? "|" : undefined; }
console.log([1, 2].join(optionalSeparator(true)), [1, 2].join(optionalSeparator(false)));
+1 -1
View File
@@ -48,7 +48,7 @@ const localized = (1234.5).toLocaleString();
// no fenced form remains to pin.)
const nums = [1, 2, 3];
const found = nums.indexOf(2, 1); // the fromIndex overload lowers
const joined = nums.join();
const joined = nums.join(); // the omitted separator lowers
nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
const rev = JSON.parse("1", (_k, v) => v);
@@ -224,18 +224,9 @@ stdlib-fence.ts:46:19 - error SC2020: 'Number.prototype.toLocaleString without a
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
stdlib-fence.ts:51:16 - error SC2020: '.join with 0 arguments' is part of the standard library types but has no scriptc lowering yet
50 | const found = nums.indexOf(2, 1); // the fromIndex overload lowers
51 | const joined = nums.join();
| ^~~~~~~~~~~
52 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
hint: pass the separator explicitly: join(",")
stdlib-fence.ts:52:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet
51 | const joined = nums.join();
51 | const joined = nums.join(); // the omitted separator lowers
52 | nums.forEach((x) => console.log(x), { unused: true }); // thisArg stays fenced
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | const pos = "abc".includes("b", 1); // the position form lowers now (indexOf's clamp)
+6
View File
@@ -46,6 +46,8 @@
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js",
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js",
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T1.js",
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T2.js",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js",
@@ -55,6 +57,7 @@
"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/S15.4.4.12_A6.1_T1.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",
@@ -228,6 +231,8 @@
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-1.js": "db248a7b40b4285a0331fa7eaee0a78e6a9af4e57294c9ce9e89fe392845ca2e",
"test/built-ins/Array/prototype/indexOf/15.4.4.14-9-b-ii-4.js": "07b115c3b90dfb5bcb9e38990fd1db57720c4ac423e052082828ed8ccb6b4214",
"test/built-ins/Array/prototype/indexOf/fromindex-zero-conversion.js": "ababfd2ad12b4099f9d2108ba6e58ff33feb358522f3bbd4ddde5144cbf0bc7f",
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T1.js": "eab57335e972d5f6e5b0e62a6c951ca38ecbc76c3068a7306e6295d4c35219b3",
"test/built-ins/Array/prototype/join/S15.4.4.5_A1.2_T2.js": "ea0b6435d60901e5eee67e0657bbf699726fc73adf4f0a1a5ae17f7aa841dfc1",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js": "58e752c5eda47f23bdc281f13ac7f3d3df37a0051b0ccd194198987796c8a841",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-2.js": "70cf040ba435efad144dcdbab8dc29547174ea3af9a7a5750eb0b0939110f497",
"test/built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js": "448016548d98ed3c4cefb81f0d995cd12e3d9d13fd90a5d119dd072108979419",
@@ -237,6 +242,7 @@
"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/S15.4.4.12_A6.1_T1.js": "9c1bd5c44cdba0a240fbd9476c450047bcdc3c809a6bc1faf1c2ef62542beac4",
"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",
@@ -0,0 +1,26 @@
// Copyright 2009 the Sputnik authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
info: If separator is undefined, a single comma is used as the separator
esid: sec-array.prototype.join
description: Checking this use new Array() and []
---*/
var x = new Array(0, 1, 2, 3);
if (x.join() !== "0,1,2,3") {
throw new Test262Error('#1: x = new Array(0,1,2,3); x.join() === "0,1,2,3". Actual: ' + (x.join()));
}
x = [];
x[0] = 0;
x[3] = 3;
if (x.join() !== "0,,,3") {
throw new Test262Error('#2: x = []; x[0] = 0; x[3] = 3; x.join() === "0,,,3". Actual: ' + (x.join()));
}
x = [];
x[0] = 0;
if (x.join() !== "0") {
throw new Test262Error('#3: x = []; x[0] = 0; x.join() === "0". Actual: ' + (x.join()));
}
@@ -0,0 +1,26 @@
// Copyright 2009 the Sputnik authors. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
info: If separator is undefined, a single comma is used as the separator
esid: sec-array.prototype.join
description: Checking this use new Array() and []
---*/
var x = new Array(0, 1, 2, 3);
if (x.join(undefined) !== "0,1,2,3") {
throw new Test262Error('#1: x = new Array(0,1,2,3); x.join(undefined) === "0,1,2,3". Actual: ' + (x.join(undefined)));
}
x = [];
x[0] = 0;
x[3] = 3;
if (x.join(undefined) !== "0,,,3") {
throw new Test262Error('#2: x = []; x[0] = 0; x[3] = 3; x.join(undefined) === "0,,,3". Actual: ' + (x.join(undefined)));
}
x = [];
x[0] = 0;
if (x.join(undefined) !== "0") {
throw new Test262Error('#3: x = []; x[0] = 0; x.join(undefined) === "0". Actual: ' + (x.join(undefined)));
}
@@ -0,0 +1,24 @@
// Copyright 2014 Ecma International. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
info: Array.prototype.splice sets `length` on `this`
esid: sec-array.prototype.splice
description: Array.prototype.splice sets `length` on Array
---*/
var a = [0, 1, 2];
a.splice(1, 2, 4);
if (a.length !== 2) {
throw new Test262Error("Expected a.length === 2, actually " + a.length);
}
if (a[0] !== 0) {
throw new Test262Error("Expected a[0] === 0, actually " + a[0]);
}
if (a[1] !== 4) {
throw new Test262Error("Expected a[1] === 4, actually " + a[1]);
}