diff --git a/src/mir/join_ir/lowering/expr_lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer.rs deleted file mode 100644 index f9405e75..00000000 --- a/src/mir/join_ir/lowering/expr_lowerer.rs +++ /dev/null @@ -1,875 +0,0 @@ -//! 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 super::condition_lowerer::{lower_condition_to_joinir, lower_condition_to_joinir_no_body_locals}; -use super::scope_manager::ScopeManager; -use crate::ast::ASTNode; -use crate::mir::builder::MirBuilder; -use crate::mir::join_ir::JoinInst; -use crate::mir::ValueId; - -mod ast_support; -mod scope_resolution; -#[cfg(test)] -mod test_helpers; - -/// 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, - - /// Last lowered instruction sequence (for testing/inspection) - /// - /// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not - /// instructions are emitted for supported patterns. Productionコードからは未使用。 - last_instructions: Vec, -} - -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, - last_instructions: Vec::new(), - } - } - - /// Enable debug output - pub fn with_debug(mut self, debug: bool) -> Self { - self.debug = debug; - self - } - - /// Take the last lowered instruction sequence (mainly for tests) - /// - /// Phase 235: This allows unit tests to validate that Compare / BinOp / Not - /// instructions are present without影響を与えずに ExprLowerer の外から観察できる。 - pub fn take_last_instructions(&mut self) -> Vec { - std::mem::take(&mut self.last_instructions) - } - - /// 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 !ast_support::is_supported_condition(ast) { - return Err(ExprLoweringError::UnsupportedNode(format!( - "Unsupported condition node: {:?}", - ast - ))); - } - - // 2. Build ConditionEnv from ScopeManager - // Phase 79-1: Use BindingId-aware version when available - #[cfg(feature = "normalized_dev")] - let condition_env = scope_resolution::build_condition_env_from_scope_with_binding( - self.scope, - ast, - self.builder, - )?; - - #[cfg(not(feature = "normalized_dev"))] - let condition_env = scope_resolution::build_condition_env_from_scope(self.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_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2 - .map_err(|e| ExprLoweringError::LoweringError(e))?; - - // Phase 235: 保存しておき、テストから観察できるようにする - self.last_instructions = instructions; - - if self.debug { - eprintln!( - "[expr_lowerer/phase231] Lowered condition → ValueId({:?})", - result_value - ); - } - - Ok(result_value) - } - - /// Public helper used by Pattern2/3 callers to gate ExprLowerer usage. - pub fn is_supported_condition(ast: &ASTNode) -> bool { - ast_support::is_supported_condition(ast) - } -} - -// ============================================================================ -// Phase 244: ConditionLoweringBox trait implementation -// ============================================================================ - -use super::condition_lowering_box::{ConditionContext, ConditionLoweringBox}; - -impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'env, 'builder, S> { - /// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer - /// - /// This allows ExprLowerer to be used interchangeably with other condition - /// lowering implementations through the unified ConditionLoweringBox interface. - /// - /// # Design - /// - /// This implementation is a thin wrapper around the existing `lower()` method. - /// The `ConditionContext` parameter is currently unused because ExprLowerer - /// already has access to ScopeManager through its constructor. - /// - /// # Example - /// - /// ```ignore - /// // Pattern 2: Use ExprLowerer via ConditionLoweringBox trait - /// let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - /// - /// let context = ConditionContext { - /// loop_var_name: "i".to_string(), - /// loop_var_id: ValueId(1), - /// scope: &scope, - /// alloc_value: &mut alloc_fn, - /// }; - /// - /// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?; - /// ``` - fn lower_condition( - &mut self, - condition: &ASTNode, - context: &mut ConditionContext, - ) -> Result { - // Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller. - // - // JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions. - // If we allocate locally here (e.g. starting from 1000), we can collide with - // other JoinIR value users (main params, carrier slots), and after remapping - // this becomes a MIR-level ValueId collision. - if !ast_support::is_supported_condition(condition) { - return Err(format!("Unsupported condition node: {:?}", condition)); - } - - // Build ConditionEnv from the provided scope (the caller controls the scope + allocator). - #[cfg(feature = "normalized_dev")] - let condition_env = scope_resolution::build_condition_env_from_scope_with_binding( - context.scope, - condition, - self.builder, - ) - .map_err(|e| e.to_string())?; - - #[cfg(not(feature = "normalized_dev"))] - let condition_env = - scope_resolution::build_condition_env_from_scope(context.scope, condition) - .map_err(|e| e.to_string())?; - - // Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT). - // Phase 256.7: Pass current_static_box_name for this.method(...) support - let (result_value, instructions) = lower_condition_to_joinir( - condition, - &mut *context.alloc_value, - &condition_env, - None, // body_local_env - context.current_static_box_name.as_deref(), // Phase 256.7 - ) - .map_err(|e| e.to_string())?; - - self.last_instructions = instructions; - - if self.debug { - eprintln!( - "[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)", - result_value - ); - } - - Ok(result_value) - } - - /// Phase 244: Check if ExprLowerer supports a given condition pattern - /// - /// This delegates to the existing `is_supported_condition()` static method, - /// allowing callers to check support before attempting lowering. - fn supports(&self, condition: &ASTNode) -> bool { - ast_support::is_supported_condition(condition) - } -} - -#[cfg(test)] -mod tests { - use super::test_helpers::{bin, lit_i, span, var}; - use super::*; - use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator}; - use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; - use crate::mir::join_ir::lowering::condition_env::ConditionEnv; - use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; - use crate::mir::join_ir::{BinOpKind, MirLikeInst, UnaryOp as JoinUnaryOp}; - - // Helper to create a test MirBuilder (Phase 231: minimal stub) - fn create_test_builder() -> MirBuilder { - MirBuilder::new() - } - - fn not(expr: ASTNode) -> ASTNode { - ASTNode::UnaryOp { - operator: UnaryOperator::Not, - operand: Box::new(expr), - span: span(), - } - } - - #[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![], - #[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 = 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![], - #[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 = 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![], - #[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 = create_test_builder(); - - // AST: Break (unsupported in condition context) - let ast = ASTNode::Break { - 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 - )); - - // Supported: 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 - )); - - // Unsupported: Break node - let ast = ASTNode::Break { - span: Span::unknown(), - }; - assert!(!ExprLowerer::::is_supported_condition(&ast)); - } - - // Phase 235: Additional patterns for condition lowering - - fn make_basic_scope() -> Pattern2ScopeManager<'static> { - // NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply. - // テスト専用なので許容する。 - let mut condition_env = ConditionEnv::new(); - condition_env.insert("i".to_string(), ValueId(1)); - condition_env.insert("j".to_string(), ValueId(2)); - - let boxed_env: Box = Box::new(condition_env); - let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env); - - 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 boxed_carrier: Box = Box::new(carrier_info); - let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier); - - Pattern2ScopeManager { - condition_env: condition_env_ref, - loop_body_local_env: None, - captured_env: None, - carrier_info: carrier_ref, - } - } - - fn make_scope_with_p_and_s() -> Pattern2ScopeManager<'static> { - // Leak these tiny envs for test lifetime convenience only. - let mut condition_env = ConditionEnv::new(); - condition_env.insert("p".to_string(), ValueId(1)); - condition_env.insert("s".to_string(), ValueId(2)); - - let boxed_env: Box = Box::new(condition_env); - let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env); - - let carrier_info = CarrierInfo { - loop_var_name: "p".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 boxed_carrier: Box = Box::new(carrier_info); - let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier); - - Pattern2ScopeManager { - condition_env: condition_env_ref, - loop_body_local_env: None, - captured_env: None, - carrier_info: carrier_ref, - } - } - - fn assert_has_compare(instructions: &[JoinInst]) { - assert!( - instructions - .iter() - .any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))), - "Expected at least one Compare instruction, got {:?}", - instructions - ); - } - - fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) { - assert!( - instructions.iter().any(|inst| matches!( - inst, - JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op - )), - "Expected at least one BinOp {:?}, got {:?}", - op, - instructions - ); - } - - fn assert_has_not(instructions: &[JoinInst]) { - assert!( - instructions.iter().any(|inst| matches!( - inst, - JoinInst::Compute(MirLikeInst::UnaryOp { - op: JoinUnaryOp::Not, - .. - }) - )), - "Expected at least one UnaryOp::Not, got {:?}", - instructions - ); - } - - #[test] - fn test_expr_lowerer_var_less_literal_generates_compare() { - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - // i < 10 - let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "i < 10 should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "instructions should not be empty"); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_literal_less_var_generates_compare() { - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - // 0 < i - let ast = bin(BinaryOperator::Less, lit_i(0), var("i")); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "0 < i should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "instructions should not be empty"); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_greater_than_between_vars() { - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - // i > j - let ast = bin(BinaryOperator::Greater, var("i"), var("j")); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "i > j should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "instructions should not be empty"); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_and_combination() { - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - // i > 0 && j < 5 - let left = bin(BinaryOperator::Greater, var("i"), lit_i(0)); - let right = bin(BinaryOperator::Less, var("j"), lit_i(5)); - let ast = bin(BinaryOperator::And, left, right); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "instructions should not be empty"); - assert_has_compare(&instructions); - assert_has_binop(&instructions, BinOpKind::And); - } - - #[test] - fn test_expr_lowerer_not_of_comparison() { - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - // !(i < 10) - let inner = bin(BinaryOperator::Less, var("i"), lit_i(10)); - let ast = not(inner); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "!(i < 10) should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "instructions should not be empty"); - assert_has_compare(&instructions); - assert_has_not(&instructions); - } - - #[test] - fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() { - let mut condition_env = ConditionEnv::new(); - condition_env.insert("digit_pos".to_string(), ValueId(10)); - - 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 = create_test_builder(); - - // digit_pos < 0 - let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0)); - assert!( - ExprLowerer::::is_supported_condition(&ast), - "digit_pos < 0 should be supported in Pattern2 break condition" - ); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - assert!(result.is_ok(), "digit_pos < 0 should lower successfully"); - - let instructions = expr_lowerer.take_last_instructions(); - assert!( - !instructions.is_empty(), - "instructions for digit_pos < 0 should not be empty" - ); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_methodcall_unknown_method_is_rejected() { - let scope = make_scope_with_p_and_s(); - let mut builder = create_test_builder(); - - // Unknown method name should fail through MethodCallLowerer - let ast = ASTNode::MethodCall { - object: Box::new(var("s")), - method: "unknown_method".to_string(), - arguments: vec![], - span: span(), - }; - - assert!( - ExprLowerer::::is_supported_condition(&ast), - "MethodCall nodes should be routed to MethodCallLowerer for validation" - ); - - let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - let result = expr_lowerer.lower(&ast); - - assert!( - matches!(result, Err(ExprLoweringError::LoweringError(msg)) if msg.contains("MethodCall")), - "Unknown method should fail-fast via MethodCallLowerer" - ); - } - - // Phase 240-EX: Header condition patterns - - #[test] - fn test_expr_lowerer_supports_simple_header_condition_i_less_literal() { - // header pattern: i < 10 - let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); - assert!( - ExprLowerer::::is_supported_condition(&ast), - "i < 10 should be supported for Pattern2 header condition" - ); - - // lower and verify success - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - - let result = lowerer.lower(&ast); - assert!(result.is_ok(), "i < 10 should lower successfully"); - - // Compare instruction should be present - let instructions = lowerer.take_last_instructions(); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_supports_header_condition_var_less_var() { - // header pattern: i < n (variable vs variable) - let ast = bin(BinaryOperator::Less, var("i"), var("j")); - assert!( - ExprLowerer::::is_supported_condition(&ast), - "i < n should be supported for Pattern2 header condition" - ); - - // lower and verify success - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - - let result = lowerer.lower(&ast); - assert!(result.is_ok(), "i < n should lower successfully"); - - // Compare instruction should be present - let instructions = lowerer.take_last_instructions(); - assert_has_compare(&instructions); - } - - #[test] - fn test_expr_lowerer_supports_header_condition_with_length_call() { - // header pattern: p < s.length() - let length_call = ASTNode::MethodCall { - object: Box::new(var("s")), - method: "length".to_string(), - arguments: vec![], - span: span(), - }; - let ast = bin(BinaryOperator::Less, var("p"), length_call); - - assert!( - ExprLowerer::::is_supported_condition(&ast), - "p < s.length() should be supported for Pattern2 header condition" - ); - - let scope = make_scope_with_p_and_s(); - let mut builder = create_test_builder(); - let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - - let result = lowerer.lower(&ast); - assert!(result.is_ok(), "p < s.length() should lower successfully"); - - let instructions = lowerer.take_last_instructions(); - assert_has_compare(&instructions); - assert!( - instructions.iter().any(|inst| matches!( - inst, - JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "length" - )), - "Expected BoxCall for length receiver in lowered instructions" - ); - } - - #[test] - fn test_expr_lowerer_header_condition_generates_expected_instructions() { - // Test that header condition i < 10 generates proper Compare instruction - let scope = make_basic_scope(); - let mut builder = create_test_builder(); - - let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); - let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); - - let result = lowerer.lower(&ast); - assert!(result.is_ok()); - - let instructions = lowerer.take_last_instructions(); - assert!(!instructions.is_empty(), "Should generate instructions"); - - // Should have Compare instruction - assert_has_compare(&instructions); - } -} diff --git a/src/mir/join_ir/lowering/expr_lowerer/lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer/lowerer.rs new file mode 100644 index 00000000..8c483f65 --- /dev/null +++ b/src/mir/join_ir/lowering/expr_lowerer/lowerer.rs @@ -0,0 +1,263 @@ +use super::ast_support; +use super::scope_resolution; +use super::{ExprContext, ExprLoweringError}; +use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::JoinInst; +use crate::mir::ValueId; + +use super::super::condition_lowerer::{ + lower_condition_to_joinir, lower_condition_to_joinir_no_body_locals, +}; +use super::super::condition_lowering_box::{ConditionContext, ConditionLoweringBox}; +use super::super::scope_manager::ScopeManager; + +/// 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, + + /// Last lowered instruction sequence (for testing/inspection) + /// + /// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not + /// instructions are emitted for supported patterns. Productionコードからは未使用。 + last_instructions: Vec, +} + +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, + last_instructions: Vec::new(), + } + } + + /// Enable debug output + pub fn with_debug(mut self, debug: bool) -> Self { + self.debug = debug; + self + } + + /// Take the last lowered instruction sequence (mainly for tests) + /// + /// Phase 235: This allows unit tests to validate that Compare / BinOp / Not + /// instructions are present without影響を与えずに ExprLowerer の外から観察できる。 + pub fn take_last_instructions(&mut self) -> Vec { + std::mem::take(&mut self.last_instructions) + } + + /// 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 !ast_support::is_supported_condition(ast) { + return Err(ExprLoweringError::UnsupportedNode(format!( + "Unsupported condition node: {:?}", + ast + ))); + } + + // 2. Build ConditionEnv from ScopeManager + // Phase 79-1: Use BindingId-aware version when available + #[cfg(feature = "normalized_dev")] + let condition_env = scope_resolution::build_condition_env_from_scope_with_binding( + self.scope, + ast, + self.builder, + )?; + + #[cfg(not(feature = "normalized_dev"))] + let condition_env = scope_resolution::build_condition_env_from_scope(self.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_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2 + .map_err(|e| ExprLoweringError::LoweringError(e))?; + + // Phase 235: 保存しておき、テストから観察できるようにする + self.last_instructions = instructions; + + if self.debug { + eprintln!( + "[expr_lowerer/phase231] Lowered condition → ValueId({:?})", + result_value + ); + } + + Ok(result_value) + } + + /// Public helper used by Pattern2/3 callers to gate ExprLowerer usage. + pub fn is_supported_condition(ast: &ASTNode) -> bool { + ast_support::is_supported_condition(ast) + } +} + +impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'env, 'builder, S> { + /// Phase 244: Implement ConditionLoweringBox trait for ExprLowerer + /// + /// This allows ExprLowerer to be used interchangeably with other condition + /// lowering implementations through the unified ConditionLoweringBox interface. + /// + /// # Design + /// + /// This implementation is a thin wrapper around the existing `lower()` method. + /// The `ConditionContext` parameter is currently unused because ExprLowerer + /// already has access to ScopeManager through its constructor. + /// + /// # Example + /// + /// ```ignore + /// // Pattern 2: Use ExprLowerer via ConditionLoweringBox trait + /// let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + /// + /// let context = ConditionContext { + /// loop_var_name: "i".to_string(), + /// loop_var_id: ValueId(1), + /// scope: &scope, + /// alloc_value: &mut alloc_fn, + /// }; + /// + /// let cond_value = lowerer.lower_condition(&break_cond_ast, &context)?; + /// ``` + fn lower_condition( + &mut self, + condition: &ASTNode, + context: &mut ConditionContext, + ) -> Result { + // Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller. + // + // JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions. + // If we allocate locally here (e.g. starting from 1000), we can collide with + // other JoinIR value users (main params, carrier slots), and after remapping + // this becomes a MIR-level ValueId collision. + if !ast_support::is_supported_condition(condition) { + return Err(format!("Unsupported condition node: {:?}", condition)); + } + + // Build ConditionEnv from the provided scope (the caller controls the scope + allocator). + #[cfg(feature = "normalized_dev")] + let condition_env = scope_resolution::build_condition_env_from_scope_with_binding( + context.scope, + condition, + self.builder, + ) + .map_err(|e| e.to_string())?; + + #[cfg(not(feature = "normalized_dev"))] + let condition_env = + scope_resolution::build_condition_env_from_scope(context.scope, condition) + .map_err(|e| e.to_string())?; + + // Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT). + // Phase 256.7: Pass current_static_box_name for this.method(...) support + let (result_value, instructions) = lower_condition_to_joinir( + condition, + &mut *context.alloc_value, + &condition_env, + None, // body_local_env + context.current_static_box_name.as_deref(), // Phase 256.7 + ) + .map_err(|e| e.to_string())?; + + self.last_instructions = instructions; + + if self.debug { + eprintln!( + "[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)", + result_value + ); + } + + Ok(result_value) + } + + /// Phase 244: Check if ExprLowerer supports a given condition pattern + /// + /// This delegates to the existing `is_supported_condition()` static method, + /// allowing callers to check support before attempting lowering. + fn supports(&self, condition: &ASTNode) -> bool { + ast_support::is_supported_condition(condition) + } +} diff --git a/src/mir/join_ir/lowering/expr_lowerer/mod.rs b/src/mir/join_ir/lowering/expr_lowerer/mod.rs new file mode 100644 index 00000000..fa421aee --- /dev/null +++ b/src/mir/join_ir/lowering/expr_lowerer/mod.rs @@ -0,0 +1,28 @@ +//! 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. + +mod ast_support; +mod scope_resolution; +#[cfg(test)] +mod test_helpers; +mod types; +mod lowerer; +#[cfg(test)] +mod tests; + +pub use lowerer::ExprLowerer; +pub use types::{ExprContext, ExprLoweringError}; diff --git a/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs b/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs index 78dfaeba..2974525c 100644 --- a/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs +++ b/src/mir/join_ir/lowering/expr_lowerer/scope_resolution.rs @@ -1,4 +1,5 @@ -use super::{ExprLoweringError, ScopeManager}; +use super::ExprLoweringError; +use super::super::scope_manager::ScopeManager; use crate::ast::ASTNode; #[cfg(feature = "normalized_dev")] use crate::mir::builder::MirBuilder; diff --git a/src/mir/join_ir/lowering/expr_lowerer/tests.rs b/src/mir/join_ir/lowering/expr_lowerer/tests.rs new file mode 100644 index 00000000..a312f98c --- /dev/null +++ b/src/mir/join_ir/lowering/expr_lowerer/tests.rs @@ -0,0 +1,544 @@ +use super::test_helpers::{bin, lit_i, span, var}; +use super::{ExprContext, ExprLowerer, ExprLoweringError}; +use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator}; +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::condition_env::ConditionEnv; +use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; +use crate::mir::join_ir::{BinOpKind, JoinInst, MirLikeInst, UnaryOp as JoinUnaryOp}; +use crate::mir::{builder::MirBuilder, ValueId}; + +// Helper to create a test MirBuilder (Phase 231: minimal stub) +fn create_test_builder() -> MirBuilder { + MirBuilder::new() +} + +fn not(expr: crate::ast::ASTNode) -> crate::ast::ASTNode { + crate::ast::ASTNode::UnaryOp { + operator: UnaryOperator::Not, + operand: Box::new(expr), + span: span(), + } +} + +#[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![], + #[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 = create_test_builder(); + + // AST: i < 10 + let ast = crate::ast::ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(crate::ast::ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(crate::ast::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![], + #[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 = create_test_builder(); + + // AST: unknown_var < 10 + let ast = crate::ast::ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(crate::ast::ASTNode::Variable { + name: "unknown_var".to_string(), + span: Span::unknown(), + }), + right: Box::new(crate::ast::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![], + #[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 = create_test_builder(); + + // AST: Break (unsupported in condition context) + let ast = crate::ast::ASTNode::Break { + 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 = crate::ast::ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(crate::ast::ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(crate::ast::ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + assert!(ExprLowerer::::is_supported_condition( + &ast + )); + + // Supported: MethodCall + let ast = crate::ast::ASTNode::MethodCall { + object: Box::new(crate::ast::ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + assert!(ExprLowerer::::is_supported_condition( + &ast + )); + + // Unsupported: Break node + let ast = crate::ast::ASTNode::Break { + span: Span::unknown(), + }; + assert!(!ExprLowerer::::is_supported_condition(&ast)); +} + +// Phase 235: Additional patterns for condition lowering + +fn make_basic_scope() -> Pattern2ScopeManager<'static> { + // NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply. + // テスト専用なので許容する。 + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(1)); + condition_env.insert("j".to_string(), ValueId(2)); + + let boxed_env: Box = Box::new(condition_env); + let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env); + + 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 boxed_carrier: Box = Box::new(carrier_info); + let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier); + + Pattern2ScopeManager { + condition_env: condition_env_ref, + loop_body_local_env: None, + captured_env: None, + carrier_info: carrier_ref, + } +} + +fn make_scope_with_p_and_s() -> Pattern2ScopeManager<'static> { + // Leak these tiny envs for test lifetime convenience only. + let mut condition_env = ConditionEnv::new(); + condition_env.insert("p".to_string(), ValueId(1)); + condition_env.insert("s".to_string(), ValueId(2)); + + let boxed_env: Box = Box::new(condition_env); + let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env); + + let carrier_info = CarrierInfo { + loop_var_name: "p".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 boxed_carrier: Box = Box::new(carrier_info); + let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier); + + Pattern2ScopeManager { + condition_env: condition_env_ref, + loop_body_local_env: None, + captured_env: None, + carrier_info: carrier_ref, + } +} + +fn assert_has_compare(instructions: &[JoinInst]) { + assert!( + instructions + .iter() + .any(|inst| matches!(inst, JoinInst::Compute(MirLikeInst::Compare { .. }))), + "Expected at least one Compare instruction, got {:?}", + instructions + ); +} + +fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) { + assert!( + instructions.iter().any(|inst| matches!( + inst, + JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op + )), + "Expected at least one BinOp {:?}, got {:?}", + op, + instructions + ); +} + +fn assert_has_not(instructions: &[JoinInst]) { + assert!( + instructions.iter().any(|inst| matches!( + inst, + JoinInst::Compute(MirLikeInst::UnaryOp { + op: JoinUnaryOp::Not, + .. + }) + )), + "Expected at least one UnaryOp::Not, got {:?}", + instructions + ); +} + +#[test] +fn test_expr_lowerer_var_less_literal_generates_compare() { + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + // i < 10 + let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "i < 10 should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "instructions should not be empty"); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_literal_less_var_generates_compare() { + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + // 0 < i + let ast = bin(BinaryOperator::Less, lit_i(0), var("i")); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "0 < i should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "instructions should not be empty"); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_greater_than_between_vars() { + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + // i > j + let ast = bin(BinaryOperator::Greater, var("i"), var("j")); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "i > j should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "instructions should not be empty"); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_and_combination() { + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + // i > 0 && j < 5 + let left = bin(BinaryOperator::Greater, var("i"), lit_i(0)); + let right = bin(BinaryOperator::Less, var("j"), lit_i(5)); + let ast = bin(BinaryOperator::And, left, right); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "instructions should not be empty"); + assert_has_compare(&instructions); + assert_has_binop(&instructions, BinOpKind::And); +} + +#[test] +fn test_expr_lowerer_not_of_comparison() { + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + // !(i < 10) + let inner = bin(BinaryOperator::Less, var("i"), lit_i(10)); + let ast = not(inner); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "!(i < 10) should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "instructions should not be empty"); + assert_has_compare(&instructions); + assert_has_not(&instructions); +} + +#[test] +fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("digit_pos".to_string(), ValueId(10)); + + 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 = create_test_builder(); + + // digit_pos < 0 + let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0)); + assert!( + ExprLowerer::::is_supported_condition(&ast), + "digit_pos < 0 should be supported in Pattern2 break condition" + ); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + assert!(result.is_ok(), "digit_pos < 0 should lower successfully"); + + let instructions = expr_lowerer.take_last_instructions(); + assert!( + !instructions.is_empty(), + "instructions for digit_pos < 0 should not be empty" + ); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_methodcall_unknown_method_is_rejected() { + let scope = make_scope_with_p_and_s(); + let mut builder = create_test_builder(); + + // Unknown method name should fail through MethodCallLowerer + let ast = crate::ast::ASTNode::MethodCall { + object: Box::new(var("s")), + method: "unknown_method".to_string(), + arguments: vec![], + span: span(), + }; + + assert!( + ExprLowerer::::is_supported_condition(&ast), + "MethodCall nodes should be routed to MethodCallLowerer for validation" + ); + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + + assert!( + matches!(result, Err(ExprLoweringError::LoweringError(msg)) if msg.contains("MethodCall")), + "Unknown method should fail-fast via MethodCallLowerer" + ); +} + +// Phase 240-EX: Header condition patterns + +#[test] +fn test_expr_lowerer_supports_simple_header_condition_i_less_literal() { + // header pattern: i < 10 + let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); + assert!( + ExprLowerer::::is_supported_condition(&ast), + "i < 10 should be supported for Pattern2 header condition" + ); + + // lower and verify success + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + + let result = lowerer.lower(&ast); + assert!(result.is_ok(), "i < 10 should lower successfully"); + + // Compare instruction should be present + let instructions = lowerer.take_last_instructions(); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_supports_header_condition_var_less_var() { + // header pattern: i < n (variable vs variable) + let ast = bin(BinaryOperator::Less, var("i"), var("j")); + assert!( + ExprLowerer::::is_supported_condition(&ast), + "i < n should be supported for Pattern2 header condition" + ); + + // lower and verify success + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + + let result = lowerer.lower(&ast); + assert!(result.is_ok(), "i < n should lower successfully"); + + // Compare instruction should be present + let instructions = lowerer.take_last_instructions(); + assert_has_compare(&instructions); +} + +#[test] +fn test_expr_lowerer_supports_header_condition_with_length_call() { + // header pattern: p < s.length() + let length_call = crate::ast::ASTNode::MethodCall { + object: Box::new(var("s")), + method: "length".to_string(), + arguments: vec![], + span: span(), + }; + let ast = bin(BinaryOperator::Less, var("p"), length_call); + + assert!( + ExprLowerer::::is_supported_condition(&ast), + "p < s.length() should be supported for Pattern2 header condition" + ); + + let scope = make_scope_with_p_and_s(); + let mut builder = create_test_builder(); + let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + + let result = lowerer.lower(&ast); + assert!(result.is_ok(), "p < s.length() should lower successfully"); + + let instructions = lowerer.take_last_instructions(); + assert_has_compare(&instructions); + assert!( + instructions.iter().any(|inst| matches!( + inst, + JoinInst::Compute(MirLikeInst::BoxCall { method, .. }) if method == "length" + )), + "Expected BoxCall for length receiver in lowered instructions" + ); +} + +#[test] +fn test_expr_lowerer_header_condition_generates_expected_instructions() { + // Test that header condition i < 10 generates proper Compare instruction + let scope = make_basic_scope(); + let mut builder = create_test_builder(); + + let ast = bin(BinaryOperator::Less, var("i"), lit_i(10)); + let mut lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + + let result = lowerer.lower(&ast); + assert!(result.is_ok()); + + let instructions = lowerer.take_last_instructions(); + assert!(!instructions.is_empty(), "Should generate instructions"); + + // Should have Compare instruction + assert_has_compare(&instructions); +} diff --git a/src/mir/join_ir/lowering/expr_lowerer/types.rs b/src/mir/join_ir/lowering/expr_lowerer/types.rs new file mode 100644 index 00000000..859b7a7e --- /dev/null +++ b/src/mir/join_ir/lowering/expr_lowerer/types.rs @@ -0,0 +1,43 @@ +/// 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), + } + } +}