Summary
The current state of hono compile end-to-end (post v0.5.594):
- Construction works.
- Route registration works (`app.routes.length === 2`).
- `app.fetch(req)` triggers SmartRouter.match → RegExpRouter.match → `this.buildAllMatchers()` — but inside `match`, `this` is a number, so `this.buildAllMatchers` is undefined and the call throws.
Repro
import { Hono } from 'hono';
const app = new Hono();
app.get('/', (c: any) => 'hi');
console.log('typeof router.match:', typeof app.router.match); // undefined ← bug A
const m = app.router.match('GET', '/'); // ↓
// TypeError: (number).buildAllMatchers is not a function ← bug B
Why
Two related issues:
Bug A — app.router.match reads as undefined. perry's PropertyGet on a class instance only sees own-fields, not prototype methods. SmartRouter has `match()` as a regular class method (lives in the vtable, not on the instance), so `app.router.match` reads undefined. The CALL form `app.router.match('GET', '/')` should still dispatch via the vtable correctly — and it does — but the read form returns undefined.
Same shape as #446, where calling worked but `typeof obj.method` read undefined. That issue's fix landed v0.5.503 but only for direct-instance method access; the cross-module field-stored-instance case (`this.router` set in a constructor body, where the field's static type is unknown) doesn't take the same path.
Bug B — this.buildAllMatchers invokes on a number. When the runtime tower's vtable dispatch fires for SmartRouter.match, it calls `router.match(method, path)` inside the method body — but `this` in that nested call resolves to a number value (probably the integer 0 or 1, the class id of one of the routers in the array). Inside RegExpRouter.match, `this.buildAllMatchers()` is called on `this` which is now a number — TypeError.
The runtime's vtable dispatch (`call_vtable_method`) passes the receiver as `this_i64` (the instance pointer). For nested method calls inside the body, `this` is read from the function's `this_arg` — perry's codegen for cross-module methods may be passing the wrong value.
Likely fix
Both bugs share a root cause: cross-module class-method dispatch when the receiver is dynamically-typed (`Record<string, any>` field, `this.x = new Foo()` in another module's constructor).
Bug A's fix: have PropertyGet on a class-typed instance auto-bind a closure for prototype methods (similar to v0.5.503's `js_class_method_bind`, but for cross-module receivers stored in unknown-typed fields).
Bug B's fix: ensure cross-module method dispatch passes the correct `this` value at every nested call site. The vtable lookup gets the right func_ptr, but the call must have the receiver's pointer (not the array's first element interpreted as a number) as `this_arg`.
Why it matters
Last blocker between v0.5.594 and a 30-line hono acceptance test (#486). Once `app.fetch()` runs to completion, the rest of the dispatch chain (`compose()` / `Context.text()` / `new Response`) likely has its own gaps but each is incrementally tractable.
Summary
The current state of hono compile end-to-end (post v0.5.594):
Repro
Why
Two related issues:
Bug A —
app.router.matchreads as undefined. perry'sPropertyGeton a class instance only sees own-fields, not prototype methods. SmartRouter has `match()` as a regular class method (lives in the vtable, not on the instance), so `app.router.match` reads undefined. The CALL form `app.router.match('GET', '/')` should still dispatch via the vtable correctly — and it does — but the read form returns undefined.Same shape as #446, where calling worked but `typeof obj.method` read undefined. That issue's fix landed v0.5.503 but only for direct-instance method access; the cross-module field-stored-instance case (`this.router` set in a constructor body, where the field's static type is unknown) doesn't take the same path.
Bug B —
this.buildAllMatchersinvokes on a number. When the runtime tower's vtable dispatch fires for SmartRouter.match, it calls `router.match(method, path)` inside the method body — but `this` in that nested call resolves to a number value (probably the integer 0 or 1, the class id of one of the routers in the array). Inside RegExpRouter.match, `this.buildAllMatchers()` is called on `this` which is now a number — TypeError.The runtime's vtable dispatch (`call_vtable_method`) passes the receiver as `this_i64` (the instance pointer). For nested method calls inside the body, `this` is read from the function's `this_arg` — perry's codegen for cross-module methods may be passing the wrong value.
Likely fix
Both bugs share a root cause: cross-module class-method dispatch when the receiver is dynamically-typed (`Record<string, any>` field, `this.x = new Foo()` in another module's constructor).
Bug A's fix: have PropertyGet on a class-typed instance auto-bind a closure for prototype methods (similar to v0.5.503's `js_class_method_bind`, but for cross-module receivers stored in unknown-typed fields).
Bug B's fix: ensure cross-module method dispatch passes the correct `this` value at every nested call site. The vtable lookup gets the right func_ptr, but the call must have the receiver's pointer (not the array's first element interpreted as a number) as `this_arg`.
Why it matters
Last blocker between v0.5.594 and a 30-line hono acceptance test (#486). Once `app.fetch()` runs to completion, the rest of the dispatch chain (`compose()` / `Context.text()` / `new Response`) likely has its own gaps but each is incrementally tractable.