You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perry-codegen: s[0] on any-typed param holding a string returns length instead of first char #514
s[0] (and s?.[0]) on a parameter typed any that holds a string at runtime returns the string's length as a subnormal float instead of the first character. With an explicit string type annotation the same code works correctly.
This is a hono blocker (#421): hono's mergePath = (base, sub, ...rest) => … has untyped params, so every base?.[0] === "/" ? "" : "/" check inside its template literal misfires. Result: mergePath('/', '/api') returns ////api instead of /api, every registered route's path field is a mangled string.
Repro
// perry compile test.ts -o test && ./testfunctionfn(s: any){console.log('any s[0]:',s[0]);// expected: '/' actual: 2e-323 (= 4 as subnormal, the length of '/foo')}fn('/foo');constf2=(s: string)=>{console.log('typed s[0]:',s[0]);// expected: '/' actual: '/' ✓ works};f2('/foo');
Output:
any s[0]: 2e-323
typed s[0]: /
2e-323 ≈ 1.97626e-323 = 4 as a subnormal double bit pattern. For a 1-char string '/', the same path returns 5e-324 ≈ 1.
Why
The HIR lowers s[0] to Expr::IndexGet { object: LocalGet(s), index: 0 }. Codegen's IndexGet dispatch in crates/perry-codegen/src/expr.rs walks the receiver type:
string → js_string_char_at(s, 0) ✓
any → falls through to the array fast-path which reads length (or some array slot 0) on the NaN-boxed string pointer interpreted as an ArrayHeader.
The receiver-class-resolution is_string_expr doesn't fire on a generic any parameter. Without a static guarantee, codegen emits the inline array-IndexGet instructions, which read the length field of what it thinks is an ArrayHeader — but the pointer is actually a StringHeader, and the offsets happen to land on the byte-length field.
Likely fix
crates/perry-codegen/src/expr.rs::IndexGet — when the receiver is any-typed (or static type can't be proven array vs string), emit the runtime dispatch path that NaN-tag-checks the value:
STRING_TAG/SHORT_STRING_TAG → js_string_char_at
POINTER_TAG + GC type ARRAY → array fast-path
pointer to non-array → treat as object with numeric key (existing fallback)
A simpler intermediate fix: in IndexGet's any-typed arm, route through js_index_get_dynamic(value, idx_value) runtime helper that does the tag-check at runtime. There's already similar dispatch in js_native_call_method for .map/.filter/.forEach on any-typed receivers — same shape.
Why it matters
Blocks #421 (hono compile end-to-end) past route registration. Probably also affects #420 (drizzle) which uses similar untyped helper functions. Any TS code without strict types + string indexing trips this path.
Activity
chore: closes #515 — record version + changelog for the with-method d…
fix(runtime+stdlib): closes #421 — hono compiles end-to-end (v0.5.613)
fix(hir): #1069 — synthetic-arguments callees: don't pad missing args…