Fundamental patterns
Fundamental patterns check a value's type or contents. A successful pattern can also give the matched value a name for use in that branch.
Type— type pattern. Succeeds when the scrutinee can be treated asType. If the pattern introduces no designation, it behaves like a type test.Type name— typed binding. Succeeds when the scrutinee can be treated asType, then binds the converted value tonameas an immutable local in the success scope.When
Typeis an open generic type name written without explicit type arguments (for exampleBoxwhereBox<T>exists), Raven infers type arguments from the scrutinee when possible.This inference applies uniformly in:
ispatterns (if value is Box box { ... })matchexpression arms (match value { Box box => ... })matchstatement arms (match value { Box box => ... })
Example:
class Box<T> {} let value: Box<int> = Box<int>() if value is Box box { // box : Box<int> }let name/var name/val name— variable pattern. Always matches and introduces a binding.let/valproduce an immutable local;varproduces a mutable one.Parenthesized designations such as
let (first, second): (int, string)bind each element positionally.Explicit binding keyword required. In pattern position, introducing a new binding always requires an explicit binding keyword (
let,var, orval). A bare identifier never introduces a binding; it is interpreted as a value pattern (constant) or, if applicable, as a type name.For example:
Ok(42)matches the literal value42.Ok(discountedProduct)matches the runtime value of the in-scope symboldiscountedProduct.Ok(let n)binds the payload to a new immutable localn.
The same rule applies uniformly in positional patterns and discriminated-union case payloads.
Discards
_/Type _— discard. Matches without introducing a binding. The typed form asserts the value can be treated asTypewhile still discarding it. Because_is reserved for discards, writing_never creates a binding.Discards participate in exhaustiveness: an unguarded
_arm is a catch-all and satisfies any remaining cases (even if earlier arms introduced bindings).
Constant patterns
literal— constant pattern. Matches when the scrutinee equals the literal value (true,"on",42, ornull).identifier— value pattern. When a bare identifier appears in pattern position and resolves to an in-scope value (local, parameter, field, or property), the pattern matches when the scrutinee equals the runtime value of that identifier.Value patterns are not bindings. To introduce a new binding, an explicit binding keyword (
let,var, orval) is required.Type.Member— qualified constant/value pattern. When the qualified name resolves to a static constant-like value, such asMath.PIor an enum member such asJsonValueKind.True, the pattern matches when the scrutinee equals that value. Qualified names that resolve to types remain type/declaration patterns..Member— target-typed value pattern. When the scrutinee type is known (for example, an enum type or a type with static fields), the leading-dot expression resolves against that target type and matches the resulting value. Equality expressions also provide a target type from the opposite operand, sovalue == .Memberis equivalent tovalue == EnumType.Memberwhenvaluehas the enum/member-bearing type.
🧭 Disambiguation: A bare identifier in pattern position is context-sensitive. If the name resolves to a value symbol, it forms a value pattern. Otherwise, it is interpreted as a type name and participates in a type or declaration pattern. This disambiguation is performed by the binder, not the grammar.
Comparison patterns
< expr,<= expr,> expr,>= expr,== expr,!= expr— comparison pattern. Matches when the scrutinee compares to the operand using the given operator.The operand must be a side-effect-free expression (for example, literals, constants, or other stable values), ensuring comparison patterns remain predictable and optimizable.
The operand type must match the scrutinee type after nullable/plain-type unwrapping. Ordinary implicit numeric widening is not applied inside comparison patterns, so matching an
intscrutinee with> 0.5is an error.Comparison patterns are commonly used under
not,and, and property patterns, e.g.{ Age: not > 30 }.
Range patterns
A range pattern matches values that fall within a lower and/or upper bound
using .. (inclusive upper bound) or ..< (exclusive upper bound). Range
patterns are valid when the scrutinee type is orderable (for example
numeric types, char, or other types that support relational comparison).
Like comparison patterns, range bounds must match the scrutinee type after nullable/plain-type unwrapping; normal implicit numeric conversions are not applied to range bounds.
Both bounds are optional:
lo..hi— matches values greater than or equal toloand less than or equal tohi.lo..<hi— matches values greater than or equal toloand less thanhi...hi— matches values less than or equal tohi...<hi— matches values less thanhi.lo..— matches values greater than or equal tolo.
Bounds are written as expressions, but the binder may restrict them to constant‑like values depending on context.
let value = 42
let result = match value {
..4 => "No"
40..43 => "Yes"
_ => "Other"
}
Range patterns participate in exhaustiveness and subsumption analysis alongside comparison patterns. They are treated as syntactic sugar for a conjunction of comparison operators (for example 40..43 behaves like >= 40 and <= 43).
🧭 Disambiguation: In pattern position,
..introduces a range pattern rather than a range expression. The expression parser stops at..so the bounds are parsed independently.
Positional patterns
(element1, element2, …)— positional pattern. Matches when the scrutinee can be deconstructed positionally with the same arity (either as a tuple or via a compatibleDeconstructmethod).Positional patterns destructure by position. Each element is itself a pattern and may introduce bindings. For example:
If the scrutinee type exposes a
Deconstructmethod with matching arity (including as an extension method), the positional pattern uses that method to obtain positional values.✅
(let a, var b)(explicit mutability)✅
(a: int, b: string)(inline type annotations)✅
(int a, string b)(type-pattern + capture)✅
(a: int, _)✅
(let a, == existingValue)(explicit capture + value pattern)✅
(existingA, == existingValue)(existing-value comparison)
In freestanding and inline positional patterns, captured variables must use an explicit binding keyword (
let,var, orval). A bare identifier is treated as a value pattern against an existing in-scope value. In assignment and declaration deconstruction (let (a, b) = expr,(a, b) = expr), bare identifiers continue to act as deconstruction targets. To constrain by type and capture a value in an element, both forms are valid:let name: Type(Raven-native typed binding style) andType name(type-pattern style). This is equivalent to introducing a binding and adding awhenguard that compares the bound value, but== exprkeeps the constraint local to the pattern and avoids an additional arm condition.In compiler APIs,
== expris represented asComparisonPatternSyntax(distinct fromConstantPatternSyntax) so tools can preserve user intent.An element may optionally include a name before the colon (
name: pattern) to bind the element value while still applying a nested pattern. When the positional pattern is backed byDeconstructrather than a tuple, named elements bind byDeconstructparameter name and may appear in any order. Unnamed elements continue to consume the remaining unmatched parameters in declaration order. If a supplied name does not match any supported deconstruction member/parameter, the pattern is invalid.