core: match guards through typing + interp + wasm (#67)#74
Merged
Conversation
End-to-end implementation of `MatchArm::guard` (the `if e` clause that
runs after the pattern matches and before the arm body). The
typechecker enforces `Ty::Bool`, the interpreter falls through on
`false`, codegen emits an `i32.eqz + br_if $arm_i_fail` after the
destructure.
## Typing — `ephapax-typing`
* `check_match` now type-checks the guard under the arm's pattern
bindings; the guard's type is unified with `Bool` at the guard's
span. Non-Bool guards produce `TypeMismatch`.
* `check_exhaustiveness` skips guarded arms when building the
matrix — guards can refute at runtime, so they don't contribute
to coverage. `Some(v) if v > 0 -> ... | None -> ...` is
non-exhaustive (missing the Some-non-positive case); only
`Some(v) -> ... | None -> ...` (no guard) covers Some entirely.
## Interp — `ephapax-interp`
* `eval_match` evaluates the guard after applying pattern bindings.
If the guard returns `false`, the bindings are restored to their
prior state and the next arm is tried. Failed guards must not
leak bindings — confirmed by a regression test where an outer
`v=999` survives a refuted inner `Some(v)` arm.
* Non-Bool guard values raise `RuntimeError::TypeError { expected:
"Bool", found: ... }` — should be unreachable for well-typed
programs.
## Wasm — `ephapax-wasm`
* `compile_match` emits the guard expression after the pattern
destructure. The guard pushes an `i32` onto the stack; `i32.eqz`
+ `br_if 0` falls through to the surrounding `$arm_i_fail` block
when the guard is false, exactly like a refuted nested pattern.
* The fall-through machinery established by #68 is reused — no new
control-flow structure needed.
## Linear — `ephapax-linear`
* No changes. Both `linear.rs::walk_expr` and `affine.rs::walk_expr`
already walked into `arm.guard` when computing per-arm snapshots
(in place since the variant landed); the existing N-arm branch-
agreement check correctly enforces consumption parity across
guarded and non-guarded arms.
## Tests
* `cargo test -p ephapax-typing --lib` → 73 pass (was 70), +3:
- `test_match_guard_typechecks_with_pattern_binding` — guard
reads `v` bound by `Some(v)`.
- `test_match_guard_non_bool_rejected` — guard of type `I32`
rejected as `TypeMismatch`.
- `test_match_guarded_arm_not_counted_for_exhaustiveness` —
`Some(v) if v > 0 | None` produces `NonExhaustiveMatch`.
* `cargo test -p ephapax-interp --lib` → 17 pass (was 14), +3:
- `test_eval_match_guard_pass` — `Some(7) if v > 0 => v` ⇒ 7.
- `test_eval_match_guard_fail_falls_through` — `Some(-3) if v > 0`
falls through to wildcard ⇒ 0.
- `test_eval_match_failed_guard_does_not_leak_bindings` —
refuted arm's binding is reverted before the next arm runs.
* `cargo test -p ephapax-wasm --lib` → 79 pass (was 77), +2:
- `compile_module_match_guard_simple` — `Some(v) if v > 0 | Some(_)
| None`, validates via `wasmparser`.
- `compile_module_match_guard_on_wildcard` — guard on a wildcard
catch-all, validates via `wasmparser`.
## Out of scope
* Or-patterns inside a single arm — separate feature.
* Guard expressions that themselves contain `match` — should fall
out for free; no targeted test added.
Closes #67.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #67.
What changed
Match guards (
if eafter a pattern) now work end-to-end throughephapax-typing,ephapax-interp, andephapax-wasm. The grammar already carriesMatchArm::guard: Option<Box<Expr>>from #61 — this PR replaces theNotYetSupportedInCorestub in typing, theUnimplementedstub in interp, and adds thei32.eqz + br_ifemission in wasm.Per-crate changes
ephapax-typingcheck_matchtype-checks the guard under the arm's pattern bindings; unified againstBoolat the guard's span. Non-Bool guards returnTypeMismatch.check_exhaustivenessnow skips guarded arms when building the Maranget matrix — guards can refute at runtime so they don't contribute to coverage.Some(v) if v > 0 -> ... | None -> ...is correctly reported as non-exhaustive.ephapax-interpeval_matchevaluates the guard after applying the pattern bindings.false⇒ restore bindings, continue to next arm.true⇒ run body.v=999survives a refutedSome(v)arm.ephapax-wasmcompile_matchemits the guard expression after the destructure.i32.eqz + br_if 0falls through to the surrounding$arm_i_failblock, reusing the per-arm fall-through machinery from wasm: nested constructor patterns inside ExprKind::Match arm args #68.ephapax-lineararm.guard, so guarded-arm linearity is enforced automatically.Behaviour table
match x { Some(v) if v > 0 => v | Some(v) => v | None => 0 }match x { Some(v) if v > 0 => v | None => 0 }NonExhaustiveMatch { missing: "Some(_)" }.Some(_) if 42 => ...TypeMismatch(guard isI32, expectedBool).Some(7) if v > 0 => vSome(-3) if v > 0 => v | _ => 0Test plan
cargo test -p ephapax-typing --lib→ 73 pass (was 70), +3 guard tests.cargo test -p ephapax-interp --lib→ 17 pass (was 14), +3 guard tests.cargo test -p ephapax-wasm --lib→ 79 pass (was 77), +2 guard tests (validated viawasmparser).Out of scope
match— should fall out for free; no targeted test added.🤖 Generated with Claude Code