1686 lines
38 KiB
Markdown
1686 lines
38 KiB
Markdown
|
|
# @match Macro Implementation Specification
|
|||
|
|
|
|||
|
|
**✅ Status**: **Phase 19 完了(2025-10-08)** - 正規match構文として実装済み
|
|||
|
|
|
|||
|
|
**Created**: 2025-10-08
|
|||
|
|
**Target Phase**: ~~Phase 20 (VariantBox)~~ **✅ Phase 19完了**
|
|||
|
|
**Strategy**: ~~Choice A'' (macro-only)~~ **正規match式として実装**
|
|||
|
|
**MIR Compatibility**: MIR16 Frozen (no new instructions) ✅
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎉 実装完了状況(2025-10-08)
|
|||
|
|
|
|||
|
|
- ✅ **match式 Parser**: `src/parser/expr/match_expr.rs` (392行)
|
|||
|
|
- ✅ **Literal patterns**: 整数・文字列・bool リテラルマッチング対応
|
|||
|
|
- ✅ **Type patterns**: Box型パターンマッチング対応
|
|||
|
|
- ✅ **Guards**: `if` ガード条件対応
|
|||
|
|
- ⚠️ **実装方式**: `@match`マクロではなく、正規`match`**式**として実装
|
|||
|
|
- ⚠️ **脱糖化**: マクロではなくParser/MIR段階で直接処理
|
|||
|
|
|
|||
|
|
**実装場所**:
|
|||
|
|
- Parser: `src/parser/expr/match_expr.rs` (392行)
|
|||
|
|
- AST: `ASTNode::Match` variant
|
|||
|
|
- MIR: if-elseチェインに直接変換
|
|||
|
|
|
|||
|
|
**使用例** (実装済み):
|
|||
|
|
```hakorune
|
|||
|
|
local result = match value {
|
|||
|
|
0 => "zero"
|
|||
|
|
1 => "one"
|
|||
|
|
n if n > 10 => "large"
|
|||
|
|
_ => "other"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 Table of Contents
|
|||
|
|
|
|||
|
|
1. [Design Goals](#design-goals)
|
|||
|
|
2. [Parser Changes](#parser-changes)
|
|||
|
|
3. [Macro Desugaring Rules](#macro-desugaring-rules)
|
|||
|
|
4. [Pattern Types Support](#pattern-types-support)
|
|||
|
|
5. [Edge Cases Handling](#edge-cases-handling)
|
|||
|
|
6. [Test Suite Design](#test-suite-design)
|
|||
|
|
7. [Implementation Task Breakdown](#implementation-task-breakdown)
|
|||
|
|
8. [Integration with @enum](#integration-with-enum)
|
|||
|
|
9. [Desugaring Algorithm](#desugaring-algorithm)
|
|||
|
|
10. [Error Handling Design](#error-handling-design)
|
|||
|
|
11. [Risk Analysis](#risk-analysis)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Design Goals
|
|||
|
|
|
|||
|
|
### Philosophy Alignment
|
|||
|
|
- ✅ **Everything-is-Box**: Result/Option are existing Box types with `_tag`/`_value` fields
|
|||
|
|
- ✅ **MIR16 Frozen**: Desugar to existing if/else/compare MIR instructions
|
|||
|
|
- ✅ **Macro-Only**: No runtime support, pure AST transformation
|
|||
|
|
- ✅ **Fail-Fast**: Runtime panic for non-exhaustive matches (compile-time check is future work)
|
|||
|
|
|
|||
|
|
### Technical Goals
|
|||
|
|
- Pattern matching for Result/Option (existing boxes)
|
|||
|
|
- Variable binding extraction (`Ok(v)` → `local v = result._value`)
|
|||
|
|
- Exhaustiveness check via runtime panic
|
|||
|
|
- Expression-level match (return value support)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 Parser Changes
|
|||
|
|
|
|||
|
|
### File: `src/parser/mod.rs`
|
|||
|
|
|
|||
|
|
### 1. New AST Node Variant
|
|||
|
|
|
|||
|
|
Add to `ASTNode` enum in `src/ast.rs`:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
/// Pattern match expression: @match expr { Pattern(vars) => body ... }
|
|||
|
|
MatchPattern {
|
|||
|
|
scrutinee: Box<ASTNode>,
|
|||
|
|
arms: Vec<MatchArm>,
|
|||
|
|
span: Span,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. New Struct Definitions
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
/// Match arm: Pattern(x, y) => expression/block
|
|||
|
|
#[derive(Debug, Clone, PartialEq)]
|
|||
|
|
pub struct MatchArm {
|
|||
|
|
pub pattern: Pattern,
|
|||
|
|
pub body: Vec<ASTNode>, // Can be single expression or multi-statement block
|
|||
|
|
pub span: Span,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Pattern types
|
|||
|
|
#[derive(Debug, Clone, PartialEq)]
|
|||
|
|
pub enum Pattern {
|
|||
|
|
/// Variant pattern: Ok(v), Err(e), Some(x), None
|
|||
|
|
Variant {
|
|||
|
|
name: String, // "Ok", "Err", "Some", "None"
|
|||
|
|
bindings: Vec<String>, // ["v"], ["e"], ["x"], []
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
/// Wildcard pattern: _
|
|||
|
|
Wildcard {
|
|||
|
|
span: Span,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Parser Method: `parse_match_pattern()`
|
|||
|
|
|
|||
|
|
Add to `src/parser/statements/mod.rs`:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
fn parse_match_pattern(&mut self) -> Result<ASTNode, ParseError> {
|
|||
|
|
let start = self.current_token().line;
|
|||
|
|
|
|||
|
|
// Consume @match
|
|||
|
|
self.advance();
|
|||
|
|
|
|||
|
|
// Parse scrutinee expression
|
|||
|
|
let scrutinee = Box::new(self.parse_expression()?);
|
|||
|
|
|
|||
|
|
// Expect {
|
|||
|
|
self.expect(TokenType::LeftBrace)?;
|
|||
|
|
|
|||
|
|
// Parse match arms
|
|||
|
|
let mut arms = Vec::new();
|
|||
|
|
while !self.check(&TokenType::RightBrace) {
|
|||
|
|
let arm = self.parse_match_arm()?;
|
|||
|
|
arms.push(arm);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Expect }
|
|||
|
|
self.expect(TokenType::RightBrace)?;
|
|||
|
|
|
|||
|
|
// Validation: at least one arm required
|
|||
|
|
if arms.is_empty() {
|
|||
|
|
return Err(ParseError::InvalidExpression {
|
|||
|
|
line: start,
|
|||
|
|
message: "Match expression must have at least one arm".to_string()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(ASTNode::MatchPattern {
|
|||
|
|
scrutinee,
|
|||
|
|
arms,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn parse_match_arm(&mut self) -> Result<MatchArm, ParseError> {
|
|||
|
|
let start_line = self.current_token().line;
|
|||
|
|
|
|||
|
|
// Parse pattern
|
|||
|
|
let pattern = self.parse_pattern()?;
|
|||
|
|
|
|||
|
|
// Expect =>
|
|||
|
|
self.expect(TokenType::FatArrow)?; // => operator
|
|||
|
|
|
|||
|
|
// Parse body (can be single expression or block with {})
|
|||
|
|
let body = if self.check(&TokenType::LeftBrace) {
|
|||
|
|
// Multi-statement block
|
|||
|
|
self.advance(); // consume {
|
|||
|
|
let mut statements = Vec::new();
|
|||
|
|
while !self.check(&TokenType::RightBrace) {
|
|||
|
|
statements.push(self.parse_statement()?);
|
|||
|
|
}
|
|||
|
|
self.expect(TokenType::RightBrace)?;
|
|||
|
|
statements
|
|||
|
|
} else {
|
|||
|
|
// Single expression (implicit return)
|
|||
|
|
vec![self.parse_expression()?]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
Ok(MatchArm {
|
|||
|
|
pattern,
|
|||
|
|
body,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
|
|||
|
|
let start_line = self.current_token().line;
|
|||
|
|
|
|||
|
|
match &self.current_token().token_type {
|
|||
|
|
// Wildcard: _
|
|||
|
|
TokenType::Underscore => {
|
|||
|
|
self.advance();
|
|||
|
|
Ok(Pattern::Wildcard {
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// Variant pattern: Ok(v), Err(e), Some(x), None
|
|||
|
|
TokenType::Identifier(name) => {
|
|||
|
|
let variant_name = name.clone();
|
|||
|
|
self.advance();
|
|||
|
|
|
|||
|
|
// Check for bindings: (x, y, z)
|
|||
|
|
let bindings = if self.check(&TokenType::LeftParen) {
|
|||
|
|
self.advance(); // consume (
|
|||
|
|
let mut vars = Vec::new();
|
|||
|
|
|
|||
|
|
while !self.check(&TokenType::RightParen) {
|
|||
|
|
if let TokenType::Identifier(var) = &self.current_token().token_type {
|
|||
|
|
vars.push(var.clone());
|
|||
|
|
self.advance();
|
|||
|
|
|
|||
|
|
// Optional comma
|
|||
|
|
if self.check(&TokenType::Comma) {
|
|||
|
|
self.advance();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return Err(ParseError::ExpectedIdentifier { line: start_line });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.expect(TokenType::RightParen)?;
|
|||
|
|
vars
|
|||
|
|
} else {
|
|||
|
|
Vec::new() // 0-field variant (e.g., None)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
Ok(Pattern::Variant {
|
|||
|
|
name: variant_name,
|
|||
|
|
bindings,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
_ => Err(ParseError::InvalidExpression {
|
|||
|
|
line: start_line,
|
|||
|
|
message: "Expected pattern (variant or wildcard)".to_string(),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Token Type Addition
|
|||
|
|
|
|||
|
|
Add to `src/tokenizer.rs`:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
pub enum TokenType {
|
|||
|
|
// ... existing tokens ...
|
|||
|
|
|
|||
|
|
/// => (fat arrow for match arms)
|
|||
|
|
FatArrow,
|
|||
|
|
|
|||
|
|
/// _ (wildcard pattern)
|
|||
|
|
Underscore,
|
|||
|
|
|
|||
|
|
/// @match, @enum, etc.
|
|||
|
|
MacroKeyword(String),
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Tokenizer Update
|
|||
|
|
|
|||
|
|
Modify `src/tokenizer.rs` to recognize:
|
|||
|
|
- `@match` as `MacroKeyword("match")`
|
|||
|
|
- `=>` as `FatArrow`
|
|||
|
|
- `_` as `Underscore` (if not already present)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 Macro Desugaring Rules
|
|||
|
|
|
|||
|
|
### Core Principle
|
|||
|
|
**Every @match transforms to a chain of if/else with field access and runtime exhaustiveness check.**
|
|||
|
|
|
|||
|
|
### Example 1: Basic Result Matching
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => print("Success: " + v)
|
|||
|
|
Err(e) => print("Error: " + e)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared Output**:
|
|||
|
|
```hakorune
|
|||
|
|
if result._ok == 1 {
|
|||
|
|
local v
|
|||
|
|
v = result._val
|
|||
|
|
print("Success: " + v)
|
|||
|
|
} else if result._ok == 0 {
|
|||
|
|
local e
|
|||
|
|
e = result._err
|
|||
|
|
print("Error: " + e)
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Result: unknown state")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Implementation Notes**:
|
|||
|
|
- Result uses `_ok` (1=Ok, 0=Err) instead of `_tag` string
|
|||
|
|
- Field mapping: `Ok` → `_val`, `Err` → `_err`
|
|||
|
|
- Exhaustiveness: catch-all else for safety
|
|||
|
|
|
|||
|
|
### Example 2: Option Matching with Wildcard
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match option {
|
|||
|
|
Some(x) => return x * 2
|
|||
|
|
None => return 0
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared Output**:
|
|||
|
|
```hakorune
|
|||
|
|
if option._is_some == 1 {
|
|||
|
|
local x
|
|||
|
|
x = option._value
|
|||
|
|
return x * 2
|
|||
|
|
} else if option._is_some == 0 {
|
|||
|
|
return 0
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Option: unknown state")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Implementation Notes**:
|
|||
|
|
- Option uses `_is_some` (1=Some, 0=None) instead of `_tag`
|
|||
|
|
- Field mapping: `Some` → `_value`, `None` → (no field)
|
|||
|
|
- Exhaustiveness: still required for robustness
|
|||
|
|
|
|||
|
|
### Example 3: Generic VariantBox (Future @enum)
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@enum Point {
|
|||
|
|
Cartesian(x, y)
|
|||
|
|
Polar(r, theta)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@match point {
|
|||
|
|
Cartesian(x, y) => print("x=" + x + " y=" + y)
|
|||
|
|
Polar(r, theta) => print("r=" + r + " theta=" + theta)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared Output**:
|
|||
|
|
```hakorune
|
|||
|
|
if point._tag == "Cartesian" {
|
|||
|
|
local x
|
|||
|
|
local y
|
|||
|
|
x = point._x
|
|||
|
|
y = point._y
|
|||
|
|
print("x=" + x + " y=" + y)
|
|||
|
|
} else if point._tag == "Polar" {
|
|||
|
|
local r
|
|||
|
|
local theta
|
|||
|
|
r = point._r
|
|||
|
|
theta = point._theta
|
|||
|
|
print("r=" + r + " theta=" + theta)
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Point: " + point._tag)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Implementation Notes**:
|
|||
|
|
- Generic VariantBox uses string `_tag` field
|
|||
|
|
- Field mapping: `Variant(a, b)` → `_a`, `_b` (lowercase field names)
|
|||
|
|
- Exhaustiveness: show actual unknown tag in panic message
|
|||
|
|
|
|||
|
|
### Example 4: Match as Expression (Return Value)
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
local result
|
|||
|
|
result = @match status {
|
|||
|
|
Success(val) => val * 2
|
|||
|
|
Failure(msg) => 0
|
|||
|
|
}
|
|||
|
|
print(result)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared Output**:
|
|||
|
|
```hakorune
|
|||
|
|
local result
|
|||
|
|
local __match_tmp
|
|||
|
|
if status._tag == "Success" {
|
|||
|
|
local val
|
|||
|
|
val = status._val
|
|||
|
|
__match_tmp = val * 2
|
|||
|
|
} else if status._tag == "Failure" {
|
|||
|
|
local msg
|
|||
|
|
msg = status._msg
|
|||
|
|
__match_tmp = 0
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Status: " + status._tag)
|
|||
|
|
__match_tmp = null
|
|||
|
|
}
|
|||
|
|
result = __match_tmp
|
|||
|
|
print(result)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Implementation Notes**:
|
|||
|
|
- Introduce temporary variable `__match_tmp` to hold result
|
|||
|
|
- Each arm assigns to `__match_tmp` instead of returning
|
|||
|
|
- Final assignment outside if/else chain
|
|||
|
|
- Exhaustiveness fallback assigns `null`
|
|||
|
|
|
|||
|
|
### Example 5: Nested Blocks with Multiple Statements
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => {
|
|||
|
|
local doubled
|
|||
|
|
doubled = v * 2
|
|||
|
|
print("Result: " + doubled)
|
|||
|
|
return doubled
|
|||
|
|
}
|
|||
|
|
Err(e) => {
|
|||
|
|
print("Error: " + e)
|
|||
|
|
return -1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared Output**:
|
|||
|
|
```hakorune
|
|||
|
|
if result._ok == 1 {
|
|||
|
|
local v
|
|||
|
|
v = result._val
|
|||
|
|
local doubled
|
|||
|
|
doubled = v * 2
|
|||
|
|
print("Result: " + doubled)
|
|||
|
|
return doubled
|
|||
|
|
} else if result._ok == 0 {
|
|||
|
|
local e
|
|||
|
|
e = result._err
|
|||
|
|
print("Error: " + e)
|
|||
|
|
return -1
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Result: unknown state")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Implementation Notes**:
|
|||
|
|
- Multi-statement blocks preserved as-is
|
|||
|
|
- Variable bindings inserted at top of block
|
|||
|
|
- No wrapping needed (already in block context)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 Pattern Types Support
|
|||
|
|
|
|||
|
|
### Phase 1 (MVP Implementation)
|
|||
|
|
|
|||
|
|
| Pattern Type | Syntax | Example | Status |
|
|||
|
|
|--------------|--------|---------|--------|
|
|||
|
|
| **Variant 0-field** | `Name` | `None` | ✅ MVP |
|
|||
|
|
| **Variant 1-field** | `Name(x)` | `Ok(v)`, `Some(x)` | ✅ MVP |
|
|||
|
|
| **Variant N-field** | `Name(x, y, ...)` | `Cartesian(x, y)` | ✅ MVP |
|
|||
|
|
| **Wildcard** | `_` | `_ => default` | ✅ MVP |
|
|||
|
|
|
|||
|
|
### Phase 2 (Future Enhancements)
|
|||
|
|
|
|||
|
|
| Pattern Type | Syntax | Example | Status |
|
|||
|
|
|--------------|--------|---------|--------|
|
|||
|
|
| **Literal** | `42`, `"str"` | `42 => ...` | ❌ Future |
|
|||
|
|
| **Guard Clause** | `pattern if cond` | `Some(x) if x > 10` | ❌ Future |
|
|||
|
|
| **Nested Pattern** | `Outer(Inner(x))` | `Some(Ok(v))` | ❌ Future |
|
|||
|
|
| **Multiple Patterns** | `A \| B` | `Ok(v) \| Some(v)` | ❌ Future |
|
|||
|
|
|
|||
|
|
**MVP Focus**: Variant patterns only (sufficient for Result/Option/VariantBox)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ Edge Cases Handling
|
|||
|
|
|
|||
|
|
### 1. Match as Expression
|
|||
|
|
|
|||
|
|
**Issue**: Match must produce a value for assignment
|
|||
|
|
|
|||
|
|
**Solution**: Introduce temporary variable pattern (see Example 4 above)
|
|||
|
|
|
|||
|
|
**Test Case**:
|
|||
|
|
```hakorune
|
|||
|
|
local result = @match opt {
|
|||
|
|
Some(x) => x * 2
|
|||
|
|
None => 0
|
|||
|
|
}
|
|||
|
|
assert(result == (opt._is_some == 1 ? opt._value * 2 : 0))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Single Pattern Match
|
|||
|
|
|
|||
|
|
**Issue**: Single arm still needs exhaustiveness check
|
|||
|
|
|
|||
|
|
**Solution**: Always add catch-all else clause
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => print(v)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared**:
|
|||
|
|
```hakorune
|
|||
|
|
if result._ok == 1 {
|
|||
|
|
local v
|
|||
|
|
v = result._val
|
|||
|
|
print(v)
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match: missing Err case")
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Empty Match
|
|||
|
|
|
|||
|
|
**Issue**: Match with zero arms is invalid
|
|||
|
|
|
|||
|
|
**Solution**: Parser error at parse time
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Match expression must have at least one arm at line 42
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Duplicate Patterns
|
|||
|
|
|
|||
|
|
**Issue**: Two arms with same pattern
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => print("first")
|
|||
|
|
Ok(x) => print("second") // ❌ Duplicate
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Solution**: Warning (first arm wins, second unreachable)
|
|||
|
|
|
|||
|
|
**Warning Message**:
|
|||
|
|
```
|
|||
|
|
[WARN] Unreachable pattern 'Ok' at line 43 (already matched at line 42)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Variable Shadowing
|
|||
|
|
|
|||
|
|
**Issue**: Pattern variable shadows local variable
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
local v = 10
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => print(v) // Different 'v'
|
|||
|
|
}
|
|||
|
|
print(v) // Original 'v' = 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Solution**: Each arm introduces new scope (via if block)
|
|||
|
|
|
|||
|
|
**Desugared**:
|
|||
|
|
```hakorune
|
|||
|
|
local v = 10
|
|||
|
|
if result._ok == 1 {
|
|||
|
|
local v // New scope, shadows outer v
|
|||
|
|
v = result._val
|
|||
|
|
print(v)
|
|||
|
|
}
|
|||
|
|
print(v) // Original v still 10
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. Match on Non-Enum Value
|
|||
|
|
|
|||
|
|
**Issue**: Match on primitive type (IntegerBox, StringBox)
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match 42 {
|
|||
|
|
Ok(v) => print(v) // ❌ 42 has no _ok field
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Solution**: Runtime error (field access fails)
|
|||
|
|
|
|||
|
|
**Alternative**: Static check (Phase 2, requires type system)
|
|||
|
|
|
|||
|
|
### 7. Early Return in Match Arm
|
|||
|
|
|
|||
|
|
**Issue**: Return statement inside match arm
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
local compute() {
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => return v
|
|||
|
|
Err(e) => return -1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared**:
|
|||
|
|
```hakorune
|
|||
|
|
local compute() {
|
|||
|
|
if result._ok == 1 {
|
|||
|
|
local v
|
|||
|
|
v = result._val
|
|||
|
|
return v // ✅ Works (early exit from function)
|
|||
|
|
} else if result._ok == 0 {
|
|||
|
|
local e
|
|||
|
|
e = result._err
|
|||
|
|
return -1
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Result")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Result**: Works correctly (return exits function)
|
|||
|
|
|
|||
|
|
### 8. Match Inside Loop with Break/Continue
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
loop(i < 10) {
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => {
|
|||
|
|
if x > 5 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
None => i = i + 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Desugared**:
|
|||
|
|
```hakorune
|
|||
|
|
loop(i < 10) {
|
|||
|
|
if opt._is_some == 1 {
|
|||
|
|
local x
|
|||
|
|
x = opt._value
|
|||
|
|
if x > 5 {
|
|||
|
|
break // ✅ Breaks from loop
|
|||
|
|
}
|
|||
|
|
continue // ✅ Continues loop
|
|||
|
|
} else if opt._is_some == 0 {
|
|||
|
|
i = i + 1
|
|||
|
|
} else {
|
|||
|
|
print("[PANIC] non-exhaustive match on Option")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Result**: Works correctly (break/continue affect loop)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 Test Suite Design
|
|||
|
|
|
|||
|
|
### Minimum 15 Test Patterns
|
|||
|
|
|
|||
|
|
### Basic Matching (5 tests)
|
|||
|
|
|
|||
|
|
#### Test 1: Result 2-Variant Match
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_result_basic.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local r = Result.ok(42)
|
|||
|
|
|
|||
|
|
local result = @match r {
|
|||
|
|
Ok(v) => v * 2
|
|||
|
|
Err(e) => -1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert(result == 84)
|
|||
|
|
print("PASS: Result basic match")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 2: Option 2-Variant Match
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_option_basic.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local opt = Option.some(10)
|
|||
|
|
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => print("Value: " + x)
|
|||
|
|
None => print("No value")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print("PASS: Option basic match")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 3: 3+ Variant Enum Match (Future VariantBox)
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_variant_3way.hako
|
|||
|
|
@enum Status {
|
|||
|
|
Pending
|
|||
|
|
Success(result)
|
|||
|
|
Failure(error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local s = Status.Success("Done")
|
|||
|
|
|
|||
|
|
@match s {
|
|||
|
|
Pending => print("Waiting...")
|
|||
|
|
Success(r) => print("OK: " + r)
|
|||
|
|
Failure(e) => print("Error: " + e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print("PASS: 3-way variant match")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 4: Match with Return Values
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_match_expression.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
compute(r) {
|
|||
|
|
return @match r {
|
|||
|
|
Ok(v) => v * 2
|
|||
|
|
Err(e) => 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main() {
|
|||
|
|
local r1 = Result.ok(21)
|
|||
|
|
local r2 = Result.err("fail")
|
|||
|
|
|
|||
|
|
assert(me.compute(r1) == 42)
|
|||
|
|
assert(me.compute(r2) == 0)
|
|||
|
|
|
|||
|
|
print("PASS: Match as expression")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 5: Match with Multi-Statement Blocks
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_match_blocks.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local opt = Option.some(5)
|
|||
|
|
local result = 0
|
|||
|
|
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => {
|
|||
|
|
local doubled = x * 2
|
|||
|
|
local tripled = doubled + x
|
|||
|
|
result = tripled
|
|||
|
|
print("Computed: " + result)
|
|||
|
|
}
|
|||
|
|
None => {
|
|||
|
|
result = -1
|
|||
|
|
print("No value")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert(result == 15)
|
|||
|
|
print("PASS: Multi-statement blocks")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Variable Binding (3 tests)
|
|||
|
|
|
|||
|
|
#### Test 6: Single Variable Binding
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_binding_single.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local r = Result.ok("hello")
|
|||
|
|
|
|||
|
|
@match r {
|
|||
|
|
Ok(msg) => {
|
|||
|
|
assert(msg == "hello")
|
|||
|
|
print("PASS: Single variable binding")
|
|||
|
|
}
|
|||
|
|
Err(e) => print("Error: " + e)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 7: Multi-Variable Binding
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_binding_multi.hako
|
|||
|
|
@enum Point {
|
|||
|
|
Cartesian(x, y)
|
|||
|
|
Polar(r, theta)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local p = Point.Cartesian(3, 4)
|
|||
|
|
|
|||
|
|
@match p {
|
|||
|
|
Cartesian(x, y) => {
|
|||
|
|
assert(x == 3)
|
|||
|
|
assert(y == 4)
|
|||
|
|
print("PASS: Multi-variable binding")
|
|||
|
|
}
|
|||
|
|
Polar(r, theta) => print("Polar")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 8: Variable Shadowing Test
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_shadowing.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local x = 100
|
|||
|
|
local opt = Option.some(42)
|
|||
|
|
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => {
|
|||
|
|
// Inner x = 42
|
|||
|
|
assert(x == 42)
|
|||
|
|
}
|
|||
|
|
None => print("None")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Outer x still 100
|
|||
|
|
assert(x == 100)
|
|||
|
|
print("PASS: Variable shadowing")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Exhaustiveness (3 tests)
|
|||
|
|
|
|||
|
|
#### Test 9: Exhaustive Match (All Variants)
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_exhaustive_ok.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local r = Result.ok(42)
|
|||
|
|
|
|||
|
|
local handled = 0
|
|||
|
|
@match r {
|
|||
|
|
Ok(v) => handled = 1
|
|||
|
|
Err(e) => handled = 2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert(handled == 1)
|
|||
|
|
print("PASS: Exhaustive match")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 10: Non-Exhaustive Match (Runtime Panic)
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_exhaustive_fail.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
// Manually create invalid Result state
|
|||
|
|
local r = new ResultBox()
|
|||
|
|
r._ok = 99 // Invalid state
|
|||
|
|
|
|||
|
|
// This should trigger panic
|
|||
|
|
@match r {
|
|||
|
|
Ok(v) => print("OK")
|
|||
|
|
Err(e) => print("Err")
|
|||
|
|
}
|
|||
|
|
// Expected output: [PANIC] non-exhaustive match on Result: unknown state
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 11: Wildcard Pattern Exhaustiveness
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_wildcard.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local opt = Option.none()
|
|||
|
|
|
|||
|
|
local result = @match opt {
|
|||
|
|
Some(x) => x
|
|||
|
|
_ => -1 // Wildcard catches None and any invalid states
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert(result == -1)
|
|||
|
|
print("PASS: Wildcard pattern")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Edge Cases (4 tests)
|
|||
|
|
|
|||
|
|
#### Test 12: Match on 0-Field Variant
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_0field_variant.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local opt = Option.none()
|
|||
|
|
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => print("Value: " + x)
|
|||
|
|
None => print("PASS: 0-field variant")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 13: Nested If-Else Inside Match Arm
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_nested_if.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local r = Result.ok(10)
|
|||
|
|
|
|||
|
|
@match r {
|
|||
|
|
Ok(v) => {
|
|||
|
|
if v > 5 {
|
|||
|
|
print("Large: " + v)
|
|||
|
|
} else {
|
|||
|
|
print("Small: " + v)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Err(e) => print("Error: " + e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print("PASS: Nested if-else")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 14: Match Inside Loop with Break/Continue
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_loop_control.hako
|
|||
|
|
using "../../lib/boxes/option.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
main() {
|
|||
|
|
local i = 0
|
|||
|
|
local found = 0
|
|||
|
|
|
|||
|
|
loop(i < 10) {
|
|||
|
|
local opt = (i == 5) ? Option.some(i) : Option.none()
|
|||
|
|
|
|||
|
|
@match opt {
|
|||
|
|
Some(x) => {
|
|||
|
|
found = x
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
None => i = i + 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
i = i + 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert(found == 5)
|
|||
|
|
print("PASS: Loop control with match")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Test 15: Match with Early Return in Each Arm
|
|||
|
|
```hakorune
|
|||
|
|
// apps/tests/match/test_early_return.hako
|
|||
|
|
using "../../lib/boxes/result.hako"
|
|||
|
|
|
|||
|
|
static box Main {
|
|||
|
|
compute(r) {
|
|||
|
|
@match r {
|
|||
|
|
Ok(v) => return v * 2
|
|||
|
|
Err(e) => return -1
|
|||
|
|
}
|
|||
|
|
// This line is unreachable
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main() {
|
|||
|
|
local r1 = Result.ok(21)
|
|||
|
|
local r2 = Result.err("fail")
|
|||
|
|
|
|||
|
|
assert(me.compute(r1) == 42)
|
|||
|
|
assert(me.compute(r2) == -1)
|
|||
|
|
|
|||
|
|
print("PASS: Early return in arms")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📅 Implementation Task Breakdown
|
|||
|
|
|
|||
|
|
### 6-Day Implementation Plan
|
|||
|
|
|
|||
|
|
#### Day 1: Parser Infrastructure (8 hours)
|
|||
|
|
- [ ] Add `MatchPattern` AST node variant
|
|||
|
|
- [ ] Add `Pattern` and `MatchArm` struct definitions
|
|||
|
|
- [ ] Implement `parse_match_pattern()` method
|
|||
|
|
- [ ] Implement `parse_match_arm()` method
|
|||
|
|
- [ ] Implement `parse_pattern()` method
|
|||
|
|
- [ ] Add `FatArrow` and `Underscore` token types
|
|||
|
|
- [ ] Update tokenizer to recognize `@match`, `=>`, `_`
|
|||
|
|
- [ ] **Milestone**: Parser can parse @match syntax without errors
|
|||
|
|
|
|||
|
|
#### Day 2: Pattern Analysis & Tag Comparison (8 hours)
|
|||
|
|
- [ ] Create field mapping registry (Pattern → field names)
|
|||
|
|
- Result: `Ok` → `_ok==1, _val`, `Err` → `_ok==0, _err`
|
|||
|
|
- Option: `Some` → `_is_some==1, _value`, `None` → `_is_some==0`
|
|||
|
|
- VariantBox: `Tag` → `_tag=="Tag", _field0, _field1, ...`
|
|||
|
|
- [ ] Implement tag comparison code generation
|
|||
|
|
- [ ] Generate if-condition AST nodes for each pattern
|
|||
|
|
- [ ] **Milestone**: Can generate if/else skeleton for patterns
|
|||
|
|
|
|||
|
|
#### Day 3: Variable Binding Extraction (8 hours)
|
|||
|
|
- [ ] Implement binding extraction (`Variant(x, y)` → field names)
|
|||
|
|
- [ ] Generate `local` declaration AST nodes
|
|||
|
|
- [ ] Generate field access AST nodes (`result._val`, etc.)
|
|||
|
|
- [ ] Insert binding code at top of each arm
|
|||
|
|
- [ ] Handle 0-field variants (no bindings)
|
|||
|
|
- [ ] **Milestone**: Variable bindings work correctly
|
|||
|
|
|
|||
|
|
#### Day 4: Exhaustiveness Check & Panic Generation (8 hours)
|
|||
|
|
- [ ] Generate catch-all else clause AST
|
|||
|
|
- [ ] Generate panic print statement
|
|||
|
|
- [ ] Implement exhaustiveness check logic
|
|||
|
|
- [ ] Add tag name to panic message (for VariantBox)
|
|||
|
|
- [ ] Handle match-as-expression case (assign null in panic)
|
|||
|
|
- [ ] **Milestone**: Exhaustiveness panic works
|
|||
|
|
|
|||
|
|
#### Day 5: Test Suite - Basic & Variable Binding (8 hours)
|
|||
|
|
- [ ] Write Test 1-5 (Basic Matching)
|
|||
|
|
- [ ] Write Test 6-8 (Variable Binding)
|
|||
|
|
- [ ] Run tests, fix issues
|
|||
|
|
- [ ] Verify desugared code with `--dump-mir`
|
|||
|
|
- [ ] **Milestone**: 8/15 tests passing
|
|||
|
|
|
|||
|
|
#### Day 6: Test Suite - Exhaustiveness & Edge Cases (8 hours)
|
|||
|
|
- [ ] Write Test 9-11 (Exhaustiveness)
|
|||
|
|
- [ ] Write Test 12-15 (Edge Cases)
|
|||
|
|
- [ ] Run full test suite
|
|||
|
|
- [ ] Document known issues/limitations
|
|||
|
|
- [ ] Add smoke tests to CI
|
|||
|
|
- [ ] **Milestone**: 15/15 tests passing, MVP complete
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔗 Integration with @enum
|
|||
|
|
|
|||
|
|
### Phase 1 (MVP): Hardcoded Field Mappings
|
|||
|
|
|
|||
|
|
**Strategy**: Use fixed field mapping for existing boxes (Result, Option)
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// In desugaring logic
|
|||
|
|
fn get_field_mapping(variant: &str, index: usize) -> String {
|
|||
|
|
match variant {
|
|||
|
|
// Result
|
|||
|
|
"Ok" => if index == 0 { "_val" } else { panic!("Ok has 1 field") },
|
|||
|
|
"Err" => if index == 0 { "_err" } else { panic!("Err has 1 field") },
|
|||
|
|
|
|||
|
|
// Option
|
|||
|
|
"Some" => if index == 0 { "_value" } else { panic!("Some has 1 field") },
|
|||
|
|
"None" => panic!("None has 0 fields"),
|
|||
|
|
|
|||
|
|
// Unknown: assume generic VariantBox convention
|
|||
|
|
_ => format!("_{}", index), // _0, _1, _2, ...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn get_tag_check(variant: &str) -> (String, String) {
|
|||
|
|
match variant {
|
|||
|
|
// Result: check _ok field
|
|||
|
|
"Ok" => ("_ok".to_string(), "1".to_string()),
|
|||
|
|
"Err" => ("_ok".to_string(), "0".to_string()),
|
|||
|
|
|
|||
|
|
// Option: check _is_some field
|
|||
|
|
"Some" => ("_is_some".to_string(), "1".to_string()),
|
|||
|
|
"None" => ("_is_some".to_string(), "0".to_string()),
|
|||
|
|
|
|||
|
|
// Unknown: assume generic _tag field
|
|||
|
|
_ => ("_tag".to_string(), format!("\"{}\"", variant)),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Phase 2 (Future): @enum Integration
|
|||
|
|
|
|||
|
|
**When @enum is implemented**:
|
|||
|
|
1. @enum generates EnumSchemaBox metadata
|
|||
|
|
2. @match queries EnumSchemaBox for field names
|
|||
|
|
3. Compile-time exhaustiveness check possible
|
|||
|
|
|
|||
|
|
**Example**:
|
|||
|
|
```hakorune
|
|||
|
|
@enum Status {
|
|||
|
|
Pending
|
|||
|
|
Success(result)
|
|||
|
|
Failure(error, code)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// EnumSchemaBox generated:
|
|||
|
|
// Status._schema.variants = {
|
|||
|
|
// "Pending": { arity: 0, fields: [] },
|
|||
|
|
// "Success": { arity: 1, fields: ["result"] },
|
|||
|
|
// "Failure": { arity: 2, fields: ["error", "code"] }
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
@match status {
|
|||
|
|
Pending => ...
|
|||
|
|
Success(r) => ...
|
|||
|
|
// ❌ Missing Failure → Compile-time error (Phase 2)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Exhaustiveness Check Algorithm (Phase 2)**:
|
|||
|
|
```rust
|
|||
|
|
fn check_exhaustiveness(scrutinee_type: &str, arms: &[MatchArm]) -> Result<(), String> {
|
|||
|
|
// 1. Get enum schema
|
|||
|
|
let schema = get_enum_schema(scrutinee_type)?;
|
|||
|
|
|
|||
|
|
// 2. Collect covered variants
|
|||
|
|
let mut covered = HashSet::new();
|
|||
|
|
let mut has_wildcard = false;
|
|||
|
|
|
|||
|
|
for arm in arms {
|
|||
|
|
match &arm.pattern {
|
|||
|
|
Pattern::Variant { name, .. } => { covered.insert(name); }
|
|||
|
|
Pattern::Wildcard { .. } => { has_wildcard = true; }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. Check if all variants covered
|
|||
|
|
if !has_wildcard {
|
|||
|
|
for variant in schema.variants.keys() {
|
|||
|
|
if !covered.contains(variant) {
|
|||
|
|
return Err(format!("Missing pattern: {}", variant));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Ok(())
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛠️ Desugaring Algorithm
|
|||
|
|
|
|||
|
|
### High-Level Algorithm
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
INPUT: MatchPattern { scrutinee, arms }
|
|||
|
|
OUTPUT: If { condition, then_body, else_body }
|
|||
|
|
|
|||
|
|
ALGORITHM:
|
|||
|
|
1. Generate temporary variable (if match is expression)
|
|||
|
|
2. For each arm in arms:
|
|||
|
|
a. Extract pattern and body
|
|||
|
|
b. Generate tag comparison condition
|
|||
|
|
c. Generate variable bindings (local declarations)
|
|||
|
|
d. Prepend bindings to body
|
|||
|
|
e. If expression: wrap body with assignment to temp var
|
|||
|
|
3. Chain arms into if/else-if/else structure
|
|||
|
|
4. Add exhaustiveness panic as final else clause
|
|||
|
|
5. If expression: append temp var access after if/else
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pseudo-Code Implementation
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
fn desugar_match(
|
|||
|
|
scrutinee: &ASTNode,
|
|||
|
|
arms: &[MatchArm],
|
|||
|
|
is_expression: bool
|
|||
|
|
) -> ASTNode {
|
|||
|
|
// Step 1: Temp var for expression case
|
|||
|
|
let temp_var = if is_expression {
|
|||
|
|
Some(format!("__match_tmp_{}", unique_id()))
|
|||
|
|
} else {
|
|||
|
|
None
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Step 2: Build if/else chain
|
|||
|
|
let mut current_else: Option<Vec<ASTNode>> = Some(vec![
|
|||
|
|
// Final else: exhaustiveness panic
|
|||
|
|
ASTNode::Print {
|
|||
|
|
expression: Box::new(ASTNode::Literal {
|
|||
|
|
value: LiteralValue::String(
|
|||
|
|
"[PANIC] non-exhaustive match".to_string()
|
|||
|
|
),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// Build chain in reverse order (last arm first)
|
|||
|
|
for arm in arms.iter().rev() {
|
|||
|
|
// Step 3a: Extract pattern
|
|||
|
|
let (tag_field, tag_value) = get_tag_check(&arm.pattern);
|
|||
|
|
|
|||
|
|
// Step 3b: Generate condition
|
|||
|
|
let condition = ASTNode::BinaryOp {
|
|||
|
|
operator: BinaryOperator::Equals,
|
|||
|
|
left: Box::new(ASTNode::FieldAccess {
|
|||
|
|
object: Box::new(scrutinee.clone()),
|
|||
|
|
field: tag_field,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
right: Box::new(ASTNode::Literal {
|
|||
|
|
value: tag_value,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Step 3c: Generate bindings
|
|||
|
|
let bindings = generate_bindings(
|
|||
|
|
scrutinee,
|
|||
|
|
&arm.pattern
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Step 3d: Build then_body
|
|||
|
|
let mut then_body = bindings;
|
|||
|
|
|
|||
|
|
if let Some(ref temp) = temp_var {
|
|||
|
|
// Expression case: wrap body with assignment
|
|||
|
|
let value = if arm.body.len() == 1 {
|
|||
|
|
arm.body[0].clone()
|
|||
|
|
} else {
|
|||
|
|
// Multi-statement: last statement is value
|
|||
|
|
arm.body.last().unwrap().clone()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
then_body.extend(arm.body[..arm.body.len()-1].to_vec());
|
|||
|
|
then_body.push(ASTNode::Assignment {
|
|||
|
|
target: Box::new(ASTNode::Variable {
|
|||
|
|
name: temp.clone(),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
value: Box::new(value),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// Statement case: just append body
|
|||
|
|
then_body.extend(arm.body.clone());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Step 3e: Create if node
|
|||
|
|
current_else = Some(vec![ASTNode::If {
|
|||
|
|
condition: Box::new(condition),
|
|||
|
|
then_body,
|
|||
|
|
else_body: current_else,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Step 4: Return final structure
|
|||
|
|
if let Some(temp) = temp_var {
|
|||
|
|
// Expression: declare temp, if/else, return temp
|
|||
|
|
let mut result = vec![
|
|||
|
|
ASTNode::Local {
|
|||
|
|
variables: vec![temp.clone()],
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
result.extend(current_else.unwrap());
|
|||
|
|
result.push(ASTNode::Variable {
|
|||
|
|
name: temp,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
ASTNode::Program {
|
|||
|
|
statements: result,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Statement: just if/else
|
|||
|
|
current_else.unwrap()[0].clone()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn generate_bindings(
|
|||
|
|
scrutinee: &ASTNode,
|
|||
|
|
pattern: &Pattern
|
|||
|
|
) -> Vec<ASTNode> {
|
|||
|
|
match pattern {
|
|||
|
|
Pattern::Variant { name, bindings, .. } => {
|
|||
|
|
let mut result = Vec::new();
|
|||
|
|
|
|||
|
|
for (i, var) in bindings.iter().enumerate() {
|
|||
|
|
let field_name = get_field_mapping(name, i);
|
|||
|
|
|
|||
|
|
// local var
|
|||
|
|
result.push(ASTNode::Local {
|
|||
|
|
variables: vec![var.clone()],
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// var = scrutinee._field
|
|||
|
|
result.push(ASTNode::Assignment {
|
|||
|
|
target: Box::new(ASTNode::Variable {
|
|||
|
|
name: var.clone(),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
value: Box::new(ASTNode::FieldAccess {
|
|||
|
|
object: Box::new(scrutinee.clone()),
|
|||
|
|
field: field_name,
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
}),
|
|||
|
|
span: Span::unknown(),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
Pattern::Wildcard { .. } => {
|
|||
|
|
Vec::new() // No bindings
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ Error Handling Design
|
|||
|
|
|
|||
|
|
### Parser Errors
|
|||
|
|
|
|||
|
|
#### 1. Pattern Syntax Error
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v => // ❌ Missing )
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Expected ')' at line 2, found '=>'
|
|||
|
|
Context: Parsing variant pattern 'Ok'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. Unknown Variant (Future, with @enum)
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Success(v) => ... // ❌ Result has Ok/Err, not Success
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Unknown variant 'Success' for type 'Result' at line 2
|
|||
|
|
Available variants: Ok(value), Err(error)
|
|||
|
|
Help: Did you mean 'Ok'?
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. Duplicate Pattern
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => print("first")
|
|||
|
|
Ok(x) => print("second") // ❌ Duplicate
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Warning Message**:
|
|||
|
|
```
|
|||
|
|
[WARN] Unreachable pattern 'Ok' at line 3
|
|||
|
|
Note: This pattern is already matched at line 2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4. Type Mismatch (Future, with type system)
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match 42 {
|
|||
|
|
Ok(v) => ... // ❌ 42 is IntegerBox, not Result
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Type mismatch in match expression at line 1
|
|||
|
|
Expected: Result, Option, or VariantBox
|
|||
|
|
Found: IntegerBox
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 5. Missing Fat Arrow
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) print(v) // ❌ Missing =>
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Expected '=>' at line 2, found 'print'
|
|||
|
|
Context: After pattern 'Ok(v)'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 6. Empty Match Arms
|
|||
|
|
|
|||
|
|
**Input**:
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Error Message**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Match expression must have at least one arm at line 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Runtime Errors
|
|||
|
|
|
|||
|
|
#### 1. Non-Exhaustive Match Panic
|
|||
|
|
|
|||
|
|
**Trigger**: Unknown variant tag at runtime
|
|||
|
|
|
|||
|
|
**Output**:
|
|||
|
|
```
|
|||
|
|
[PANIC] non-exhaustive match on Result: unknown state
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**For VariantBox with _tag**:
|
|||
|
|
```
|
|||
|
|
[PANIC] non-exhaustive match on Point: InvalidTag
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. Field Access Error (Arity Mismatch)
|
|||
|
|
|
|||
|
|
**Trigger**: Pattern has wrong number of bindings
|
|||
|
|
|
|||
|
|
**Input** (if validation skipped):
|
|||
|
|
```hakorune
|
|||
|
|
@match result {
|
|||
|
|
Ok(v, x) => ... // ❌ Ok has 1 field, not 2
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Runtime Error**:
|
|||
|
|
```
|
|||
|
|
[ERROR] Field access failed: result._field1 does not exist
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Future Prevention**: Static arity check (Phase 2)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚨 Risk Analysis
|
|||
|
|
|
|||
|
|
### 1. Parser Conflicts with Existing Syntax
|
|||
|
|
|
|||
|
|
**Risk**: `=>` arrow operator may conflict with other syntax
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Check existing uses of `>` and `=` tokens
|
|||
|
|
- Ensure tokenizer prioritizes `=>` as single token
|
|||
|
|
- Run full parser test suite
|
|||
|
|
|
|||
|
|
**Impact**: Medium (parser failure)
|
|||
|
|
|
|||
|
|
### 2. Pattern Variable Naming Conflicts
|
|||
|
|
|
|||
|
|
**Risk**: Pattern variable shadows important local/field
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Each if arm creates new scope (natural shadowing)
|
|||
|
|
- Warning for shadowing (optional)
|
|||
|
|
- Document scoping rules
|
|||
|
|
|
|||
|
|
**Impact**: Low (expected behavior)
|
|||
|
|
|
|||
|
|
### 3. Exhaustiveness Check False Positives/Negatives
|
|||
|
|
|
|||
|
|
**Risk**: Runtime panic when all cases actually covered
|
|||
|
|
|
|||
|
|
**False Negative Example**:
|
|||
|
|
```hakorune
|
|||
|
|
// All cases covered, but panic still present
|
|||
|
|
@match result {
|
|||
|
|
Ok(v) => ...
|
|||
|
|
Err(e) => ...
|
|||
|
|
}
|
|||
|
|
// Else clause still added: panic on invalid state
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Phase 1: Always add panic (safe but verbose)
|
|||
|
|
- Phase 2: Static analysis to remove panic if proven exhaustive
|
|||
|
|
|
|||
|
|
**Impact**: Low (extra safety, no correctness issue)
|
|||
|
|
|
|||
|
|
**False Positive Risk**: None (panic only triggers on actual invalid state)
|
|||
|
|
|
|||
|
|
### 4. Performance of If/Else Chain vs Jump Table
|
|||
|
|
|
|||
|
|
**Risk**: Long match arms → slow O(n) check instead of O(1) jump
|
|||
|
|
|
|||
|
|
**Benchmark**:
|
|||
|
|
```hakorune
|
|||
|
|
@match status {
|
|||
|
|
Case1 => ...
|
|||
|
|
Case2 => ...
|
|||
|
|
// ... 100 cases
|
|||
|
|
Case100 => ...
|
|||
|
|
}
|
|||
|
|
// Worst case: 100 comparisons
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Phase 1: Accept O(n) for simplicity
|
|||
|
|
- Phase 2: Optimize to jump table (requires MIR switch instruction)
|
|||
|
|
|
|||
|
|
**Impact**: Low (typical match has 2-5 arms)
|
|||
|
|
|
|||
|
|
### 5. Debugging Challenges (Pattern → Field Mapping)
|
|||
|
|
|
|||
|
|
**Risk**: Hard to trace which field maps to which variable
|
|||
|
|
|
|||
|
|
**Example Debug Scenario**:
|
|||
|
|
```hakorune
|
|||
|
|
@match point {
|
|||
|
|
Cartesian(x, y) => print(x) // Which field is 'x'?
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Clear documentation of field naming convention
|
|||
|
|
- `--dump-mir` flag shows desugared code
|
|||
|
|
- Error messages show field names
|
|||
|
|
|
|||
|
|
**Example Desugared (for debugging)**:
|
|||
|
|
```hakorune
|
|||
|
|
if point._tag == "Cartesian" {
|
|||
|
|
local x // Maps to point._x
|
|||
|
|
local y // Maps to point._y
|
|||
|
|
x = point._x
|
|||
|
|
y = point._y
|
|||
|
|
print(x)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Impact**: Medium (developer experience)
|
|||
|
|
|
|||
|
|
### 6. Integration Risks with @enum
|
|||
|
|
|
|||
|
|
**Risk**: @match implemented before @enum, field mappings hardcoded
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Phase 1: Hardcode Result/Option mappings
|
|||
|
|
- Phase 1.5: Assume generic VariantBox convention (`_tag`, `_0`, `_1`, ...)
|
|||
|
|
- Phase 2: Replace with EnumSchemaBox query
|
|||
|
|
|
|||
|
|
**Impact**: Low (phased approach, backwards compatible)
|
|||
|
|
|
|||
|
|
### 7. Macro Expansion Order Issues
|
|||
|
|
|
|||
|
|
**Risk**: @match expands before @enum, can't find schema
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Phase 1: @match doesn't depend on @enum (hardcoded mappings)
|
|||
|
|
- Phase 2: Macro dependency ordering system
|
|||
|
|
|
|||
|
|
**Impact**: None (Phase 1 independent)
|
|||
|
|
|
|||
|
|
### 8. AST Size Explosion
|
|||
|
|
|
|||
|
|
**Risk**: Large match → large if/else AST → slow compilation
|
|||
|
|
|
|||
|
|
**Example**:
|
|||
|
|
```hakorune
|
|||
|
|
@match value {
|
|||
|
|
Case1 => { /* 100 lines */ }
|
|||
|
|
Case2 => { /* 100 lines */ }
|
|||
|
|
// ... 50 cases
|
|||
|
|
}
|
|||
|
|
// Desugared AST: 5000+ nodes
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Mitigation**:
|
|||
|
|
- Accept for Phase 1 (typical case: 2-5 arms)
|
|||
|
|
- Optimize AST representation (Phase 2)
|
|||
|
|
|
|||
|
|
**Impact**: Low (rare in practice)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 Success Metrics
|
|||
|
|
|
|||
|
|
### MVP Completion Criteria
|
|||
|
|
|
|||
|
|
- ✅ All 15 test cases pass
|
|||
|
|
- ✅ Result/Option matching works correctly
|
|||
|
|
- ✅ Variable bindings extract fields properly
|
|||
|
|
- ✅ Exhaustiveness panic triggers on invalid states
|
|||
|
|
- ✅ Match-as-expression works (returns value)
|
|||
|
|
- ✅ No regressions in existing tests
|
|||
|
|
- ✅ Documentation complete (this spec + user guide)
|
|||
|
|
|
|||
|
|
### Performance Benchmarks (Optional)
|
|||
|
|
|
|||
|
|
- Match with 2 arms: <1ms overhead vs hand-written if/else
|
|||
|
|
- Match with 10 arms: <5ms overhead
|
|||
|
|
- Compilation time: <10% increase for typical codebase
|
|||
|
|
|
|||
|
|
### Quality Metrics
|
|||
|
|
|
|||
|
|
- Code coverage: >90% for match desugaring logic
|
|||
|
|
- Zero parser crashes on valid input
|
|||
|
|
- Clear error messages for all invalid inputs
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 Related Documentation
|
|||
|
|
|
|||
|
|
- **VariantBox Design**: [DESIGN.md](./DESIGN.md)
|
|||
|
|
- **@enum Macro Spec**: (To be created)
|
|||
|
|
- **Result/Option API**: [apps/lib/boxes/result.hako](../../../apps/lib/boxes/result.hako), [option.hako](../../../apps/lib/boxes/option.hako)
|
|||
|
|
- **Macro System**: [docs/private/roadmap/phases/phase-16-macro-revolution/](../phase-16-macro-revolution/)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Next Steps After MVP
|
|||
|
|
|
|||
|
|
### Phase 20.1: @enum Macro
|
|||
|
|
- Implement @enum desugaring to static box + VariantBox
|
|||
|
|
- Generate EnumSchemaBox metadata
|
|||
|
|
- Connect @match to schema for field name resolution
|
|||
|
|
|
|||
|
|
### Phase 20.2: Compile-Time Exhaustiveness Check
|
|||
|
|
- Implement exhaustiveness checker
|
|||
|
|
- Remove runtime panic for proven-exhaustive matches
|
|||
|
|
- Add warnings for unreachable patterns
|
|||
|
|
|
|||
|
|
### Phase 20.3: Advanced Patterns
|
|||
|
|
- Literal patterns (`42 => ...`)
|
|||
|
|
- Guard clauses (`Some(x) if x > 10`)
|
|||
|
|
- Nested patterns (`Some(Ok(v))`)
|
|||
|
|
- Multiple patterns (`Ok(v) | Some(v)`)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Document Status**: ✅ Complete, ready for implementation
|
|||
|
|
**Estimated Effort**: 6 person-days (48 hours)
|
|||
|
|
**Dependencies**: None (self-contained)
|
|||
|
|
**Blocking**: None (can start immediately)
|