Files
hakorune/src/mir/join_ir/lowering/update_env.rs

249 lines
8.3 KiB
Rust
Raw Normal View History

feat(joinir): Phase 184 - Body-local MIR Lowering Infrastructure 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>
2025-12-08 23:59:19 +09:00
//! Phase 184: Update Expression Environment
//!
//! This module provides a unified variable resolution layer for carrier update expressions.
//! It combines ConditionEnv (condition variables) and LoopBodyLocalEnv (body-local variables)
//! with clear priority order.
//!
//! ## Design Philosophy
//!
//! **Single Responsibility**: This module ONLY handles variable resolution priority logic.
//! It does NOT:
//! - Store variables (that's ConditionEnv and LoopBodyLocalEnv)
//! - Lower AST to JoinIR (that's JoinIrBuilder)
//! - Emit update instructions (that's CarrierUpdateEmitter)
//!
//! ## Box-First Design
//!
//! Following 箱理論 (Box Theory) principles:
//! - **Composition**: Combines two environments without owning them
//! - **Clear priority**: Condition variables take precedence
//! - **Lightweight**: No allocation, just references
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::ValueId;
/// Unified environment for carrier update expression variable resolution
///
/// This structure provides a composition layer that resolves variables
/// with the following priority order:
///
/// 1. **Condition variables** (ConditionEnv) - Highest priority
/// - Loop parameters (e.g., `i`, `end`, `p`)
/// - Variables used in condition expressions
///
/// 2. **Body-local variables** (LoopBodyLocalEnv) - Fallback priority
/// - Variables declared in loop body (e.g., `local temp`)
/// - Only accessible if not shadowed by condition variables
///
/// # Example
///
/// ```nyash
/// loop(i < 5) { // i in ConditionEnv
/// local temp = i * 2 // temp in LoopBodyLocalEnv
/// sum = sum + temp // Resolves: sum (cond), temp (body)
/// i = i + 1
/// }
/// ```
///
/// ```ignore
/// let condition_env = /* ... i, sum ... */;
/// let body_local_env = /* ... temp ... */;
/// let update_env = UpdateEnv::new(&condition_env, &body_local_env);
///
/// // Resolve "sum" → ConditionEnv (priority 1)
/// assert_eq!(update_env.resolve("sum"), Some(ValueId(X)));
///
/// // Resolve "temp" → LoopBodyLocalEnv (priority 2)
/// assert_eq!(update_env.resolve("temp"), Some(ValueId(Y)));
///
/// // Resolve "unknown" → None
/// assert_eq!(update_env.resolve("unknown"), None);
/// ```
#[derive(Debug)]
pub struct UpdateEnv<'a> {
/// Condition variable environment (priority 1)
condition_env: &'a ConditionEnv,
/// Body-local variable environment (priority 2)
body_local_env: &'a LoopBodyLocalEnv,
}
impl<'a> UpdateEnv<'a> {
/// Create a new UpdateEnv with priority-ordered resolution
///
/// # Arguments
///
/// * `condition_env` - Condition variable environment (highest priority)
/// * `body_local_env` - Body-local variable environment (fallback)
pub fn new(
condition_env: &'a ConditionEnv,
body_local_env: &'a LoopBodyLocalEnv,
) -> Self {
Self {
condition_env,
body_local_env,
}
}
/// Resolve a variable name to JoinIR ValueId
///
/// Resolution order:
/// 1. Try condition_env.get(name)
/// 2. If not found, try body_local_env.get(name)
/// 3. If still not found, return None
///
/// # Arguments
///
/// * `name` - Variable name to resolve
///
/// # Returns
///
/// * `Some(ValueId)` - Variable found in one of the environments
/// * `None` - Variable not found in either environment
pub fn resolve(&self, name: &str) -> Option<ValueId> {
self.condition_env
.get(name)
.or_else(|| self.body_local_env.get(name))
}
/// Check if a variable exists in either environment
pub fn contains(&self, name: &str) -> bool {
self.resolve(name).is_some()
}
/// Get reference to condition environment (for debugging/diagnostics)
pub fn condition_env(&self) -> &ConditionEnv {
self.condition_env
}
/// Get reference to body-local environment (for debugging/diagnostics)
pub fn body_local_env(&self) -> &LoopBodyLocalEnv {
self.body_local_env
}
}
#[cfg(test)]
mod tests {
use super::*;
// Helper: Create a test ConditionEnv
fn test_condition_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
env.insert("i".to_string(), ValueId(10));
env.insert("sum".to_string(), ValueId(20));
env.insert("end".to_string(), ValueId(30));
env
}
// Helper: Create a test LoopBodyLocalEnv
fn test_body_local_env() -> LoopBodyLocalEnv {
let mut env = LoopBodyLocalEnv::new();
env.insert("temp".to_string(), ValueId(50));
env.insert("digit".to_string(), ValueId(60));
env
}
#[test]
fn test_resolve_condition_priority() {
// Condition variables should be found first
let cond_env = test_condition_env();
let body_env = LoopBodyLocalEnv::new(); // Empty
let update_env = UpdateEnv::new(&cond_env, &body_env);
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
assert_eq!(update_env.resolve("end"), Some(ValueId(30)));
}
#[test]
fn test_resolve_body_local_fallback() {
// Body-local variables should be found when not in condition env
let cond_env = ConditionEnv::new(); // Empty
let body_env = test_body_local_env();
let update_env = UpdateEnv::new(&cond_env, &body_env);
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
}
#[test]
fn test_resolve_priority_order() {
// Condition env takes priority over body-local env
let mut cond_env = ConditionEnv::new();
cond_env.insert("x".to_string(), ValueId(100)); // Condition: x=100
let mut body_env = LoopBodyLocalEnv::new();
body_env.insert("x".to_string(), ValueId(200)); // Body-local: x=200
let update_env = UpdateEnv::new(&cond_env, &body_env);
// Should resolve to condition env value (100), not body-local (200)
assert_eq!(update_env.resolve("x"), Some(ValueId(100)));
}
#[test]
fn test_resolve_not_found() {
// Variable not in either environment → None
let cond_env = test_condition_env();
let body_env = test_body_local_env();
let update_env = UpdateEnv::new(&cond_env, &body_env);
assert_eq!(update_env.resolve("unknown"), None);
assert_eq!(update_env.resolve("nonexistent"), None);
}
#[test]
fn test_resolve_combined_lookup() {
// Mixed lookup: some in condition, some in body-local
let cond_env = test_condition_env();
let body_env = test_body_local_env();
let update_env = UpdateEnv::new(&cond_env, &body_env);
// Condition variables
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
// Body-local variables
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
// Not found
assert_eq!(update_env.resolve("unknown"), None);
}
#[test]
fn test_contains() {
let cond_env = test_condition_env();
let body_env = test_body_local_env();
let update_env = UpdateEnv::new(&cond_env, &body_env);
assert!(update_env.contains("i"));
assert!(update_env.contains("temp"));
assert!(!update_env.contains("unknown"));
}
#[test]
fn test_empty_environments() {
// Both environments empty
let cond_env = ConditionEnv::new();
let body_env = LoopBodyLocalEnv::new();
let update_env = UpdateEnv::new(&cond_env, &body_env);
assert_eq!(update_env.resolve("anything"), None);
assert!(!update_env.contains("anything"));
}
#[test]
fn test_accessor_methods() {
// Test diagnostic accessor methods
let cond_env = test_condition_env();
let body_env = test_body_local_env();
let update_env = UpdateEnv::new(&cond_env, &body_env);
// Should return references to underlying environments
assert_eq!(update_env.condition_env().len(), 3);
assert_eq!(update_env.body_local_env().len(), 2);
}
}