Repository navigation
perry-codegen: residual link errors on Effect — bare _pull + class-as-function references (Union/Emit/Order) #461
Description
Symptom
After #460 (contextual-keyword re-exports) lands, the link of the Effect repro from #321 still fails on ~7 missing symbols that don't fit the keyword pattern. Filing as a single investigation bucket since they likely share root causes (mangling / class-vs-function emission) and are easier to triage together than as 7 micro-issues.
Missing symbols (post-#460)
_pull # bare, unmangled — single occurrence
_perry_fn_node_modules_effect_src_Order_ts__Order # namespace-pattern Order.Order
_perry_fn_node_modules_effect_src_SchemaAST_ts__Union # `export class Union`
_perry_fn_node_modules_effect_src_internal_channel_subexecutor_ts__Emit # `export class Emit`
Bug 1 — bare _pull symbol (no module prefix)
"_pull", referenced from:
_perry_closure_node_modules_effect_src_internal_stream_ts__1115
_perry_closure_node_modules_effect_src_internal_stream_ts__1121
_perry_closure_node_modules_effect_src_internal_stream_ts__1125
... (~7 closures, all in internal/stream.ts)
Source: effect/src/internal/stream.ts defines const pull = (...) => { ... } as a local arrow function inside three different exported functions (lines 7839, 8114, 8516). Closures inside those functions capture pull — and the captured reference is being emitted as the bare LLVM symbol _pull without the _perry_closure_<module>_<n> or _perry_local_<scope>_pull mangling.
Two follow-on questions:
- Why no module prefix? (probable bug: closure-capture path for nested-arrow
const-bound functions falls back to the raw identifier.) - The same name
pullis bound three times in disjoint scopes in the same file — even after mangling is fixed, codegen needs to keep them distinct. Likely already handled by closure-id, but worth confirming once the prefix is restored.
Bug 2 — class-as-function references (Union, Emit, Order)
SchemaAST.ts:1709 declares export class Union<M>. internal/channel/subexecutor.ts:215 declares export class Emit<R>. The link errors reference these as _perry_fn_<module>__Union / __Emit — i.e., codegen emits a function reference at the call site for what is in source a class.
Likely scenarios:
- The reference site sees
Union(...)(called withoutnew) — TS allows this forclassif a constructor signature is callable, but Perry's mangling needs to look up the class registry rather than emit_perry_fn_. - Or: the reference is on the type side (
Order.Order) and the type-only import path is leaking into value-position symbol emission.
Order.ts has no export const Order — only export interface OrderTypeLambda and a bunch of named arrow functions. The reference _perry_fn_..._Order_ts__Order originating from DateTime_ts__init, Fiber_ts__init, LogLevel_ts__init smells like the namespace-pattern import type * as order from \"./Order.js\" — Effect uses Order.Order in TS-type position throughout, and one of those references is probably being lowered to a value emit by mistake.
Reproduction
cd /tmp/effect-compat && perry compile test_minimal.ts -o /tmp/effect_out
# After #460 lands, only the symbols above will remain undefined.Suggested investigation order
_pullfirst — most likely a single-line fix in the closure-capture mangling path; small blast radius.Union/Emitnext — single test case (any class referenced withoutnew) should reproduce in isolation.Orderlast — probably depends on type-only-import leak fixes already in flight.
Cross-references
- Parent: Tracking: Effect framework end-to-end compat (post-#309 / #310) #321 (Effect end-to-end compat tracking)
- Sibling: perry-codegen:
export { local as keyword }re-exports don't emit definitions for contextual-keyword names (void/async/await/try/delete/continue) #460 (contextual-keyword re-exports — fix this before this issue, since the keyword bugs dominate the link error count and may be masking smaller issues) - Adjacent precedent: perry-codegen:
@perry_class_keys_<module>__<ClassName>symbol-name collisions when the same class name appears twice in one module #336 (@perry_class_keys_<module>__<ClassName>collision) and state.rs format_value doesn't handle NaN-boxed strings on Windows / Android / GTK4 — string-typed states render as 'NaN' through stateBindTextNumeric / TEXT_BINDINGS #443 (NaN-boxed-string mis-detection in formatting) — both class-mangling-adjacent
Activity
fix(codegen): refs #461 — emit fallback `perry_fn_<mod>__<exported>` …
fix(hir): closes #461 — pre-register local for `const f = (arrow)` de…
fix(hir+codegen): closes #593 — self-referential const closure inside…
fix(codegen): #5431 — cross-module call to a `$`-named exported funct…
fix(codegen): follow re-export hops when resolving an export's origin (…
fix(codegen): follow re-export hops when resolving an export's origin (…