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

17 KiB

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:

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:

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:

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

//! 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:

// 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:

// 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:

// 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)

#[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

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)

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

# 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

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

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