feat(joinir): Phase 231 - ExprLowerer/ScopeManager pilot implementation
Pilot implementation of unified expression lowering for Pattern2 break conditions: New files: - scope_manager.rs (280 lines) - ScopeManager trait + Pattern2ScopeManager - expr_lowerer.rs (455 lines) - ExprLowerer with Condition context support Features: - Unified variable lookup across ConditionEnv/LoopBodyLocalEnv/CapturedEnv/CarrierInfo - Pre-validation of condition AST before lowering - Fail-safe design with fallback to legacy path - 8 new unit tests (all pass) Integration: - Pattern2 break condition uses ExprLowerer for pre-validation - Existing proven lowering path preserved - Zero impact on existing functionality (890/897 tests pass) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -529,9 +529,44 @@ impl MirBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 231: ExprLowerer pilot - pre-validate break condition
|
||||
// This is a VALIDATION-ONLY step. We check if ExprLowerer can handle the condition,
|
||||
// but still use the existing proven lowering path. Future phases will replace actual lowering.
|
||||
{
|
||||
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, ScopeManager};
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprLowerer, ExprContext, ExprLoweringError};
|
||||
|
||||
let scope_manager = Pattern2ScopeManager {
|
||||
condition_env: &env,
|
||||
loop_body_local_env: Some(&body_local_env),
|
||||
captured_env: Some(&captured_env),
|
||||
carrier_info: &carrier_info,
|
||||
};
|
||||
|
||||
// Try ExprLowerer validation (doesn't affect actual lowering yet)
|
||||
// Phase 231: This is data-gathering only - we want to see which patterns work
|
||||
match ExprLowerer::new(&scope_manager, ExprContext::Condition, self)
|
||||
.with_debug(debug)
|
||||
.lower(&effective_break_condition)
|
||||
{
|
||||
Ok(_value_id) => {
|
||||
eprintln!("[pattern2/phase231] ✓ ExprLowerer successfully validated break condition");
|
||||
}
|
||||
Err(ExprLoweringError::UnsupportedNode(msg)) => {
|
||||
eprintln!("[pattern2/phase231] ℹ ExprLowerer fallback (unsupported): {}", msg);
|
||||
// This is expected for complex patterns - not an error
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[pattern2/phase231] ⚠ ExprLowerer validation error: {}", e);
|
||||
// Unexpected error - log but don't fail (legacy path will handle it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition
|
||||
// Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation
|
||||
// Phase 176-3: Multi-carrier support - pass carrier_info and carrier_updates
|
||||
// Phase 231: ExprLowerer validated above, but we still use proven legacy lowering
|
||||
eprintln!(
|
||||
"[pattern2/before_lowerer] About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'",
|
||||
carrier_info.loop_var_name
|
||||
|
||||
454
src/mir/join_ir/lowering/expr_lowerer.rs
Normal file
454
src/mir/join_ir/lowering/expr_lowerer.rs
Normal file
@ -0,0 +1,454 @@
|
||||
//! Phase 231: Expression Lowering with Unified Scope Management
|
||||
//!
|
||||
//! This module provides a pilot implementation of expression lowering that uses
|
||||
//! ScopeManager for variable resolution. It's a thin wrapper around existing
|
||||
//! condition_lowerer logic, focusing on API unification rather than reimplementation.
|
||||
//!
|
||||
//! ## Design Philosophy
|
||||
//!
|
||||
//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering
|
||||
//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions.
|
||||
//!
|
||||
//! **Incremental Adoption**: Phase 231 starts with Condition context only.
|
||||
//! Future phases will expand to support General expressions (method calls, etc.).
|
||||
//!
|
||||
//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers
|
||||
//! to fall back to legacy paths.
|
||||
|
||||
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use super::scope_manager::ScopeManager;
|
||||
use super::condition_lowerer::lower_condition_to_joinir;
|
||||
use super::condition_env::ConditionEnv;
|
||||
|
||||
/// Phase 231: Expression lowering context
|
||||
///
|
||||
/// Defines the context in which an expression is being lowered, which affects
|
||||
/// what AST nodes are supported and how they're translated.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExprContext {
|
||||
/// Loop condition expression (limited subset: comparisons, logical ops)
|
||||
Condition,
|
||||
|
||||
/// General expression (future: method calls, box ops, etc.)
|
||||
#[allow(dead_code)] // Phase 231: Not yet implemented
|
||||
General,
|
||||
}
|
||||
|
||||
/// Phase 231: Expression lowering error
|
||||
///
|
||||
/// Explicit error types allow callers to handle different failure modes
|
||||
/// (e.g., fall back to legacy path for unsupported nodes).
|
||||
#[derive(Debug)]
|
||||
pub enum ExprLoweringError {
|
||||
/// AST node type not supported in this context
|
||||
UnsupportedNode(String),
|
||||
|
||||
/// Variable not found in any scope
|
||||
VariableNotFound(String),
|
||||
|
||||
/// Type error during lowering (e.g., non-boolean in condition)
|
||||
TypeError(String),
|
||||
|
||||
/// Internal lowering error (from condition_lowerer)
|
||||
LoweringError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExprLoweringError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg),
|
||||
ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name),
|
||||
ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||||
ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 231: Expression lowerer (pilot implementation)
|
||||
///
|
||||
/// This struct provides a unified interface for lowering AST expressions to
|
||||
/// JoinIR instructions, using ScopeManager for variable resolution.
|
||||
///
|
||||
/// ## Current Scope (Phase 231)
|
||||
///
|
||||
/// - **Context**: Condition only (loop/break conditions)
|
||||
/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not)
|
||||
/// - **Not Supported**: Method calls, NewBox, complex expressions
|
||||
///
|
||||
/// ## Usage Pattern
|
||||
///
|
||||
/// ```ignore
|
||||
/// let scope = Pattern2ScopeManager { ... };
|
||||
/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder);
|
||||
///
|
||||
/// match expr_lowerer.lower(&break_condition_ast) {
|
||||
/// Ok(value_id) => {
|
||||
/// // Use value_id in JoinIR
|
||||
/// }
|
||||
/// Err(ExprLoweringError::UnsupportedNode(_)) => {
|
||||
/// // Fall back to legacy condition_to_joinir path
|
||||
/// }
|
||||
/// Err(e) => {
|
||||
/// // Handle other errors (variable not found, etc.)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||||
/// Scope manager for variable resolution
|
||||
scope: &'env S,
|
||||
|
||||
/// Expression context (Condition vs General)
|
||||
context: ExprContext,
|
||||
|
||||
/// MIR builder (for ValueId allocation, not used in Phase 231)
|
||||
#[allow(dead_code)] // Phase 231: Reserved for future use
|
||||
builder: &'builder mut MirBuilder,
|
||||
|
||||
/// Debug flag (inherited from caller)
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
/// Create a new expression lowerer
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `scope` - ScopeManager for variable resolution
|
||||
/// * `context` - Expression context (Condition or General)
|
||||
/// * `builder` - MIR builder (for future use)
|
||||
pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self {
|
||||
Self {
|
||||
scope,
|
||||
context,
|
||||
builder,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable debug output
|
||||
pub fn with_debug(mut self, debug: bool) -> Self {
|
||||
self.debug = debug;
|
||||
self
|
||||
}
|
||||
|
||||
/// Lower an expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: This is the main entry point. Currently delegates to
|
||||
/// lower_condition for Condition context.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Expression result ValueId
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy)
|
||||
pub fn lower(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
match self.context {
|
||||
ExprContext::Condition => self.lower_condition(ast),
|
||||
ExprContext::General => {
|
||||
Err(ExprLoweringError::UnsupportedNode(
|
||||
"General expression context not yet implemented (Phase 231)".to_string()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower a condition expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: Thin wrapper around condition_lowerer. The main innovation
|
||||
/// is using ScopeManager for variable resolution instead of direct ConditionEnv.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(ValueId)` - Condition result ValueId (boolean)
|
||||
/// * `Err(ExprLoweringError)` - Lowering failed
|
||||
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
|
||||
// 1. Check if AST is supported in condition context
|
||||
if !Self::is_supported_condition(ast) {
|
||||
return Err(ExprLoweringError::UnsupportedNode(
|
||||
format!("Unsupported condition node: {:?}", ast)
|
||||
));
|
||||
}
|
||||
|
||||
// 2. Build ConditionEnv from ScopeManager
|
||||
// This is the key integration point: we translate ScopeManager's view
|
||||
// into the ConditionEnv format expected by condition_lowerer.
|
||||
let condition_env = self.build_condition_env_from_scope(ast)?;
|
||||
|
||||
// 3. Delegate to existing condition_lowerer
|
||||
// Phase 231: We use the existing, well-tested lowering logic.
|
||||
let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, _instructions) = lower_condition_to_joinir(
|
||||
ast,
|
||||
&mut alloc_value,
|
||||
&condition_env,
|
||||
).map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
if self.debug {
|
||||
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
|
||||
}
|
||||
|
||||
Ok(result_value)
|
||||
}
|
||||
|
||||
/// Build ConditionEnv from ScopeManager
|
||||
///
|
||||
/// This method extracts all variables referenced in the AST and resolves
|
||||
/// them through ScopeManager, building a ConditionEnv for condition_lowerer.
|
||||
fn build_condition_env_from_scope(&self, ast: &ASTNode) -> Result<ConditionEnv, ExprLoweringError> {
|
||||
let mut env = ConditionEnv::new();
|
||||
|
||||
// Extract all variable names from the AST
|
||||
let var_names = Self::extract_variable_names(ast);
|
||||
|
||||
// Resolve each variable through ScopeManager
|
||||
for name in var_names {
|
||||
if let Some(value_id) = self.scope.lookup(&name) {
|
||||
env.insert(name.clone(), value_id);
|
||||
} else {
|
||||
return Err(ExprLoweringError::VariableNotFound(name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
/// Extract all variable names from an AST node (recursively)
|
||||
fn extract_variable_names(ast: &ASTNode) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
Self::extract_variable_names_recursive(ast, &mut names);
|
||||
names.sort();
|
||||
names.dedup();
|
||||
names
|
||||
}
|
||||
|
||||
/// Recursive helper for variable name extraction
|
||||
fn extract_variable_names_recursive(ast: &ASTNode, names: &mut Vec<String>) {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
names.push(name.clone());
|
||||
}
|
||||
ASTNode::BinaryOp { left, right, .. } => {
|
||||
Self::extract_variable_names_recursive(left, names);
|
||||
Self::extract_variable_names_recursive(right, names);
|
||||
}
|
||||
ASTNode::UnaryOp { operand, .. } => {
|
||||
Self::extract_variable_names_recursive(operand, names);
|
||||
}
|
||||
// Phase 231: Only support simple expressions
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if an AST node is supported in condition context
|
||||
///
|
||||
/// Phase 231: Conservative whitelist. We only support patterns we know work.
|
||||
fn is_supported_condition(ast: &ASTNode) -> bool {
|
||||
match ast {
|
||||
// Literals: Integer, Bool
|
||||
ASTNode::Literal { .. } => true,
|
||||
|
||||
// Variables
|
||||
ASTNode::Variable { .. } => true,
|
||||
|
||||
// Comparison operators
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
let op_supported = matches!(
|
||||
operator,
|
||||
BinaryOperator::Less
|
||||
| BinaryOperator::Greater
|
||||
| BinaryOperator::Equal
|
||||
| BinaryOperator::NotEqual
|
||||
| BinaryOperator::LessEqual
|
||||
| BinaryOperator::GreaterEqual
|
||||
| BinaryOperator::And
|
||||
| BinaryOperator::Or
|
||||
);
|
||||
|
||||
op_supported
|
||||
&& Self::is_supported_condition(left)
|
||||
&& Self::is_supported_condition(right)
|
||||
}
|
||||
|
||||
// Unary operators (not)
|
||||
ASTNode::UnaryOp { operator, operand, .. } => {
|
||||
matches!(operator, UnaryOperator::Not)
|
||||
&& Self::is_supported_condition(operand)
|
||||
}
|
||||
|
||||
// Everything else is unsupported
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Span, LiteralValue};
|
||||
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
|
||||
// Helper to create a test MirBuilder (Phase 231: minimal stub)
|
||||
fn create_test_builder() -> MirBuilder {
|
||||
MirBuilder::new()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_simple_comparison() {
|
||||
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,
|
||||
};
|
||||
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// AST: i < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(result.is_ok(), "Should lower simple comparison successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_variable_not_found() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// AST: unknown_var < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "unknown_var".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(result, Err(ExprLoweringError::VariableNotFound(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_unsupported_node() {
|
||||
let condition_env = ConditionEnv::new();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// AST: MethodCall (unsupported in condition context)
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_supported_condition() {
|
||||
// Supported: i < 10
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(10),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
|
||||
// Unsupported: MethodCall
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,7 @@ pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrato
|
||||
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
||||
pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST
|
||||
pub mod continue_branch_normalizer; // Phase 33-19: Continue branch normalization for Pattern B
|
||||
pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management
|
||||
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
|
||||
pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection
|
||||
pub(crate) mod exit_args_resolver; // Internal exit argument resolution
|
||||
@ -52,6 +53,7 @@ pub mod inline_boundary; // Phase 188-Impl-3: JoinIR→Host boundary
|
||||
pub mod inline_boundary_builder; // Phase 200-2: Builder pattern for JoinInlineBoundary
|
||||
pub mod join_value_space; // Phase 201: Unified JoinIR ValueId allocation
|
||||
pub(crate) mod loop_form_intake; // Internal loop form intake
|
||||
pub mod scope_manager; // Phase 231: Unified variable scope management
|
||||
pub(crate) mod loop_pattern_router; // Phase 33-12: Loop pattern routing (re-exported)
|
||||
pub(crate) mod loop_pattern_validator; // Phase 33-23: Loop structure validation
|
||||
pub(crate) mod loop_patterns; // Phase 188: Pattern-based loop lowering (3 patterns)
|
||||
|
||||
326
src/mir/join_ir/lowering/scope_manager.rs
Normal file
326
src/mir/join_ir/lowering/scope_manager.rs
Normal file
@ -0,0 +1,326 @@
|
||||
//! 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;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// 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
|
||||
// If this variable was promoted, try to find its carrier equivalent
|
||||
if self.carrier_info.promoted_loopbodylocals.contains(&name.to_string()) {
|
||||
// Try naming conventions
|
||||
for carrier_name in &[
|
||||
format!("is_{}", name), // DigitPos pattern
|
||||
format!("is_{}_match", name), // Trim pattern
|
||||
] {
|
||||
// Check if it's the loop variable (unlikely but possible)
|
||||
if carrier_name == &self.carrier_info.loop_var_name {
|
||||
if let Some(id) = self.condition_env.get(&self.carrier_info.loop_var_name) {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise check carriers
|
||||
if let Some(carrier) = self.carrier_info.carriers.iter().find(|c| c.name == *carrier_name) {
|
||||
if let Some(join_id) = carrier.join_id {
|
||||
return Some(join_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user