//! 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 { 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); } }