Files
hakorune/docs/private/roadmap/phases/phase-20-variant-box/@match-quick-start.md

461 lines
10 KiB
Markdown
Raw Normal View History

# @match Macro Quick Start Guide
**✅ Status**: **Phase 19 完了2025-10-08** - match式 完全実装済み(@matchマクロではなく、正規match構文として
**For**: ~~Developers implementing @match macro~~ **実装完了記録**
**Time**: ~~6 days~~ **✅ 完了2025-10-08**
**Difficulty**: Medium
**Prerequisites**: Understanding of Rust parser, AST, macros
---
## 🎉 実装完了状況2025-10-08
-**match式 Parser**: `src/parser/expr/match_expr.rs` (392行)
-**Literal patterns**: 整数・文字列・bool対応
-**Type patterns**: Box型パターン対応
-**Guards**: `if` ガード条件対応
- ⚠️ **注意**: `@match`マクロ**ではなく**、正規`match`構文として実装
**使用例** (実装済み):
```hakorune
match result {
Ok(value) if value > 0 => print("Positive")
Ok(value) => print("Non-positive")
Err(e) => print("Error: " + e)
}
```
---
## 📋 Quick Links
- **Complete Spec**: [@match-macro-implementation-spec.md](./@match-macro-implementation-spec.md)
- **VariantBox Design**: [DESIGN.md](./DESIGN.md)
- **Existing Boxes**:
- [apps/lib/boxes/result.hako](../../../../apps/lib/boxes/result.hako)
- [apps/lib/boxes/option.hako](../../../../apps/lib/boxes/option.hako)
---
## 🎯 What You're Building
Transform this:
```hakorune
@match result {
Ok(v) => print("Success: " + v)
Err(e) => print("Error: " + e)
}
```
Into this:
```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")
}
```
---
## 🚀 6-Day Implementation Plan
### Day 1: Parser Foundation
**Goal**: Parse @match syntax without errors
**Tasks**:
1. Add `MatchPattern` to `ASTNode` enum
2. Add `Pattern` and `MatchArm` structs
3. Add `FatArrow` and `Underscore` tokens
4. Implement `parse_match_pattern()` method
**Files to Modify**:
- `src/ast.rs` - AST nodes
- `src/tokenizer.rs` - Token types
- `src/parser/statements/mod.rs` - Parser methods
**Test**:
```bash
# Should parse without error
echo '@match x { Ok(v) => v }' | ./target/release/hako --dump-ast -
```
### Day 2: Tag Comparison Generation
**Goal**: Generate if-conditions for patterns
**Tasks**:
1. Create field mapping registry
2. Implement tag comparison code
3. Generate if-condition AST nodes
**Hardcoded Mappings**:
```rust
// Result
Ok → _ok==1, field: _val
Err → _ok==0, field: _err
// Option
Some → _is_some==1, field: _value
None → _is_some==0
// Generic VariantBox
Variant → _tag=="Variant", fields: _0, _1, ...
```
**Test**:
```hakorune
@match result { Ok(v) => v }
// Should generate: if result._ok == 1 { ... }
```
### Day 3: Variable Binding
**Goal**: Extract pattern variables to local declarations
**Tasks**:
1. Implement binding extraction
2. Generate `local` declarations
3. Generate field access code
4. Insert bindings at top of arm
**Test**:
```hakorune
@match point { Cartesian(x, y) => print(x + y) }
// Should generate:
// local x
// local y
// x = point._x
// y = point._y
```
### Day 4: Exhaustiveness Check
**Goal**: Add safety panic for unknown variants
**Tasks**:
1. Generate catch-all else clause
2. Generate panic message
3. Handle match-as-expression case
**Test**:
```hakorune
@match result { Ok(v) => v }
// Should generate final else:
// else { print("[PANIC] non-exhaustive match...") }
```
### Day 5: Basic Tests (8/15)
**Goal**: Verify core functionality
**Test Files**:
- Test 1-5: Basic matching
- Test 6-8: Variable binding
**Run**:
```bash
./target/release/hako apps/tests/match/test_result_basic.hako
./target/release/hako apps/tests/match/test_option_basic.hako
# ... etc
```
### Day 6: Edge Cases (7/15)
**Goal**: Handle corner cases
**Test Files**:
- Test 9-11: Exhaustiveness
- Test 12-15: Edge cases (loops, early return, etc.)
**Final Verification**:
```bash
# Run all match tests
bash tools/test_match_patterns.sh
# Run smoke tests
tools/smokes/v2/run.sh --profile quick
```
---
## 🔑 Key Implementation Points
### 1. AST Node Structure
```rust
// src/ast.rs
pub enum ASTNode {
// ... existing variants ...
MatchPattern {
scrutinee: Box<ASTNode>,
arms: Vec<MatchArm>,
span: Span,
},
}
pub struct MatchArm {
pub pattern: Pattern,
pub body: Vec<ASTNode>,
pub span: Span,
}
pub enum Pattern {
Variant {
name: String,
bindings: Vec<String>,
span: Span,
},
Wildcard {
span: Span,
},
}
```
### 2. Desugaring Algorithm (Simplified)
```rust
fn desugar_match(scrutinee: &ASTNode, arms: &[MatchArm]) -> ASTNode {
let mut if_chain = None;
// Build if/else chain (reverse order)
for arm in arms.iter().rev() {
let condition = generate_tag_check(scrutinee, &arm.pattern);
let bindings = generate_bindings(scrutinee, &arm.pattern);
let mut then_body = bindings;
then_body.extend(arm.body.clone());
if_chain = Some(ASTNode::If {
condition: Box::new(condition),
then_body,
else_body: if_chain.map(|node| vec![node]),
span: Span::unknown(),
});
}
// Add exhaustiveness panic
let panic = ASTNode::Print {
expression: Box::new(ASTNode::Literal {
value: LiteralValue::String("[PANIC] non-exhaustive match".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
};
if let Some(ASTNode::If { else_body, .. }) = &mut if_chain {
*else_body = Some(vec![panic]);
}
if_chain.unwrap()
}
```
### 3. Field Mapping Function
```rust
fn get_field_mapping(variant: &str, binding_index: usize) -> String {
match variant {
"Ok" => "_val".to_string(),
"Err" => "_err".to_string(),
"Some" => "_value".to_string(),
"None" => panic!("None has no fields"),
_ => format!("_{}", binding_index), // Generic: _0, _1, ...
}
}
fn get_tag_check(variant: &str) -> (&str, &str) {
match variant {
"Ok" => ("_ok", "1"),
"Err" => ("_ok", "0"),
"Some" => ("_is_some", "1"),
"None" => ("_is_some", "0"),
_ => ("_tag", variant), // Generic: _tag == "Variant"
}
}
```
---
## 🧪 Testing Strategy
### Smoke Test Template
```hakorune
// apps/tests/match/test_NAME.hako
using "../../lib/boxes/result.hako"
static box Main {
main() {
// Setup
local r = Result.ok(42)
// Test @match
local result = @match r {
Ok(v) => v * 2
Err(e) => -1
}
// Assert
if result != 84 {
print("FAIL: Expected 84, got " + result)
return 1
}
print("PASS: test_NAME")
return 0
}
}
```
### Running Tests
```bash
# Single test
./target/release/hako apps/tests/match/test_result_basic.hako
# With MIR dump (verify desugaring)
./target/release/hako --dump-mir apps/tests/match/test_result_basic.hako
# All tests
for f in apps/tests/match/*.hako; do
echo "Testing $f..."
./target/release/hako "$f" || echo "FAILED: $f"
done
```
---
## 🚨 Common Pitfalls
### 1. Parser Order
**Problem**: `=>` tokenized as `>` + `=`
**Solution**: Tokenize `=>` as single `FatArrow` token first
### 2. Variable Scope
**Problem**: Pattern variables leak to outer scope
**Solution**: Each if-arm creates new scope (via if block)
### 3. Expression vs Statement
**Problem**: Match used as expression (returns value)
**Solution**: Introduce temporary variable, assign in each arm
### 4. Exhaustiveness False Positives
**Problem**: All cases covered, but panic still added
**Solution**: Phase 1: Always add panic (safe). Phase 2: Static analysis
### 5. Field Name Conflicts
**Problem**: Multiple variants with same field name
**Solution**: Use variant-specific field names (`_val` vs `_err`)
---
## 📊 Progress Checklist
### Day 1: Parser
- [ ] `MatchPattern` AST node added
- [ ] `Pattern` enum added
- [ ] `FatArrow` token added
- [ ] `parse_match_pattern()` implemented
- [ ] Can parse basic @match without error
### Day 2: Tag Comparison
- [ ] Field mapping registry created
- [ ] Tag check generation works
- [ ] If-condition AST generation works
- [ ] Can generate if/else skeleton
### Day 3: Variable Binding
- [ ] Binding extraction works
- [ ] `local` declarations generated
- [ ] Field access generated
- [ ] Bindings inserted correctly
### Day 4: Exhaustiveness
- [ ] Catch-all else clause works
- [ ] Panic message generated
- [ ] Match-as-expression works
- [ ] Null assignment in panic case
### Day 5: Basic Tests
- [ ] Test 1: Result basic - PASS
- [ ] Test 2: Option basic - PASS
- [ ] Test 3: 3-way variant - PASS
- [ ] Test 4: Expression - PASS
- [ ] Test 5: Multi-statement - PASS
- [ ] Test 6: Single binding - PASS
- [ ] Test 7: Multi binding - PASS
- [ ] Test 8: Shadowing - PASS
### Day 6: Edge Cases
- [ ] Test 9: Exhaustive - PASS
- [ ] Test 10: Non-exhaustive - PASS
- [ ] Test 11: Wildcard - PASS
- [ ] Test 12: 0-field variant - PASS
- [ ] Test 13: Nested if - PASS
- [ ] Test 14: Loop control - PASS
- [ ] Test 15: Early return - PASS
### Final
- [ ] All 15 tests PASS
- [ ] Smoke tests PASS
- [ ] Documentation complete
- [ ] Ready for production
---
## 🎓 Learning Resources
### Rust Parser References
- `src/parser/expressions.rs` - Expression parsing patterns
- `src/parser/statements/mod.rs` - Statement parsing
- `src/ast.rs` - AST node definitions
### Existing Macro Examples
- `src/macro/macro_box.rs` - MacroBox trait
- `apps/macros/loop_normalize_macro.nyash` - Complex desugaring
### Hakorune Box Examples
- `apps/lib/boxes/result.hako` - Result implementation
- `apps/lib/boxes/option.hako` - Option implementation
---
## 💡 Tips for Success
1. **Start Simple**: Test with 2-arm match first (Ok/Err)
2. **Use --dump-mir**: Verify desugared code constantly
3. **Incremental Testing**: Test each day's work immediately
4. **Reference Phase 16**: @derive implementation is similar pattern
5. **Ask Questions**: Consult complete spec for details
---
## 🆘 When Stuck
### Debug Commands
```bash
# See parsed AST
./target/release/hako --dump-ast test.hako
# See desugared code
./target/release/hako --dump-mir test.hako
# Verbose diagnostics
NYASH_CLI_VERBOSE=1 ./target/release/hako test.hako
```
### Common Issues
- **Parse Error**: Check tokenizer output
- **Wrong Desugaring**: Check field mapping registry
- **Test Failure**: Use --dump-mir to compare expected vs actual
---
**Ready to Start?** Begin with Day 1 tasks and refer to the complete spec for detailed algorithms!
**Questions?** Check [@match-macro-implementation-spec.md](./@match-macro-implementation-spec.md) for comprehensive details.