Extend CapturedEnv to include function parameters used in loop conditions, enabling ExprLowerer to resolve variables like `s` in `loop(p < s.length())`. Phase 245C changes: - function_scope_capture.rs: Add collect_names_in_loop_parts() helper - function_scope_capture.rs: Extend analyze_captured_vars_v2() with param capture logic - function_scope_capture.rs: Add 4 new comprehensive tests Test fix: - expr_lowerer/ast_support.rs: Accept all MethodCall nodes for syntax support (validation happens during lowering in MethodCallLowerer) Problem solved: "Variable not found: s" errors in loop conditions Test results: 924/924 PASS (+13 from baseline 911) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
336 lines
11 KiB
Rust
336 lines
11 KiB
Rust
//! 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<ValueId>;
|
||
|
||
/// 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<VarScopeKind>;
|
||
}
|
||
|
||
/// 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<ValueId> {
|
||
// 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<VarScopeKind> {
|
||
// 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));
|
||
}
|
||
}
|