Files
hakorune/src/mir/join_ir/lowering/scope_manager.rs
nyash-codex d4597dacfa feat(joinir): Phase 245C - Function parameter capture + test fix
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>
2025-12-11 13:13:08 +09:00

336 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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