Repository navigation
Description
Goal
Get Effect (effect@3.21.2 and later) to compile + link + run end-to-end via perry compile with perry.compilePackages: ["effect"]. This validates Perry on one of the largest pure-TypeScript libraries in the ecosystem (~150 modules, ~30k LOC for Effect.ts alone) and proves Perry can handle the heavy-generics / heavy-closures / fiber-runtime patterns that mid-to-large TS apps use.
Status snapshot (perry 0.5.405)
✅ Module collection — 362 modules collected (#310 closed: namespace re-exports traverse correctly)
✅ Codegen completes — no OOM, codegen runs to completion (#309 closed: was 34 GB RSS / 7m SIGKILL)
🔴 Codegen produces 26 module failures — see sub-issues
🔴 Link fails — 901 missing-symbol references resolving to ~5 distinct root causes
Sub-issues blocking end-to-end
| # | Issue | Modules affected | Severity |
|---|---|---|---|
| #318 | closure-collector dead arms (use of undefined value '@perry_closure_*') |
17 | largest — single fix flips many modules |
| #314 | closure-call arity ceiling at 5 args | 5 | high — easy to bump constant if runtime supports it |
| #320 | ReadableStream / Web Streams stdlib not implemented | needed by Stream module | medium — requires new stdlib work |
| #319 | string self-append: local N not in scope |
1 | low — likely subsumed by #318 |
| #315 | String.startsWith 2-arg form |
1 | low |
| #316 | String.slice() 0-arg form |
1 | low |
| #317 | String.matchAll codegen |
1 | low |
Reproducible test bed
mkdir effect-compat && cd effect-compat
cat > package.json <<'JSON'
{
"name": "effect-compat",
"version": "0.0.0",
"type": "module",
"perry": { "compilePackages": ["effect"] }
}
JSON
bun add effect// test_minimal.ts
import { Effect } from "effect";
const program = Effect.succeed(42);
const result = Effect.runSync(program);
console.log("result:", result);perry compile test_minimal.ts -o /tmp/out
# Expected eventually: "result: 42"
# Today: link fails (post-#309/#310)Recommended fix order (by leverage)
- perry-codegen: closure-collector misses HIR variants —
use of undefined value '@perry_closure_*'on Effect #318 — closure-collector walker fix is the biggest win and prevents this bug class from recurring on any future package. Recommend the v0.5.329 Tier 1.1 pattern (centralize viaperry_hir::walker::walk_expr_children). - perry-codegen Phase D.1: closure-call arity ceiling at 5 args blocks Effect #314 — single-line fix if the runtime already supports up to 16 args (it does).
- perry-codegen: String.startsWith 2-arg form (with position) unsupported #315 + perry-codegen: String.slice() 0-arg form unsupported #316 + perry-codegen Phase 2: String.matchAll codegen not implemented #317 — small focused fixes in
lower_string_method.rs, can land as one PR. - perry-codegen:
string self-append: local 441 not in scopeon Effect's internal/channel.ts #319 — recheck after perry-codegen: closure-collector misses HIR variants —use of undefined value '@perry_closure_*'on Effect #318; likely subsumed. - perry-stdlib: ReadableStream / Web Streams API not implemented #320 — biggest scope; can be deferred until everything else is done since Effect's Stream is one of many features users want, but other Effect parts (Effect.gen, Layer, Schedule, etc.) work without it.
Definition of done
The minimal repro above produces a binary that prints result: 42 and exits 0. Bonus: Effect.gen + Effect.runPromise of an async program also works.
Backlog beyond the link errors
Even after #314-#320 are fixed, two pre-existing categorical gaps will surface in user code that touches Effect deeply:
argumentsmagic object — Effect'sdual()helper uses it 148 times to implement data-first / data-last API duality. Already documented indocs/src/language/limitations.md. Workaround: keep using "unknown identifier 'arguments'" warnings for now; long-term, supportargumentsor land adual()shim.Intl.*— DateTime / Schedule / Duration formatting. Adjacent to existing perry-runtime i18n work.
Cross-references: #309 (OOM, fixed), #310 (silent-zero, fixed).
Activity
fix(codegen): closes #318 — delegate closure-collector descent to cen…
chore(stdlib): closes #320 — pin ReadableStream regression test (link…
fix(codegen): closes #319 — gate string self-append fast path on allo…
fix(codegen): closes #316 — accept String.slice/substring 0-arg form …
fix(codegen): closes #316 — accept String.slice/substring 0-arg form …
fix(codegen): closes #316 — accept String.slice/substring 0-arg form …
fix(codegen): closes #316 — accept String.slice/substring 0-arg form …
proggeramlug commentedon Apr 30, 2026
All seven sub-issues called out in the tracking table are now closed:
The remaining work to close this umbrella is the Definition of Done smoke test — the 4-line
Effect.runSync(Effect.succeed(42))repro from this issue's body should compile, link, and printresult: 42. Recommend running that on a fresh checkout ofmain(≥ v0.5.424) and closing once verified.Backlog notes in the issue body about
argumentsmagic object (Effect'sdual()helper, 148 sites) andIntl.*formatting still apply as separate follow-up work for users compiling Effect deeply.proggeramlug commentedon Apr 30, 2026
Status update — perry 0.5.424
All 7 original sub-issues (#314–#320) are closed. Re-running the minimal repro on v0.5.424:
Roughly 60% reduction in failure surface, but the binary still doesn't link. Three new failure modes have surfaced from the residual tail and are filed as new sub-issues:
@perry_class_keys_<module>__<ClassName>collisions on same-named classes in different scopes@__perry_wrap_perry_unknown_funcreferenced in IR but never defined@perry_closure_..._SchemaAST_ts__142Plus #320 (ReadableStream / Web Streams stdlib) is still the largest stdlib gap.
Updated checklist
export * as X from ...) #310 — silent-zero on namespace re-export — closed v0.5.405use of undefined value '@perry_closure_*'on Effect #318 — closure-collector dead arms (initial sweep) — closed (residual: perry-codegen: closure-collector still misses some HIR variants —use of undefined value '@perry_closure_..._SchemaAST_ts__142'after #318 #338)string self-append: local 441 not in scopeon Effect's internal/channel.ts #319 — string self-append local OOS — closed@perry_class_keys_<module>__<ClassName>symbol-name collisions when the same class name appears twice in one module #336 — class-keys symbol-name collisions — new@__perry_wrap_perry_unknown_funcreferenced in IR but never defined #337 —__perry_wrap_perry_unknown_funcundefined — newuse of undefined value '@perry_closure_..._SchemaAST_ts__142'after #318 #338 — residual closure-collector miss on SchemaAST.ts — newEnd-to-end definition of done (binary prints
result: 42and exits 0) requires #336 + #337 + #338 + #320.fix(hir,codegen): closes #336 — dedup same-named classes in one module
fix(codegen): closes #337 — emit fallback __perry_wrap_perry_unknown_…
proggeramlug commentedon May 1, 2026
Status update post-v0.5.464:
The smaller sub-issues called out in this umbrella are all closed (#314, #315, #316, #317, #318, #319, #320 — all per the changelog). However, the end-to-end
Effect.runSync(Effect.succeed(42))repro from this issue's "definition of done" still fails at link with 248 unresolved@perry_closure_*symbol references across multiple Effect modules.This is the same closure-collector walker bug class that #318 closed — but for closure shapes the v0.5.408 fix didn't yet enumerate. Each new shape needs another pass through
collectors.rs::collect_closures_in_expr's walker delegation or a newExprvariant added to the centralized walker.Leaving this open as the umbrella tracking issue. The hot-fix pattern is: rerun the Effect compile, find the closure ID symbol mentioned in the linker error, find its source HIR site, identify which
Exprvariant the codegen-emit path is descending through but the collector isn't, fix the walker.proggeramlug commentedon May 3, 2026
Re-tested perry 0.5.495 against Effect 3.21.2 (same minimal repro from this issue). Two new sub-issues blocking the link:
@perry_class_keys_<module>__<ClassName>symbol-name collisions when the same class name appears twice in one module #336, hits 8 Effect modules:Inspectable.ts,Streamable.ts,internal/queue.ts,internal/fiberId.ts,ParseResult.ts,Schema.ts,SchemaAST.ts, plus the secondary one namedClasscollisions). Has a 2-file minimal repro.@js_array_createreferenced withoutdeclarein fiberRuntime.ts IR (Effect) #432 — perry-codegen:@js_array_createreferenced withoutdeclareinfiberRuntime.tsIR. Independent of perry-codegen: cross-module class-name collision emits method bodies under the wrong module prefix #431.Status snapshot at v0.5.495:
@js_array_createreferenced withoutdeclarein fiberRuntime.ts IR (Effect) #432..os.argumentswarnings still fire ~60×; tracked separately as a known limitation.Once #431 and #432 land, expect the same minimal repro (
Effect.runSync(Effect.succeed("hi"))) to be the next thing to test end-to-end. If it links, the next likely runtime layer (generators /Effect.gen/yield*delegation) hasn't been exercised yet.fix(codegen): #431 — cross-module class-name collision emitted method…
79 remaining items
fix(runtime): validate raw string pointer via GcHeader in js_dyn_inde…
Improve Zod compatibility support (#5874)
fix(codegen): resolve class-field index/type via the authoritative in…
perf(runtime,codegen): stop unrelated descriptor installs disabling t…
fix(codegen): #5094/#26/#321 — derive GC raw-f64/pointer masks from t…
fix(runtime): dynamic index of a subnormal number is undefined on Lin…
fix(runtime): numeric-key computed calls must read the element, not d…
fix(runtime): resolve `[Symbol.iterator]` on a class declaration (#6454…
fix(runtime): static-member CALL walks the parent-closure chain (#6475)
fix: static/namespace resolution for effect Tag & Schema — web.ts ser…
fix(hir): fold an imported binding's array-named method only with arr…
fix(runtime): give iterator helpers their own class id (#7576)
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…