ReviewOS

also looking at this

home-lang/home

fix(interpreter): match range and or patterns in match statements

#930
Open mkantautas wants to merge fix/interpreter-range-or-patterns into main
2 files +90 -0
8 days ago

Problem

Range and or-patterns in a match statement never match under home run:

fn kind(age: int): string {
  match age {
    0..13 => return "child",
    13..20 => return "teenager",
    65 | 66 | 67 => return "retiring",
    _ => return "adult",
  }
  return "none"
}

kind(12), kind(13) and kind(66) all return "adult" instead of "child", "teenager" and "retiring". Interpreter.matchPatternNode has no case for Pattern.Range or Pattern.Or, so both fall into else => return false and control moves on to later arms. The parser produces these patterns, native codegen compiles them, and examples/wip/test_pattern_matching.home uses both forms.

Fix

matchPatternNode handles both patterns. A range compares integers like native codegen: inclusive start, inclusive end for ..=, exclusive end for ... An or-pattern tries each alternative in its own scope and copies only the matching alternative's bindings into the current environment before the guard runs, as other binding patterns do, so an alternative that binds a variable and then fails, such as P { a: x, b: 0 } | P { a: x, b: 3 }, leaves an existing x untouched. Match expressions use a separate path and are not changed here.

Tests

Two new tests in packages/interpreter/tests/program_eval_test.zig. eval: match statement range and or patterns checks both range ends, an or-pattern of literals, an or-pattern that mixes a literal and a range, and literal and guarded binding arms in the same match; on main it fails with Assertion failed: 0 Equal 1. eval: failed or-pattern alternatives leave no bindings checks that a failed alternative does not overwrite an outer variable and that the matching alternative's binding reaches the arm; on main it fails with Assertion failed: -1 Equal 1, and it also fails if all alternatives share one scope (1 Equal 99) or if the winning bindings are dropped. Both pass with this change; zig build test -Denable_jsc=false -Dfilter=interpreter on macOS: 25/25. On Linux the interpreter test step does not compile on main or on this branch, because the parser's module resolver calls libc access and that test target does not link libc.

GitHub Actions does not run on this repo at the moment, so I ran the full suite on Linux x86_64 with the pinned Zig 0.17.0-dev.2163, on main (63dd64e) and on this branch: zig build test --summary all passes 9151/9162 on both, with the same 3 failing tests, 8 skips and 21 failed build steps, and the diagnostic snapshot harness output is identical. With the built home, the program above plus a failed struct or-pattern prints adult, adult, adult, 99 on main and child, teenager, retiring, 99 on this branch.

2 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.