Skip to content

AOT: Array.prototype.unshift returns the array instead of the new length #656

Description

@proggeramlug

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions