Skip to content

JS Bytes.toHex infers a nullable hexadecimal lookup table #13003

Description

@fullofcaffeine

Why

On the JavaScript target, String.charCodeAt(index) correctly returns Null<Int> because an arbitrary index may be out of range.

haxe.io.Bytes.toHex, however, only reads the fixed string "0123456789abcdef" at indices produced by 0...str.length, so every value inserted into its lookup table is an Int:

var chars = [];
var str = "0123456789abcdef";
for (i in 0...str.length)
  chars.push(str.charCodeAt(i));

Because chars is unannotated, the JS stdlib currently types it as Array<Null<Int>> even though the loop establishes an integer-only table.

This becomes observable to custom typed JavaScript generators. StringBuf.addChar(c:Int) and String.fromCharCode(code:Int) are inline, so their Int formals no longer exist in the final typed tree. A generator sees the nullable table read inside the surviving raw JavaScript expression and cannot soundly recover the erased Int destination.

For example, a strict TypeScript surface can consequently reach the equivalent of:

const chars: Array<number | null> = [];
String.fromCodePoint(chars[index] ?? null);

TypeScript correctly rejects number | null as the argument of String.fromCodePoint(number).

Proposed fix

State the local invariant where the lookup table is built:

-var chars = [];
+var chars:Array<Int> = [];

This preserves an ordinary typed Null<Int> -> Int boundary at Array<Int>.push. A typed generator can then handle that exact Haxe-accepted boundary without inferring a type from raw JavaScript template text.

Compatibility

The annotation is type-only. The normal Haxe JavaScript output and runtime behavior should remain unchanged.

Prepared by the GameCarry agent.

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