RFC 071: Pattern alternation in match and if let¶
- Status: Implemented
- Created: 2026-04-23
- Author(s): Danny Meijer (@dannymeijer)
- Related:
- RFC 000 (core language surface)
- RFC 029 (union types and type narrowing)
- RFC 049 (
if letsingle-arm conditional match)
- Issue: https://github.com/dannys-code-corner/incan/issues/387
- RFC PR: —
- Written against: v0.2
- Shipped in: v0.3.0-dev.38
Summary¶
This RFC introduces pattern alternation in Incan pattern positions used by match arms and if let. In practical terms, a form such as PATTERN_A | PATTERN_B means "match either pattern and execute the same branch." The feature removes repetitive duplicated branches for cases where several patterns share identical behavior, keeps the branch structure explicit, and aligns Incan with the pattern alternation capability readers already expect from languages such as Rust and Python structural matching. Alternation is deliberately constrained: all alternatives in one alternation must bind the same names with the same types, or bind no names at all.
Core model¶
Read this RFC as one foundation plus three mechanisms:
- Foundation: a single branch may be guarded by more than one pattern.
- Mechanism A:
P1 | P2 | P3is a valid pattern inmatcharms andif let. - Mechanism B: alternation does not weaken type safety; alternatives must agree on binding shape and binding types.
- Mechanism C: alternation is branch-sharing syntax, not boolean logic and not value-level operator overloading.
Motivation¶
Today, when several patterns should take the same branch, authors must spell those branches separately. That is noisy in ordinary code and especially awkward in compiler, planner, and runtime-adjacent code where enum dispatch is common.
Typical current code has to look like:
match node.kind:
PrismNodeKind.Filter => return lower_passthrough(node)
PrismNodeKind.OrderBy => return lower_passthrough(node)
PrismNodeKind.Limit => return lower_passthrough(node)
PrismNodeKind.Explode => return lower_passthrough(node)
That is repetitive without being more precise. The important semantic fact is that these four patterns share one branch. The language should let authors say that directly.
This matters beyond cosmetics. Incan uses Python-shaped syntax in places where readers often expect compiler or systems code. Repetition in those parts of the language surface makes dispatch logic longer, harder to scan, and easier to drift when one branch is edited but its siblings are not. Pattern alternation is the honest construct for "these cases are semantically identical."
RFC 049 already extends pattern matching into if let, and RFC 029 already makes pattern matching more expressive for unions and narrowing. Without alternation, both features still force duplicated boilerplate when multiple patterns share the same success path. The absence of alternation is now a real ergonomic gap in the pattern-matching story.
Goals¶
- Allow multiple patterns to share one
matcharm. - Allow
if letto use the same alternation grammar asmatch. - Preserve explicit branch structure without forcing duplicated branch bodies.
- Define strict rules for name binding across alternatives.
- Keep diagnostics clear when alternatives disagree on bindings or types.
Non-Goals¶
- Introducing guards on
matcharms in this RFC. - Extending alternation to every pattern-bearing construct in the language.
- Defining nested destructuring over arbitrary new pattern families.
- Turning
|in pattern position into value-level boolean logic or operator overloading. - Changing union-type syntax or value-level
|behavior.
Guide-level explanation¶
Pattern alternation lets one branch match more than one shape.
Basic match example¶
match node.kind:
PrismNodeKind.Filter | PrismNodeKind.OrderBy | PrismNodeKind.Limit | PrismNodeKind.Explode =>
return lower_passthrough(node)
PrismNodeKind.Project =>
return lower_project(node)
That reads as: if node.kind is any of those four enum variants, run the same branch body.
if let example¶
if let Some(value) | Ok(value) = result_like:
print(value)
That shape is only valid if both alternatives bind the same name with the same type. If they do not, the language must reject it.
Binding rules¶
These are good:
match value:
Some(item) | Ok(item) => handle(item)
None | Err(_) => pass
These are not:
match value:
Some(item) | None => handle(item) # one side binds `item`, the other does not
match value:
Ok(value) | Err(error) => log(value) # different binding names
Alternation is for "same branch, same binding shape." If the alternatives do not agree on that, authors should use separate branches.
Reference-level explanation¶
Syntax¶
This RFC extends pattern grammar in match arms and if let with pattern alternation:
pattern ::= alternative_pattern ("|" alternative_pattern)*
alternative_pattern ::= existing_match_pattern
| in pattern position is alternation, not a value-level operator. It is valid only where the grammar expects a pattern.
This RFC applies that grammar extension to:
matcharm patternsif letpatterns as defined by RFC 049
It does not, by itself, require alternation to be supported in every other construct that may later reuse pattern grammar.
Semantics¶
P1 | P2 | ... | Pn matches if any alternative pattern matches the scrutinee.
For match, the arm is selected when any alternative matches.
For if let, the success branch executes when any alternative matches.
Alternation is semantically equivalent to repeating the same branch body for each alternative in source order, except that it is one branch for formatting, diagnostics, and readability purposes.
Binding agreement¶
All alternatives in one alternation must satisfy one of the following:
- every alternative binds no names, or
- every alternative binds exactly the same set of names, and each name has the same type in every alternative
The language must reject alternations where:
- one alternative binds a name and another does not
- alternatives bind different name sets
- the same binding name would have different inferred types across alternatives
Scope¶
Bindings introduced by a successful alternation are in scope exactly where the surrounding pattern construct would normally place them:
- within the selected
matcharm body - within the success branch of
if let
Bindings from failed alternatives must not leak.
Type checking¶
Type checking must validate each alternative against the scrutinee type under the same rules already used for ordinary patterns.
If all alternatives are valid, the alternation itself is valid only if binding agreement also holds.
Diagnostics should point to the specific alternatives that disagree on bindings or binding types.
Exhaustiveness¶
For match, alternation contributes to exhaustiveness exactly as though each alternative were written as its own separate arm with the same body.
For example:
match value:
int(_) | str(_) => use_present_value()
None => pass
must be checked as covering int, str, and None, subject to the existing exhaustiveness rules for the scrutinee type.
Design details¶
Syntax¶
The chosen surface is infix | between patterns:
match kind:
A | B | C => ...
This is the most familiar spelling for grouped pattern arms and keeps the "shared branch" structure visually obvious.
Semantics¶
Alternation is a pattern-level construct, not a new expression feature. The same token already appears in type expressions and value expressions in different grammatical positions. That reuse is acceptable because the parser can distinguish pattern position from type position and expression position.
Binding agreement is the key semantic constraint. Without it, alternation would either force ad-hoc partial binding rules or create confusing branch-local name availability. Requiring agreement keeps alternation predictable.
Interaction with existing features¶
- RFC 029 (union types and type narrowing): type patterns such as
int(n)andstr(s)may participate in alternation as long as binding agreement holds. Exhaustiveness must count each alternative normally. - RFC 049 (
if let):if letreuses the same pattern grammar asmatch, so it should gain alternation together withmatchrather than drifting into a separate capability set. - Testing / assertion patterns: this RFC does not automatically extend alternation to any limited pattern-binding forms introduced by RFC 018. Those should opt in deliberately if and when their own pattern subset is expanded.
- Operator overloading / expressions:
|in pattern position must not consult value-level operator overloading or expression semantics.
Compatibility / migration¶
This feature is additive. Existing code remains valid.
The migration effect is optional simplification. Authors may collapse repeated identical branches into one alternation arm, but they are not required to do so.
Because alternation is only accepted in pattern position, there is no compatibility risk for existing value-level uses of |.
Alternatives considered¶
- Keep duplicated branches. Rejected because it preserves unnecessary repetition in a part of the language where enum dispatch and narrowing are already central.
- Support grouped arms only in
match, notif let. Rejected because RFC 049 explicitly reusesmatchpattern grammar; diverging them would create an avoidable inconsistency. - Allow alternatives with different bindings and make some names optional. Rejected because that would complicate scope and typing rules significantly and make branch-local names harder to reason about.
- Use a different grouped-arm syntax such as comma-separated patterns. Rejected because
|is the pattern-alternation spelling most readers already expect, and it distinguishes alternation from tuple-like or list-like punctuation.
Drawbacks¶
- Reusing
|in one more grammatical position increases parser and formatter complexity. - Readers must understand the distinction between pattern-position
|, type-expression|, and value-expression|. - Binding-agreement rules add one more diagnostic path to pattern checking.
Implementation architecture¶
Non-normative recommended shape:
- extend the internal pattern representation with an alternation node rather than desugaring too early
- type-check each alternative independently, then enforce binding agreement at the alternation node
- lower alternation through the existing match-oriented control-flow machinery rather than inventing a separate execution path
Layers affected¶
- Parser / AST: pattern grammar must accept
|alternation inmatchandif letpattern positions, and the AST should preserve alternation as a first-class pattern shape. - Typechecker / Symbol resolution: pattern checking must validate each alternative, enforce binding agreement, and emit clear diagnostics when alternatives disagree.
- IR Lowering: lowering must preserve the semantics of grouped alternatives without changing existing branch behavior or exhaustiveness reasoning.
- Emission: emitted Rust must represent grouped alternatives faithfully, whether through native Rust pattern alternation or an equivalent lowered form.
- Formatter: formatter support is needed so long alternation arms remain readable and stable.
- LSP / Tooling: completions, hover, and diagnostics should treat grouped alternatives as one branch with ordinary per-pattern checking.
Implementation Plan¶
Phase 1: Parser, AST, and formatter¶
- Extend the pattern AST with a first-class alternation shape.
- Parse
|as pattern alternation inmatcharm andif letpattern positions. - Preserve ordinary value-level and type-level
|behavior outside pattern positions. - Format short alternations on one line and wrap long alternatives with existing multiline grouping rules.
Phase 2: Typechecker and diagnostics¶
- Type-check every alternative against the same scrutinee type.
- Enforce identical binding name sets across alternatives.
- Enforce identical binding types across alternatives.
- Emit span-precise diagnostics for mismatched bindings and mismatched binding types.
- Treat wildcard-containing alternations such as
A | _as valid patterns without adding a style diagnostic.
Phase 3: Lowering, IR, and emission¶
- Lower AST alternation into the existing IR pattern alternation representation.
- Emit Rust pattern alternation or an equivalent lowered form without changing branch semantics.
- Preserve exhaustiveness behavior as though each alternative appeared as a separate arm with the same body.
Phase 4: Tests and docs¶
- Add parser and formatter coverage for
matchandif letalternation. - Add typechecker coverage for valid no-binding and same-binding alternatives.
- Add diagnostic coverage for missing bindings, different binding names, and different binding types.
- Add codegen snapshot coverage showing emitted branch sharing.
- Update user-facing language docs and release notes for the new pattern syntax.
Implementation log¶
Spec / design¶
- Resolve scope inheritance: RFC 071 applies to
matcharms andif let; future pattern-bearing constructs opt in deliberately. - Resolve formatter policy: short alternations stay inline; long alternatives use existing multiline grouping behavior, including parenthesized grouping when needed.
- Resolve wildcard policy:
_may appear in alternations,A | _is valid and exhaustive, and this RFC does not add redundancy/style diagnostics.
Parser / AST / formatter¶
- AST: add a first-class pattern alternation shape.
- Parser: parse
|alternation inmatcharm patterns. - Parser: parse
|alternation inif letpatterns. - Parser: keep expression/type
|behavior unchanged outside pattern positions. - Formatter: render short alternations stably.
- Formatter: render long alternations with stable multiline grouping.
Typechecker / diagnostics¶
- Typechecker: validate each alternative against the scrutinee type.
- Typechecker: accept alternatives with identical empty binding sets.
- Typechecker: accept alternatives with identical binding names and types.
- Diagnostic: reject alternatives where one side binds a name and another does not.
- Diagnostic: reject alternatives with different binding names.
- Diagnostic: reject alternatives with different inferred binding types.
Lowering / IR / emission¶
- Lowering: map AST alternation to IR pattern alternation.
- Emission: emit faithful Rust grouped patterns or equivalent lowered branches.
- Exhaustiveness: count every alternative as branch coverage for
match.
Tests / docs / release¶
- Parser tests cover
matchandif letalternation. - Formatter tests cover inline and multiline alternation formatting.
- Typechecker tests cover valid and invalid binding agreement.
- Codegen snapshots cover emitted alternation.
- User-facing language docs explain pattern alternation.
- Release notes mention RFC 071 and issue #387.
- Development version is bumped from the active
0.3.0-dev.Nline.
Design Decisions¶
- Pattern alternation is limited to
matcharms andif letfor this RFC. Future pattern-bearing constructs should opt in deliberately when they adopt the same pattern grammar. - The formatter should keep short alternations on one line. When an alternation becomes too long, it may use multiline parenthesized grouping so the result remains parseable as one pattern.
_may appear in an alternation like any other no-binding pattern.A | _is valid and exhaustive. This RFC does not introduce redundancy or style diagnostics for wildcard-containing alternations; a future lint policy may add those separately.