//! Phase 244: Unified Condition Lowering Interface //! //! This module provides a trait-based abstraction for condition lowering, //! allowing different implementations (ExprLowerer, legacy lowerers) to //! be used interchangeably. //! //! ## Design Philosophy //! //! **Box-First**: ConditionLoweringBox is a "box" trait that encapsulates //! all condition lowering logic with a single, unified interface. //! //! **Single Responsibility**: Implementations ONLY perform AST → ValueId lowering. //! They do NOT manage scopes, extract variables, or handle PHI generation. //! //! **Fail-Safe**: Implementations return explicit errors for unsupported patterns, //! allowing callers to fall back to alternative paths. use super::scope_manager::ScopeManager; use crate::ast::ASTNode; use crate::mir::ValueId; /// Phase 244: Context for condition lowering /// /// This struct encapsulates all the necessary context for lowering a condition /// expression to JoinIR, including loop variable information and scope access. /// /// # Fields /// /// * `loop_var_name` - Name of the loop variable (e.g., "i") /// * `loop_var_id` - ValueId of the loop variable in JoinIR space /// * `scope` - Reference to ScopeManager for variable lookup /// * `alloc_value` - ValueId allocator function /// /// # Example /// /// ```ignore /// let context = ConditionContext { /// loop_var_name: "i".to_string(), /// loop_var_id: ValueId(1), /// scope: &scope_manager, /// alloc_value: &mut alloc_fn, /// }; /// /// let value_id = lowerer.lower_condition(&ast, &context)?; /// ``` pub struct ConditionContext<'a, S: ScopeManager> { /// Name of the loop variable (e.g., "i", "pos") pub loop_var_name: String, /// ValueId of the loop variable in JoinIR space pub loop_var_id: ValueId, /// Scope manager for variable resolution pub scope: &'a S, /// ValueId allocator function pub alloc_value: &'a mut dyn FnMut() -> ValueId, } /// Phase 244: Unified condition lowering interface /// /// This trait provides a common interface for all condition lowering implementations. /// It allows Pattern 2/3/4 to use any lowering strategy (ExprLowerer, legacy, etc.) /// without coupling to specific implementation details. /// /// # Design Principles /// /// 1. **Single Method**: `lower_condition()` is the only required method /// 2. **Context-Based**: All necessary information passed via ConditionContext /// 3. **Fail-Fast**: Errors returned immediately (no silent fallbacks) /// 4. **Stateless**: Implementations should be reusable across multiple calls /// /// # Example Implementation /// /// ```ignore /// impl ConditionLoweringBox for ExprLowerer<'_, '_, S> { /// fn lower_condition( /// &mut self, /// condition: &ASTNode, /// context: &ConditionContext, /// ) -> Result { /// // Delegate to existing ExprLowerer::lower() method /// self.lower(condition) /// } /// /// fn supports(&self, condition: &ASTNode) -> bool { /// Self::is_supported_condition(condition) /// } /// } /// ``` pub trait ConditionLoweringBox { /// Lower condition AST to ValueId /// /// This method translates an AST condition expression (e.g., `i < 10`, /// `digit_pos < 0`) into a JoinIR ValueId representing the boolean result. /// /// # Arguments /// /// * `condition` - AST node representing the boolean condition /// * `context` - Lowering context (loop var, scope, allocator) /// /// # Returns /// /// * `Ok(ValueId)` - Boolean result ValueId /// * `Err(String)` - Lowering error (unsupported pattern, variable not found, etc.) /// /// # Fail-Fast Principle /// /// Implementations MUST return `Err` immediately for unsupported patterns. /// Callers can then decide whether to fall back to alternative lowering paths. /// /// # Example /// /// ```ignore /// // Pattern 2: Break condition lowering /// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?; /// /// // Use cond_value in conditional Jump /// let break_jump = JoinInst::Jump { /// target: k_exit, /// args: vec![loop_var_id], /// condition: Some(cond_value), /// }; /// ``` fn lower_condition( &mut self, condition: &ASTNode, context: &ConditionContext, ) -> Result; /// Check if this lowerer supports the given condition pattern /// /// This method allows callers to check support BEFORE attempting lowering, /// enabling early fallback to alternative strategies. /// /// # Arguments /// /// * `condition` - AST node to check /// /// # Returns /// /// * `true` - This lowerer can handle this pattern /// * `false` - This lowerer cannot handle this pattern (caller should use alternative) /// /// # Example /// /// ```ignore /// if lowerer.supports(&break_cond_ast) { /// let value = lowerer.lower_condition(&break_cond_ast, &context)?; /// } else { /// // Fall back to legacy lowering /// let value = legacy_lower_condition(&break_cond_ast, env)?; /// } /// ``` fn supports(&self, condition: &ASTNode) -> bool; } #[cfg(test)] mod tests { use super::*; use crate::ast::{BinaryOperator, LiteralValue, Span}; use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; fn span() -> Span { Span::unknown() } fn var(name: &str) -> ASTNode { ASTNode::Variable { name: name.to_string(), span: span(), } } fn lit_i(value: i64) -> ASTNode { ASTNode::Literal { value: LiteralValue::Integer(value), span: span(), } } fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode { ASTNode::BinaryOp { operator: op, left: Box::new(left), right: Box::new(right), span: span(), } } #[test] fn test_condition_lowering_box_trait_exists() { // This test verifies that the ConditionLoweringBox trait can be used // with ExprLowerer (implementation added in Step 2). 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, }; let mut builder = MirBuilder::new(); let mut alloc_counter = 1000u32; let mut alloc_fn = || { let id = ValueId(alloc_counter); alloc_counter += 1; id }; let context = ConditionContext { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), scope: &scope, alloc_value: &mut alloc_fn, }; // AST: i < 10 let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); // ExprLowerer implements ConditionLoweringBox (Step 2) let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); // Check support assert!( expr_lowerer.supports(&ast), "ExprLowerer should support i < 10" ); // Lower condition via ConditionLoweringBox trait (Step 2 implemented) let result = expr_lowerer.lower_condition(&ast, &context); assert!(result.is_ok(), "i < 10 should lower successfully via trait"); } #[test] fn test_condition_context_structure() { // Verify ConditionContext can be constructed and fields are accessible 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, }; let mut alloc_counter = 1000u32; let mut alloc_fn = || { let id = ValueId(alloc_counter); alloc_counter += 1; id }; let context = ConditionContext { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), scope: &scope, alloc_value: &mut alloc_fn, }; assert_eq!(context.loop_var_name, "i"); assert_eq!(context.loop_var_id, ValueId(1)); // Verify allocator works let vid = (context.alloc_value)(); assert_eq!(vid, ValueId(1000)); } }