Files
hakorune/src/mir/join_ir/lowering/scope_manager.rs
nyash-codex e404746612 refactor(mir): Phase 139-P3-B - RoutingDecision を enum 対応 + レガシー削除
- RoutingDecision の missing_caps を Vec<CapabilityTag> に変更(型安全化)
- error_tags は to_tag() メソッドで自動生成
- 全 callsite を enum variant に修正
- capability_tags モジュール(文字列定数群)を完全削除
- 全テスト PASS(型安全性向上を確認)
- フォーマット適用
2025-12-16 07:02:14 +09:00

491 lines
18 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 super::carrier_info::CarrierInfo;
use super::condition_env::ConditionEnv;
use super::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
#[cfg(feature = "normalized_dev")]
use crate::mir::BindingId; // Phase 75: BindingId-based lookup pilot
use crate::mir::ValueId;
/// 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 75: BindingId-based lookup (dev-only, pilot integration)
///
/// Look up variable by BindingId first, falling back to name-based lookup.
/// This supports gradual migration from name-based to BindingId-based
/// variable resolution.
///
/// # Arguments
///
/// * `binding_id` - Optional BindingId for the variable
/// * `name` - Variable name (used as fallback if BindingId lookup fails)
///
/// # Returns
///
/// `Some(ValueId)` if found via BindingId or name, `None` otherwise.
///
/// # Example
///
/// ```ignore
/// // BindingId available - priority lookup
/// let value_id = scope.lookup_with_binding(Some(BindingId(5)), "x");
///
/// // BindingId not available - legacy name-based fallback
/// let value_id = scope.lookup_with_binding(None, "x");
/// ```
#[cfg(feature = "normalized_dev")]
fn lookup_with_binding(&self, binding_id: Option<BindingId>, name: &str) -> Option<ValueId> {
// Default implementation: BindingId not supported, use name lookup
let _ = binding_id; // Suppress unused warning
self.lookup(name)
}
}
/// 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 側に集約)
// Phase 77: promoted_bindings は導入済みだが、ここScopeManager::lookupは依然として
// “name-only” 入力なので、legacy の name-based promoted 解決を残す。
#[allow(deprecated)]
{
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
}
/// Phase 76: BindingId-based lookup with promoted binding support (dev-only)
///
/// Extends Phase 75's BindingId priority lookup to check promoted_bindings map.
/// This eliminates name-based hacks (`format!("is_{}", name)`) by using type-safe
/// BindingId → BindingId mapping from CarrierInfo.
///
/// ## Lookup Order
///
/// 1. Direct BindingId lookup in ConditionEnv (if BindingId provided)
/// 2. **NEW (Phase 76)**: Promoted BindingId lookup in CarrierInfo.promoted_bindings
/// 3. Fallback to legacy name-based lookup (Phase 75 behavior)
///
/// # Arguments
///
/// * `binding_id` - Optional BindingId from MirBuilder's binding_map
/// * `name` - Variable name (fallback for legacy paths)
///
/// # Returns
///
/// `Some(ValueId)` if found via BindingId/promoted/name, `None` otherwise.
///
/// # Example (Phase 76 Promotion Path)
///
/// ```ignore
/// // Given:
/// // - "digit_pos" has BindingId(5)
/// // - "is_digit_pos" has BindingId(10)
/// // - CarrierInfo.promoted_bindings[BindingId(5)] = BindingId(10)
/// // - ConditionEnv.binding_id_map[BindingId(10)] = ValueId(102)
///
/// let scope = Pattern2ScopeManager { ... };
///
/// // Phase 76: BindingId-based promoted resolution (NEW!)
/// let result = scope.lookup_with_binding(Some(BindingId(5)), "digit_pos");
/// // Step 1: ConditionEnv[BindingId(5)] → None (not a carrier)
/// // Step 2: CarrierInfo.promoted_bindings[BindingId(5)] → BindingId(10) ✓
/// // Step 3: ConditionEnv[BindingId(10)] → ValueId(102) ✓
/// assert_eq!(result, Some(ValueId(102)));
///
/// // Phase 75: Legacy name-based fallback still works
/// let result = scope.lookup_with_binding(None, "digit_pos");
/// // → Falls back to format!("is_digit_pos") lookup
/// assert_eq!(result, Some(ValueId(102)));
/// ```
///
/// # Phase 77 Migration Note
///
/// The legacy name-based path (step 3) will be removed in Phase 77 after all
/// promoters populate promoted_bindings map and all call sites provide BindingId.
#[cfg(feature = "normalized_dev")]
fn lookup_with_binding(&self, binding_id: Option<BindingId>, name: &str) -> Option<ValueId> {
use super::debug_output_box::DebugOutputBox;
let debug = DebugOutputBox::new("phase76");
if let Some(bid) = binding_id {
// Step 1: Try direct BindingId lookup in ConditionEnv (Phase 75)
if let Some(value_id) = self.condition_env.resolve_var_with_binding(Some(bid), name) {
debug.log(
"direct",
&format!(
"BindingId({}) -> ValueId({}) for '{}'",
bid.0, value_id.0, name
),
);
return Some(value_id);
}
// Step 2: **NEW (Phase 76)**: Check promoted_bindings map
if let Some(promoted_bid) = self.carrier_info.resolve_promoted_with_binding(bid) {
// Promoted BindingId found, lookup in ConditionEnv
if let Some(value_id) = self
.condition_env
.resolve_var_with_binding(Some(promoted_bid), name)
{
debug.log(
"promoted",
&format!(
"BindingId({}) promoted to BindingId({}) -> ValueId({}) for '{}'",
bid.0, promoted_bid.0, value_id.0, name
),
);
return Some(value_id);
}
}
// Step 3: Fallback to legacy name-based lookup (Phase 75 fallback path)
// Phase 77: DEPRECATED - Will be removed in Phase 78+
#[cfg(feature = "normalized_dev")]
eprintln!(
"[phase77/fallback] WARNING: BindingId({}) for '{}' not resolved, falling back to name-based lookup (DEPRECATED)",
bid.0, name
);
#[cfg(not(feature = "normalized_dev"))]
debug.log(
"fallback",
&format!(
"BindingId({}) miss, falling back to name '{}' lookup",
bid.0, name
),
);
}
// Step 4: Legacy name-based lookup (Phase 75 behavior)
// Phase 77: DEPRECATED - Will be removed in Phase 78+ after all call sites use BindingId
self.lookup(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole, CarrierVar};
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![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
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,
#[cfg(feature = "normalized_dev")]
binding_id: None,
}],
trim_helper: None,
promoted_loopbodylocals: vec![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
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),
#[cfg(feature = "normalized_dev")]
binding_id: None,
}],
trim_helper: None,
promoted_loopbodylocals: vec!["digit_pos".to_string()],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
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![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
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![],
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
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));
}
}