Pattern matching in the compiler #
Pattern matching is where several compiler phases meet. Parsing records the source pattern. Name resolution decides which constructor or field each name refers to. Type checking verifies that the pattern can match the subject type and records the variables it binds. Lowering turns that checked pattern into IR tests and explicit local writes. Code generation emits the tests, jumps, and field loads needed to run the match.
Gleam uses case expressions for pattern matching and performs exhaustiveness
checking so the branches cover the possible subject values.[1]
Regulus follows the same direction: pattern matching should be checked before
lowering, and lowering should receive enough type information to make bindings
and failure paths explicit.
This chapter covers:
- compiling pattern matching into tests and branches
- binding variables from nested patterns
- exhaustiveness, unreachable branches, and redundant patterns
A compiler-shaped example #
pub type Result(ok, error) {
Ok(ok)
Error(error)
}
fn unwrap_or(result: Result(Int, String), default: Int) -> Int {
case result {
Ok(value) -> value
Error(_) -> default
}
}
The checker sees result: Result(Int, String). That lets it instantiate
Ok(ok) as Ok(Int) and bind value: Int. Lowering then records that the
first branch succeeds only if the subject has the Ok constructor tag. If it
succeeds, the payload field is written into the local for value.
The backend should not rediscover any of that from source strings. It should receive an IR branch with typed subjects, lowered patterns, branch bodies, and a fallthrough failure path.
Gleam Language Tour, "Case expressions": https://tour.gleam.run/flow-control/case-expressions/ ↩︎