Out-of-bounds array access throws RangeError where Node evaluates to undefined. The build is clean; the divergence only shows up at runtime, and it hits the most ordinary JS idioms there are — destructuring a short array, probing arr[i] before checking length, growing an array by index.
This one cost me the most time while porting a real CLI: six separate sites, each compiling without a diagnostic and then aborting on the first run.
Repro
1. Destructuring past the end
export {}
function main() {
const a = ['x']
const [first, second] = a
console.log(`first=${first} second=${second}`)
}
main()
$ node --experimental-strip-types oob.ts
first=x second=undefined
$ scriptc build oob.ts -o oob && ./oob
scriptc: RangeError: array index 1 out of bounds (length 1)
# exit 134
2. Indexed read past the end
export {}
function main() {
const a: string[] = ['x']
console.log(`read=${a[1]}`)
}
main()
Node: read=undefined — scriptc: RangeError: array index 1 out of bounds (length 1).
3. Write past the end (array growth)
export {}
function main() {
const a: string[] = []
a[2] = 'z'
console.log(`len=${a.length} v=${a[2]}`)
}
main()
Node: len=3 v=z — scriptc: RangeError: array index 2 out of bounds (length 0).
Expected
Per the README's "What compiles behaves byte-for-byte like Node", an out-of-range read should evaluate to undefined and an out-of-range write should extend the array.
Why it is easy to hit
These are the shapes real code uses, and none of them look risky:
const [src, selector] = positionals // optional second CLI argument
const peek = () => toks[pos] // recursive-descent parser at EOF
if (bytes[0] === 0xef && bytes[1] === 0xbb) // BOM sniff on an empty body
while (grid[r][c] !== undefined) c++ // HTML table grid fill
argv[index - 1] === flag // look-behind at index 0
Under noUncheckedIndexedAccess the checker already types these as T | undefined, so the source is honest about the undefined case — the runtime just never produces it.
Suggestions
Ideally the read lowers to a bounds test yielding undefined, and the write extends. If matching Node here is genuinely out of scope for the value representation, a compile-time diagnostic would still be a large improvement over a runtime abort — the alternative today is discovering each site by running the binary.
Environment
- scriptc 0.0.17,
@scriptc/compiler 0.0.17
- macOS 26.5.2, arm64, Node v24.18.0
- Static build (no
--dynamic), default backend
Out-of-bounds array access throws
RangeErrorwhere Node evaluates toundefined. The build is clean; the divergence only shows up at runtime, and it hits the most ordinary JS idioms there are — destructuring a short array, probingarr[i]before checking length, growing an array by index.This one cost me the most time while porting a real CLI: six separate sites, each compiling without a diagnostic and then aborting on the first run.
Repro
1. Destructuring past the end
2. Indexed read past the end
Node:
read=undefined— scriptc:RangeError: array index 1 out of bounds (length 1).3. Write past the end (array growth)
Node:
len=3 v=z— scriptc:RangeError: array index 2 out of bounds (length 0).Expected
Per the README's "What compiles behaves byte-for-byte like Node", an out-of-range read should evaluate to
undefinedand an out-of-range write should extend the array.Why it is easy to hit
These are the shapes real code uses, and none of them look risky:
Under
noUncheckedIndexedAccessthe checker already types these asT | undefined, so the source is honest about theundefinedcase — the runtime just never produces it.Suggestions
Ideally the read lowers to a bounds test yielding
undefined, and the write extends. If matching Node here is genuinely out of scope for the value representation, a compile-time diagnostic would still be a large improvement over a runtime abort — the alternative today is discovering each site by running the binary.Environment
@scriptc/compiler0.0.17--dynamic), default backend