Files
hakorune/src/mir/join_ir/lowering/expr_lowerer.rs

849 lines
30 KiB
Rust
Raw Normal View History

//! 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<JoinInst>,
}
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<JoinInst> {
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<ValueId, ExprLoweringError> {
match self.context {
ExprContext::Condition => self.lower_condition(ast),
ExprContext::General => {
Err(ExprLoweringError::UnsupportedNode(
"General expression context not yet implemented (Phase 231)".to_string()
))
}
}
}
/// Lower a condition expression to JoinIR ValueId
///
/// Phase 231: Thin wrapper around condition_lowerer. The main innovation
/// is using ScopeManager for variable resolution instead of direct ConditionEnv.
///
/// # Returns
///
/// * `Ok(ValueId)` - Condition result ValueId (boolean)
/// * `Err(ExprLoweringError)` - Lowering failed
fn lower_condition(&mut self, ast: &ASTNode) -> Result<ValueId, ExprLoweringError> {
// 1. Check if AST is supported in condition context
if !Self::is_supported_condition(ast) {
return Err(ExprLoweringError::UnsupportedNode(
format!("Unsupported condition node: {:?}", ast)
));
}
// 2. Build ConditionEnv from ScopeManager
// This is the key integration point: we translate ScopeManager's view
// into the ConditionEnv format expected by condition_lowerer.
let condition_env = self.build_condition_env_from_scope(ast)?;
// 3. Delegate to existing condition_lowerer
// Phase 231: We use the existing, well-tested lowering logic.
let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let (result_value, instructions) = lower_condition_to_joinir(
ast,
&mut alloc_value,
&condition_env,
).map_err(|e| ExprLoweringError::LoweringError(e))?;
// 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<ConditionEnv, ExprLoweringError> {
let mut env = ConditionEnv::new();
// Extract all variable names from the AST
let var_names = Self::extract_variable_names(ast);
// Resolve each variable through ScopeManager
for name in var_names {
if let Some(value_id) = self.scope.lookup(&name) {
env.insert(name.clone(), value_id);
} else {
return Err(ExprLoweringError::VariableNotFound(name));
}
}
Ok(env)
}
/// Extract all variable names from an AST node (recursively)
fn extract_variable_names(ast: &ASTNode) -> Vec<String> {
let mut names = Vec::new();
Self::extract_variable_names_recursive(ast, &mut names);
names.sort();
names.dedup();
names
}
/// Recursive helper for variable name extraction
fn extract_variable_names_recursive(ast: &ASTNode, names: &mut Vec<String>) {
match ast {
ASTNode::Variable { name, .. } => {
names.push(name.clone());
}
ASTNode::BinaryOp { left, right, .. } => {
Self::extract_variable_names_recursive(left, names);
Self::extract_variable_names_recursive(right, names);
}
ASTNode::UnaryOp { operand, .. } => {
Self::extract_variable_names_recursive(operand, names);
}
// Phase 231: Only support simple expressions
_ => {}
}
}
/// Check if an AST node is supported in condition context
///
/// Phase 231: Conservative whitelist. We only support patterns we know work.
/// Phase 240-EX: Made public to allow callers to check before attempting lowering.
pub 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,
}
}
}
// ============================================================================
// Phase 244: ConditionLoweringBox trait implementation
// ============================================================================
use super::condition_lowering_box::{ConditionLoweringBox, ConditionContext};
impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox<S> 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: &ConditionContext<S>,
) -> Result<ValueId, String> {
// Delegate to existing lower() method
// ConditionContext is unused because ExprLowerer already has scope access
self.lower(condition).map_err(|e| e.to_string())
}
/// 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 {
Self::is_supported_condition(condition)
}
}
#[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::<Pattern2ScopeManager>::is_supported_condition(&ast));
// Unsupported: MethodCall
let ast = ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "length".to_string(),
arguments: vec![],
span: Span::unknown(),
};
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
}
// 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<ConditionEnv> = 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<CarrierInfo> = 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::<Pattern2ScopeManager>::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::<Pattern2ScopeManager>::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"
);
}
// 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::<Pattern2ScopeManager>::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::<Pattern2ScopeManager>::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_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);
}
}