//! 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::join_ir::{JoinInst, MirLikeInst, BinOpKind, UnaryOp as JoinUnaryOp}; 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, /// 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 !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))?; // Phase 235: 保存しておき、テストから観察できるようにする self.last_instructions = instructions; 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; 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() } 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(), } } 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![], }; 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)); } // 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![], }; 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![], }; 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_pattern2_break_methodcall_is_unsupported() { let scope = make_basic_scope(); let mut builder = create_test_builder(); // s.length() (MethodCall) is not supported for Pattern2 break condition let ast = ASTNode::MethodCall { object: Box::new(var("s")), method: "length".to_string(), arguments: vec![], span: span(), }; assert!( !ExprLowerer::::is_supported_condition(&ast), "MethodCall should be rejected for Pattern2 break condition" ); let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); let result = expr_lowerer.lower(&ast); assert!( matches!(result, Err(ExprLoweringError::UnsupportedNode(_))), "MethodCall should fail-fast with UnsupportedNode" ); } }