Definition and scope
LinkA pattern describes a value to recognize and the names to bind from it. A match expression selects a case by applying a pattern and, when present, a guard.
This page owns pattern syntax, case selection, and the checking rules stated below. The execution of if let and while let, including their branches and repetition, belongs to Blocks and control flow. Collection construction, nominal declarations, and general name resolution belong to their respective Language pages.
This page defines source-order selection, exhaustive matches, and checking of unreachable cases. Each example states its expected result independently of recorded observations.
Syntax
LinkPattern = PrimaryPattern ("|" PrimaryPattern)* ;PrimaryPattern ="_" | Identifier | Literal | ConstructorPattern | ListPattern| RecordPattern | NominalRecordPattern | TypedPattern | RangePattern ;ConstructorPattern =Identifier "(" (Pattern ("," Pattern)* ","?)? ")" ;ListPattern = "[" (ListElement ("," ListElement)* ","?)? "]" ;ListElement = Pattern | ".." Pattern ;RecordPattern = "{" FieldPattern ("," FieldPattern)* ","? "}" ;FieldPattern = FieldName ":" Pattern ;NominalRecordPattern = Identifier RecordPattern ;TypedPattern = Identifier ":" Type ;RangePattern = "(" RangeEndpoint (".." | "..<") RangeEndpoint ")" ;
This is a hand-transcribed source grammar excerpt. Type includes the type parser's own alternatives; the excerpt does not settle every interaction between a type annotation and an or-pattern. Range imports the expression subset accepted at a range-pattern boundary.
The grammar defines pattern syntax. Scrutinee types, constructor arity, binding rules, and unresolved rest boundaries also affect valid programs.
Source evidence
- parseRecordPatternFields; parseListPattern; parseConstructorPattern; parseTypedPattern; parsePattern at
b48527d0a174· 341-692
MatchShape = "match" "(" Expression ")" "{" CaseShape+ "}" ;CaseShape = "case" Pattern ("if" "(" Expression ")")? "=>" CaseBody ;CaseBody = Expression ;
The + requires at least one case. This shape excerpt omits case separators and expression/block disambiguation. A case body is an expression, which may be a block expression. Write a return statement inside that block. Its transfer and cleanup follow Blocks and control flow.
Source evidence
- parseMatchCase; parseMatch at
b48527d0a174· 910-1002
Pattern forms
LinkThe table is a syntax index for the grammar excerpt. Its spellings are fragments, not separately executed programs. Constructor argument lists identify constructor patterns. A bare name attempts to bind, subject to the constructor-name collision rule.
| Pattern form | Representative spelling |
|---|---|
| Binding | name |
| Wildcard | _ |
| Literal | 0, true, "ready", () |
| Constructor | Some(item), None(), Empty(), Tag(payload) |
| List | [first, second], [head, . |
| Rest within a list | ., . |
| Structural record | { x: x, y: other } |
| Nominal record | Point { x: x, y: y } |
| Or-pattern | 1 \| 2 |
| Integer range | (0 ., (0 . |
| Typed binding | item: int |
Pattern forms and name interpretation
The pattern forms are binding, wildcard, literal, constructor, list, record, nominal-record, or, range, typed. Rest patterns appear in list patterns. A record pattern has at least 1 field. Each field uses field: pattern. A field without a colon and pattern is rejected with TPZ2001 during parsing.
Every constructor pattern has an argument list. A constructor with no payload uses Name(), including None(). List-element bindings have the array element type, and a rest binding has an array type.
Why
A shorthand field and an explicit mapping to the same name both bind that field value under that name. Explicit field mappings preserve this intent and use the same syntax for renaming and nested patterns. Pattern bindings preserve each field type.
Source evidence
- parseRecordPatternFields; parseListPattern; parseConstructorPattern; parseTypedPattern; parsePattern at
b48527d0a174· 341-692 - bindListPattern at
b48527d0a174· 1282-1384 - patternBindingTypeAt at
b48527d0a174· 1578-1648 - bindRecordPattern at
b48527d0a174· 823-856;957-988 - parseRecordPatternFields still accepts a missing colon and subpattern at
9b1bbdeceba9· 341-377 - issues/31 at
b48527d0a174
Use Name(...) to test a constructor and a fresh name or _ to capture or ignore the whole value. A bare constructor name is rejected instead of becoming a catch-all.
Constructor syntax and binding names
Write Name(patterns) in every pattern position. The payload list has the declared arity. A zero-payload constructor uses Name(), including None() and user enum variants such as Empty(). Nesting does not change this syntax. Parenthesized patterns with the wrong payload count retain the existing constructor-arity diagnostic.
Write Name(arguments) in every value position at every arity. Constructor calls use None(), Empty(), or Some(value). Qualified enum construction uses the same argument list, as in Choice.. The grammar rejects a bare prelude None value. User enum constructor names are resolved and checked after parsing.
A bare identifier in a pattern attempts to bind a new name. Its capitalization never selects a constructor role. A typed binding uses the same name rule.
The constructor namespace includes prelude constructors, visible declarations and imports, and variants obtained from the known expected type after resolving aliases. A pattern binder that has the same identifier as one of those constructors is rejected with TPZ5712. This check applies at every nested pattern, to typed binders, and to constructors of another visible type. A value binding does not hide a constructor for this check. Choose a fresh name to capture the value or use _ to ignore it.
None is already a reserved binding name. Without the constructor argument list it is rejected during parsing with TPZ2012. Other bare constructor names reach the namespace check. A removed bare enum pattern is never widened into a catch-all.
Why
Bare and parenthesized nullary constructors denote the same variant without a payload. Parentheses preserve that construction or matching intent and identify constructors at every arity. A bare pattern name attempts to bind. Rejecting constructor-name collisions prevents a former tag test from silently becoming a catch-all. Capitalization, type annotations, nesting, and value shadowing do not change this rule.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248 - issues/31 at
039476b62c76 - inferIdentifierExpression at
f3807a34248e· 594-615 - builtinFunction at
f3807a34248e· 301-327 - enumVariantType at
f3807a34248e· 691-713 - checkedCallResultCandidates at
f3807a34248e· 1319-1340 - emitOrdinaryCallWith at
f3807a34248e· 1091-1175 - call_value at
f3807a34248e· 1030-1037
Selecting a case
LinkCase selection and guards
A match has at least one case and evaluates its scrutinee once. It tries cases in source order and tests the pattern of each case in turn. After a pattern succeeds it installs the bindings and evaluates the guard once if present. The guard has type bool. Bindings are available in the guard and body.
An absent guard counts as true. A false guard continues with the next case, including a case with the same pattern. The first case whose pattern and guard succeed is selected. A catch-all serves as a fallback at its written position. The selected body produces the match value. Other bodies are not evaluated.
Every match must satisfy the exhaustiveness rule during checking.
An arm body is an expression. Write a return statement inside a block, as in case true => { return value }. A return without that block is rejected during parsing with TPZ2001.
Every match follows the exhaustiveness rule.
Why
Evaluating the scrutinee once avoids repeating its effects. Source order makes the exceptional case explicit before a more general case handles the remaining inputs. A failed guard leaves later cases available. Rejecting cases that earlier unguarded patterns make unreachable prevents an arm from being silently hidden. A bare return in an arm and a block containing that return request the same exit from the enclosing function with the same value. The block expresses that transfer through ordinary statement rules while each arm has an expression body.
Source evidence
- parseMatchCase; parseMatch at
b48527d0a174· 910-1002 - inferMatchExpression at
b48527d0a174· 213-311 - emitMatch at
b48527d0a174· 2274-2308 - parseMatchCase still admits a direct return body at
9b1bbdeceba9· 910-945 - Expression parser reports an expected expression for an unsupported atom at
9b1bbdeceba9· 1282-1298 - issues/31 at
b48527d0a174 - inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094 - issues/31 at
72a59a31125f - issues/31 at
1cbf38d3d422
Wildcard pattern
_ matches any scrutinee. It is irrefutable and introduces no bindings.
Why
Ignoring a value should not introduce a name that could hide another binding. The wildcard makes that intent explicit.
Source evidence
- patternBindingTypeAt at
b48527d0a174· 1692-1695 - planPattern at
b48527d0a174· 2055-2094
The selection example records scrutinee evaluation, guard visits, and body visits. The expected trace makes skipped guards and unselected bodies observable.
Bindings and scalar patterns
LinkNames in or-pattern alternatives
Each alternative of an or-pattern must bind the same set of names. Different name sets are rejected during checking with TPZ5710. The TPZ5711 examples reject a shared name with int and string payloads and accept matching int payloads. The full binding-type compatibility algorithm across alternatives remains Pending.
Why
Every successful alternative must provide the names used by the shared guard and body. Equal binding sets prevent a name from being absent on one path.
Source evidence
- bindOrPattern at
b48527d0a174· 1414-1574
Typed binding patterns
For a known scrutinee type that overlaps the annotation, a typed binding has the overlapping part of those two types. The annotation selects the matched type.
Why
The overlap identifies values that can satisfy both the scrutinee type and the annotation. It gives the binding a type valid on a successful match.
Source evidence
- bindNamedPattern at
b48527d0a174· 585-655
Integer range patterns
An integer range pattern compares an int scrutinee with int endpoints. The value must be greater than or equal to the lower endpoint and satisfy the upper comparison. Both comparisons must succeed. The pattern introduces no bindings.
| Pattern operator | Upper comparison |
|---|---|
. | Less than or equal to the upper endpoint |
. | Less than the upper endpoint |
Why
Using the same endpoint operators as range expressions keeps boundary meaning consistent between matching and iteration.
Source evidence
- parseRecordPatternFields; parseListPattern; parseConstructorPattern; parseTypedPattern; parsePattern at
b48527d0a174· 341-692 - planPattern at
b48527d0a174· 2055-2094
A single list rest and its surrounding elements
A pattern after the rest marker is required. Write . to bind the rest or . to ignore it. An omitted pattern is rejected during parsing with TPZ2001.
A list pattern with one rest may put it before, between, or after fixed elements. The input array must contain at least as many elements as the fixed prefix and suffix together. Those elements are matched in order. The rest has type Array<T> and may be empty. Admission of multiple rest elements remains Pending. Their implementation limitation is documented separately.
Why
An omitted rest pattern and an explicit wildcard both discard the unmatched slice. Writing the wildcard preserves that intent and distinguishes it from binding the slice. One rest determines the slice between the fixed prefix and suffix.
Source evidence
- parseListPattern accepts rest elements at
151141d7bb44· 387-448 - bindListPattern types rest as Array at
151141d7bb44· 1282-1378 - planPattern list slice with prefix and suffix at
151141d7bb44· 2224-2243 - Multiple-rest emission refusal at
151141d7bb44· 272-278 - Unsupported emission maps to TPZ6001 at
151141d7bb44· 195-210 - Runtime multiple-rest guard at
151141d7bb44· 1082-1095 - parseListPattern still omits a pattern before a comma or closing bracket at
9b1bbdeceba9· 387-448 - Required patterns dispatch to expression parsing when a token cannot begin another pattern form at
9b1bbdeceba9· 589-654 - Expression parser reports an expected expression for an unsupported atom at
9b1bbdeceba9· 1282-1298 - issues/31 at
b48527d0a174
Range pattern boundaries
Write a range pattern as (lower . or (lower .. The outer parentheses belong to the range pattern and close before the case arrow. Endpoints use the ordinary additive-expression grammar and must have type int. An arm may produce a lambda without taking the case arrow into an endpoint. An unwrapped range pattern is rejected during parsing with TPZ2001.
Why
A bare range pattern and the same range inside parentheses test the same endpoints with the same inclusive or exclusive upper bound. The parentheses preserve that interval intent and give the pattern a closing boundary before the case arrow. This separates endpoint expressions from an arm that produces a lambda without changing expression parsing according to the endpoint spelling.
Source evidence
- identifierStartsBindingPattern; parseExpressionPattern; parsePrimaryPattern at
72a59a31125f· 537-658 - expect uses the existing TPZ2001 expected-token failure at
72a59a31125f· 195-204 - issues/31 at
26da6a582932
Accepted matches and coverage
LinkCoverage of a known bool
The bool examples apply the exhaustiveness rule to true, false. The missing-case example reports TPZ5021.
Why
These examples show the closed-type coverage required by the general exhaustiveness rule.
Source evidence
- coverageGap at
b48527d0a174· 376-505 - inferMatchExpression at
b48527d0a174· 213-311 - issues/31 at
1cbf38d3d422
The exhaustiveness rule applies to closed and open types, including nested payloads.
Cases are tried in source order. A later case may overlap an earlier case when it still matches some reachable input. An unguarded wildcard or total binding selects at its written position. Earlier unguarded cases that cover every value a later pattern can match make that later case a checking error. Every arm is still type-checked.
Reachable overlapping cases
Cases are tried in source order. A case may overlap an earlier case if it can match an input that reaches it. A specific case can therefore precede a general case. An unguarded wildcard or total binding is an ordinary case and acts as a fallback at its written position. There is no separate residual fallback selection.
A later case is unreachable when the union of preceding unguarded patterns covers every value its pattern could match. This includes a case shadowed by a single catch-all or by several earlier cases. Pattern typing runs before this comparison. A guard cannot establish unconditional coverage because it may fail. Overlapping cases with reachable inputs remain valid even when their intersection is unknown.
Alternatives of one or-pattern describe one case domain. Their overlap does not make a later case unreachable. Binding consistency is checked by its separate rule. All case bodies are checked even when their patterns are unreachable.
An unreachable case is rejected during checking with TPZ5713. A typed pattern incompatible with the known scrutinee type still uses TPZ5001. Match completeness follows the exhaustiveness rule.
Why
An exceptional case can intentionally precede a general case. That use of overlap has a different intent from dividing a domain into mutually exclusive categories. Source order expresses which matching case takes priority. A failed guard lets later cases handle the input. Rejecting a case hidden by earlier unguarded patterns prevents code order from silently disabling a branch.
Source evidence
- inferMatchExpression checks every case at
151141d7bb44· 231-294 - bindNamedPattern overlap test at
151141d7bb44· 614-631 - emitMatch first-match chain at
151141d7bb44· 2274-2308 - inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094 - issues/31 at
72a59a31125f - issues/31 at
1cbf38d3d422
Every match is exhaustive
Every match must be exhaustive during checking. A closed type has cases that can all be enumerated. Enums, Result, Option, bool, and closed unions must cover every case. Coverage also examines closed types inside payloads. For example, Option<bool> is covered by Some(true), Some(false), and None().
Open types include int, float, string, arrays, ranges, type parameters, and other types whose cases cannot be enumerated. A match over an open type ends with an unguarded wildcard or name pattern. A guarded arm does not contribute to coverage, even when its guard is a literal true.
Missing coverage is rejected during checking with TPZ5021. TPZ4006 is retired in 6.0.0 and is replaced by TPZ5021. This rule does not change refutable let or for patterns.
Case selection follows source order. TPZ5021 reports missing coverage. TPZ4006 records the retired runtime failure.
Why
Checking every match removes execution-time match failure. A program handles each input before it runs.
Source evidence
- issues/31 at
1cbf38d3d422 - inferMatchExpression at
1cbf38d3d422· 220-306 - coverageCoversAt and coverageGap at
1cbf38d3d422· 268-462
Examples and expected results
LinkEach example lists its expected result and the recorded public CLI observation.
Observe the first successful case and guard
Linkimport std.iofunction readValue() -> int {io.writeLine("scrutinee")2}function testCase(label: string, answer: bool) -> bool {io.writeLine(label)answer}function selectedBody(label: string) -> string {io.writeLine("body: {label}")label}let answer = match (readValue()) {case 1 if (testCase("unmatched guard", true)) => selectedBody("one")case 2 if (testCase("selected guard", true)) => selectedBody("selected")case _ => selectedBody("fallback")}io.writeLine(answer)
scrutinee selected guard body: selected selected
Expected result
Expected result
Execution completes. Exit code 0.
scrutineeselected guardbody: selectedselected
The input selects one explicit pattern. Its guard and body run once. A guard belonging to another pattern is not evaluated. This revised expectation has no current product receipt.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Literal alternatives and integer range boundaries
Linkimport std.iofunction classify(value: int) -> string {match (value) {case 1 | 2 => "choice"case (3..<6) => "inside"case (7..9) => "closed"case _ => "outside"}}io.writeLine(classify(1))io.writeLine(classify(2))io.writeLine(classify(3))io.writeLine(classify(5))io.writeLine(classify(6))io.writeLine(classify(9))io.writeLine(classify(10))
choice choice inside inside outside closed outside
Expected result
Expected result
Execution completes. Exit code 0.
choicechoiceinsideinsideoutsideclosedoutside
Observed assertions · passed · 5.21.3
Exit code 0.
choicechoiceinsideinsideoutsideclosedoutside
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
The expected values exercise both endpoints of the half-open interval and the upper endpoint of the closed interval. They do not establish coverage of an integer domain.
Source evidence
- parseRecordPatternFields; parseListPattern; parseConstructorPattern; parseTypedPattern; parsePattern at
b48527d0a174· 341-692 - planPattern at
b48527d0a174· 2055-2094 - emitMatch at
b48527d0a174· 2274-2308
Check field, rest, typed and constructor bindings
Linkrecord Point {x: int,y: int,}function pointTotal(value: Point) -> int {match (value) {case Point { x: 0, y: other } => othercase remaining => remaining.x + remaining.y}}function structuralTotal(value: { x: int, y: int }) -> int {match (value) {case { x: 0, y: other } => othercase remaining => remaining.x + remaining.y}}function remainder(values: Array<int>) -> Array<int> {match (values) {case [_, ..tail] => tailcase _ => []}}function integerPart(value: int | string) -> int {match (value) {case item: int => itemcase _ => 0}}function selected(value: Result<int, int>) -> int {match (value) {case Ok(item) | Err(item) => item}}
Expected result
Expected result
Checking accepts the program. Exit code 0.
This is a complete checking fixture. Explicit result types make the field, remainder and typed-binding uses visible to checking. The record declaration and collection literals provide context; their construction rules are owned by other Language pages.
Source evidence
- parseRecordPatternFields; parseListPattern; parseConstructorPattern; parseTypedPattern; parsePattern at
b48527d0a174· 341-692 - bindListPattern at
b48527d0a174· 1282-1384 - bindNamedPattern at
b48527d0a174· 585-655 - bindOrPattern at
b48527d0a174· 1414-1574 - bindRecordPattern at
b48527d0a174· 823-856;957-988 - issues/31 at
1cbf38d3d422
A known bool with one missing case
Linkfunction flag(value: bool) -> int {match (value) {case true => 1}}
No stdout.
error[TPZ5021]: non-exhaustive match: missing `false`
--> example.tpz:2:5
|
2 | match value {
| ^^^^^^^^^^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5021missing-unguarded-false-case-for-known-bool atexample.tpz:2:5
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5021]: non-exhaustive match: missing `false`--> example.tpz:2:5|2 | match value {| ^^^^^^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5021; cause missing-unguarded-false-case-for-known-bool; primary location asserted; additional diagnostics allow
The independent rejection expectation requires the match's missing bool coverage. A different parsing or name-resolution failure does not prove this claim. Additional diagnostics may be present; exact prose and diagnostic count are not fixed.
Source evidence
- coverageGap at
b48527d0a174· 376-505 - inferMatchExpression at
b48527d0a174· 213-311
Cover both bool values
Linkfunction flag(value: bool) -> int {match (value) {case true => 1case false => 0}}
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
The repaired program adds the missing false case. Its expected result is successful checking of the complete fixture.
Source evidence
- coverageGap at
b48527d0a174· 376-505 - inferMatchExpression at
b48527d0a174· 213-311
Reject different names across alternatives
Linkfunction selected(value: Result<int, int>) -> int {match (value) {case Ok(left) | Err(right) => 0}}
No stdout.
error[TPZ5710]: or-pattern alternatives must bind the same names — `left` is bound in one alternative but not another --> example.tpz:3:14 | 3 | case Ok(left) | Err(right) => 0 | ^^^^^^^^ error[TPZ5710]: or-pattern alternatives must bind the same names — `right` is bound in one alternative but not another --> example.tpz:3:25 | 3 | case Ok(left) | Err(right) => 0 | ^^^^^^^^^^ example.tpz: 2 type diagnostics
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5710or-pattern-alternatives-bind-different-name-sets atexample.tpz:3:14
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5710]: or-pattern alternatives must bind the same names — `left` is bound in one alternative but not another--> example.tpz:3:14|3 | case Ok(left) | Err(right) => 0| ^^^^^^^^error[TPZ5710]: or-pattern alternatives must bind the same names — `right` is bound in one alternative but not another--> example.tpz:3:25|3 | case Ok(left) | Err(right) => 0| ^^^^^^^^^^example.tpz: 2 type diagnostics
- Passed · expected nonzero; observed 1
- Passed · checking TPZ5710; cause or-pattern-alternatives-bind-different-name-sets; primary location asserted; additional diagnostics allow
The intended cause is the unequal binding-name sets, not constructor arity or a missing case. The checker can report both directions of the disagreement. The accompanying accepted selected function binds item in both alternatives.
Source evidence
- bindOrPattern at
b48527d0a174· 1414-1574
An unguarded catch-all makes the following case unreachable
Linklet value: int = 7let selected = match (value) {case _ => "first"case later: int => "second"}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5713match-unreachable-case atexample.tpz:4:10
The first wildcard covers every integer. The later typed binding can never be selected and is rejected during checking. The rejection expects TPZ5713 during checking with cause match-.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
A later wildcard is covered by two earlier unguarded cases
Linklet value: bool = truelet result = match (value) {case true => "yes"case false => "no"case _ => "hidden"}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5713match-unreachable-case atexample.tpz:5:10
The true and false cases together cover every Boolean value. Their union makes the final wildcard unreachable. The rejection expects TPZ5713 during checking with cause match-.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Every case is checked even after a catch-all
Linklet value: int = 7let selected = match (value) {case _ => "first"case impossible: string => "second"}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001pattern-cannot-match-scrutinee-type atexample.tpz:4:10
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Source evidence
- inferMatchExpression checks every case at
151141d7bb44· 231-294 - bindNamedPattern overlap test at
151141d7bb44· 614-631 - emitMatch first-match chain at
151141d7bb44· 2274-2308
One rest may precede, separate, or follow fixed elements
Linkimport std.iolet middle = match ([1, 2, 3, 4]) {case [first, ..rest, last] => "{first} {rest.length} {last}"case _ => "miss"}let leading = match ([1, 2, 3, 4]) {case [..rest, last] => "{rest.length} {last}"case _ => "miss"}let trailing = match ([1, 2, 3, 4]) {case [first, ..rest] => "{first} {rest.length}"case _ => "miss"}let empty: Array<int> = []let remaining = match (empty) {case [..rest] if (rest.length == 0) => rest.lengthcase remaining => remaining.length}io.writeLine(middle)io.writeLine(leading)io.writeLine(trailing)io.writeLine("{remaining}")
1 2 4 3 4 1 3 0
Expected result
Expected result
Execution completes. Exit code 0.
1 2 43 41 30
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Source evidence
- parseListPattern accepts rest elements at
151141d7bb44· 387-448 - bindListPattern types rest as Array at
151141d7bb44· 1282-1378 - planPattern list slice with prefix and suffix at
151141d7bb44· 2224-2243 - Multiple-rest emission refusal at
151141d7bb44· 272-278 - Unsupported emission maps to TPZ6001 at
151141d7bb44· 195-210 - Runtime multiple-rest guard at
151141d7bb44· 1082-1095 - issues/31 at
1cbf38d3d422
Multiple rest elements retain an intended emission refusal
Linklet value = match ([1, 2]) {case [..left, ..right] => 0case _ => 1}
Expected result
Expected result
Rejection during emit. Nonzero exit.
TPZ6001multiple-rest-emission-refusal
The required primary-location contract is Pending.
The existing Rust backend refuses this shape before generating output. Current SELF emission does not establish the same refusal. The rejection expectation is retained; its exact product diagnostic and location await verification.
Source evidence
- parseListPattern accepts rest elements at
151141d7bb44· 387-448 - bindListPattern types rest as Array at
151141d7bb44· 1282-1378 - planPattern list slice with prefix and suffix at
151141d7bb44· 2224-2243 - Multiple-rest emission refusal at
151141d7bb44· 272-278 - Unsupported emission maps to TPZ6001 at
151141d7bb44· 195-210 - Runtime multiple-rest guard at
151141d7bb44· 1082-1095
Rejecting a structural field shorthand
Linklet { value } = { value: 1 }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001record-pattern-field-colon atexample.tpz:1:13
Write the field name, a colon, and its binding pattern. The missing colon is reported at the closing brace.
Source evidence
- parseRecordPatternFields still accepts a missing colon and subpattern at
9b1bbdeceba9· 341-377 - named parameter annotations and bare-self syntax at
151141d7bb44· 148-183
Rejecting a nominal field shorthand
Linkrecord Point { x: int }let Point { x } = Point { x: 1 }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001record-pattern-field-colon atexample.tpz:2:15
Write the field name, a colon, and its binding pattern. The missing colon is reported at the closing brace.
Source evidence
- parseRecordPatternFields still accepts a missing colon and subpattern at
9b1bbdeceba9· 341-377 - named parameter annotations and bare-self syntax at
151141d7bb44· 148-183
Binding and renaming fields explicitly
Linkimport std.iorecord Point { x: int, y: int }let { value: value } = { value: 7 }let Point { x: first, y: second } = Point { x: 2, y: 3 }io.writeLine("{value} {first} {second}")
7 2 3
Expected result
Expected result
Execution completes. Exit code 0.
7 2 3
The same field mapping syntax binds a field to its own name or to a different name.
Source evidence
- bindRecordPattern at
b48527d0a174· 823-856;957-988
Rejecting a return without an arm block
Linkfunction choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => return 7case false => 3}return value}0}
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001match-arm-return-needs-block atexample.tpz:4:26
A return statement needs a block when used as a match arm body. The rejection points to the return keyword.
Source evidence
- parseMatchCase still admits a direct return body at
9b1bbdeceba9· 910-945 - Expression parser reports an expected expression for an unsupported atom at
9b1bbdeceba9· 1282-1298
Returning through a match arm block
Linkimport std.iofunction choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => { return 7 }case false => 3}return value}0}io.writeLine("{choose(true)}")io.writeLine("{choose(false)}")
7 3
Expected result
Expected result
Execution completes. Exit code 0.
73
The true arm returns from the function through the surrounding binding and loop. The false arm supplies a value to the ordinary return statement.
Source evidence
- emitMatch at
b48527d0a174· 2274-2308 - checkReturn at
b48527d0a174· 613-662 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991
Rejecting a rest marker without a pattern
Linklet count = match ([1, 2]) {case [..] => 2}
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001list-rest-pattern-required atexample.tpz:2:13
Write . to ignore the rest. The error points to the delimiter where the required pattern is missing.
Source evidence
- parseListPattern still omits a pattern before a comma or closing bracket at
9b1bbdeceba9· 387-448 - Required patterns dispatch to expression parsing when a token cannot begin another pattern form at
9b1bbdeceba9· 589-654 - Expression parser reports an expected expression for an unsupported atom at
9b1bbdeceba9· 1282-1298
Rejecting an omitted middle rest pattern
Linklet total = match ([1, 2, 3]) {case [first, .., last] => first + lastcase _ => 0}
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001list-rest-pattern-required atexample.tpz:2:20
Write . to ignore the rest. The error points to the delimiter where the required pattern is missing.
Source evidence
- parseListPattern still omits a pattern before a comma or closing bracket at
9b1bbdeceba9· 387-448 - Required patterns dispatch to expression parsing when a token cannot begin another pattern form at
9b1bbdeceba9· 589-654 - Expression parser reports an expected expression for an unsupported atom at
9b1bbdeceba9· 1282-1298
Ignoring a rest with an explicit wildcard
Linkimport std.iolet middle = match ([1, 2, 3, 4]) {case [first, .._, last] => "{first} {last}"case _ => "miss"}let leading = match ([1, 2, 3, 4]) {case [.._, last] => lastcase _ => 0}let trailing = match ([1, 2, 3, 4]) {case [first, .._] => firstcase _ => 0}let empty: Array<int> = []let ignored = match (empty) {case [.._] if (empty.length == 0) => truecase _ => false}io.writeLine(middle)io.writeLine("{leading}")io.writeLine("{trailing}")io.writeLine("{ignored}")
1 4 4 1 true
Expected result
Expected result
Execution completes. Exit code 0.
1 441true
The wildcard ignores a leading, middle, or trailing slice. A rest wildcard also matches an empty array.
Source evidence
- bindListPattern types rest as Array at
151141d7bb44· 1282-1378 - planPattern list slice with prefix and suffix at
151141d7bb44· 2224-2243 - planPattern at
b48527d0a174· 2055-2094 - issues/31 at
1cbf38d3d422
Constructor patterns keep parentheses at every arity
Linkimport std.ioenum Choice { Empty, Filled(int), Pair(int, int) }function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => numbercase Pair(left, right) => left + right}}function nested(value: Option<Choice>) -> int {match (value) {case None() => -1case Some(Empty()) => 0case Some(Filled(number)) => numbercase Some(Pair(left, right)) => left + right}}io.writeLine("{read(Choice.Empty())}")io.writeLine("{read(Choice.Filled(7))}")io.writeLine("{read(Choice.Pair(2, 3))}")io.writeLine("{nested(None())}")io.writeLine("{nested(Some(Choice.Empty()))}")
0 7 5 -1 0
Expected result
Expected result
Execution completes. Exit code 0.
075-10
Value expressions and constructor patterns keep parentheses at every arity. The nested match covers every Option and Choice variant.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
A fresh bare name captures an enum value
Linkimport std.ioenum Choice { Empty, Filled(int) }function identity(value: Choice) -> Choice {match (value) {case captured => captured}}function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => number}}io.writeLine("{read(identity(Choice.Filled(8)))}")
8
Expected result
Expected result
Execution completes. Exit code 0.
8
The bare name captured is a binding in every expected type. SELF currently treats an unknown top-level enum-case spelling as a variant typo, so this intended acceptance is recorded under the implementation limitation.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
A fresh typed name captures the whole enum
Linkimport std.ioenum Choice { Empty, Filled(int) }function identity(value: Choice) -> Choice {match (value) {case captured: Choice => captured}}function upper(value: int) -> int {match (value) {case UPPER => UPPER}}function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => number}}io.writeLine("{read(identity(Choice.Filled(8)))}")io.writeLine("{upper(9)}")
8 9
Expected result
Expected result
Execution completes. Exit code 0.
89
An annotation refines the matched type. It does not turn a binding into a constructor or permit a constructor-name collision. An uppercase fresh name is still a binding.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
A bare nullary variant cannot become a catch-all
Linkenum Choice { Empty, Filled(int) }function read(value: Choice) -> int {match (value) {case Empty => 0case Filled(number) => number}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5712pattern-constructor-binding-collision atexample.tpz:4:14
Write Empty() to test the nullary variant. The removed spelling must be rejected instead of accepting every Choice value.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
Nested constructors require the same argument list
Linkenum Choice { Empty, Filled(int) }function read(value: Option<Choice>) -> int {match (value) {case Some(Empty) => 0case _ => 1}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5712pattern-constructor-binding-collision atexample.tpz:4:19
Write Some(Empty()). Nesting does not turn a constructor spelling into a binding.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
A type annotation cannot bypass a constructor-name collision
Linkenum Choice { Empty, Filled(int) }function read(value: Choice) -> int {match (value) {case Empty: Choice => 0}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5712pattern-constructor-binding-collision atexample.tpz:4:14
Choose a fresh name such as captured: Choice for a typed catch-all, or write Empty() to test the variant.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
Constructor names stay reserved for patterns across expected types
Linkenum Mode { Idle }function read(value: int) -> int {match (value) {case Idle => 0}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5712pattern-constructor-binding-collision atexample.tpz:4:14
Idle is visible in the constructor namespace. Changing the scrutinee type or introducing a value with that name does not make this spelling a pattern binding.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
None needs parentheses in a constructor pattern
Linkfunction read(value: Option<int>) -> int {match (value) {case Some(number) => numbercase None => 0}}
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2012pattern-reserved-none-binding atexample.tpz:4:14
Write None() for the constructor pattern. None remains a reserved binding name. The rejection uses the existing TPZ2012 identity and reserved-None producer message.
Source evidence
- parseIdentifierPattern at
72a59a31125f· 537-585 - patternBindingTypeAt at
72a59a31125f· 1578-1648 - rejectReservedBinding at
72a59a31125f· 175-186 - bindConstructorPattern at
72a59a31125f· 1040-1248
None needs parentheses in a constructor value
Linklet maybe: Option<int> = None
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2012pattern-reserved-none-binding atexample.tpz:1:26
Write None() when constructing an absent Option value. Bare None is rejected during parsing with TPZ2012 and the same reserved-None cause as the bare pattern example.
Source evidence
- inferIdentifierExpression at
f3807a34248e· 594-615 - builtinFunction at
f3807a34248e· 301-327 - enumVariantType at
f3807a34248e· 691-713 - checkedCallResultCandidates at
f3807a34248e· 1319-1340 - emitOrdinaryCallWith at
f3807a34248e· 1091-1175 - call_value at
f3807a34248e· 1030-1037
Specific cases before a general fallback
Linkimport std.iofunction first(value: int) -> string {match (value) {case 1 => "one"case _ => "rest"}}function typed(value: int) -> string {match (value) {case 1 => "one"case captured: int => "rest"}}function last(value: int) -> string {match (value) {case 2 => "two"case 1 => "one"case _ => "rest"}}io.writeLine("{first(1)} {typed(1)} {last(1)}")io.writeLine("{first(3)} {typed(3)} {last(3)}")
one one one rest rest rest
Expected result
Expected result
Execution completes. Exit code 0.
one one onerest rest rest
Specific cases are tried first. The wildcard and total typed binding handle values that reach them. Putting the general case first makes the following specific cases unreachable and causes a checking error.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
A failed guard lets the same pattern match later
Linkimport std.iofunction check(label: string, ok: bool) -> bool {io.writeLine(label)ok}function choose(value: int) -> string {match (value) {case 2 if (check("guard for two", false)) => "guarded two"case 2 => "unguarded two"case _ => "other"}}io.writeLine(choose(2))io.writeLine(choose(3))
guard for two unguarded two other
Expected result
Expected result
Execution completes. Exit code 0.
guard for twounguarded twoother
The first case matches 2 but its guard is false. Selection continues to the next case, which uses the same pattern without a guard. The other input reaches the wildcard.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Cover type and list domains with a record fallback
Linkimport std.iofunction kind(value: int | string) -> string {match (value) {case number: int => "integer"case text: string => "string"}}function row(value: { x: int, y: int }) -> string {match (value) {case { x: 0, y: _ } => "zero"case { x: 1, y: _ } => "one"case _ => "other"}}function size(values: Array<int>) -> string {match (values) {case [] => "empty"case remaining => "nonempty"}}io.writeLine(kind(1))io.writeLine(kind("a"))io.writeLine(row({ x: 0, y: 9 }))io.writeLine(row({ x: 1, y: 9 }))io.writeLine(size([]))io.writeLine(size([1]))
integer string zero one empty nonempty
Expected result
Expected result
Execution completes. Exit code 0.
integerstringzerooneemptynonempty
The typed cases cover the union. The list cases cover empty and nonempty arrays. The record match reaches its final case for other field values.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094 - issues/31 at
1cbf38d3d422
Adjacent half-open and closed intervals do not overlap
Linkimport std.iofunction band(value: int) -> string {match (value) {case (0 ..< 5) => "low"case (5 .. 9) => "high"case _ => "outside"}}io.writeLine(band(0))io.writeLine(band(4))io.writeLine(band(5))io.writeLine(band(9))io.writeLine(band(10))
low low high high outside
Expected result
Expected result
Execution completes. Exit code 0.
lowlowhighhighoutside
The first interval excludes 5 and the second includes it. The wildcard follows both intervals and handles values outside them.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Dynamic bounds with a later catch-all
Linkimport std.iofunction bound(value: int) -> int { value }function band(value: int, low: int, high: int) -> string {match (value) {case (low ..< bound(high)) => "inside"case _ => "outside"}}io.writeLine(band(2, 0, 5))io.writeLine(band(5, 0, 5))
inside outside
Expected result
Expected result
Execution completes. Exit code 0.
insideoutside
The dynamic range is tried before the wildcard. Its runtime bound test selects the first case when it succeeds. Otherwise the wildcard handles the value.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
A repeated unguarded literal case is unreachable
Linkfunction choose(value: int) -> int {match (value) {case 1 => 10case 1 => 20case _ => 0}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5713match-unreachable-case atexample.tpz:4:14
The first unguarded case already handles every value the second identical literal pattern can match. The rejection expects TPZ5713 during checking with cause match-.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
A specific constructor can precede a general constructor
Linkimport std.iofunction choose(value: Option<int>) -> string {match (value) {case Some(0) => "zero"case Some(number) => "other {number}"case None() => "none"}}io.writeLine(choose(Some(0)))io.writeLine(choose(Some(7)))io.writeLine(choose(None()))
zero other 7 none
Expected result
Expected result
Execution completes. Exit code 0.
zeroother 7none
Some(0) matches both constructor patterns. The first handles zero while the next handles other payloads.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Overlapping record patterns leave later inputs
Linkimport std.iofunction choose(value: { x: int, y: int }) -> string {match (value) {case { x: 0 } => "x"case { y: 0 } => "y"case _ => "other"}}io.writeLine(choose({ x: 0, y: 0 }))io.writeLine(choose({ x: 3, y: 0 }))io.writeLine(choose({ x: 3, y: 4 }))
x y other
Expected result
Expected result
Execution completes. Exit code 0.
xyother
Both field tests succeed for the first value, so the first case wins. A value with nonzero x and zero y still reaches the second case.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Overlapping list patterns follow source order
Linkimport std.iofunction choose(values: Array<int>) -> string {match (values) {case [1, .._] => "starts with one"case [_, 1] => "ends with one"case _ => "other"}}io.writeLine(choose([1, 1]))io.writeLine(choose([2, 1]))io.writeLine(choose([2, 2]))
starts with one ends with one other
Expected result
Expected result
Execution completes. Exit code 0.
starts with oneends with oneother
The first pattern handles arrays starting with one. The second can still handle arrays ending with one that do not start with one.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Overlapping guarded cases follow source order
Linkimport std.iofunction choose(value: int) -> string {match (value) {case number if (number > 0) => "positive"case number if (number <= 0) => "nonpositive"case _ => "other"}}io.writeLine(choose(4))io.writeLine(choose(0))
positive nonpositive
Expected result
Expected result
Execution completes. Exit code 0.
positivenonpositive
Both binding patterns accept every integer. The first guard decides whether to continue to the next case, and neither guard establishes unconditional coverage.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Overlapping alternatives describe one match case
Linkimport std.iofunction choose(value: int) -> string {match (value) {case 1 | 1 => "one"case _ => "other"}}io.writeLine(choose(1))io.writeLine(choose(3))
one other
Expected result
Expected result
Execution completes. Exit code 0.
oneother
Two alternatives can recognize the same input without creating different case priorities. Both alternatives select the same body.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Overlapping integer intervals keep later cases reachable
Linkimport std.iofunction choose(value: int) -> string {match (value) {case (0 .. 5) => "lower"case (5 ..< 9) => "upper"case _ => "outside"}}io.writeLine(choose(5))io.writeLine(choose(7))io.writeLine(choose(10))
lower upper outside
Expected result
Expected result
Execution completes. Exit code 0.
lowerupperoutside
Five belongs to both intervals and selects the first. Seven reaches the second interval. The intervals do not need a disjointness proof.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Dynamic range overlap uses ordinary source order
Linkimport std.iofunction bound(value: int) -> int { value }function choose(value: int, low: int, high: int) -> string {match (value) {case (low ..< bound(high)) => "range"case 100 => "hundred"case _ => "outside"}}io.writeLine(choose(100, 0, 5))io.writeLine(choose(100, 0, 105))io.writeLine(choose(200, 0, 105))
hundred range outside
Expected result
Expected result
Execution completes. Exit code 0.
hundredrangeoutside
When the dynamic range excludes 100, the literal case succeeds. When it includes 100, the earlier range succeeds. An unknown intersection does not require rejection.
Source evidence
- inferMatchExpression at
72a59a31125f· 216-316 - emitMatch at
72a59a31125f· 2274-2308 - planPattern at
72a59a31125f· 2055-2094
Reject an unwrapped range pattern
Linklet value = match (2) {case 1 .. 3 => 1case _ => 0}
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001range-pattern-parentheses atexample.tpz:2:10
Source evidence
- identifierStartsBindingPattern; parseExpressionPattern; parsePrimaryPattern at
72a59a31125f· 537-658 - expect uses the existing TPZ2001 expected-token failure at
72a59a31125f· 195-204
A delimited range precedes a lambda-valued arm
Linkimport std.iolet low = 1let high = 3let chosen = match (2) {case (low .. high) => (value: int) => value + 1case _ => (value: int) => value}io.writeLine("{chosen(6)}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Source evidence
- identifierStartsBindingPattern; parseExpressionPattern; parsePrimaryPattern at
72a59a31125f· 537-658 - expect uses the existing TPZ2001 expected-token failure at
72a59a31125f· 195-204
A missing enum variant is rejected
Linkenum Choice { Left(int), Right(int) }function read(value: Choice) -> int {match (value) {case Left(number) => number}}
Expected result
Source evidence
- issues/31 at
1cbf38d3d422
Both enum variants are covered
Linkimport std.ioenum Choice { Left(int), Right(int) }function read(value: Choice) -> int {match (value) {case Left(number) => numbercase Right(number) => number + 10}}io.writeLine("{read(Choice.Left(2))}")io.writeLine("{read(Choice.Right(3))}")
2 13
Expected result
Expected result
Execution completes. Exit code 0.
213
Source evidence
- issues/31 at
1cbf38d3d422
An integer match needs a final fallback
Linkfunction read(value: int) -> int {match (value) {case 0 => 10}}
Expected result
Source evidence
- issues/31 at
1cbf38d3d422
A final name pattern covers remaining integers
Linkimport std.iofunction read(value: int) -> int {match (value) {case 0 => 10case remaining => remaining}}io.writeLine("{read(0)}")io.writeLine("{read(7)}")
10 7
Expected result
Expected result
Execution completes. Exit code 0.
107
Source evidence
- issues/31 at
1cbf38d3d422
Guarded bool arms do not establish coverage
Linkfunction read(value: bool) -> int {match (value) {case true if (true) => 1case false if (true) => 0}}
Expected result
Source evidence
- issues/31 at
1cbf38d3d422
A missing nested bool payload is rejected
Linkfunction read(value: Option<bool>) -> int {match (value) {case Some(true) => 1case None() => -1}}
Expected result
Source evidence
- issues/31 at
1cbf38d3d422
Option bool covers both payloads and None
Linkimport std.iofunction read(value: Option<bool>) -> int {match (value) {case Some(true) => 1case Some(false) => 0case None() => -1}}let flags = map { "on": true, "off": false }io.writeLine("{read(flags.get("on"))}")io.writeLine("{read(flags.get("off"))}")io.writeLine("{read(flags.get("missing"))}")
1 0 -1
Expected result
Expected result
Execution completes. Exit code 0.
10-1
Map lookup supplies Some and None values. Exhaustiveness includes each closed bool payload inside Some.
Source evidence
- issues/31 at
1cbf38d3d422
Pending contracts
LinkRelated diagnostics and owners
LinkOr-pattern alternatives bind different names
See Blocks and control flow for if let, while let, and return; Expressions and operators for expression grouping and range expressions; and Functions and generics for function declarations and return inference. A pattern range is defined on this page; the expression form has its own owner.
Native emitter cannot lower this construct yet