10 KiB
@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構文として実装
使用例 (実装済み):
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
- VariantBox Design: DESIGN.md
- Existing Boxes:
🎯 What You're Building
Transform this:
@match result {
Ok(v) => print("Success: " + v)
Err(e) => print("Error: " + e)
}
Into this:
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:
- Add
MatchPatterntoASTNodeenum - Add
PatternandMatchArmstructs - Add
FatArrowandUnderscoretokens - Implement
parse_match_pattern()method
Files to Modify:
src/ast.rs- AST nodessrc/tokenizer.rs- Token typessrc/parser/statements/mod.rs- Parser methods
Test:
# 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:
- Create field mapping registry
- Implement tag comparison code
- Generate if-condition AST nodes
Hardcoded Mappings:
// 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:
@match result { Ok(v) => v }
// Should generate: if result._ok == 1 { ... }
Day 3: Variable Binding
Goal: Extract pattern variables to local declarations
Tasks:
- Implement binding extraction
- Generate
localdeclarations - Generate field access code
- Insert bindings at top of arm
Test:
@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:
- Generate catch-all else clause
- Generate panic message
- Handle match-as-expression case
Test:
@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:
./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:
# 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
// 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)
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
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
// 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
# 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
MatchPatternAST node addedPatternenum addedFatArrowtoken addedparse_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
localdeclarations 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 patternssrc/parser/statements/mod.rs- Statement parsingsrc/ast.rs- AST node definitions
Existing Macro Examples
src/macro/macro_box.rs- MacroBox traitapps/macros/loop_normalize_macro.nyash- Complex desugaring
Hakorune Box Examples
apps/lib/boxes/result.hako- Result implementationapps/lib/boxes/option.hako- Option implementation
💡 Tips for Success
- Start Simple: Test with 2-arm match first (Ok/Err)
- Use --dump-mir: Verify desugared code constantly
- Incremental Testing: Test each day's work immediately
- Reference Phase 16: @derive implementation is similar pattern
- Ask Questions: Consult complete spec for details
🆘 When Stuck
Debug Commands
# 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 for comprehensive details.