Summary
Under perry compile, arr.unshift(value) returns the array itself (NaN-boxed pointer) instead of the new length. JS spec says unshift returns the new length of the array.
This is independent of #655 — it reproduces on the simple-identifier alias path that already worked before the #655 fix. Surfaced while validating #655: extended the repro to cover unshift, and the divergence remained even on the path the issue did not touch.
Environment
Minimal repro
const arr = [1, 2, 3];
const r = arr.unshift(0);
console.log("ret:", r); // perry: [ 0, 1, 2, 3 ] node: 4
console.log("after:", arr); // both: [ 0, 1, 2, 3 ] (mutation correct)
$ perry compile repro.ts -o repro && ./repro
ret: [ 0, 1, 2, 3 ]
after: [ 0, 1, 2, 3 ]
$ node --experimental-strip-types repro.ts
ret: 4
after: [ 0, 1, 2, 3 ]
The mutation is correct; only the returned value is wrong.
Likely root cause
Both lowering paths return the (possibly-reallocated) array handle from js_array_unshift_f64:
- HIR-level
Expr::ArrayUnshift codegen at crates/perry-codegen/src/expr.rs:7131 ends with Ok(new_box) where new_box = nanbox_pointer_inline(blk, &new_handle).
- Generic dispatch
lower_array_method at crates/perry-codegen/src/lower_array_method.rs "unshift" arm does the same.
The runtime js_array_unshift_f64 returns *mut ArrayHeader (the new array). To match spec, the lowering should follow the call with a load of (*new_arr).length and return that as a Number.
Impact
Code that uses unshift's return value as the new length silently sees the array. Common pattern:
if (queue.unshift(item) > MAX_DEPTH) { /* drain */ }
Under AOT this branch becomes truthy unconditionally (any non-empty array is truthy when coerced to boolean in a numeric comparison).
Notes
Summary
Under
perry compile,arr.unshift(value)returns the array itself (NaN-boxed pointer) instead of the new length. JS spec saysunshiftreturns the new length of the array.This is independent of #655 — it reproduces on the simple-identifier alias path that already worked before the #655 fix. Surfaced while validating #655: extended the repro to cover
unshift, and the divergence remained even on the path the issue did not touch.Environment
Minimal repro
$ perry compile repro.ts -o repro && ./repro ret: [ 0, 1, 2, 3 ] after: [ 0, 1, 2, 3 ] $ node --experimental-strip-types repro.ts ret: 4 after: [ 0, 1, 2, 3 ]The mutation is correct; only the returned value is wrong.
Likely root cause
Both lowering paths return the (possibly-reallocated) array handle from
js_array_unshift_f64:Expr::ArrayUnshiftcodegen atcrates/perry-codegen/src/expr.rs:7131ends withOk(new_box)wherenew_box = nanbox_pointer_inline(blk, &new_handle).lower_array_methodatcrates/perry-codegen/src/lower_array_method.rs"unshift"arm does the same.The runtime
js_array_unshift_f64returns*mut ArrayHeader(the new array). To match spec, the lowering should follow the call with a load of(*new_arr).lengthand return that as a Number.Impact
Code that uses
unshift's return value as the new length silently sees the array. Common pattern:Under AOT this branch becomes truthy unconditionally (any non-empty array is truthy when coerced to boolean in a numeric comparison).
Notes
lengthproperty on the result IS correct, so a workaround isarr.unshift(x); arr.length.