Repository navigation
Class declarations in .js files inside compilePackages drop entirely from HIR #485
Description
Background
While shaking out #421 (compile hono end-to-end), we hit a blocker: `new Hono()` constructs without crashing as of v0.5.585 (post #482 + #484 fixes), but the resulting instance has only `router` as an own property. All of HonoBase's arrow-class-field methods (`request`, `fetch`, `get`, `post`, `put`, `delete`, `options`, `patch`, `all`, `on`, `use`, `notFound`) are missing → `app.fetch is not a function`.
Repro
```bash
mkdir test && cd test
echo '{ "type": "module", "perry": { "compilePackages": ["hono"] } }' > package.json
bun add hono
cat > app.ts <<EOJ
import { Hono } from 'hono';
const app = new Hono();
console.log('keys:', Object.keys(app).join(',')); // 'router' only
console.log('typeof fetch:', typeof app.fetch); // undefined
console.log('typeof request:', typeof app.request); // undefined
EOJ
perry app.ts -o app && ./app
```
Why
Hono's bundle has `class Hono extends HonoBase`. `HonoBase` lives in another file in node_modules/hono/. Its class fields are arrow initializers:
```js
var HonoBase = class {
router;
request = (input, ...) => { /* ... / };
fetch = (request, ...) => { / ... */ };
// ... etc
};
```
Perry's `apply_field_initializers_recursive` (perry-codegen/src/lower_call.rs) walks the class chain and applies parent's field initializers in the subclass's constructor. But for imported classes, the stub created in `imported_class_stubs` (perry-codegen/src/codegen.rs:407+) carries `field_names` without `init` expressions — so when Hono's constructor walks the chain into HonoBase (the stub), HonoBase.fields all have `init: None` and nothing gets applied to `this`.
Same-file inheritance works (verified locally: `class Base { request = (p) => ... } class Sub extends Base {}` produces a Sub instance with a working `.request`).
Likely fix
Imported class metadata (the `ImportedClass` struct in `perry-codegen/src/codegen.rs`) needs to carry field initializers, or perry-codegen needs to look up the parent class via the cross-module import resolver and walk the parent's HIR. Today `ImportedClass` only has `field_names: Vec` and `field_types: Vec` — no init expressions. Adding a third parallel `field_inits: Vec<Option>` field is the mechanical move; serializing the init expressions across module boundaries needs a stable encoding (HIR Expr is not stable; either snapshot the Function pointer for arrow inits or emit a synthetic per-(class, field) constructor-fragment helper).
Why it matters
Blocks #421 (hono compile). Probably also blocks #420 (drizzle) and any other npm package that uses class inheritance with arrow class fields across module boundaries.
Activity
proggeramlug commentedon May 6, 2026
Update after deeper investigation:
The bug is broader than I initially described. Class declarations in
.jsfiles insidecompilePackagespackages aren't being lowered into the HIR'sclasseslist at all. Inheritance is incidental — even the simplest case fails:Same exact code as a
.tsfile (index.ts) works perfectly:function,5,Foo. The HIR--print-hirconfirms:classeslist (HIR --print-hir doesn't show it but codegen finds it).Imports: 0, Exports: 1, Functions: 0, Init statements: 0— class is DROPPED.Same shape, different extension. perry-parser uses
Syntax::Typescriptregardless of extension, so SWC parses both correctly. The drop happens in HIR lowering or somewhere between parser and codegen.Likely root cause is in how perry distinguishes JS from TS during lowering — possibly the cjs_wrap path (used for CommonJS) is hitting a heuristic and re-shaping the file's AST in a way that strips top-level class declarations, OR the
is_in_compile_package + is_js_fileresolution path skips class lowering. Needs perry-parser/perry-hir investigation.The hono failure mode is one consequence: HonoBase is in a
.jsbundle, so its class declaration gets dropped, so all its arrow class fields and methods are missing. Same applies to anything else in compilePackages whose files are.js.This bug is likely the load-bearing blocker for most
compilePackagesuse cases — anything published as bundled JS (which is the npm norm) hits it. Renaming the issue to reflect the broader scope is appropriate.[-]Hono compile blocker: class-field arrow methods on inherited classes lost across compilePackages[/-][+]Class declarations in .js files inside compilePackages drop entirely from HIR[/+]proggeramlug commentedon May 6, 2026
v0.5.586 actually delivers the fix. The fix landed three coordinated changes: 1)
Expr::SuperCallfalls through toimported_class_ctorswhen parent is imported (so super() actually invokes parent ctor body); 2)exported_classesindexesexport { Local as Exported }renames; 3) packed-keys / total_field_count walks include parent stubs across module boundaries, so subclass instances reserve enough inline slots for parent + child fields. End-to-end: a 4-file repro (handcl/base.js + handcl/index.js subclass + main.tsnew Sub()) now correctly observes all parent fields + arrow methods. With hono itself,new Hono()instances now exposeget/post/fetch/use/on/routes/_basePathcorrectly. Parity: 204 pass / 8 known fails — no regressions. Routes registration (the next blocker for hono —app.get('/', cb)still keepsapp.routes.lengthat 0) tracked separately.fix(runtime+stdlib): closes #421 — hono compiles end-to-end (v0.5.613)
fix(hir): lower inline `export default class` bodies — methods/fields…
fix(compile): guard the transitive class closure against global names…
fix(compile): guard the transitive class closure against global names…
fix(compile): guard the transitive class closure against global names…