Repository navigation
Cross-module imports: TopConst not threaded by gen_imports / flatten_imports #107
Description
Summary
A const declared pub in module M cannot be imported by another module. use M::{c} silently drops c from the importer's environment, and any reference to c then fails at codegen with Codegen.UnboundVariable. The intra-module case was closed by #73; the cross-module case is the still-open half of the same feature.
SPEC.adoc §8.4 (after #106) now records this as a known restriction; this issue tracks closing it.
Reproducer
a.affine:
pub const inputSuffix: String = ":in";
b.affine:
use a::{inputSuffix};
pub fn withInput(port: String) -> String {
port ++ inputSuffix
}
Typecheck passes; affinescript compile b.affine fails with:
Code generation error: (Codegen.UnboundVariable "inputSuffix")
Root cause
Codegen.gen_imports (lib/codegen.ml) walks prog.prog_imports and, for each entry, looks for a matching TopFn in the loaded module's prog_decls:
let fn_decl_opt = List.find_map (function
| TopFn fd when fd.fd_name.name = orig_name -> Some fd
| _ -> None
) loaded.mod_program.prog_decls inTopConst is not in the pattern, so imported constants are silently filtered out — func_indices never gains an entry for inputSuffix, and the later ExprVar lookup in withInput's body fails.
Module_loader.flatten_imports (used by the non-WASM back-ends) has the same restriction: only public TopFns flow.
Spec status
SPEC.adoc §8.4 records:
Currently:
fnandextern fnitems flow across module boundaries.constitems do not yet flow across module boundaries; aconst
declared in moduleMand named byuse M::{c}is a known
restriction, not a language-level prohibition.
docs/specs/codegen-environment.adoc §5 states the same restriction at the implementation level:
TopConstitems are not currently threaded across module boundaries bygen_imports. ... addressing it requires emitting the constant as a global in the importing module (or via WASM module-linking) and remains future work.
The spec does not require this gap; closing this issue closes a documented restriction, not a language design question.
Likely implementation shape
For the WASM back-end (lib/codegen.ml):
- Extend
gen_imports'sList.find_mapto also matchTopConst. - For a matched imported const, compile the initialiser against the importer's context (the same lowering
gen_decl TopConstperforms), append toctx.globals, and register(local_name, -(global_idx + 1))infunc_indices. - Alternatively (cleaner long-term), emit a WASM
(import "<modpath>" "<name>" (global …))entry and register the negative-sentinel index against the import position. This requires plumbing through public const globals as exports on the producing module's WASM emission.
Option (2) inlines the value; option (3) makes the consumer share the producer's global. (2) is simpler, (3) preserves identity across modules and matches how the language treats cross-module fn references.
For the non-WASM back-ends, Module_loader.flatten_imports needs to additionally inline imported TopConsts into the importer's prog_decls (deduplicating on name as it currently does for TopFn).
Cross-references
SPEC.adoc§8.4 — known-restriction wordingdocs/specs/codegen-environment.adoc§5 — implementation-level statement- Codegen.UnboundVariable for top-level
constbindings (typecheck OK, compile fails) #73 — intra-module case (closed) - Documentation: Clarify codegen environment rules for top-level const and function bindings #89 — original env-rules doc ticket (closed)
- PR docs: renew SPEC §8 as real spec; expand codegen-environment to full realisation #106 — renewed §8 spec that surfaces this as the still-open gap
Acceptance criteria
- The reproducer above compiles and runs end-to-end on WASM and at least one non-WASM back-end (e.g.
js_codegen.ml). SPEC.adoc§8.4 is amended to drop the "do not yet flow" bullet.docs/specs/codegen-environment.adoc§5 is amended accordingly.- A regression test under the existing cross-module test harness covers the reproducer.
Activity
feat(codegen): thread imported TopConst across module boundaries (closes
docs: bindings + stdlib + alib roadmaps (3 satellites) (#410)
fix(loader): discover stdlib via walk-up + user-share fallbacks (closes
feat(diagnostics): prelude-hint for UndefinedVariable / Unbound (stdlib