Dictionary patterns and exhaustiveness
Dictionary patterns check for required keys and match their values at the same time. They are useful when a branch only cares about a few known entries.
Dictionary patterns use bracket syntax with keyed entries:
if lookup is ["a": let first, "b": 2] {
WriteLine(first)
}
Each dictionary-pattern entry evaluates its key expression and then matches the corresponding value against the nested pattern. A dictionary pattern succeeds only when:
- the scrutinee is dictionary-compatible,
- every requested key exists, and
- every nested value pattern succeeds.
Dictionary compatibility is defined in terms of the dictionary surfaces Raven
binds directly today: IDictionary<TKey, TValue>,
IReadOnlyDictionary<TKey, TValue>, and types that implement one of those
interfaces. Key expressions are converted to TKey; nested patterns are bound
against TValue.
Dictionary deconstruction uses the same keyed syntax in declaration or assignment position:
val values: IReadOnlyDictionary<string, int> = ["a": 2, "b": 3]
let ["a": first, "b": second] = values
This is extraction-oriented rather than boolean. After validating that the source is dictionary-compatible, each entry reads the requested key and assigns the resulting value through the nested designation/pattern.
Only match participates in exhaustiveness checking.
Exhaustiveness
A match expression is exhaustive when its set of arms covers every
possible runtime value of the input expression (the scrutinee).
When a match expression (and statement form) is exhaustive, a discard arm (_ => ...) is
unnecessary and is reported as unreachable.
Exhaustiveness is determined by analyzing the runtime value space implied by the scrutinee’s static type. The compiler can prove exhaustiveness without a discard arm when that value space is finite or otherwise fully covered by the provided patterns.
Exhaustiveness analysis applies in particular to:
- Discriminated unions declared with the
unionkeyword. - Enums.
bool(trueandfalse).- Finite tuple products whose element value spaces can all be enumerated,
including nested tuples composed from
bool, enums, and discriminated unions. - Sealed hierarchies — a
sealedclass with apermitsclause (or otherwise participating in a closed inheritance set). Closed branches contribute concrete leaves; open intermediate branches must be covered by matching the intermediate type.
For discriminated unions, exhaustiveness is computed from the union's declared
case set (equivalent to sealed-hierarchy reasoning over a closed subtype set).
Each declared case and its payload value space must be covered by unguarded
arms, or by an unguarded catch-all arm. Multiple arms may collectively cover a
case payload. For example, separate Error(.A) and Error(.B) arms cover the
entire Error case when the payload union declares only A and B. The same
rule applies to other finite payload domains such as bool and to the Cartesian
product of multiple finite payloads. Coverage must include every combination;
independently mentioning every value in each payload position is insufficient.
When a finite union payload is wholly or partly uncovered, the missing-case
diagnostic identifies each uncovered case-and-payload alternative, such as
Error(OverflowException) for a parenthesized union or
Error(.ServiceUnavailable) for a nested discriminated union. If the payload
domain cannot be usefully enumerated, the diagnostic conservatively reports the
enclosing case instead.
Positional tuple patterns use the same Cartesian-product analysis. Pattern
combinators (not, and, and or) are evaluated over each finite value, so
complementary tuple rows can establish exhaustiveness. The analysis is bounded;
domains that cannot be enumerated within the compiler's finite-product limit
remain conservative and require a total pattern. Property and nominal
deconstruction patterns can establish exhaustiveness when a single pattern is
itself total, but separate constrained property/deconstruction arms are not
combined: evaluating user-defined accessors or deconstruction methods across
arms is not assumed to be stable or side-effect free.
Boolean pattern combinators also participate directly in closed enum and
discriminated-union domains, closed type unions, sealed hierarchies, and
nullable domains. In particular, not CasePattern covers the complement of a
fully covered case pattern, null and not null partition a nullable domain,
and and covers the intersection of its operands. For closed type sets the
compiler distinguishes patterns that cover none, some, or all values of each
candidate type; complements of partial structural patterns remain partial.
Guards never contribute coverage unless they are compile-time guaranteed to
match.
A nested guarded pattern contributes coverage only when its expression guard
is compile-time true; a nested pattern guard is intersected with the guarded
pattern. Dynamic and compile-time-false guards contribute no coverage. For
sequence patterns, a single unconstrained rest segment (for example [...])
covers every length and is a total pattern for a compatible non-null sequence.
Patterns containing required elements or fixed segments remain length
constrained.
The unit domain contains only (), and the null-only domain contains only
null; matching those respective values is exhaustive. A compile-time-true
match-arm guard preserves the arm pattern's coverage in every domain, while a
false or dynamic arm guard contributes no coverage.
For nullable discriminated union carriers (U?), exhaustiveness is computed
from the underlying union's declared case set plus the nullable wrapper's
null value. This rule is the same for union struct and union class; the
null arm covers the nullable wrapper state, not a union pseudo-case.
In addition, exhaustiveness may be proven through type analysis even when no explicit finite-case construct is involved. For example:
- Covering all concrete types in a closed inheritance chain.
- Covering both the non-null and
nullcases of a nullable type. - Using type patterns (for example,
string,MyBaseType, or other concrete runtime types) such that all possible runtime types implied by the scrutinee are handled.
For sealed hierarchies with sub-hierarchies:
- If an intermediate subtype is
sealed, exhaustiveness can be satisfied by covering its concrete descendants. - If an intermediate subtype is not
sealed, exhaustiveness for that branch requires a pattern that targets the intermediate type itself.
If every possible runtime value implied by the scrutinee type is handled by explicit pattern arms, a discard arm is redundant.
When the scrutinee type is open-ended (for example, object or another
extensible base type), exhaustiveness generally cannot be proven without a
discard arm, or type pattern for type object.