Summary
arr.splice(start, deleteCount, ...items) and arr.unshift(...items) drop the spread flag and insert the spread source as a single nested array element instead of spreading it.
push and concat are correct — only splice and unshift are affected.
Repro
const src = ["X", "Y"];
const a: string[] = ["a", "b"]; a.splice(1, 0, ...src);
const b: string[] = ["a", "b"]; b.unshift(...src);
const c: string[] = ["a", "b"]; c.push(...src);
console.log("splice :", JSON.stringify(a));
console.log("unshift:", JSON.stringify(b));
console.log("push :", JSON.stringify(c));
node --experimental-strip-types perry 0.5.1239
splice : ["a","X","Y","b"] splice : ["a",["X","Y"],"b"] <-- wrong
unshift: ["X","Y","a","b"] unshift: [["X","Y"],"a","b"] <-- wrong
push : ["a","b","X","Y"] push : ["a","b","X","Y"] ok
Passing the items as explicit arguments (a.splice(1, 0, "X", "Y")) is correct, so the defect is specific to spread.
Cause
crates/perry-hir/src/lower/expr_call/local_array_methods.rs.
The push arm checks the AST spread flag (line ~328):
let any_spread = call.args.iter().any(|a| a.spread.is_some());
if any_spread { /* ArrayPushSpread ... */ }
The splice arm (line ~456) never consults call.args[i].spread — it just collects the lowered args:
let items: Vec<Expr> = args_iter.collect();
return Ok(Ok(Expr::ArraySplice { array_id, start, delete_count, items }));
so ...src arrives as one Expr and is stored as one element. unshift (line ~374) has the same shape.
Impact
Found while compiling the Milo compiler (https://github.com/milo-language/milo), ~32k lines of TS, with Perry. Its LLVM emitter hoists entry-block allocas with:
lines.splice(insertAt, 0, ...hoisted); // codegen.ts hoistAllocas
Under Perry the nested array stringified through lines.join("\n") using the default , separator, so the compiled Milo compiler emitted malformed LLVM IR and every Milo program failed to build:
error: expected value token
%notFound.addr = alloca i64, %base.addr = alloca i64, %nptr.addr = alloca ptr, ...
This is a silent wrong-answer bug: no diagnostic, and the corruption only surfaces far downstream.
Test coverage
Nothing in test-files/ exercises spread-into-splice (grep -rn "splice([^)]*\.\.\." test-files/ finds only comments). Worth adding alongside the fix, plus an unshift case.
Environment
- perry 0.5.1239, macOS arm64
Summary
arr.splice(start, deleteCount, ...items)andarr.unshift(...items)drop the spread flag and insert the spread source as a single nested array element instead of spreading it.pushandconcatare correct — onlyspliceandunshiftare affected.Repro
Passing the items as explicit arguments (
a.splice(1, 0, "X", "Y")) is correct, so the defect is specific to spread.Cause
crates/perry-hir/src/lower/expr_call/local_array_methods.rs.The
pusharm checks the AST spread flag (line ~328):The
splicearm (line ~456) never consultscall.args[i].spread— it just collects the lowered args:so
...srcarrives as oneExprand is stored as one element.unshift(line ~374) has the same shape.Impact
Found while compiling the Milo compiler (https://github.com/milo-language/milo), ~32k lines of TS, with Perry. Its LLVM emitter hoists entry-block allocas with:
Under Perry the nested array stringified through
lines.join("\n")using the default,separator, so the compiled Milo compiler emitted malformed LLVM IR and every Milo program failed to build:This is a silent wrong-answer bug: no diagnostic, and the corruption only surfaces far downstream.
Test coverage
Nothing in
test-files/exercises spread-into-splice (grep -rn "splice([^)]*\.\.\." test-files/finds only comments). Worth adding alongside the fix, plus anunshiftcase.Environment