Files
scriptc/tests/corpus/4483-array-splice-statement.ts
Malte Ubl de2100515a Splice in place when the removed elements are discarded
A statement-position `a.splice(start, deleteCount, ...items)` built a
one-element (or n-element) items array, then scr_arr_splice_insert built
the removed-elements array that the caller released straight away: three
or more allocations per sorted insert in tsc-ts's insertType. When the
result is discarded and the items are plain evaluated values, the emitter
now evaluates receiver, start, count, and items in order and calls
scr_arr_splice_drop with the owned value slots. It applies the same
clamping and state shifts on dense receivers, releases removed values
only after the receiver is consistent again, and keeps the general
implementation for sparse receivers and sparse-sized results.
2026-10-09 10:10:23 -07:00

122 lines
4.0 KiB
TypeScript

// splice() in statement position discards its removed elements. Insertion,
// removal, clamping, negative and fractional starts, holes, sparse
// receivers, evaluation order, and the lifetime of removed and inserted
// references must match Node exactly.
class Tag {
name: string;
constructor(name: string) {
this.name = name;
}
}
function show(list: readonly Tag[]): string {
const parts: string[] = [];
for (let i = 0; i < list.length; i++) {
parts.push(i in list ? (list[i] === undefined ? "undef" : list[i]!.name) : "hole");
}
return `${list.length}:[${parts.join(",")}]`;
}
const t = (name: string): Tag => new Tag(name);
// The insertType shape: a sorted insert at a binary-search position.
function insertSorted(list: number[], value: number): boolean {
let lo = 0;
let hi = list.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
if (list[mid]! === value) return false;
if (list[mid]! < value) lo = mid + 1;
else hi = mid - 1;
}
list.splice(lo, 0, value);
return true;
}
const sorted: number[] = [];
for (const v of [5, 3, 9, 1, 7, 3, 5, 11, 0, 8]) insertSorted(sorted, v);
console.log("sorted", JSON.stringify(sorted));
// References: insert, replace, remove; removed values stay usable when
// another reference holds them.
const tags: Tag[] = [t("a"), t("b"), t("c"), t("d"), t("e")];
const held = tags[1]!;
tags.splice(1, 2, t("x"));
console.log("replace", show(tags), held.name);
tags.splice(-1, 1);
console.log("negative", show(tags));
tags.splice(1.7, 0, t("y"), t("z"));
console.log("fractional", show(tags));
tags.splice(99, 5, t("end"));
console.log("past end", show(tags));
tags.splice(-99, 1);
console.log("before start", show(tags));
tags.splice(2, Infinity);
console.log("to end", show(tags));
tags.splice(NaN, NaN, t("nan"));
console.log("nan", show(tags));
tags.splice(0, -3, t("neg-count"));
console.log("negative count", show(tags));
tags.splice(1, 1);
console.log("remove one", show(tags));
tags.splice(0, 0);
console.log("no-op", show(tags));
// Holes before and after the spliced range keep their positions shifted.
const gappy: Tag[] = [t("p")];
gappy[3] = t("q");
gappy[5] = t("r");
gappy.splice(1, 1, t("s"));
console.log("holes", show(gappy), 2 in gappy, 4 in gappy);
gappy.splice(4, 0, t("u"));
console.log("holes insert", show(gappy), 2 in gappy, 5 in gappy);
// A sparse receiver keeps sparse semantics.
const sparse: Tag[] = [t("s0")];
sparse[2000000] = t("far");
sparse.splice(1, 0, t("s1"));
console.log("sparse", sparse.length, sparse[1]!.name, sparse[2000001]!.name, 2000000 in sparse);
sparse.splice(0, 1);
console.log("sparse removed", sparse.length, sparse[0]!.name, sparse[2000000]!.name);
// Evaluation order: receiver, start, count, then each item.
const log: string[] = [];
const ordered: Tag[] = [t("o1"), t("o2")];
const pick = (label: string, value: number): number => {
log.push(label);
return value;
};
const item = (label: string): Tag => {
log.push(label);
return t(label);
};
ordered.splice(pick("start", 1), pick("count", 0), item("i1"), item("i2"));
console.log("order", log.join(","), show(ordered));
// Items that mutate the receiver while they evaluate.
const mutating: Tag[] = [t("m1"), t("m2"), t("m3")];
mutating.splice(1, 1, ((mutating.length = 1), t("after")));
console.log("mutating", show(mutating));
// Numbers, booleans, and strings.
const nums = [1, 2, 3, 4];
nums.splice(1, 2, 20, 30, 40);
const flags = [true, false];
flags.splice(1, 0, true);
const words = ["a", "b", "c"];
words.splice(0, 2);
console.log("scalars", JSON.stringify(nums), JSON.stringify(flags), JSON.stringify(words));
// The result is still observed when it is used.
const kept = [t("k1"), t("k2"), t("k3")];
const removed = kept.splice(0, 2, t("k0"));
console.log("result", show(removed), show(kept));
// Many inserts and removals in a loop: stable counts and contents.
const churn: Tag[] = [];
for (let i = 0; i < 300; i++) {
churn.splice(i % 7, 0, t("c" + i));
if (i % 3 === 0) churn.splice(i % 5, 1);
}
console.log("churn", churn.length, churn[0]!.name, churn[churn.length - 1]!.name);