Read spread tuple fields before later rest arguments run

The packing path for `(T | undefined)[]` rest parameters saved plain
arguments to temporaries but read tuple fields only when it built the
pack, after every later argument had run. A later argument that wrote
the tuple changed values already spread into the call. Each tuple field
is now saved where its spread appears, in argument order.
This commit is contained in:
Chris Tate
2026-10-24 17:30:32 -05:00
parent 01e6d55fc5
commit 2edebe7e2f
2 changed files with 25 additions and 5 deletions
@@ -421,12 +421,16 @@ export function completeArgs(
) {
const stmts: IrStmt[] = [];
const values = restNodes.map((a): IrExpr => {
// A tuple field is read where its spread appears: a later
// argument may write the tuple.
let value: IrExpr;
if (isIr(a)) {
if (a.ir.kind !== "seqExpr") return a.ir;
stmts.push(...a.ir.stmts);
return a.ir.result;
}
const value = lowerer.lowerExpr(a as ts.Expression);
value = a.ir;
if (value.kind === "seqExpr") {
stmts.push(...value.stmts);
value = value.result;
}
} else value = lowerer.lowerExpr(a as ts.Expression);
const saved = lowerer.declareHiddenLocal("%callArg", value.type);
stmts.push({ kind: "varDecl", localId: saved.id, init: value, loc: value.loc });
return { kind: "varRef", localId: saved.id, type: value.type, loc: value.loc };
@@ -33,6 +33,22 @@ function label(first: string, ...rest: (string | undefined)[]): string {
}
const words: [string, string | undefined, string | undefined] = ["a", undefined, "c"];
console.log(label(...words), label("b", undefined));
// Spread tuple fields are read before later arguments run.
const changing: [boolean, boolean | undefined] = [true, undefined];
function flip(pair: [boolean, boolean | undefined], second: boolean | undefined): boolean {
pair[0] = !pair[0];
pair[1] = second;
return pair[0];
}
console.log(flags(...changing, flip(changing, true)));
console.log(flags(...changing, flip(changing, undefined), ...changing));
const swapped: [string, string | undefined] = ["x", "y"];
function swap(pair: [string, string | undefined]): string {
pair[0] = "w";
pair[1] = undefined;
return "v";
}
console.log(label("z", ...swapped, swap(swapped), ...swapped));
function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string {
let a = start;