Skip to content

Array.isArray answers from the binding's initializer, not the value — wrong in BOTH directions, so a guard admits a number #7844

Description

@proggeramlug

Summary

Array.isArray returns false for a genuine array held in an any-typed local that was reassigned to it. Every other test of the same value says it is an array, including Perry's own instanceof Array, so the runtime contradicts itself.

Three lines, no loop, no library:

let b: any = 0;
b = [b];
console.log(Array.isArray(b), b instanceof Array, b.length, JSON.stringify(b));
perry 0.5.1464   false true 1 [0]      <-- Array.isArray disagrees with instanceof Array
node  26.5.1     true  true 1 [0]

Reproduces identically with and without PERRY_NO_AUTO_OPTIMIZE=1, so it is not an auto-optimize artifact.

What triggers it

The value must reach the local by assignment, not by initializer. An any local initialized to an array literal is fine:

case perry node
const a: any = [1] true true
let b: any = 0; b = [b] false true
let c: any = 0; for (let i=0;i<1;i++) c = [c] false true
let d: any = 0; for (let i=0;i<3;i++) d = [d] false true
let e: any = 0; for (...) { const w: any[] = [e]; e = w } false true
const N = Number("3"); let f: any = 0; for (let i=0;i<N;i++) f = [f] false true

So it is the reassignment of an any binding, not the loop, not a constant trip count, and routing through an explicitly any[]-typed temporary does not rescue it.

Why it matters

Array.isArray is the standard way to branch on "array vs not" — it is what user code, polyfills and vendored npm packages use precisely because it is supposed to be the reliable check (it works cross-realm where instanceof does not). A silent false sends such code down the non-array branch with an array in hand. This is a wrong-answer bug, not a crash, so it fails quietly.

The self-inconsistency is the sharp edge: b instanceof Array is true, b.length is 1, typeof b is "object", and JSON.stringify(b) is [0]. Only Array.isArray dissents, so nothing downstream has a reason to suspect the value.

Related

#267 (Array.isArray() returns false on the result of [...].map(fn)) is the same symptom in a different shape and is closed; this looks like a surviving member of that family rather than a regression of it.

Found by

Probing the recursive-consumer neighbours of #7792 on main @ b1edd2340 (perry 0.5.1464), macOS arm64, oracle Node 26.5.1. The depth probe I was writing reported structuredClone losing its structure — that turned out to be this bug in the probe's own Array.isArray walk, not a structuredClone defect.

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