Skip to content

EIR VM: hard-error instead of silent () on unbound symbols, min/max/assoc misuse, and non-exhaustive match#78

Merged
ecto merged 3 commits into
mainfrom
claude/awesome-kepler-aff9b2
Jul 7, 2026
Merged

EIR VM: hard-error instead of silent () on unbound symbols, min/max/assoc misuse, and non-exhaustive match#78
ecto merged 3 commits into
mainfrom
claude/awesome-kepler-aff9b2

Conversation

@ecto

@ecto ecto commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

The EIR VM (default loon run backend) silently produced () in places the legacy interpreter errors loudly. This brings the VM to the loud behavior, with error wording matching the interp so both backends fail the same way:

New VmErrorKind variants (UnboundSymbol, NoMatch, BuiltinType) have stable class() names for trace/verify, following the precedent of the earlier VM effect-op hard-error change.

Convergences the loud path surfaced

  • First-class binary operators now lower to an arity-2 closure, so [fold xs 0 +] prints 10 on both backends — the pinned fold-builtin-arg divergence in backend_parity.rs is retired as CONVERGED, and and-or-value.oo flips from expect-fail: legacy to expect-fail: wasm (the VM now matches legacy; wasm codegen has no operator values).
  • First-class arity-N ADT constructors (e.g. [map Some xs]) wrap in a real closure instead of the bogus string path.
  • Const.* physics constants (legacy-only until now) resolve in lowering — physics.oo was silently field-accessing a string.

Reviewer notes

  • Wasm/native compile the two new builtins through their existing unit fallback, so those backends keep prior behavior rather than gaining new traps.
  • Legacy implements sqrt; the VM still lacks it but now at least names the gap (unbound symbol 'sqrt'). Porting it, and wiring the type checker into loon run for static unbound-symbol detection, are follow-ups.
  • Coverage: new silent_unit_traps_error_loudly test in crates/loon-lang/tests/backend_parity.rs asserts all error cases on both backends (message substrings on EIR) plus the working counterparts for agreement. cargo test --workspace is green.

🤖 Generated with Claude Code

…ssoc misuse, and non-exhaustive match

The default backend silently produced unit in several places the legacy
interpreter errors loudly. Now, matching the interp's wording:

- Unbound symbols lower to a Built::UnboundSym trap that raises
  "unbound symbol '<name>'" (with span) instead of a string literal.
- Binary [min 1 2] / [max 5 3] error "min requires a vector"; empty
  vectors error "min: empty vector"; vector min/max now also compare
  floats. assoc on a non-map errors "assoc requires a map" (#20, #21).
- A match with no matching arm raises "no match arm matched value: <v>"
  via Built::MatchFail carrying the scrutinee. Wasm/native compile the
  new builtins to their existing unit fallback, preserving behavior.

Convergences surfaced by the loud path:
- First-class binary operators wrap in an arity-2 closure, so
  [fold xs 0 +] now agrees with the interp (pinned divergence retired);
  and-or-value.oo flips to expect-fail: wasm.
- First-class arity-N ADT constructors wrap in a real closure.
- Const.* physics constants resolve in lowering (physics.oo used the
  silent field-on-string path).

New VmErrorKind variants (UnboundSymbol, NoMatch, BuiltinType) carry
stable class() names for trace/verify. Coverage added in
backend_parity.rs (silent_unit_traps_error_loudly).

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:46am

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 converts several silent-unit paths in the EIR VM into hard errors (unbound symbols, binary min/max misuse, assoc on non-map, non-exhaustive match) and fixes two correctness gaps (first-class binary operators wrap as arity-2 closures, physics constants resolve at lowering time). The implementation is clean and well-tested. I found one narrow correctness concern with NaN handling in the new min/max comparator, but it is a pre-existing edge case that the old code also mishandled (it silently dropped non-numeric elements), so it does not block the PR. No blockers.

Correctness

  • Major · Correctness — NaN key values silently skip updates, so a vector with non-numeric elements may return the wrong min/max crates/loon-lang/src/eir/vm.rs:1641
    The comparator maps non-numeric values to f64::NAN. NAN.partial_cmp(&anything) returns None, so ord == Some(Ordering::Less/Greater) is always false when either operand is NaN. This means non-numeric elements are silently ignored rather than causing an error, and if the first element is non-numeric, best starts as that NaN-keyed value and can never be displaced — the result is the first non-numeric element regardless of what follows.

The old code filtered to is_int() only, so mixed vectors were also broken (differently). The new behavior is arguably more consistent but the semantics are undocumented. If the intent is "numeric elements only, error on others", add an explicit type check before the key() call and return a BuiltinType error. If the intent is "skip non-numerics", document it. Either way the current silent-skip is a latent correctness trap.

1 minor finding
  • Minor · Structural quality — Inline function-body construction is duplicated verbatim for ADT constructors and binary operators crates/loon-lang/src/eir/lower.rs:636
    The pattern of saving cur_func/cur_block/next_reg/scopes, calling begin_func, building a body, then restoring state appears twice in lower_symbol (ADT arity-N closure and binary-operator closure). Extracting a helper like build_synthetic_func(|lower| { ... }) would reduce the duplication and make future additions (e.g. arity-N builtins) a one-liner. Not a blocker — just worth noting for the next pass.

Choji ran your change live — checks failed.

No live preview was run for this backend/compiler change. The verification check (cargo check) failed with ENOENT — the Cargo toolchain could not be found in the environment, so this is an infrastructure failure rather than evidence of a code defect. The test suite and type-checker results are therefore unavailable from this run. That said, one concrete blocker is observable from the check output itself: cargo could not execute at all, meaning zero verification was performed. The PR's stated intent — hard errors instead of silent () for unbound symbols, min/max/assoc misuse, and non-exhaustive match in the EIR VM — cannot be confirmed as working from this run.

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 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 b01b408 · Choji keeps this comment up to date as you push.

Comment thread crates/loon-lang/src/eir/vm.rs
Review follow-up: the NaN-keyed comparator silently ignored non-numeric
elements (and pinned `best` to a leading non-numeric one). Non-numeric
elements now raise "min: non-numeric element", consistent with the
loud-over-silent policy of this branch. Note: the interp diverges here
(its generic value_cmp orders strings); pinned in the parity test.

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.

…riant sets

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 3f4d05c into main Jul 7, 2026
6 checks passed
@chojiai

chojiai Bot commented Jul 7, 2026

Copy link
Copy Markdown

What shipped

This is a purely internal change with no user-visible effect. The EIR execution backend now produces the same error messages as the legacy interpreter when programs reference undefined names, misuse built-in functions, or write incomplete match expressions — previously those situations silently returned an empty value instead of failing. No behavior visible to end users has changed.


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