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.
Summary
Array.isArrayreturnsfalsefor a genuine array held in anany-typed local that was reassigned to it. Every other test of the same value says it is an array, including Perry's owninstanceof Array, so the runtime contradicts itself.Three lines, no loop, no library:
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
anylocal initialized to an array literal is fine:const a: any = [1]let b: any = 0; b = [b]let c: any = 0; for (let i=0;i<1;i++) c = [c]let d: any = 0; for (let i=0;i<3;i++) d = [d]let e: any = 0; for (...) { const w: any[] = [e]; e = w }const N = Number("3"); let f: any = 0; for (let i=0;i<N;i++) f = [f]So it is the reassignment of an
anybinding, not the loop, not a constant trip count, and routing through an explicitlyany[]-typed temporary does not rescue it.Why it matters
Array.isArrayis 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 whereinstanceofdoes not). A silentfalsesends 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 Arrayistrue,b.lengthis1,typeof bis"object", andJSON.stringify(b)is[0]. OnlyArray.isArraydissents, 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 reportedstructuredClonelosing its structure — that turned out to be this bug in the probe's ownArray.isArraywalk, not astructuredClonedefect.