You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
correctness: a const-bound array literal containing an inline array literal reads undefined, serves stale data, and silently drops compound assignments #10733
A const binding whose initialiser is an array literal containing an inline array literal produces a scalar-replaced copy that diverges from the heap object. Reads of the inner array in a value position return undefined, chained reads return stale data while the heap holds the updated value, and a compound assignment through the chain is a silent no-op.
The write landed — JSON.stringify(arr) sees [[1,99,3]] in both runtimes — but the read returns 2. The program's own data is inconsistent with itself, which is the most confusing possible form of this bug.
(c) a compound assignment through the chain is a complete no-op:
All of these are correct in both runtimes, which narrows the trigger sharply:
let or var for the outer binding
binding the inner array first — const inner = [1,2,3]; const arr = [inner];
building the outer array with push
reassigning the outer element before use
for (const x of arr)
console.log(arr[0])directly — served from the scalar slot, so it happens to work
arr[0].length
three-deep nesting, [[[1,2,3]]]
an object outer, { a: [1,2,3] }
a plain arr[0][1] = 99 with no compound operator
So it needs: const outer binding, an array literal initialiser, an inline array literal element, and a read of that element into a value position.
Relationship to existing work
This is the scalar_replaced_arrays family already documented at crates/perry-codegen/src/expr/index_get.rs:751 — except that here the outer array also exists on the heap. Every mutation is visible through JSON.stringify(arr), so the scalar copy and the heap object diverge rather than one simply being absent. That is what makes (b) possible, and it is the part the existing note does not cover.
It is also the array analogue of #10689 (fixed in #10705), where a read on a scalar-replaced object folded to the constant undefined because the escape check did not consult the class chain. Same shape of failure — silent, no error, and repaired by anything that makes the receiver escape — on a different container.
Provenance
Found while working on #10718. It is pre-existing and unrelated to #10731 — verified on base 4715bc2fa and on that PR's head 1246fd9df, byte-identical output on all 19 probes. Compared against node v26.8.1 on Linux x86-64.
An earlier description of this defect as "b()[k()] += 10 silently drops the store" was wrong and has been retracted: the call and the compound assignment are both incidental, and a flat array behaves correctly. The trigger is the nested literal.
Activity
perf(codegen): let `a[i] += 1` reach the loop tier `a[i] = a[i] + 1` …
fix(codegen): preserve observable nested array rows (#10733)
fix(codegen): preserve observable nested array rows (#10733)
fix(codegen): preserve observable nested array rows (#10733)
fix(codegen): preserve observable nested array rows (#10733)
fix(codegen): preserve observable nested array rows (#10733)
fix(codegen): preserve observable nested array rows (#10733)