1943 lines
41 KiB
Markdown
1943 lines
41 KiB
Markdown
|
|
# @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**
|