# @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, arms: Vec, 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, // 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, // ["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 { 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 { 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 { 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> = 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 { 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)