//! Phase 231: Scope Manager for Unified Variable Lookup //! //! This module provides a unified interface for variable lookup across different //! scopes in JoinIR lowering. It abstracts over the complexity of multiple //! environments (ConditionEnv, LoopBodyLocalEnv, CapturedEnv, CarrierInfo). //! //! ## Design Philosophy //! //! **Box-First**: ScopeManager is a trait-based "box" that encapsulates variable //! lookup logic, making it easy to swap implementations or test in isolation. //! //! **Single Responsibility**: Variable resolution only. Does NOT: //! - Lower AST to JoinIR (that's ExprLowerer) //! - Manage ValueId allocation (that's JoinValueSpace) //! - Handle HOST ↔ JoinIR bindings (that's InlineBoundary) //! //! ## Pattern2 Pilot Implementation //! //! Phase 231 starts with Pattern2-specific implementation to validate the design. //! Future phases will generalize to Pattern1, Pattern3, etc. use crate::mir::ValueId; use super::condition_env::ConditionEnv; use super::loop_body_local_env::LoopBodyLocalEnv; use super::carrier_info::CarrierInfo; use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; /// Phase 231: Scope kind for variables /// /// Helps distinguish where a variable comes from, which affects how it's /// treated during lowering (e.g., PHI generation, exit handling). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VarScopeKind { /// Loop control variable (i, p) LoopVar, /// Carrier variable (sum, count, is_digit_pos) Carrier, /// Loop body-local variable (ch, digit_pos before promotion) LoopBodyLocal, /// Captured from outer function scope (digits, s, len) Captured, } /// Phase 231: Scope manager trait for unified variable lookup /// /// This trait provides a unified interface for looking up variables across /// multiple environments. Implementations can aggregate different environment /// types (ConditionEnv, LoopBodyLocalEnv, etc.) and provide consistent lookup. /// /// # Example /// /// ```ignore /// let scope: &dyn ScopeManager = &Pattern2ScopeManager { ... }; /// if let Some(value_id) = scope.lookup("sum") { /// // Use value_id in expression lowering /// } /// ``` pub trait ScopeManager { /// Look up variable by name, return ValueId if found /// /// This method searches across all available scopes and returns the first /// match. The search order is implementation-defined but should be /// documented in the implementing struct. fn lookup(&self, name: &str) -> Option; /// Get the scope kind of a variable /// /// This helps the caller understand where the variable comes from, which /// can affect code generation (e.g., PHI node generation, exit handling). fn scope_of(&self, name: &str) -> Option; } /// Phase 231: Pattern2-specific scope manager (pilot implementation) /// /// This implementation aggregates all the environments used in Pattern2 loop /// lowering and provides unified variable lookup. /// /// ## Lookup Order /// /// 1. ConditionEnv (includes loop var, carriers, condition-only vars) /// 2. LoopBodyLocalEnv (body-local variables before promotion) /// 3. CapturedEnv (function-scoped captured variables) /// 4. Promoted LoopBodyLocal → Carrier (using naming convention) /// /// ## Naming Convention for Promoted Variables /// /// - DigitPos pattern: `"digit_pos"` → `"is_digit_pos"` /// - Trim pattern: `"ch"` → `"is_ch_match"` /// /// # Example /// /// ```ignore /// let scope = Pattern2ScopeManager { /// condition_env: &env, /// loop_body_local_env: Some(&body_local_env), /// captured_env: Some(&captured_env), /// carrier_info: &carrier_info, /// }; /// /// // Lookup loop variable /// assert_eq!(scope.lookup("i"), Some(ValueId(100))); /// /// // Lookup carrier /// assert_eq!(scope.lookup("sum"), Some(ValueId(101))); /// /// // Lookup promoted variable (uses naming convention) /// assert_eq!(scope.lookup("digit_pos"), Some(ValueId(102))); // Resolves to "is_digit_pos" /// ``` pub struct Pattern2ScopeManager<'a> { /// Condition environment (loop var + carriers + condition-only vars) pub condition_env: &'a ConditionEnv, /// Loop body-local environment (optional, may be empty) pub loop_body_local_env: Option<&'a LoopBodyLocalEnv>, /// Captured environment (optional, may be empty) pub captured_env: Option<&'a CapturedEnv>, /// Carrier information (includes promoted_loopbodylocals list) pub carrier_info: &'a CarrierInfo, } impl<'a> ScopeManager for Pattern2ScopeManager<'a> { fn lookup(&self, name: &str) -> Option { // 1. ConditionEnv (highest priority: loop var, carriers, condition-only) if let Some(id) = self.condition_env.get(name) { return Some(id); } // 2. LoopBodyLocalEnv (body-local variables) if let Some(env) = self.loop_body_local_env { if let Some(id) = env.get(name) { return Some(id); } } // 3. CapturedEnv (function-scoped captured variables) if let Some(env) = self.captured_env { for var in &env.vars { if var.name == name { // Captured variables are already in condition_env, so this // should have been caught in step 1. But check here for safety. return self.condition_env.get(name); } } } // 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約) self.carrier_info .resolve_promoted_join_id(name) } fn scope_of(&self, name: &str) -> Option { // Check loop variable first if name == self.carrier_info.loop_var_name { return Some(VarScopeKind::LoopVar); } // Check carriers if self.carrier_info.carriers.iter().any(|c| c.name == name) { return Some(VarScopeKind::Carrier); } // Check body-local if let Some(env) = self.loop_body_local_env { if env.contains(name) { return Some(VarScopeKind::LoopBodyLocal); } } // Check captured if let Some(env) = self.captured_env { if env.vars.iter().any(|v| v.name == name) { return Some(VarScopeKind::Captured); } } None } } #[cfg(test)] mod tests { use super::*; use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; use crate::mir::loop_pattern_detection::function_scope_capture::CapturedVar; #[test] fn test_pattern2_scope_manager_loop_var() { let mut condition_env = ConditionEnv::new(); condition_env.insert("i".to_string(), ValueId(100)); let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), carriers: vec![], trim_helper: None, promoted_loopbodylocals: vec![], }; let scope = Pattern2ScopeManager { condition_env: &condition_env, loop_body_local_env: None, captured_env: None, carrier_info: &carrier_info, }; assert_eq!(scope.lookup("i"), Some(ValueId(100))); assert_eq!(scope.scope_of("i"), Some(VarScopeKind::LoopVar)); } #[test] fn test_pattern2_scope_manager_carrier() { let mut condition_env = ConditionEnv::new(); condition_env.insert("i".to_string(), ValueId(100)); condition_env.insert("sum".to_string(), ValueId(101)); let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), carriers: vec![ CarrierVar { name: "sum".to_string(), host_id: ValueId(2), join_id: Some(ValueId(101)), role: CarrierRole::LoopState, init: CarrierInit::FromHost, }, ], trim_helper: None, promoted_loopbodylocals: vec![], }; let scope = Pattern2ScopeManager { condition_env: &condition_env, loop_body_local_env: None, captured_env: None, carrier_info: &carrier_info, }; assert_eq!(scope.lookup("sum"), Some(ValueId(101))); assert_eq!(scope.scope_of("sum"), Some(VarScopeKind::Carrier)); } #[test] fn test_pattern2_scope_manager_promoted_variable() { let mut condition_env = ConditionEnv::new(); condition_env.insert("i".to_string(), ValueId(100)); let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), carriers: vec![ CarrierVar { name: "is_digit_pos".to_string(), host_id: ValueId(2), join_id: Some(ValueId(102)), role: CarrierRole::ConditionOnly, init: CarrierInit::BoolConst(false), }, ], trim_helper: None, promoted_loopbodylocals: vec!["digit_pos".to_string()], }; let scope = Pattern2ScopeManager { condition_env: &condition_env, loop_body_local_env: None, captured_env: None, carrier_info: &carrier_info, }; // Lookup "digit_pos" should resolve to "is_digit_pos" carrier assert_eq!(scope.lookup("digit_pos"), Some(ValueId(102))); } #[test] fn test_pattern2_scope_manager_body_local() { let mut condition_env = ConditionEnv::new(); condition_env.insert("i".to_string(), ValueId(100)); let mut body_local_env = LoopBodyLocalEnv::new(); body_local_env.insert("temp".to_string(), ValueId(200)); let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), carriers: vec![], trim_helper: None, promoted_loopbodylocals: vec![], }; let scope = Pattern2ScopeManager { condition_env: &condition_env, loop_body_local_env: Some(&body_local_env), captured_env: None, carrier_info: &carrier_info, }; assert_eq!(scope.lookup("temp"), Some(ValueId(200))); assert_eq!(scope.scope_of("temp"), Some(VarScopeKind::LoopBodyLocal)); } #[test] fn test_pattern2_scope_manager_captured() { let mut condition_env = ConditionEnv::new(); condition_env.insert("i".to_string(), ValueId(100)); condition_env.insert("len".to_string(), ValueId(201)); let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new(); captured_env.add_var(CapturedVar { name: "len".to_string(), host_id: ValueId(42), is_immutable: true, }); let carrier_info = CarrierInfo { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), carriers: vec![], trim_helper: None, promoted_loopbodylocals: vec![], }; let scope = Pattern2ScopeManager { condition_env: &condition_env, loop_body_local_env: None, captured_env: Some(&captured_env), carrier_info: &carrier_info, }; assert_eq!(scope.lookup("len"), Some(ValueId(201))); assert_eq!(scope.scope_of("len"), Some(VarScopeKind::Captured)); } }