Files
hakorune/docs/private/roadmap/phases/phase-20-variant-box/@enum-macro-implementation-spec.md

1943 lines
41 KiB
Markdown
Raw Normal View History

# @enum Macro Implementation Specification (Choice A'': Macro-Only)
**Version**: 1.0
**Date**: 2025-10-08
**Status**: ✅ **完了2025-10-08** - Phase 19 実装済み
**Approach**: Macro-only desugaring (no VariantBox Core implementation)
---
## 🎉 実装完了状況2025-10-08
-**@enum Parser**: `src/parser/declarations/enum_parser.rs` (147行)
-**Macro Expansion**: `src/macro/engine.rs` (expand_enum_to_boxes関数)
-**match式**: `src/parser/expr/match_expr.rs` (392行、literal/type patterns + guards)
-**ResultBox Phase 1**: `apps/lib/boxes/result.hako` (103行)
-**OptionBox**: `apps/lib/boxes/option.hako` (94行、manual implementation)
- ⚠️ **API競合未解決**: 小文字版 vs @enum版(詳細は Section 1.3 参照)
---
## Table of Contents
1. [Overview](#1-overview)
2. [Parser Changes Specification](#2-parser-changes-specification)
3. [Macro Desugaring Rules](#3-macro-desugaring-rules)
4. [Edge Cases](#4-edge-cases)
5. [Test Suite Design](#5-test-suite-design)
6. [Implementation Task Breakdown](#6-implementation-task-breakdown)
7. [Integration with Existing Code](#7-integration-with-existing-code)
8. [Risk Analysis](#8-risk-analysis)
---
## 1. Overview
### 1.1 Goal
Implement `@enum` macro that desugars into:
- **1 box** (data container with `_tag` field)
- **1 static box** (constructors + helper methods)
- **All Box-typed fields** (Everything is Box principle)
- **MIR16 Frozen**: No IR changes, pure desugaring
### 1.2 Design Philosophy
- **Box-First**: All fields are Box-typed (no primitives)
- **Tag-Based**: Use `_tag: StringBox` for variant discrimination
- **Fail-Fast**: Type mismatches panic with clear error messages
- **Zero IR Impact**: Everything compiles to existing MIR16 instructions
### 1.3 Existing Foundation
- **Option/Result** already implemented (apps/lib/boxes/option.hako, result.hako)
- **Current pattern**: Uses `IntegerBox` for flags (`_is_some`, `_ok`)
- **New pattern**: Switch to `StringBox` `_tag` field for consistency
---
## 2. Parser Changes Specification
### 2.1 Token Recognition
**File**: `src/parser/mod.rs`
**New Token Pattern**:
```rust
// In tokenizer or parser
TokenType::At // '@' character
TokenType::Identifier("enum")
```
**Parsing Trigger**:
```rust
// In parse_statement() or top-level parser
if current_token == TokenType::At && peek() == TokenType::Identifier("enum") {
return parse_enum_declaration();
}
```
### 2.2 AST Node Structure
**Option A: Reuse BoxDeclaration with flag**
```rust
// In src/ast.rs ASTNode enum
BoxDeclaration {
name: String,
// ... existing fields ...
is_enum: bool, // NEW: Flag for enum boxes
enum_variants: Vec<EnumVariant>, // NEW: Variant definitions
span: Span,
}
#[derive(Debug, Clone)]
pub struct EnumVariant {
pub name: String, // "Ok", "Err", "None", etc.
pub fields: Vec<EnumField>, // Field list for this variant
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct EnumField {
pub name: String, // Field name (or auto-generated)
pub span: Span,
}
```
**Option B: New EnumDeclaration variant** (recommended for clarity)
```rust
// In src/ast.rs ASTNode enum
EnumDeclaration {
name: String, // "Result", "Option", etc.
variants: Vec<EnumVariant>, // List of variants
span: Span,
}
```
### 2.3 Parser Function
**Location**: `src/parser/declarations/mod.rs` or new `src/parser/declarations/enum_parser.rs`
```rust
/// Parse @enum declaration
///
/// Grammar:
/// @enum EnumName {
/// Variant1
/// Variant2(field)
/// Variant3(field1, field2)
/// }
fn parse_enum_declaration(&mut self) -> Result<ASTNode, ParseError> {
// 1. Consume '@enum' tokens
self.expect(TokenType::At)?;
self.expect_keyword("enum")?;
// 2. Parse enum name
let name = self.expect_identifier()?;
let enum_name = format!("{}Box", name); // Result -> ResultBox
// 3. Parse variants block
self.expect(TokenType::LeftBrace)?;
let mut variants = Vec::new();
while !self.check(TokenType::RightBrace) {
let variant = self.parse_enum_variant()?;
variants.push(variant);
// Optional newline/semicolon between variants
self.consume_if(TokenType::Newline);
self.consume_if(TokenType::Semicolon);
}
self.expect(TokenType::RightBrace)?;
// 4. Build AST node
Ok(ASTNode::EnumDeclaration {
name,
variants,
span: self.span_from(start),
})
}
/// Parse single enum variant
///
/// Grammar:
/// Variant
/// Variant(field)
/// Variant(field1, field2)
fn parse_enum_variant(&mut self) -> Result<EnumVariant, ParseError> {
let name = self.expect_identifier()?;
let mut fields = Vec::new();
// Check for field list
if self.consume_if(TokenType::LeftParen) {
while !self.check(TokenType::RightParen) {
let field_name = self.expect_identifier()?;
fields.push(EnumField {
name: field_name,
span: self.current_span(),
});
if !self.check(TokenType::RightParen) {
self.expect(TokenType::Comma)?;
}
}
self.expect(TokenType::RightParen)?;
}
Ok(EnumVariant {
name,
fields,
span: self.span_from(start),
})
}
```
### 2.4 Error Handling
**Parse Errors**:
```rust
#[derive(Error, Debug)]
pub enum ParseError {
// ... existing errors ...
#[error("Invalid enum syntax at line {line}: {message}")]
InvalidEnumSyntax { message: String, line: usize },
#[error("Duplicate variant name '{name}' in enum at line {line}")]
DuplicateVariant { name: String, line: usize },
#[error("Empty enum declaration at line {line}")]
EmptyEnum { line: usize },
}
```
**Validation**:
1. Enum must have at least 1 variant
2. Variant names must be unique
3. Variant names must be valid identifiers
4. Field names within a variant must be unique
---
## 3. Macro Desugaring Rules
### 3.1 Overall Transformation
**Input**:
```hakorune
@enum Result {
Ok(value)
Err(error)
}
```
**Output**:
```hakorune
box ResultBox {
_tag: StringBox
_value: Box
_error: Box
birth() {
me._tag = ""
me._value = null
me._error = null
}
}
static box Result {
Ok(v) {
local r = new ResultBox()
r._tag = "Ok"
r._value = v
return r
}
Err(e) {
local r = new ResultBox()
r._tag = "Err"
r._error = e
return r
}
is_Ok(variant) { return variant._tag == "Ok" }
is_Err(variant) { return variant._tag == "Err" }
as_Ok(variant) {
if variant._tag != "Ok" {
print("[PANIC] Result.as_Ok: called on " + variant._tag)
return null
}
return variant._value
}
as_Err(variant) {
if variant._tag != "Err" {
print("[PANIC] Result.as_Err: called on " + variant._tag)
return null
}
return variant._error
}
}
```
### 3.2 Naming Conventions
| Element | Convention | Example |
|---------|-----------|---------|
| **Enum Name** | Input name | `Result`, `Option` |
| **Box Name** | `{Name}Box` | `ResultBox`, `OptionBox` |
| **Tag Field** | `_tag` (always) | `_tag: StringBox` |
| **Variant Field (single)** | `_value` | For `Ok(value)``_value` |
| **Variant Field (named)** | `_{field}` | For `Err(error)``_error` |
| **Variant Field (multi)** | `_{field1}`, `_{field2}` | For `Pair(first, second)``_first`, `_second` |
| **Constructor** | Variant name | `Ok()`, `Err()` |
| **Is-checker** | `is_{Variant}(v)` | `is_Ok(v)`, `is_Err(v)` |
| **Getter** | `as_{Variant}(v)` | `as_Ok(v)`, `as_Err(v)` |
### 3.3 Field Generation Rules
**Rule 1: Collect all fields from all variants**
```
@enum Result {
Ok(value) → _value: Box
Err(error) → _error: Box
}
→ Fields: [_tag, _value, _error]
```
**Rule 2: All fields are Box-typed**
```hakorune
_tag: StringBox // Always StringBox
_value: Box // Generic Box (runtime polymorphism)
_error: Box
```
**Rule 3: Single unnamed field → `_value`**
```
Some(value) → _value: Box
```
**Rule 4: Multiple/named fields → prefix with `_`**
```
Pair(first, second) → _first: Box, _second: Box
```
### 3.4 Birth Method Generation
**Template**:
```hakorune
birth() {
me._tag = ""
{for each field}
me.{field_name} = null
{end for}
}
```
**Example (Result)**:
```hakorune
birth() {
me._tag = ""
me._value = null
me._error = null
}
```
### 3.5 Constructor Generation
**Template for each variant**:
```hakorune
{VariantName}({param1}, {param2}, ...) {
local inst = new {EnumName}Box()
inst._tag = "{VariantName}"
{for each field in variant}
inst._{field_name} = {param_name}
{end for}
return inst
}
```
**Example (Ok variant)**:
```hakorune
Ok(v) {
local r = new ResultBox()
r._tag = "Ok"
r._value = v
return r
}
```
**Example (0-field variant)**:
```hakorune
None() {
local opt = new OptionBox()
opt._tag = "None"
return opt
}
```
### 3.6 Helper Method Generation
#### 3.6.1 is_{Variant} Methods
**Template**:
```hakorune
is_{VariantName}(variant) {
return variant._tag == "{VariantName}"
}
```
**Example**:
```hakorune
is_Ok(variant) { return variant._tag == "Ok" }
is_Err(variant) { return variant._tag == "Err" }
```
#### 3.6.2 as_{Variant} Methods
**Template (single field)**:
```hakorune
as_{VariantName}(variant) {
if variant._tag != "{VariantName}" {
print("[PANIC] {EnumName}.as_{VariantName}: called on " + variant._tag)
return null
}
return variant._{field_name}
}
```
**Template (multi-field) - returns ArrayBox**:
```hakorune
as_{VariantName}(variant) {
if variant._tag != "{VariantName}" {
print("[PANIC] {EnumName}.as_{VariantName}: called on " + variant._tag)
return null
}
local result = new ArrayBox()
result.push(variant._{field1})
result.push(variant._{field2})
return result
}
```
**Example (single field)**:
```hakorune
as_Ok(variant) {
if variant._tag != "Ok" {
print("[PANIC] Result.as_Ok: called on " + variant._tag)
return null
}
return variant._value
}
```
**Example (multi-field)**:
```hakorune
as_Pair(variant) {
if variant._tag != "Pair" {
print("[PANIC] Triple.as_Pair: called on " + variant._tag)
return null
}
local result = new ArrayBox()
result.push(variant._first)
result.push(variant._second)
return result
}
```
#### 3.6.3 0-field variant getter
For variants with no fields, `as_*` still exists but returns null:
```hakorune
as_None(variant) {
if variant._tag != "None" {
print("[PANIC] Option.as_None: called on " + variant._tag)
return null
}
return null // No fields to return
}
```
### 3.7 Macro Implementation Location
**File**: `src/macro/enum_macro.rs` (new file)
```rust
use crate::ast::{ASTNode, EnumVariant, EnumField};
/// Expand @enum declaration into box + static box
pub fn expand_enum(name: &str, variants: &[EnumVariant]) -> Vec<ASTNode> {
let box_name = format!("{}Box", name);
// 1. Generate data box
let data_box = generate_data_box(&box_name, variants);
// 2. Generate static box
let static_box = generate_static_box(name, &box_name, variants);
vec![data_box, static_box]
}
fn generate_data_box(box_name: &str, variants: &[EnumVariant]) -> ASTNode {
// Collect all unique fields from all variants
let mut all_fields = vec!["_tag".to_string()];
for variant in variants {
for field in &variant.fields {
let field_name = if variant.fields.len() == 1 && field.name == "value" {
"_value".to_string()
} else {
format!("_{}", field.name)
};
if !all_fields.contains(&field_name) {
all_fields.push(field_name);
}
}
}
// Generate birth method
let birth_body = generate_birth_method(&all_fields);
// Build BoxDeclaration AST node
ASTNode::BoxDeclaration {
name: box_name.to_string(),
fields: all_fields,
methods: hashmap! {
"birth".to_string() => birth_body,
},
// ... other fields with defaults ...
}
}
fn generate_static_box(enum_name: &str, box_name: &str, variants: &[EnumVariant]) -> ASTNode {
let mut methods = HashMap::new();
for variant in variants {
// Generate constructor
let constructor = generate_constructor(box_name, &variant.name, &variant.fields);
methods.insert(variant.name.clone(), constructor);
// Generate is_* helper
let is_helper = generate_is_helper(&variant.name);
methods.insert(format!("is_{}", variant.name), is_helper);
// Generate as_* helper
let as_helper = generate_as_helper(enum_name, &variant.name, &variant.fields);
methods.insert(format!("as_{}", variant.name), as_helper);
}
ASTNode::BoxDeclaration {
name: enum_name.to_string(),
is_static: true,
methods,
// ... other fields with defaults ...
}
}
```
### 3.8 Integration Point
**File**: `src/macro/mod.rs`
```rust
mod enum_macro;
pub fn expand_macros(ast: ASTNode) -> ASTNode {
// ... existing macro expansion ...
// Apply enum expansion
ast = enum_macro::expand_all_enums(ast);
// ... continue with other macros ...
}
```
---
## 4. Edge Cases
### 4.1 Single Variant Enum
**Input**:
```hakorune
@enum Always {
Value(data)
}
```
**Output**:
```hakorune
box AlwaysBox {
_tag: StringBox
_data: Box
birth() {
me._tag = ""
me._data = null
}
}
static box Always {
Value(d) {
local inst = new AlwaysBox()
inst._tag = "Value"
inst._data = d
return inst
}
is_Value(variant) { return variant._tag == "Value" }
as_Value(variant) {
if variant._tag != "Value" {
print("[PANIC] Always.as_Value: called on " + variant._tag)
return null
}
return variant._data
}
}
```
**Note**: Still generates all helpers for consistency.
### 4.2 Zero-Field Variant
**Input**:
```hakorune
@enum Option {
Some(value)
None
}
```
**Output**:
```hakorune
box OptionBox {
_tag: StringBox
_value: Box
birth() {
me._tag = ""
me._value = null
}
}
static box Option {
Some(v) {
local opt = new OptionBox()
opt._tag = "Some"
opt._value = v
return opt
}
None() {
local opt = new OptionBox()
opt._tag = "None"
return opt
}
is_Some(variant) { return variant._tag == "Some" }
is_None(variant) { return variant._tag == "None" }
as_Some(variant) {
if variant._tag != "Some" {
print("[PANIC] Option.as_Some: called on " + variant._tag)
return null
}
return variant._value
}
as_None(variant) {
if variant._tag != "None" {
print("[PANIC] Option.as_None: called on " + variant._tag)
return null
}
return null
}
}
```
### 4.3 Multi-Field Variant
**Input**:
```hakorune
@enum Triple {
One(a)
Two(a, b)
Three(a, b, c)
}
```
**Output**:
```hakorune
box TripleBox {
_tag: StringBox
_a: Box
_b: Box
_c: Box
birth() {
me._tag = ""
me._a = null
me._b = null
me._c = null
}
}
static box Triple {
One(a) {
local inst = new TripleBox()
inst._tag = "One"
inst._a = a
return inst
}
Two(a, b) {
local inst = new TripleBox()
inst._tag = "Two"
inst._a = a
inst._b = b
return inst
}
Three(a, b, c) {
local inst = new TripleBox()
inst._tag = "Three"
inst._a = a
inst._b = b
inst._c = c
return inst
}
is_One(variant) { return variant._tag == "One" }
is_Two(variant) { return variant._tag == "Two" }
is_Three(variant) { return variant._tag == "Three" }
as_One(variant) {
if variant._tag != "One" {
print("[PANIC] Triple.as_One: called on " + variant._tag)
return null
}
return variant._a
}
as_Two(variant) {
if variant._tag != "Two" {
print("[PANIC] Triple.as_Two: called on " + variant._tag)
return null
}
local result = new ArrayBox()
result.push(variant._a)
result.push(variant._b)
return result
}
as_Three(variant) {
if variant._tag != "Three" {
print("[PANIC] Triple.as_Three: called on " + variant._tag)
return null
}
local result = new ArrayBox()
result.push(variant._a)
result.push(variant._b)
result.push(variant._c)
return result
}
}
```
### 4.4 Field Name Conflicts
**Problem**: Different variants use same field name with different semantics.
**Input**:
```hakorune
@enum Conflict {
TypeA(value)
TypeB(value)
}
```
**Resolution**: Both variants use `_value` field (shared storage).
**Output**:
```hakorune
box ConflictBox {
_tag: StringBox
_value: Box
birth() {
me._tag = ""
me._value = null
}
}
static box Conflict {
TypeA(v) {
local inst = new ConflictBox()
inst._tag = "TypeA"
inst._value = v
return inst
}
TypeB(v) {
local inst = new ConflictBox()
inst._tag = "TypeB"
inst._value = v
return inst
}
is_TypeA(variant) { return variant._tag == "TypeA" }
is_TypeB(variant) { return variant._tag == "TypeB" }
as_TypeA(variant) {
if variant._tag != "TypeA" {
print("[PANIC] Conflict.as_TypeA: called on " + variant._tag)
return null
}
return variant._value
}
as_TypeB(variant) {
if variant._tag != "TypeB" {
print("[PANIC] Conflict.as_TypeB: called on " + variant._tag)
return null
}
return variant._value
}
}
```
**Note**: This is intentional - field reuse is OK because tag discriminates.
### 4.5 Nested Enum (Not Supported in Phase 1)
**Input**:
```hakorune
@enum Outer {
@enum Inner { // ERROR
A
B
}
C
}
```
**Error**: "Nested @enum declarations are not supported"
**Workaround**: Declare enums separately.
### 4.6 Reserved Names
**Problem**: Variant name conflicts with built-in methods.
**Prohibited variant names**:
- `birth`
- `fini`
- `new` (reserved keyword)
- `me` (reserved keyword)
- `this` (reserved keyword)
**Error**: "Variant name '{name}' is reserved"
---
## 5. Test Suite Design
### 5.1 Test File Structure
```
apps/lib/boxes/tests/
├── enum_basic_test.hako # Test 1-3
├── enum_multi_variant_test.hako # Test 4
├── enum_zero_field_test.hako # Test 5
├── enum_multi_field_test.hako # Test 6
├── enum_helpers_test.hako # Test 7-8
├── enum_pattern_match_test.hako # Test 9
├── enum_integration_test.hako # Test 10
└── enum_real_world_ast_test.hako # Test 11
```
### 5.2 Test Cases
#### Test 1: Basic 2-Variant Enum (Result-like)
**File**: `enum_basic_test.hako`
```hakorune
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local r1 = Result.Ok(42)
print(r1._tag) // "Ok"
local r2 = Result.Err("failed")
print(r2._tag) // "Err"
return 0
}
}
```
**Expected Output**:
```
Ok
Err
```
#### Test 2: Basic 2-Variant Enum (Option-like)
**File**: `enum_basic_test.hako` (additional test)
```hakorune
@enum Option {
Some(value)
None
}
static box Main {
main() {
local opt1 = Option.Some(100)
print(opt1._tag) // "Some"
print(opt1._value) // "100"
local opt2 = Option.None()
print(opt2._tag) // "None"
return 0
}
}
```
**Expected Output**:
```
Some
100
None
```
#### Test 3: Constructor Field Assignment
**File**: `enum_basic_test.hako` (additional test)
```hakorune
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local r = Result.Ok("success")
if r._tag == "Ok" {
print(r._value) // "success"
}
if r._tag != "Err" {
print("not error") // "not error"
}
return 0
}
}
```
**Expected Output**:
```
success
not error
```
#### Test 4: 3+ Variant Enum
**File**: `enum_multi_variant_test.hako`
```hakorune
@enum Status {
Pending
Running(task_id)
Success(result)
Failed(error)
}
static box Main {
main() {
local s1 = Status.Pending()
local s2 = Status.Running(123)
local s3 = Status.Success("done")
local s4 = Status.Failed("timeout")
print(s1._tag) // "Pending"
print(s2._tag) // "Running"
print(s2._task_id) // "123"
print(s3._tag) // "Success"
print(s3._result) // "done"
print(s4._tag) // "Failed"
print(s4._error) // "timeout"
return 0
}
}
```
**Expected Output**:
```
Pending
Running
123
Success
done
Failed
timeout
```
#### Test 5: Variant with 0 Fields
**File**: `enum_zero_field_test.hako`
```hakorune
@enum Flag {
On
Off
}
static box Main {
main() {
local f1 = Flag.On()
local f2 = Flag.Off()
print(f1._tag) // "On"
print(f2._tag) // "Off"
return 0
}
}
```
**Expected Output**:
```
On
Off
```
#### Test 6: Variant with Multiple Fields
**File**: `enum_multi_field_test.hako`
```hakorune
@enum Point {
Point2D(x, y)
Point3D(x, y, z)
}
static box Main {
main() {
local p2 = Point.Point2D(10, 20)
local p3 = Point.Point3D(1, 2, 3)
print(p2._tag) // "Point2D"
print(p2._x) // "10"
print(p2._y) // "20"
print(p3._tag) // "Point3D"
print(p3._x) // "1"
print(p3._y) // "2"
print(p3._z) // "3"
return 0
}
}
```
**Expected Output**:
```
Point2D
10
20
Point3D
1
2
3
```
#### Test 7: is_* Helper Usage
**File**: `enum_helpers_test.hako`
```hakorune
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local r1 = Result.Ok(42)
local r2 = Result.Err("fail")
if Result.is_Ok(r1) {
print("r1 is Ok") // "r1 is Ok"
}
if Result.is_Err(r2) {
print("r2 is Err") // "r2 is Err"
}
if Result.is_Ok(r2) {
print("r2 is Ok") // (not printed)
}
return 0
}
}
```
**Expected Output**:
```
r1 is Ok
r2 is Err
```
#### Test 8: as_* Helper Success
**File**: `enum_helpers_test.hako` (additional test)
```hakorune
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local r = Result.Ok(100)
local val = Result.as_Ok(r)
print(val) // "100"
return 0
}
}
```
**Expected Output**:
```
100
```
#### Test 9: as_* Helper Panic (Wrong Variant)
**File**: `enum_helpers_test.hako` (additional test)
```hakorune
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local r = Result.Err("failed")
local val = Result.as_Ok(r)
// Expected: "[PANIC] Result.as_Ok: called on Err"
// Returns: null
if val == null {
print("got null after panic")
}
return 0
}
}
```
**Expected Output**:
```
[PANIC] Result.as_Ok: called on Err
got null after panic
```
#### Test 10: Pattern Matching Preparation
**File**: `enum_pattern_match_test.hako`
```hakorune
@enum Option {
Some(value)
None
}
static box Main {
process_option(opt) {
if opt._tag == "Some" {
print("Has value: " + opt._value)
}
if opt._tag == "None" {
print("No value")
}
}
main() {
local opt1 = Option.Some(42)
local opt2 = Option.None()
me.process_option(opt1) // "Has value: 42"
me.process_option(opt2) // "No value"
return 0
}
}
```
**Expected Output**:
```
Has value: 42
No value
```
#### Test 11: Integration with Existing Option/Result
**File**: `enum_integration_test.hako`
**Goal**: Test @enum-generated Result alongside old ResultBox.
```hakorune
using std.result as OldResult
@enum Result {
Ok(value)
Err(error)
}
static box Main {
main() {
local old_r = OldResult.ok(100)
local new_r = Result.Ok(200)
print(old_r.value()) // "100"
print(Result.as_Ok(new_r)) // "200"
return 0
}
}
```
**Expected Output**:
```
100
200
```
#### Test 12: Complex Real-World Case (AST Node Example)
**File**: `enum_real_world_ast_test.hako`
```hakorune
@enum ASTNode {
Literal(value)
Variable(name)
BinaryOp(op, left, right)
UnaryOp(op, operand)
}
static box Main {
main() {
local lit = ASTNode.Literal(42)
local var = ASTNode.Variable("x")
local binop = ASTNode.BinaryOp("+", lit, var)
print(ASTNode.is_Literal(lit)) // "1" (true)
print(ASTNode.is_BinaryOp(binop)) // "1" (true)
if ASTNode.is_BinaryOp(binop) {
local parts = ASTNode.as_BinaryOp(binop)
// parts is ArrayBox: ["+", lit, var]
print(parts.get(0)) // "+"
}
return 0
}
}
```
**Expected Output**:
```
1
1
+
```
### 5.3 Negative Tests
#### Test N1: Duplicate Variant Names
**File**: `enum_error_duplicate_variant.hako`
```hakorune
@enum Bad {
Ok(value)
Ok(other) // ERROR: Duplicate variant
}
```
**Expected Error**: "Duplicate variant name 'Ok' in enum at line X"
#### Test N2: Reserved Variant Name
**File**: `enum_error_reserved_name.hako`
```hakorune
@enum Bad {
birth(value) // ERROR: Reserved name
}
```
**Expected Error**: "Variant name 'birth' is reserved"
#### Test N3: Empty Enum
**File**: `enum_error_empty.hako`
```hakorune
@enum Empty {
// ERROR: No variants
}
```
**Expected Error**: "Empty enum declaration at line X"
### 5.4 Test Runner Script
**File**: `tools/run_enum_tests.sh`
```bash
#!/bin/bash
set -e
HAKO="./target/release/hako"
echo "=== @enum Macro Test Suite ==="
# Basic tests
echo "[1/12] Basic 2-variant enum (Result)..."
$HAKO apps/lib/boxes/tests/enum_basic_test.hako
echo "[2/12] Basic 2-variant enum (Option)..."
$HAKO apps/lib/boxes/tests/enum_basic_test.hako
echo "[3/12] Constructor field assignment..."
$HAKO apps/lib/boxes/tests/enum_basic_test.hako
echo "[4/12] Multi-variant enum (4 variants)..."
$HAKO apps/lib/boxes/tests/enum_multi_variant_test.hako
echo "[5/12] Zero-field variant..."
$HAKO apps/lib/boxes/tests/enum_zero_field_test.hako
echo "[6/12] Multi-field variant..."
$HAKO apps/lib/boxes/tests/enum_multi_field_test.hako
echo "[7/12] is_* helper usage..."
$HAKO apps/lib/boxes/tests/enum_helpers_test.hako
echo "[8/12] as_* helper success..."
$HAKO apps/lib/boxes/tests/enum_helpers_test.hako
echo "[9/12] as_* helper panic (wrong variant)..."
$HAKO apps/lib/boxes/tests/enum_helpers_test.hako
echo "[10/12] Pattern matching preparation..."
$HAKO apps/lib/boxes/tests/enum_pattern_match_test.hako
echo "[11/12] Integration with existing Option/Result..."
$HAKO apps/lib/boxes/tests/enum_integration_test.hako
echo "[12/12] Real-world AST node example..."
$HAKO apps/lib/boxes/tests/enum_real_world_ast_test.hako
# Negative tests
echo "[N1/3] Duplicate variant names..."
$HAKO apps/lib/boxes/tests/enum_error_duplicate_variant.hako 2>&1 | grep -q "Duplicate variant" && echo " ✓ Error caught" || (echo " ✗ Error not caught"; exit 1)
echo "[N2/3] Reserved variant name..."
$HAKO apps/lib/boxes/tests/enum_error_reserved_name.hako 2>&1 | grep -q "reserved" && echo " ✓ Error caught" || (echo " ✗ Error not caught"; exit 1)
echo "[N3/3] Empty enum..."
$HAKO apps/lib/boxes/tests/enum_error_empty.hako 2>&1 | grep -q "Empty enum" && echo " ✓ Error caught" || (echo " ✗ Error not caught"; exit 1)
echo "=== All tests passed ==="
```
---
## 6. Implementation Task Breakdown
### Day 1: Parser Changes + AST Nodes (6-8 hours)
**Morning (3-4h)**:
- [ ] Add `TokenType::At` if not exists
- [ ] Implement `parse_enum_declaration()` in `src/parser/declarations/enum_parser.rs`
- [ ] Implement `parse_enum_variant()` helper
- [ ] Add `EnumDeclaration`, `EnumVariant`, `EnumField` to `src/ast.rs`
**Afternoon (3-4h)**:
- [ ] Add validation (duplicate variants, empty enum, reserved names)
- [ ] Add error types to `ParseError` enum
- [ ] Write parser unit tests
- [ ] Test parser with minimal inputs (no macro expansion yet)
**Success Criteria**:
- Parser can parse `@enum` syntax without panicking
- AST nodes are created correctly
- Validation errors are caught
### Day 2: Macro Engine Integration + Code Generation (8-10 hours)
**Morning (4-5h)**:
- [ ] Create `src/macro/enum_macro.rs`
- [ ] Implement `expand_enum()` function
- [ ] Implement `generate_data_box()` - create box with fields
- [ ] Implement `generate_birth_method()` - initialize all fields to null
**Afternoon (4-5h)**:
- [ ] Implement `generate_static_box()` - create static box
- [ ] Implement `generate_constructor()` - one per variant
- [ ] Add macro invocation to `src/macro/mod.rs`
- [ ] Test expansion with debug prints (dump generated AST)
**Success Criteria**:
- `@enum Result { Ok(value) Err(error) }` expands to correct AST
- Generated AST can be printed back as code
- No panics during expansion
### Day 3: Helper Method Generation + Edge Cases (8-10 hours)
**Morning (4-5h)**:
- [ ] Implement `generate_is_helper()` - is_* methods
- [ ] Implement `generate_as_helper()` - as_* methods (single field)
- [ ] Implement multi-field `as_*` (returns ArrayBox)
- [ ] Handle 0-field variant `as_*` (returns null)
**Afternoon (4-5h)**:
- [ ] Test edge cases (single variant, zero fields, multi-field)
- [ ] Test field name conflicts (same name across variants)
- [ ] Add diagnostics/trace output (NYASH_MACRO_TRACE=1)
- [ ] Verify generated code compiles to MIR
**Success Criteria**:
- All helper methods generate correctly
- Edge cases handled without errors
- Generated code compiles and runs
### Day 4: Test Suite (6-8 hours)
**Morning (3-4h)**:
- [ ] Write tests 1-6 (basic cases)
- [ ] Write tests 7-9 (helper methods)
- [ ] Verify all tests run and produce expected output
**Afternoon (3-4h)**:
- [ ] Write tests 10-12 (pattern matching, integration, real-world)
- [ ] Write negative tests (N1-N3)
- [ ] Create `tools/run_enum_tests.sh` runner script
- [ ] Run full test suite
**Success Criteria**:
- All 12 positive tests pass
- All 3 negative tests catch errors correctly
- Test runner script exits with 0
### Day 5: Smoke Tests + Integration (6-8 hours)
**Morning (3-4h)**:
- [ ] Add @enum tests to smoke test suite
- [ ] Run `tools/smokes/v2/run.sh --profile quick`
- [ ] Fix any integration issues
- [ ] Document known limitations
**Afternoon (3-4h)**:
- [ ] Update `CURRENT_TASK.md` with @enum status
- [ ] Update `CLAUDE.md` development log
- [ ] Create migration guide for Option/Result
- [ ] Review and commit
**Success Criteria**:
- Smoke tests pass
- Documentation updated
- Ready for production use
---
## 7. Integration with Existing Code
### 7.1 Migration Strategy for Option/Result
**Current State**:
- `apps/lib/boxes/option.hako` - uses `_is_some: IntegerBox`
- `apps/lib/boxes/result.hako` - uses `_ok: IntegerBox`
- 5+ files use these boxes
**Migration Plan**:
#### Phase 1: Parallel Existence (Week 1)
1. Keep existing `option.hako` and `result.hako`
2. Create new `@enum Option` and `@enum Result` in separate files:
- `apps/lib/boxes/option_v2.hako`
- `apps/lib/boxes/result_v2.hako`
3. Update `hako.toml`:
```toml
[modules.overrides]
std.option = "apps/lib/boxes/option.hako" # Old version
std.option_v2 = "apps/lib/boxes/option_v2.hako" # New @enum version
std.result = "apps/lib/boxes/result.hako"
std.result_v2 = "apps/lib/boxes/result_v2.hako"
```
#### Phase 2: Gradual Migration (Week 2-3)
1. Migrate test files first
2. Migrate non-critical tools
3. Verify behavior matches old version
#### Phase 3: Deprecation (Week 4)
1. Rename old files:
- `option.hako``option_deprecated.hako`
- `result.hako``result_deprecated.hako`
2. Rename new files:
- `option_v2.hako``option.hako`
- `result_v2.hako``result.hako`
3. Update all import sites
#### Phase 4: Cleanup (Week 5)
1. Delete deprecated files
2. Remove old module aliases
3. Update documentation
### 7.2 API Compatibility Matrix
| Method | Old Option | @enum Option | Compatible? |
|--------|-----------|-------------|-------------|
| `Option.some(v)` | ✓ | `Option.Some(v)` | **Different name** |
| `Option.none()` | ✓ | `Option.None()` | **Different name** |
| `opt.is_some()` | ✓ | `Option.is_Some(opt)` | **Different signature** |
| `opt.unwrap()` | ✓ | `Option.as_Some(opt)` | **Different name** |
| `opt._value` | ✓ | ✓ | ✓ Compatible |
| `opt._is_some` | ✓ | ✗ (uses `_tag`) | **Breaking** |
**Conclusion**: **NOT backward compatible**. Requires explicit migration.
### 7.3 Wrapper Strategy for Compatibility
**Option**: Create compatibility wrapper
**File**: `apps/lib/boxes/option_compat.hako`
```hakorune
@enum OptionInternal {
Some(value)
None
}
static box Option {
// Old API
some(v) { return OptionInternal.Some(v) }
none() { return OptionInternal.None() }
// Adapter for instance methods
is_some(opt) { return opt._tag == "Some" }
is_none(opt) { return opt._tag == "None" }
unwrap(opt) { return OptionInternal.as_Some(opt) }
unwrap_or(opt, def) {
if opt._tag == "Some" {
return opt._value
}
return def
}
}
// Create facade OptionBox (for `new OptionBox()` compatibility)
box OptionBox {
_internal: Box
birth() {
me._internal = OptionInternal.None()
}
is_some() { return me._internal._tag == "Some" }
is_none() { return me._internal._tag == "None" }
unwrap() { return OptionInternal.as_Some(me._internal) }
}
```
**Trade-off**: Adds complexity but maintains backward compatibility.
### 7.4 Migration Guide Document
**File**: `docs/guides/enum-migration-guide.md`
**Contents**:
1. Why migrate to @enum?
2. API differences table
3. Step-by-step migration process
4. Common pitfalls
5. Examples (before/after)
---
## 8. Risk Analysis
### 8.1 Parser Conflicts
**Risk**: `@` token conflicts with other syntax (e.g., `@local` sugar)
**Mitigation**:
- Check existing `@` usage with grep
- Use lookahead: `@enum` specifically (not just `@`)
- Add parser tests for ambiguous cases
**Action Items**:
- [ ] Grep codebase for existing `@` usage
- [ ] Test parser with `@local` and `@enum` in same file
- [ ] Document precedence rules
### 8.2 Macro Expansion Order
**Risk**: @enum expansion happens before/after other macros, causing issues
**Current Macro Order** (from CLAUDE.md):
```rust
// src/macro/mod.rs
1. @local expansion
2. Map literal sugar
3. (add @enum here?)
```
**Mitigation**:
- Run @enum expansion **before** other macros (early in pipeline)
- @enum generates pure box definitions (no further macro dependencies)
**Action Items**:
- [ ] Verify macro execution order in `src/macro/mod.rs`
- [ ] Test interaction with map literal sugar
- [ ] Test interaction with @local sugar
### 8.3 Field Naming Conflicts
**Risk**: Generated `_tag`, `_value` conflict with user-defined fields
**Current Design**: Fields always prefixed with `_` (e.g., `_tag`, `_value`)
**Mitigation**:
- Document that `_`-prefixed fields are reserved for enum internals
- Add validation: reject variant field names starting with `_`
- Generate error: "Variant field names cannot start with '_' (reserved)"
**Action Items**:
- [ ] Add validation in `parse_enum_variant()`
- [ ] Test case: `Variant(_tag)` → error
- [ ] Document in language guide
### 8.4 Performance Considerations
**Risk**: String tag comparison slower than integer flag comparison
**Analysis**:
- Old: `if opt._is_some == 1` (integer compare, ~1 cycle)
- New: `if opt._tag == "Some"` (string compare, ~N cycles where N = length)
**Mitigation**:
- **VM**: String comparison already optimized in StringBox
- **LLVM**: Compiler can optimize string literals to pointer comparison
- **Measurement**: Add benchmark comparing old vs new
**Action Items**:
- [ ] Benchmark: Old Result vs @enum Result (1M operations)
- [ ] Profile: Measure hotspot in string comparison
- [ ] Document performance characteristics
**Expected Result**: <10% slowdown (acceptable for MVP)
### 8.5 Debugging Challenges
**Risk**: Expanded code hard to debug (macro generates verbose output)
**Mitigation**:
- Add `NYASH_MACRO_TRACE=1` to dump expansion
- Add `--dump-mir` flag support (show post-expansion code)
- Preserve source spans in generated AST
- Add comment markers in generated code:
```hakorune
// BEGIN @enum Result expansion
box ResultBox { ... }
// END @enum Result expansion
```
**Action Items**:
- [ ] Implement trace output in `enum_macro.rs`
- [ ] Test `--dump-mir` with @enum code
- [ ] Add source span preservation
- [ ] Add debug comments to generated AST
### 8.6 Error Message Quality
**Risk**: Users get confusing errors from generated code (not original @enum)
**Example Bad Error**:
```
Error at line 523: Field '_tag' not found in ResultBox
(User wrote @enum at line 10, error points to generated code at line 523)
```
**Mitigation**:
- Preserve original span in AST nodes
- Error formatter shows original @enum location
- Add note: "in expansion of @enum Result at line 10"
**Action Items**:
- [ ] Test error reporting with intentionally broken @enum
- [ ] Verify span preservation in macro expansion
- [ ] Update error formatter to show macro context
### 8.7 Macro-Generated Code Stability
**Risk**: Generated code doesn't compile to MIR (syntax errors, etc.)
**Mitigation**:
- Generate only well-tested AST patterns (box, static box, simple statements)
- Add roundtrip test: expand → parse → expand (should be stable)
- Add integration test: @enum → MIR → VM execution
**Action Items**:
- [ ] Test: Expand @enum Result → dump AST → parse again
- [ ] Test: @enum → MIR JSON → verify structure
- [ ] Test: @enum → VM execution → verify behavior
---
## 9. Success Criteria
**Phase 1 Complete When**:
1. All 12 positive tests pass ✓
2. All 3 negative tests catch errors ✓
3. Smoke tests pass ✓
4. Documentation complete ✓
5. Performance benchmark shows <10% regression
6. Integration guide written ✓
**Phase 2 (Future)**:
1. Pattern matching syntax (`match` expressions)
2. Exhaustiveness checking
3. Performance optimization (intern tag strings)
---
## 10. Future Enhancements (Post-MVP)
### 10.1 Pattern Matching Integration
**Syntax** (future):
```hakorune
match result {
Ok(value) => print("Success: " + value)
Err(error) => print("Error: " + error)
}
```
**Desugaring** (future):
```hakorune
if result._tag == "Ok" {
local value = result._value
print("Success: " + value)
}
if result._tag == "Err" {
local error = result._error
print("Error: " + error)
}
```
### 10.2 Exhaustiveness Checking
**Goal**: Compiler ensures all variants handled.
**Example**:
```hakorune
match option {
Some(v) => print(v)
// WARNING: Missing case for 'None'
}
```
### 10.3 String Interning for Tags
**Optimization**: Intern tag strings to enable pointer comparison.
**Implementation**:
- Tag strings stored in intern table
- Comparison becomes pointer equality (1 cycle)
- Backward compatible (still strings at API level)
---
## 11. References
### 11.1 Existing Code
- `apps/lib/boxes/option.hako` - Current Option implementation
- `apps/lib/boxes/result.hako` - Current Result implementation
- `src/parser/mod.rs` - Parser entry point
- `src/macro/macro_box.rs` - Macro system infrastructure
### 11.2 Documentation
- `CLAUDE.md` - Development log and context
- `docs/reference/language/quick-reference.md` - Language syntax
- `docs/private/roadmap/phases/phase-20-variant-box/` - Variant box proposals
### 11.3 Test Examples
- `apps/selfhost/test_*.hako` - Existing test patterns
- `tools/smokes/v2/` - Smoke test infrastructure
---
## Appendix A: Complete Example Expansion
**Input**:
```hakorune
@enum Option {
Some(value)
None
}
```
**Complete Expansion** (with all helpers):
```hakorune
// Data box
box OptionBox {
_tag: StringBox
_value: Box
birth() {
me._tag = ""
me._value = null
}
}
// Static box with constructors and helpers
static box Option {
// Constructors
Some(v) {
local opt = new OptionBox()
opt._tag = "Some"
opt._value = v
return opt
}
None() {
local opt = new OptionBox()
opt._tag = "None"
return opt
}
// is_* helpers
is_Some(variant) {
return variant._tag == "Some"
}
is_None(variant) {
return variant._tag == "None"
}
// as_* helpers
as_Some(variant) {
if variant._tag != "Some" {
print("[PANIC] Option.as_Some: called on " + variant._tag)
return null
}
return variant._value
}
as_None(variant) {
if variant._tag != "None" {
print("[PANIC] Option.as_None: called on " + variant._tag)
return null
}
return null
}
}
```
**Total Lines**: 47 (for 2-variant enum)
---
## Appendix B: Implementation Checklist
### Parser (src/parser/)
- [ ] `TokenType::At` exists or added
- [ ] `parse_enum_declaration()` implemented
- [ ] `parse_enum_variant()` implemented
- [ ] Validation: duplicate variants
- [ ] Validation: empty enum
- [ ] Validation: reserved names
- [ ] Validation: field names (no `_` prefix)
- [ ] Error types added to `ParseError`
- [ ] Parser unit tests
### AST (src/ast.rs)
- [ ] `EnumDeclaration` variant added
- [ ] `EnumVariant` struct defined
- [ ] `EnumField` struct defined
- [ ] Span preservation
### Macro (src/macro/)
- [ ] `enum_macro.rs` created
- [ ] `expand_enum()` function
- [ ] `generate_data_box()` function
- [ ] `generate_birth_method()` function
- [ ] `generate_static_box()` function
- [ ] `generate_constructor()` function
- [ ] `generate_is_helper()` function
- [ ] `generate_as_helper()` (single field)
- [ ] `generate_as_helper()` (multi-field)
- [ ] Integration with `src/macro/mod.rs`
- [ ] Trace output (NYASH_MACRO_TRACE=1)
### Tests (apps/lib/boxes/tests/)
- [ ] Test 1: Basic Result
- [ ] Test 2: Basic Option
- [ ] Test 3: Constructor field assignment
- [ ] Test 4: 3+ variants
- [ ] Test 5: Zero-field variant
- [ ] Test 6: Multi-field variant
- [ ] Test 7: is_* helpers
- [ ] Test 8: as_* success
- [ ] Test 9: as_* panic
- [ ] Test 10: Pattern matching prep
- [ ] Test 11: Integration
- [ ] Test 12: Real-world AST
- [ ] Test N1: Duplicate variants
- [ ] Test N2: Reserved names
- [ ] Test N3: Empty enum
- [ ] Test runner script
### Documentation
- [ ] Migration guide
- [ ] Language reference update
- [ ] CURRENT_TASK.md update
- [ ] CLAUDE.md update
- [ ] Performance benchmark results
### Integration
- [ ] Smoke tests pass
- [ ] hako.toml updated (if needed)
- [ ] No regressions in existing tests
- [ ] Performance <10% regression
---
**End of Specification**