//! 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 { 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 { // 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 { 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 { 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) { 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::::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::::is_supported_condition(&ast)); } }