Phase 184 implements the foundation for body-local variable support in update expressions, completing the three-box architecture: LoopBodyLocalEnv (storage), UpdateEnv (composition), and CarrierUpdateEmitter (emission). ## Implementation Summary ### Task 184-1: Design Document - Created phase184-body-local-mir-lowering.md - Two-Environment System design (ConditionEnv + LoopBodyLocalEnv) - Box-First design principles documented ### Task 184-2: LoopBodyLocalEnv Implementation - New file: src/mir/join_ir/lowering/loop_body_local_env.rs (216 lines) - Storage box for body-local variable name → ValueId mappings - BTreeMap for deterministic ordering (PHI consistency) - 7 unit tests: empty env, single/multiple locals, get/contains, iteration ### Task 184-3: UpdateEnv Implementation - New file: src/mir/join_ir/lowering/update_env.rs (237 lines) - Composition box for unified variable resolution - Priority order: ConditionEnv (condition vars) → LoopBodyLocalEnv (body-local) - 8 unit tests: priority, fallback, not found, combined lookup ### Task 184-4: CarrierUpdateEmitter Integration - Modified: src/mir/join_ir/lowering/carrier_update_emitter.rs - Added emit_carrier_update_with_env() (UpdateEnv version) - Kept emit_carrier_update() for backward compatibility - 4 new unit tests: body-local variable, priority, not found, const update - Total 10 tests PASS (6 existing + 4 new) ### Task 184-5: Representative Test Cases - apps/tests/phase184_body_local_update.hako (Pattern1 baseline) - apps/tests/phase184_body_local_with_break.hako (Pattern2, Phase 185 target) ### Task 184-6: Documentation Updates - Updated: docs/development/current/main/joinir-architecture-overview.md - Updated: CURRENT_TASK.md (Phase 184 completion record) ## Test Results All 25 unit tests PASS: - LoopBodyLocalEnv: 7 tests - UpdateEnv: 8 tests - CarrierUpdateEmitter: 10 tests (6 existing + 4 new) Build: ✅ Success (0 errors) ## Design Constraints Following 箱理論 (Box Theory) principles: - Single Responsibility: Each box has one clear purpose - Deterministic: BTreeMap ensures consistent ordering - Conservative: Pattern5 (Trim) integration deferred to Phase 185 - Fail-Fast: Explicit errors for unsupported patterns ## Scope Limitation Phase 184 provides the **infrastructure only**: - ✅ Storage box (LoopBodyLocalEnv) - ✅ Composition box (UpdateEnv) - ✅ Emission support (CarrierUpdateEmitter) - ❌ Pattern2/4 integration (requires body-local collection, Phase 185) ## Next Steps Phase 185: Pattern2/4 Integration - Integrate LoopBodyLocalEnv into Pattern2/4 lowerers - Add body-local variable collection from loop body AST - Enable full E2E body-local variable support in update expressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
Phase 184: Body-local MIR Lowering Design
Status: In Progress (2025-12-08)
Overview
Phase 183 completed LoopBodyLocal role separation (condition vs body-only). Phase 184 implements body-local MIR lowering - the ability to use body-only local variables in update expressions safely.
Problem Statement
Current limitation:
local sum = 0
local i = 0
loop(i < 5) {
local temp = i * 2 // body-local variable
sum = sum + temp // ❌ ERROR: temp not found in ConditionEnv
i = i + 1
}
Root cause:
CarrierUpdateEmitteronly has access toConditionEnvConditionEnvonly contains condition variables (loop parameters)- Body-local variables (
temp) are not inConditionEnv - Update expression
sum = sum + tempfails to resolvetemp
Goal: Enable body-local variables in update expressions while maintaining architectural clarity.
Design Solution
Architecture: Two-Environment System
┌─────────────────────┐
│ ConditionEnv │ Condition variables (loop parameters)
│ - i → ValueId(0) │ Priority: HIGH
│ - end → ValueId(1) │
└─────────────────────┘
↓
┌─────────────────────┐
│ LoopBodyLocalEnv │ Body-local variables
│ - temp → ValueId(5)│ Priority: LOW (only if not in ConditionEnv)
│ - digit → ValueId(6)│
└─────────────────────┘
↓
┌─────────────────────┐
│ UpdateEnv │ Unified resolution layer
│ resolve(name): │ 1. Try ConditionEnv first
│ cond.get(name) │ 2. Fallback to LoopBodyLocalEnv
│ .or(body.get()) │
└─────────────────────┘
Box-First Design
Following 箱理論 (Box Theory) principles:
Box 1: LoopBodyLocalEnv (Storage Box)
Single Responsibility: Collect and manage body-local variable mappings (name → ValueId).
pub struct LoopBodyLocalEnv {
locals: BTreeMap<String, ValueId>, // Deterministic ordering
}
impl LoopBodyLocalEnv {
/// Scan loop body AST and collect local definitions
pub fn from_loop_body(
body: &[ASTNode],
builder: &mut JoinIrBuilder
) -> Self;
/// Resolve a body-local variable name to JoinIR ValueId
pub fn get(&self, name: &str) -> Option<ValueId>;
}
Design rationale:
- BTreeMap: Ensures deterministic iteration (PHI ordering consistency)
- from_loop_body(): Separates collection logic from lowering logic
- get(): Simple lookup, no side effects
Box 2: UpdateEnv (Composition Box)
Single Responsibility: Unified variable resolution for update expressions.
pub struct UpdateEnv<'a> {
condition_env: &'a ConditionEnv, // Priority 1: Condition vars
body_local_env: &'a LoopBodyLocalEnv, // Priority 2: Body-local vars
}
impl<'a> UpdateEnv<'a> {
/// Resolve variable name with priority order
pub fn resolve(&self, name: &str) -> Option<ValueId> {
self.condition_env.get(name)
.or_else(|| self.body_local_env.get(name))
}
}
Design rationale:
- Composition: Combines two environments without owning them
- Priority order: Condition variables take precedence (shadowing prevention)
- Lightweight: No allocation, just references
Integration Points
Current Flow (Pattern2 Example)
// Pattern2Lowerer::lower()
let condition_env = /* ... */;
// Emit carrier update
let update_value = emit_carrier_update(
&carrier,
&update_expr,
&mut alloc_value,
&condition_env, // ❌ Only has condition variables
&mut instructions,
)?;
Phase 184 Flow
// Pattern2Lowerer::lower()
let condition_env = /* ... */;
let body_local_env = LoopBodyLocalEnv::from_loop_body(&body_nodes, builder);
// Create unified environment
let update_env = UpdateEnv {
condition_env: &condition_env,
body_local_env: &body_local_env,
};
// Emit carrier update (now with body-local support)
let update_value = emit_carrier_update_with_env(
&carrier,
&update_expr,
&mut alloc_value,
&update_env, // ✅ Has both condition and body-local variables
&mut instructions,
)?;
Scope and Constraints
In Scope (Phase 184)
- Body-local variable collection:
LoopBodyLocalEnv::from_loop_body() - Unified resolution:
UpdateEnv::resolve() - CarrierUpdateEmitter integration: Use
UpdateEnvinstead ofConditionEnv - Pattern2/4 integration: Pass
body_local_envto update lowering
Out of Scope
- Condition variable usage: Body-locals still cannot be used in conditions
- Pattern5 (Trim) integration: Defer to Phase 185
- Complex expressions: Only simple variable references in updates
- Type checking: Assume type correctness (existing type inference handles this)
Design Constraints
Following Phase 178 Fail-Fast and 箱理論 principles:
- Single Responsibility: Each Box has one clear purpose
- Deterministic: BTreeMap for consistent ordering
- Conservative: No changes to Trim/Pattern5 logic
- Explicit errors: If body-local used in condition → Fail loudly
Implementation Tasks
Task 184-1: Design Document ✅ (This document)
Task 184-2: LoopBodyLocalEnv Implementation
File: src/mir/join_ir/lowering/loop_body_local_env.rs (new)
Core logic:
impl LoopBodyLocalEnv {
pub fn from_loop_body(body: &[ASTNode], builder: &mut JoinIrBuilder) -> Self {
let mut locals = BTreeMap::new();
for node in body {
if let ASTNode::LocalDecl { name, init_value } = node {
// Lower init_value to JoinIR
let value_id = builder.lower_expr(init_value)?;
locals.insert(name.clone(), value_id);
}
}
Self { locals }
}
}
Unit tests (3-5 tests):
test_empty_body: No locals → empty envtest_single_local: One local → one mappingtest_multiple_locals: Multiple locals → sorted keystest_get_existing: Lookup succeedstest_get_nonexistent: Lookup returns None
Task 184-3: UpdateEnv Implementation
File: src/mir/join_ir/lowering/update_env.rs (new)
Core logic:
pub struct UpdateEnv<'a> {
condition_env: &'a ConditionEnv,
body_local_env: &'a LoopBodyLocalEnv,
}
impl<'a> UpdateEnv<'a> {
pub fn new(
condition_env: &'a ConditionEnv,
body_local_env: &'a LoopBodyLocalEnv,
) -> Self {
Self { condition_env, body_local_env }
}
pub fn resolve(&self, name: &str) -> Option<ValueId> {
self.condition_env.get(name)
.or_else(|| self.body_local_env.get(name))
}
}
Unit tests (2-3 tests):
test_resolve_condition_priority: Condition var found firsttest_resolve_body_local_fallback: Body-local found when condition absenttest_resolve_not_found: Neither env has variable → None
Task 184-4: CarrierUpdateEmitter Integration
File: src/mir/join_ir/lowering/carrier_update_emitter.rs (modify)
Changes:
- Add new function variant accepting
UpdateEnv:
pub fn emit_carrier_update_with_env(
carrier: &CarrierVar,
update: &UpdateExpr,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &UpdateEnv, // New: UpdateEnv instead of ConditionEnv
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Use env.resolve() instead of env.get()
}
- Keep existing
emit_carrier_update()for backward compatibility - Update Pattern2/4 callers to use new variant
Validation: Existing tests must pass (backward compatibility).
Task 184-5: Representative Tests
File: apps/tests/phase184_body_local_update.hako (new)
// Body-local used in update expression
static box Main {
main() {
local sum = 0
local i = 0
loop(i < 5) {
local temp = i * 2 // Body-local variable
sum = sum + temp // Use in update expression
i = i + 1
}
print(sum) // Expected: 0+2+4+6+8 = 20
}
}
Test commands:
# Structure trace
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
# Full execution
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
Task 184-6: Documentation Updates
-
Update:
docs/development/current/main/joinir-architecture-overview.md- Add LoopBodyLocalEnv section
- Update UpdateEnv integration diagram
-
Update:
CURRENT_TASK.md- Mark Phase 184 complete
- Add Phase 185 preview
Validation Strategy
Success Criteria
- Unit tests pass: LoopBodyLocalEnv and UpdateEnv tests green
- Backward compatibility: Existing Pattern2/4 tests still pass
- Representative test: phase184_body_local_update.hako executes correctly (output: 20)
- No regression: Trim patterns (Pattern5) unaffected
Test Commands
# Unit tests
cargo test --release --lib loop_body_local_env
cargo test --release --lib update_env
cargo test --release --lib carrier_update
# Integration test
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
# Regression check (Trim pattern)
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase172_trim_while.hako
Known Limitations
Not Supported (Explicit Design Decision)
- Body-local in conditions:
loop(i < 5) {
local temp = i * 2
if (temp > 6) break // ❌ ERROR: Body-local in condition not allowed
}
Reason: Condition variables must be loop parameters (JoinIR entry signature constraint).
- Shadowing:
loop(i < 5) {
local i = 10 // ❌ ERROR: Shadows condition variable
}
Reason: UpdateEnv prioritizes condition variables - shadowing forbidden.
- Complex expressions:
loop(i < 5) {
local temp = obj.method() // ⚠️ May not work yet
}
Reason: Limited to expressions JoinIrBuilder::lower_expr() supports.
Future Work (Phase 185+)
Phase 185: Trim Pattern Integration
- Extend LoopBodyLocalEnv to handle Trim carrier variables
- Update TrimLoopLowerer to use UpdateEnv
Phase 186: Condition Expression Support
- Allow body-local variables in break/continue conditions
- Requires inline expression evaluation in condition lowering
References
- Phase 183: LoopBodyLocal role separation (condition vs body-only)
- Phase 178: Fail-Fast error handling principles
- Phase 171-C: LoopBodyCarrierPromoter original design
- carrier_update_emitter.rs: Current update emission logic
- condition_env.rs: Condition variable environment design