Files
hakorune/docs/development/current/main/phase186-body-local-init-lowering.md
nyash-codex d4231f5d3a feat(joinir): Phase 185-187 body-local infrastructure + string design
Phase 185: Body-local Pattern2/4 integration skeleton
- Added collect_body_local_variables() helper
- Integrated UpdateEnv usage in loop_with_break_minimal
- Test files created (blocked by init lowering)

Phase 186: Body-local init lowering infrastructure
- Created LoopBodyLocalInitLowerer box (378 lines)
- Supports BinOp (+/-/*//) + Const + Variable
- Fail-Fast for method calls/string operations
- 3 unit tests passing

Phase 187: String UpdateLowering design (doc-only)
- Defined UpdateKind whitelist (6 categories)
- StringAppendChar/Literal patterns identified
- 3-layer architecture documented
- No code changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 00:59:38 +09:00

532 lines
17 KiB
Markdown

# Phase 186: Body-local Init Lowering (箱化モジュール化)
**Status**: In Progress
**Date**: 2025-12-09
**Dependencies**: Phase 184 (LoopBodyLocalEnv), Phase 185 (Pattern2 integration)
## Overview
Phase 186 introduces **LoopBodyLocalInitLowerer** - a dedicated box for lowering body-local variable initialization expressions to JoinIR. This completes the body-local variable support by handling initialization expressions like `local digit_pos = pos - start`.
## Motivation
Phase 184 introduced LoopBodyLocalEnv to track body-local variables, and Phase 185 integrated it into Pattern2 for update expressions. However, **initialization expressions** were not yet lowered to JoinIR:
```nyash
loop(pos < 10) {
local digit_pos = pos - start // ← Init expression NOT lowered yet!
sum = sum + digit_pos // ← Update expression (Phase 184)
pos = pos + 1
}
```
**Problems without Phase 186**:
- `digit_pos` was declared in LoopBodyLocalEnv but had no JoinIR ValueId
- Using `digit_pos` in update expressions failed with "variable not found"
- Body-local calculations couldn't be performed in JoinIR
**Phase 186 Solution**:
- Lower init expressions (`pos - start`) to JoinIR instructions
- Assign JoinIR ValueId to body-local variable in env
- Enable body-local variables to be used in subsequent update expressions
## Scope Definition
### In Scope (Phase 186)
**Supported init expressions** (int/arithmetic only):
- Binary operations: `+`, `-`, `*`, `/`
- Constant literals: `42`, `0`, `1`
- Variable references: `pos`, `start`, `i`
**Examples**:
```nyash
local digit_pos = pos - start // ✅ BinOp + Variables
local temp = i * 2 // ✅ BinOp + Variable + Const
local offset = base + 10 // ✅ BinOp + Variable + Const
local cnt = i + 1 // ✅ BinOp + Variable + Const
```
### Out of Scope (Phase 186)
**NOT supported** (Fail-Fast with explicit error):
- String operations: `s.substring(...)`, `s + "abc"`
- Method calls: `box.method(...)`
- Complex expressions: nested BinOps, function calls
**Examples**:
```nyash
local ch = s.substring(pos, 1) // ❌ Method call → Fail-Fast error
local msg = "Error: " + text // ❌ String concat → Fail-Fast error
local result = calc(a, b) // ❌ Function call → Fail-Fast error
```
**Rationale**: Phase 178 established Fail-Fast principle for unsupported features. String/method call support requires additional infrastructure (BoxCall lowering, type tracking) - defer to future phases.
## Architecture
### Box Theory Design
Following 箱理論 (Box-First) principles:
```
┌─────────────────────────────────────────────────────────────┐
│ LoopBodyLocalInitLowerer (NEW) │
│ - Single responsibility: Lower init expressions to JoinIR │
│ - Clear boundary: Only handles init, not updates │
│ - Fail-Fast: Unsupported expressions → explicit error │
└─────────────────────────────────────────────────────────────┘
↓ (uses)
┌─────────────────────────────────────────────────────────────┐
│ LoopBodyLocalEnv (Phase 184) │
│ - Storage box for body-local variable mappings │
│ - name → JoinIR ValueId │
└─────────────────────────────────────────────────────────────┘
↓ (used by)
┌─────────────────────────────────────────────────────────────┐
│ CarrierUpdateEmitter (Phase 184) │
│ - Emits update instructions using UpdateEnv │
│ - Resolves variables from condition + body-local envs │
└─────────────────────────────────────────────────────────────┘
```
### Pipeline Integration
**Pattern2 Pipeline** (Phase 179-B + Phase 186):
```
1. Build PatternPipelineContext (loop features, carriers)
2. LoopConditionScopeBox::analyze() → ConditionEnv
3. ⭐ LoopBodyLocalInitLowerer::lower_inits_for_loop() ← NEW (Phase 186)
- Scans body AST for local declarations
- Lowers init expressions to JoinIR
- Updates LoopBodyLocalEnv with ValueIds
4. LoopUpdateAnalyzer::analyze_carrier_updates()
5. CarrierUpdateEmitter::emit_carrier_update_with_env()
6. JoinModule construction + MIR merge
```
**Pattern4 Pipeline** (similar integration):
```
1-2. (same as Pattern2)
3. ⭐ LoopBodyLocalInitLowerer::lower_inits_for_loop() ← NEW
4. ContinueBranchNormalizer (Pattern4-specific)
5-6. (same as Pattern2)
```
## Module Design
### File Structure
```
src/mir/join_ir/lowering/
├── loop_body_local_env.rs (Phase 184 - Storage box)
├── loop_body_local_init.rs (Phase 186 - NEW! Init lowerer)
├── update_env.rs (Phase 184 - Resolution layer)
└── carrier_update_emitter.rs (Phase 184 - Update emitter)
```
### LoopBodyLocalInitLowerer API
```rust
//! Phase 186: Loop Body-Local Variable Initialization Lowerer
//!
//! Lowers body-local variable initialization expressions to JoinIR.
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::{JoinInst, MirLikeInst, ConstValue, BinOpKind};
use crate::mir::ValueId;
pub struct LoopBodyLocalInitLowerer<'a> {
/// Reference to ConditionEnv for variable resolution
cond_env: &'a ConditionEnv,
/// Output buffer for JoinIR instructions
instructions: &'a mut Vec<JoinInst>,
/// ValueId allocator
alloc_value: Box<dyn FnMut() -> ValueId + 'a>,
}
impl<'a> LoopBodyLocalInitLowerer<'a> {
/// Create a new init lowerer
pub fn new(
cond_env: &'a ConditionEnv,
instructions: &'a mut Vec<JoinInst>,
alloc_value: Box<dyn FnMut() -> ValueId + 'a>,
) -> Self {
Self {
cond_env,
instructions,
alloc_value,
}
}
/// Lower all body-local initializations in loop body
///
/// Scans body AST for local declarations, lowers init expressions,
/// and updates LoopBodyLocalEnv with computed ValueIds.
///
/// # Arguments
///
/// * `body_ast` - Loop body AST nodes
/// * `env` - LoopBodyLocalEnv to update with ValueIds
///
/// # Returns
///
/// Ok(()) on success, Err(msg) if unsupported expression found
pub fn lower_inits_for_loop(
&mut self,
body_ast: &[ASTNode],
env: &mut LoopBodyLocalEnv,
) -> Result<(), String> {
for node in body_ast {
if let ASTNode::LocalAssign { variables, values, .. } = node {
self.lower_single_init(variables, values, env)?;
}
}
Ok(())
}
/// Lower a single local assignment
fn lower_single_init(
&mut self,
variables: &[String],
values: &[ASTNode],
env: &mut LoopBodyLocalEnv,
) -> Result<(), String> {
// Handle each variable-value pair
for (var_name, init_expr) in variables.iter().zip(values.iter()) {
// Skip if already has JoinIR ValueId (avoid duplicate lowering)
if env.get(var_name).is_some() {
continue;
}
// Lower init expression to JoinIR
let value_id = self.lower_init_expr(init_expr)?;
// Store in env
env.insert(var_name.clone(), value_id);
}
Ok(())
}
/// Lower an initialization expression to JoinIR
///
/// Supported:
/// - BinOp(+, -, *, /) with Variable/Const operands
/// - Const (integer literal)
/// - Variable (condition variable reference)
///
/// Unsupported (Fail-Fast):
/// - String operations, method calls, complex expressions
fn lower_init_expr(&mut self, expr: &ASTNode) -> Result<ValueId, String> {
match expr {
// Constant integer
ASTNode::Integer { value, .. } => {
let vid = (self.alloc_value)();
self.instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: vid,
value: ConstValue::Integer(*value),
}));
Ok(vid)
}
// Variable reference (from ConditionEnv)
ASTNode::Variable { name, .. } => {
self.cond_env
.get(name)
.ok_or_else(|| format!("Init variable '{}' not found in ConditionEnv", name))
}
// Binary operation
ASTNode::BinOp { op, left, right, .. } => {
let lhs = self.lower_init_expr(left)?;
let rhs = self.lower_init_expr(right)?;
let op_kind = self.convert_binop(op)?;
let result = (self.alloc_value)();
self.instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result,
op: op_kind,
lhs,
rhs,
}));
Ok(result)
}
// Fail-Fast for unsupported expressions
ASTNode::MethodCall { .. } => {
Err("Unsupported init expression: method call (Phase 186 limitation)".to_string())
}
ASTNode::String { .. } => {
Err("Unsupported init expression: string literal (Phase 186 limitation)".to_string())
}
_ => {
Err(format!("Unsupported init expression: {:?} (Phase 186 limitation)", expr))
}
}
}
/// Convert AST BinOp to JoinIR BinOpKind
fn convert_binop(&self, op: &str) -> Result<BinOpKind, String> {
match op {
"+" => Ok(BinOpKind::Add),
"-" => Ok(BinOpKind::Sub),
"*" => Ok(BinOpKind::Mul),
"/" => Ok(BinOpKind::Div),
_ => Err(format!("Unsupported binary operator in init: {}", op)),
}
}
}
```
## Integration Points
### Pattern2 Integration (pattern2_with_break.rs)
**Before Phase 186**:
```rust
// cf_loop_pattern2_with_break()
let ctx = build_pattern_context(...)?;
let body_locals = collect_body_local_variables(...);
let body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
// ❌ body_local_env has no ValueIds yet!
```
**After Phase 186**:
```rust
// cf_loop_pattern2_with_break()
let ctx = build_pattern_context(...)?;
// 1. Collect body-local variable names (allocate placeholder ValueIds)
let body_locals = collect_body_local_variables(...);
let mut body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
// 2. ⭐ Lower init expressions to JoinIR
let mut init_lowerer = LoopBodyLocalInitLowerer::new(
&ctx.condition_env,
&mut join_instructions,
Box::new(|| alloc_join_value()),
);
init_lowerer.lower_inits_for_loop(body, &mut body_local_env)?;
// ✅ body_local_env now has JoinIR ValueIds!
// 3. Proceed with update analysis and emission
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(...);
let update_env = UpdateEnv::new(&ctx.condition_env, &body_local_env);
for (carrier, update) in updates {
emit_carrier_update_with_env(&carrier, &update, ..., &update_env, ...)?;
}
```
### Pattern4 Integration (pattern4_with_continue.rs)
Similar to Pattern2 - insert init lowering step after condition analysis and before update analysis.
## Error Handling
### Fail-Fast Principle (Phase 178)
Following Phase 178 design - reject unsupported features early with clear error messages:
```rust
// String operation detection
if matches!(init_expr, ASTNode::MethodCall { .. }) {
return Err("Unsupported: string/method call in body-local init (use Rust MIR path)".to_string());
}
// Type mismatch detection
if !is_int_compatible(init_expr) {
return Err(format!("Unsupported: body-local init must be int/arithmetic, got {:?}", init_expr));
}
```
**Error Message Format**:
```
Error: Unsupported init expression: method call (Phase 186 limitation)
Hint: Body-local init only supports int/arithmetic (BinOp, Const, Variable)
For string operations, use Rust MIR path instead of JoinIR
```
## Test Strategy
### Unit Tests
**File**: `src/mir/join_ir/lowering/loop_body_local_init.rs` (inline tests)
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lower_const_init() {
// local temp = 42
// Should emit: Const(42)
}
#[test]
fn test_lower_binop_init() {
// local digit_pos = pos - start
// Should emit: BinOp(Sub, pos_vid, start_vid)
}
#[test]
fn test_fail_fast_method_call() {
// local ch = s.substring(0, 1)
// Should return Err("Unsupported: method call ...")
}
#[test]
fn test_fail_fast_string_concat() {
// local msg = "Error: " + text
// Should return Err("Unsupported: string literal ...")
}
}
```
### Integration Tests
**New test file**: `apps/tests/phase186_p2_body_local_digit_pos_min.hako`
```nyash
static box Test {
main(start, pos) {
local sum = 0
loop (pos < 10) {
local digit_pos = pos - start // Body-local init
if digit_pos >= 3 { break }
sum = sum + digit_pos // Use body-local
pos = pos + 1
}
return sum
}
}
```
**Expected behavior**:
- `digit_pos = 0 - 0 = 0` → sum = 0
- `digit_pos = 1 - 0 = 1` → sum = 1
- `digit_pos = 2 - 0 = 2` → sum = 3
- `digit_pos = 3 - 0 = 3` → break (3 >= 3)
- Final sum: 3
**Regression tests** (ensure Phase 184/185 still work):
- `phase184_body_local_update.hako` (basic update)
- `phase184_body_local_with_break.hako` (break condition)
- `phase185_p2_body_local_int_min.hako` (JsonParser-style)
### Fail-Fast Tests
**Test file**: `apps/tests/phase186_fail_fast_string_init.hako` (expected to fail)
```nyash
static box Test {
main() {
local s = "hello"
loop (true) {
local ch = s.substring(0, 1) // ❌ Should fail with clear error
break
}
return 0
}
}
```
**Expected error**:
```
Error: Unsupported init expression: method call (Phase 186 limitation)
```
## Validation Commands
```bash
# Build
cargo build --release
# Unit tests
cargo test --release --lib loop_body_local_init
# Integration test
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
apps/tests/phase186_p2_body_local_digit_pos_min.hako
# Regression tests
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
apps/tests/phase184_body_local_update.hako
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
apps/tests/phase185_p2_body_local_int_min.hako
# Fail-Fast test (should error)
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
apps/tests/phase186_fail_fast_string_init.hako
```
## Success Criteria
### Functional Requirements
- ✅ Body-local init expressions lower to JoinIR (int/arithmetic only)
- ✅ Init ValueIds stored in LoopBodyLocalEnv
- ✅ Body-local variables usable in update expressions
- ✅ Pattern2/4 integration complete
- ✅ Fail-Fast for unsupported expressions (string/method call)
### Quality Requirements
- ✅ Box-First design (single responsibility, clear boundaries)
- ✅ No regression in existing tests (Phase 184/185)
- ✅ Clear error messages for unsupported features
- ✅ Deterministic behavior (BTreeMap-based)
### Documentation Requirements
- ✅ Design doc (this file)
- ✅ API documentation (inline rustdoc)
- ✅ Architecture update (joinir-architecture-overview.md)
- ✅ CURRENT_TASK.md update
## Future Work (Out of Scope)
### Phase 187+: String/Method Call Init Support
```nyash
loop(...) {
local ch = s.substring(pos, 1) // Future: BoxCall lowering
local msg = "Error: " + text // Future: String concat lowering
...
}
```
**Requirements**:
- BoxCall lowering to JoinIR
- Type tracking for Box values
- String operation support in JoinIR
### Phase 190+: Complex Init Expressions
```nyash
loop(...) {
local result = (a + b) * (c - d) // Nested BinOps
local value = calc(x, y) // Function calls
...
}
```
**Requirements**:
- Recursive expression lowering
- Function call lowering to JoinIR
## References
- **Phase 184**: LoopBodyLocalEnv introduction
- **Phase 185**: Pattern2 integration with body-local variables
- **Phase 178**: Fail-Fast principle for unsupported features
- **Phase 179-B**: Pattern2 pipeline architecture
- **Box Theory**: Single responsibility, clear boundaries, determinism
## Changelog
- **2025-12-09**: Initial design document created