Skip to content

Implement [use ...] module imports on the EIR backend#77

Merged
ecto merged 2 commits into
mainfrom
claude/lucid-vaughan-70d841
Jul 7, 2026
Merged

Implement [use ...] module imports on the EIR backend#77
ecto merged 2 commits into
mainfrom
claude/lucid-vaughan-70d841

Conversation

@ecto

@ecto ecto commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

Makes multi-file programs work on the default loon run (EIR VM) backend. Previously [use lib] was effectively a stub: lib.oo with [fn triple [x] [* x 3]] + [use lib] [println [lib.triple 5]] printed 15 under --legacy but died with "value is not callable" on the EIR VM.

Why

Unblocks the package manager (pkg.oo/lock.oo, grants, audit — implemented but moot without working imports on the default backend) and the loon-os multi-module work.

Changes

The lowering pass already inlined imported modules (collect_imports in eir/lower.rs); three gaps broke it in practice:

  1. Export rule — only pub fns got qualified alias.name entries. The legacy interpreter (module.rs) exports all top-level fns when a module has no pub; the EIR path now matches.
  2. Alias — the code matched a symbol as, but the syntax is the keyword :as ([use math :as m]), so samples/modules/main-alias.oo failed. Default qualification now uses the full dotted module path, exactly like the interpreter.
  3. Silent diagnostics — the checker already emits E0500 (unresolved module) / E0502 (circular dependency), but all EIR entry points discarded checker errors. Added VmErrorKind::ModuleError; eval_eir{,_with_base_dir,_recorded,_replayed,_verified} now fail fast on E05xx with the diagnostic message and span.

Verification

  • samples/modules/main.oo and main-alias.oo produce identical output on EIR and --legacy
  • Missing module and circular import now report proper E05xx-derived errors with exit 1 on both backends
  • New tests in eir/vm.rs: non-pub use, :as alias, nested module-calling-module, missing module error, circular import error
  • cargo test --workspace passes; cargo fmt clean

Known limitation (pre-existing)

Only functions get qualified names — a top-level let in a module imports bare but not as lib.name, since qualification goes through the func map. Worth a follow-up if the package manager needs module constants.

🤖 Generated with Claude Code

The EIR lowering inlined imported modules but three gaps broke real
multi-file programs on the default backend:

- Non-pub fns were never qualified: mirror the interpreter's export
  rule (all top-level fns when a module declares no pub).
- Alias syntax checked for a symbol `as`; the real syntax is the
  keyword `:as`. Default prefix now uses the full dotted module path.
- Checker E05xx module diagnostics (unresolved module, circular
  dependency) were discarded; all four EIR entry points now fail fast
  with a new VmErrorKind::ModuleError instead of a confusing
  NotCallable at the first qualified call site.

Adds integration tests: non-pub use, :as alias, nested modules,
missing module error, circular import error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
loon Ready Ready Preview, Comment Jul 7, 2026 5:32am

Request Review

@chojiai

chojiai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Choji live review — Looks good — 1 major · 1 minor

Choji review — Looks good

This PR fixes three real gaps in the EIR backend's [use ...] implementation: the export rule (all-fns when no pub is present), the :as keyword alias parsing, and silent swallowing of E05xx checker diagnostics. The logic in lower.rs is correct — the two-pass collect (pub_fns / all_fns) faithfully mirrors the interpreter's export rule, the keyword/symbol dual-match for :as is a reasonable defensive choice, and the module_error helper correctly surfaces only the first E05xx diagnostic. The five new tests cover the main scenarios. No correctness defects found.

Correctness

  • Major · Correctness — A pub fn is counted in both pub_fns and all_fns, so it gets exported twice when there are no non-pub fns crates/loon-lang/src/eir/lower.rs:406
    In the pub arm, all_fns.push(name.clone()) is called alongside pub_fns.push(name.clone()). When pub_fns is non-empty the exported selection is pub_fns, so the double-push into all_fns is harmless. But if a module has only pub fn declarations, pub_fns is non-empty and all_fns is also populated — exported becomes pub_fns (correct), so no duplicate qualified entries are emitted. The logic is actually safe as written, but the intent is obscured: all_fns is supposed to be the fallback set of all fns, yet it includes pub fns too. This means the fallback path (no pub fns) would also include pub fns, which is correct by the interpreter's rule, but a future maintainer adding a non-fn pub form could accidentally break the invariant. Consider renaming all_fns to every_fn and adding a brief comment that pub fns are intentionally included in it.
1 minor finding
  • Minor · Structural quality — Dual Symbol/Keyword match for :as silently accepts the wrong syntax crates/loon-lang/src/eir/lower.rs:388
    The match arm (ExprKind::Symbol(kw), Some(ExprKind::Symbol(a))) if kw == "as" is kept as a fallback alongside the correct ExprKind::Keyword arm. This means [use math as m] (bare symbol, not keyword) is silently accepted as an alias. If the language spec only allows :as, the Symbol arm should be removed to surface the malformed syntax rather than silently treating it as an alias. If it's intentional for compatibility, a comment explaining why would help.

Choji ran your change live — checks failed.

The ticket asked for working [use ...] module imports on the EIR backend; the diff implements the fix and adds tests. However, no live preview was run (this is a CLI/compiler change, not a web UI), so visual verification is not applicable. The sole verification signal is cargo check, which FAILED with ENOENT — the cargo binary was not found in the check environment. This is an infrastructure failure, not evidence the code is wrong. No checks could be completed against the actual compiled artifact.

Because the environment couldn't run cargo, there is no confirmed signal that the code compiles or the new tests pass. The PR description states cargo test --workspace passes locally, but that cannot be confirmed here. Marking as REQUEST_CHANGES pending a passing CI run rather than APPROVE, since the only executable signal we have is a failed check environment.

Choji sandbox notice
Choji's sandbox encountered an environment error and could not complete test execution (cargo check) — this is not a reflection of your PR. The repository's own CI/Vercel checks are green, which is the source of truth here.
The affected checks are reported as non-blocking notices, not merge blockers.

Checks — failures

  • cargo check — FAILED

No issues found in the running app.


Was this review useful? Rate the findings → — your thumbs up or down trains Choji on what's worth flagging.

Reviewed 55b6186 · Choji keeps this comment up to date as you push.

Comment thread crates/loon-lang/src/eir/lower.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@ecto ecto merged commit 9bf41b6 into main Jul 7, 2026
6 checks passed
@chojiai

chojiai Bot commented Jul 7, 2026

Copy link
Copy Markdown

What shipped

This is an internal change with no user-visible effect on most users, but it does fix a real breakage for anyone using multi-file Loon programs.

Multi-file programs using [use ...] imports now work correctly when run with the default loon run command. Previously, importing another module would silently fail and produce a confusing "value is not callable" error at runtime; the import now resolves properly. Attempting to import a missing module or a pair of modules that depend on each other now also produces a clear, specific error message instead of crashing with a misleading runtime fault.


Plain-English summary generated by Choji from this pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant