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
fix(codegen): #138 — link imported enum constructors into codegen (#602)
Closes#138
> Note: #138 itself was joint-closed (2026-05-18) when the front-end
> seeded-builtins band-aid was removed. That removal correctly routed
> `Some/None/Ok/Err` through the module path so `check` passes — but it
> exposed a **codegen** regression that this PR fixes. Verified at HEAD
> `58dc2a0`.
## Symptom
A module that imports prelude's `Option`/`Result` and applies their
constructors type-checks but fails to compile — the WASM backend raises
`UnboundVariable "...Some"`. This blocked the stdlib `option`/`result`
layer (and downstream consumers) from producing a runnable artifact.
## Root cause
Codegen learns constructor tags **only** from `TopType` decls. Two
import
paths dropped imported types before they reached that registration:
* **Core-Wasm `Codegen.gen_imports`** — the path the default `compile`
uses.
It feeds the *original* (un-flattened) `prog` to `generate_module` and
resolved imports natively, but wired up only `TopFn` (→ wasm import) and
`TopConst` (→ global); imported **types** were silently dropped, so
`variant_tags` had no `Some` → `UnboundVariable`. **This is the path the
reported failure hits.**
* **`Module_loader.flatten_imports`** — used by the
`prog_decls`-iterating
backends (Deno / JS / Julia / C / Rust / …). It carried only `TopFn`/
`TopConst`.
(The task brief pointed at `flatten_imports`; in practice the reported
`compile` failure flows through `gen_imports` — empirically, before this
PR
the Deno/JS/Julia/C/Rust backends already compiled the consumer; only
the
WASM backend was broken. Both sites are fixed for completeness.)
## Fix
1. **`Codegen.gen_imports`** now registers the constructor tags / struct
layouts of imported **public** types, reusing `gen_decl`'s local-type
registration so imported and local types share exactly one code path.
2. **`Module_loader.flatten_imports`** now inlines imported public
`TopType`
decls — a **separate namespace** from fn/const (own dedup table, so a
local `fn Foo` can't suppress an imported `type Foo`), **local-wins**,
**deduped** across paths, and **ordered before** consumers so the
single-pass codegen sees a type before any function that uses it.
Selection: `use M::{..}` carries a type when the list names the type
**or
any of its constructors** (so `use prelude::{Some}` works without naming
`Option`); `use M` / `use M::*` carry all public types.
**Scope:** directly-imported constructors lower on **every** backend.
**Transitive re-export** (a module re-exposing names it itself imported)
stays unimplemented — noted in `docs/history/MODULE-SYSTEM-PROGRESS.md`.
## Verify — before / after
Minimal `consumer.affine` (`use prelude::{Option, Some, None}` +
`Some(x)`/`None`):
```
# BEFORE (HEAD 58dc2a0)
$ affinescript check consumer.affine # Type checking passed
$ affinescript compile consumer.affine # Code generation error:
# (Codegen.UnboundVariable
# "Function or variable not found: Some")
# AFTER
$ affinescript check consumer.affine # Type checking passed
$ affinescript compile -o consumer.wasm c.affine # Compiled -> consumer.wasm (WASM)
```
Emitted WASM is valid and **runtime-correct** (via node `WebAssembly`):
`wrap(5)` → boxed `Some` (tag 0, payload 5); `empty()` → `None` (tag 1);
`Ok`/`Err` tags 0/1. Module imports only
`wasi_snapshot_preview1.fd_write`
— constructors are compiled inline, no spurious imports.
All-backend matrix on the consumer (before → after):
| backend | before | after |
|---|---|---|
| wasm (default) | **FAIL** `UnboundVariable "Some"` | **OK** |
| deno.js / jl / c / rs / js / cjs | OK | OK |
## Tests
* New Wasm-path regression test `cross-module constructor linking, Wasm
(#138)`
in `test/test_stdlib_aot.ml` — the counterpart to the #137 Deno-path
integration. Feeds the imported-constructor decl shape to all three
`variant_tags` consumers (with-arg construction, zero-arg, constructor
patterns).
* `dune test`: **458 tests, all green** (incl. #136 AOT smoke over 34
stdlib
files and #137 integration — no regression).
* `dune runtest conformance`: green.
## Out of scope (do not conflate with #138)
Pre-existing core-Wasm gaps that reproduce with **purely local** enums
and
are independent of cross-module linking:
* `stdlib/option.affine` / `result.affine` → `UnsupportedFeature "Only
variable and wildcard patterns supported in tuple patterns"`. With this
PR they get *past* the #138 `UnboundVariable` and reach this separate
tuple-pattern codegen gap.
* Mixed-representation `match` of a zero-arg variant (`None`) against a
constructor-with-args arm returns the wrong value at runtime —
reproduces
with a local `Opt = Som(Int) | Non`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Lz7pRcec2Z3tVtaAhvB3M8
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Lz7pRcec2Z3tVtaAhvB3M8)_
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments