Files
hakorune/src/mir/join_ir/lowering/condition_lowerer.rs
nyash-codex d2972c1437 feat(joinir): Phase 92完了 - ConditionalStep + body-local変数サポート
## Phase 92全体の成果

**Phase 92 P0-P2**: ConditionalStep JoinIR生成とbody-local変数サポート
- ConditionalStep(条件付きキャリア更新)のJoinIR生成実装
- Body-local変数(ch等)の条件式での参照サポート
- 変数解決優先度: ConditionEnv → LoopBodyLocalEnv

**Phase 92 P3**: BodyLocalPolicyBox + 安全ガード
- BodyLocalPolicyDecision実装(Accept/Reject判定)
- BodyLocalSlot + DualValueRewriter(JoinIR/MIR二重書き込み)
- Fail-Fast契約(Cannot promote LoopBodyLocal検出)

**Phase 92 P4**: E2E固定+回帰最小化 (本コミット)
- Unit test 3本追加(body-local変数解決検証)
- Integration smoke追加(phase92_pattern2_baseline.sh、2ケースPASS)
- P4-E2E-PLAN.md、P4-COMPLETION.md作成

## 主要な実装

### ConditionalStep(条件付きキャリア更新)
- `conditional_step_emitter.rs`: JoinIR Select命令生成
- `loop_with_break_minimal.rs`: ConditionalStep検出と統合
- `loop_with_continue_minimal.rs`: Pattern4対応

### Body-local変数サポート
- `condition_lowerer.rs`: body-local変数解決機能
  - `lower_condition_to_joinir`: body_local_env パラメータ追加
  - 変数解決優先度実装(ConditionEnv優先)
  - Unit test 3本追加: 変数解決/優先度/エラー
- `header_break_lowering.rs`: break条件でbody-local変数参照
- 7ファイルで後方互換ラッパー(lower_condition_to_joinir_no_body_locals)

### Body-local Policy & Safety
- `body_local_policy.rs`: BodyLocalPolicyDecision(Accept/Reject)
- `body_local_slot.rs`: JoinIR/MIR二重書き込み
- `dual_value_rewriter.rs`: ValueId書き換えヘルパー

## テスト体制

### Unit Tests (+3)
- `test_body_local_variable_resolution`: body-local変数解決
- `test_variable_resolution_priority`: 変数解決優先度(ConditionEnv優先)
- `test_undefined_variable_error`: 未定義変数エラー
- 全7テストPASS(cargo test --release condition_lowerer::tests)

### Integration Smoke (+1)
- `phase92_pattern2_baseline.sh`:
  - Case A: loop_min_while.hako (Pattern2 baseline)
  - Case B: phase92_conditional_step_minimal.hako (条件付きインクリメント)
  - 両ケースPASS、integration profileで発見可能

### 退行確認
-  既存Pattern2Breakテスト正常(退行なし)
-  Phase 135 smoke正常(MIR検証PASS)

## アーキテクチャ設計

### 変数解決メカニズム
```rust
// Priority 1: ConditionEnv (loop params, captured)
if let Some(value_id) = env.get(name) { return Ok(value_id); }
// Priority 2: LoopBodyLocalEnv (body-local like `ch`)
if let Some(body_env) = body_local_env {
    if let Some(value_id) = body_env.get(name) { return Ok(value_id); }
}
```

### Fail-Fast契約
- Delta equality check (conditional_step_emitter.rs)
- Variable resolution error messages (ConditionEnv)
- Body-local promotion rejection (BodyLocalPolicyDecision::Reject)

## ドキュメント

- `P4-E2E-PLAN.md`: 3レベルテスト戦略(Level 1-2完了、Level 3延期)
- `P4-COMPLETION.md`: Phase 92完了報告
- `README.md`: Phase 92全体のまとめ

## 将来の拡張(Phase 92スコープ外)

- Body-local promotionシステム拡張
- P5bパターン認識の汎化(flagベース条件サポート)
- 完全なP5b E2Eテスト(body-local promotion実装後)

🎯 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 21:37:07 +09:00

741 lines
25 KiB
Rust

//! Condition Expression Lowerer
//!
//! This module provides the core logic for lowering AST condition expressions
//! to JoinIR instructions. It handles comparisons, logical operators, and
//! arithmetic expressions.
//!
//! ## Design Philosophy
//!
//! **Single Responsibility**: This module ONLY performs AST → JoinIR lowering.
//! It does NOT:
//! - Manage variable environments (that's condition_env.rs)
//! - Extract variables from AST (that's condition_var_extractor.rs)
//! - Manage HOST ↔ JoinIR bindings (that's inline_boundary.rs)
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst, UnaryOp};
use crate::mir::ValueId;
use super::condition_env::ConditionEnv;
use super::loop_body_local_env::LoopBodyLocalEnv; // Phase 92 P2-2: Body-local support
use super::method_call_lowerer::MethodCallLowerer;
/// Lower an AST condition to JoinIR instructions
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the boolean condition
/// * `alloc_value` - ValueId allocator function
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds)
/// * `body_local_env` - Phase 92 P2-2: Optional body-local variable environment
///
/// # Returns
///
/// * `Ok((ValueId, Vec<JoinInst>))` - Condition result ValueId and evaluation instructions
/// * `Err(String)` - Lowering error message
///
/// # Supported Patterns
///
/// - Comparisons: `i < n`, `x == y`, `a != b`, `x <= y`, `x >= y`, `x > y`
/// - Logical: `a && b`, `a || b`, `!cond`
/// - Variables and literals
///
/// # Phase 92 P2-2: Body-Local Variable Support
///
/// When lowering conditions that reference body-local variables (e.g., `ch == '\\'`
/// in escape patterns), the `body_local_env` parameter provides name → ValueId
/// mappings for variables defined in the loop body.
///
/// Variable resolution priority:
/// 1. ConditionEnv (loop parameters, captured variables)
/// 2. LoopBodyLocalEnv (body-local variables like `ch`)
///
/// # Example
///
/// ```ignore
/// let mut env = ConditionEnv::new();
/// env.insert("i".to_string(), ValueId(0));
/// env.insert("end".to_string(), ValueId(1));
///
/// let mut body_env = LoopBodyLocalEnv::new();
/// body_env.insert("ch".to_string(), ValueId(5)); // Phase 92 P2-2
///
/// let mut value_counter = 2u32;
/// let mut alloc_value = || {
/// let id = ValueId(value_counter);
/// value_counter += 1;
/// id
/// };
///
/// // Lower condition: ch == '\\'
/// let (cond_value, cond_insts) = lower_condition_to_joinir(
/// condition_ast,
/// &mut alloc_value,
/// &env,
/// Some(&body_env), // Phase 92 P2-2: Body-local support
/// )?;
/// ```
pub fn lower_condition_to_joinir(
cond_ast: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
) -> Result<(ValueId, Vec<JoinInst>), String> {
let mut instructions = Vec::new();
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, body_local_env, &mut instructions)?;
Ok((result_value, instructions))
}
/// Convenience wrapper: lower a condition without body-local support.
pub fn lower_condition_to_joinir_no_body_locals(
cond_ast: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
) -> Result<(ValueId, Vec<JoinInst>), String> {
lower_condition_to_joinir(cond_ast, alloc_value, env, None)
}
/// Recursive helper for condition lowering
///
/// Handles all supported AST node types and emits appropriate JoinIR instructions.
///
/// # Phase 92 P2-2
///
/// Added `body_local_env` parameter to support body-local variable resolution.
fn lower_condition_recursive(
cond_ast: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match cond_ast {
// Comparison operations: <, ==, !=, <=, >=, >
ASTNode::BinaryOp {
operator,
left,
right,
..
} => match operator {
BinaryOperator::Less
| BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
lower_comparison(operator, left, right, alloc_value, env, body_local_env, instructions)
}
BinaryOperator::And => lower_logical_and(left, right, alloc_value, env, body_local_env, instructions),
BinaryOperator::Or => lower_logical_or(left, right, alloc_value, env, body_local_env, instructions),
_ => Err(format!(
"Unsupported binary operator in condition: {:?}",
operator
)),
},
// Unary NOT operator
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} => lower_not_operator(operand, alloc_value, env, body_local_env, instructions),
// Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv
ASTNode::Variable { name, .. } => {
// Priority 1: ConditionEnv (loop parameters, captured variables)
if let Some(value_id) = env.get(name) {
return Ok(value_id);
}
// Priority 2: LoopBodyLocalEnv (body-local variables like `ch`)
if let Some(body_env) = body_local_env {
if let Some(value_id) = body_env.get(name) {
return Ok(value_id);
}
}
Err(format!("Variable '{}' not found in ConditionEnv or LoopBodyLocalEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions),
_ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)),
}
}
/// Lower a comparison operation (e.g., `i < end`)
fn lower_comparison(
operator: &BinaryOperator,
left: &ASTNode,
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Lower left and right sides
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
let cmp_op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Greater => CompareOp::Gt,
_ => unreachable!(),
};
// Emit Compare instruction
instructions.push(JoinInst::Compute(MirLikeInst::Compare {
dst,
op: cmp_op,
lhs,
rhs,
}));
Ok(dst)
}
/// Lower logical AND operation (e.g., `a && b`)
fn lower_logical_and(
left: &ASTNode,
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Logical AND: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit BinOp And instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::And,
lhs,
rhs,
}));
Ok(dst)
}
/// Lower logical OR operation (e.g., `a || b`)
fn lower_logical_or(
left: &ASTNode,
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
// Logical OR: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit BinOp Or instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::Or,
lhs,
rhs,
}));
Ok(dst)
}
/// Lower NOT operator (e.g., `!cond`)
fn lower_not_operator(
operand: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
let operand_val = lower_condition_recursive(operand, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
// Emit UnaryOp Not instruction
instructions.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst,
op: UnaryOp::Not,
operand: operand_val,
}));
Ok(dst)
}
/// Lower a literal value (e.g., `10`, `true`, `"text"`)
fn lower_literal(
value: &LiteralValue,
alloc_value: &mut dyn FnMut() -> ValueId,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
let dst = alloc_value();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
LiteralValue::Float(_) => {
return Err("Float literals not supported in JoinIR conditions yet".to_string());
}
_ => {
return Err(format!(
"Unsupported literal type in condition: {:?}",
value
));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: const_value,
}));
Ok(dst)
}
/// Lower a value expression (for comparison operands, etc.)
///
/// This handles the common case where we need to evaluate a simple value
/// (variable or literal) as part of a comparison.
///
/// # Phase 92 P2-2
///
/// Added `body_local_env` parameter to support body-local variable resolution
/// (e.g., `ch` in `ch == '\\'`).
pub fn lower_value_expression(
expr: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match expr {
// Phase 92 P2-2: Variables - resolve from ConditionEnv or LoopBodyLocalEnv
ASTNode::Variable { name, .. } => {
// Priority 1: ConditionEnv (loop parameters, captured variables)
if let Some(value_id) = env.get(name) {
return Ok(value_id);
}
// Priority 2: LoopBodyLocalEnv (body-local variables like `ch`)
if let Some(body_env) = body_local_env {
if let Some(value_id) = body_env.get(name) {
return Ok(value_id);
}
}
Err(format!("Variable '{}' not found in ConditionEnv or LoopBodyLocalEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => lower_literal(value, alloc_value, instructions),
// Binary operations (for arithmetic in conditions like i + 1 < n)
ASTNode::BinaryOp {
operator,
left,
right,
..
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, body_local_env, instructions),
// Phase 224-C: MethodCall support with arguments (e.g., s.length(), s.indexOf(ch))
ASTNode::MethodCall {
object,
method,
arguments,
..
} => {
// 1. Lower receiver (object) to ValueId
let recv_val = lower_value_expression(object, alloc_value, env, body_local_env, instructions)?;
// 2. Lower method call using MethodCallLowerer (will lower arguments internally)
MethodCallLowerer::lower_for_condition(
recv_val,
method,
arguments,
alloc_value,
env,
instructions,
)
}
_ => Err(format!(
"Unsupported expression in value context: {:?}",
expr
)),
}
}
/// Lower an arithmetic binary operation (e.g., `i + 1`)
fn lower_arithmetic_binop(
operator: &BinaryOperator,
left: &ASTNode,
right: &ASTNode,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
body_local_env: Option<&LoopBodyLocalEnv>, // Phase 92 P2-2
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
let lhs = lower_value_expression(left, alloc_value, env, body_local_env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, body_local_env, instructions)?;
let dst = alloc_value();
let bin_op = match operator {
BinaryOperator::Add => BinOpKind::Add,
BinaryOperator::Subtract => BinOpKind::Sub,
BinaryOperator::Multiply => BinOpKind::Mul,
BinaryOperator::Divide => BinOpKind::Div,
BinaryOperator::Modulo => BinOpKind::Mod,
_ => {
return Err(format!(
"Unsupported binary operator in expression: {:?}",
operator
));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: bin_op,
lhs,
rhs,
}));
Ok(dst)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
/// Helper to create a test ConditionEnv with variables
fn create_test_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
// Register test variables (using JoinIR-local ValueIds)
env.insert("i".to_string(), ValueId(0));
env.insert("end".to_string(), ValueId(1));
env
}
#[test]
fn test_simple_comparison() {
let env = create_test_env();
let mut value_counter = 2u32; // Start after i=0, end=1
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: i < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Simple comparison should succeed");
let (_cond_value, instructions) = result.unwrap();
assert_eq!(
instructions.len(),
1,
"Should generate 1 Compare instruction"
);
}
#[test]
fn test_comparison_with_literal() {
let env = create_test_env();
let mut value_counter = 2u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// 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 result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Comparison with literal should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(10), Compare
assert_eq!(instructions.len(), 2, "Should generate Const + Compare");
}
#[test]
fn test_logical_or() {
let mut env = ConditionEnv::new();
env.insert("a".to_string(), ValueId(2));
env.insert("b".to_string(), ValueId(3));
let mut value_counter = 4u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: a < 5 || b < 5
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "a".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "b".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "OR expression should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(5), Compare(a<5), Const(5), Compare(b<5), BinOp(Or)
assert_eq!(instructions.len(), 5, "Should generate proper OR chain");
}
#[test]
fn test_not_operator() {
let env = create_test_env();
let mut value_counter = 2u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: !(i < end)
let ast = ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "NOT operator should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Compare, UnaryOp(Not)
assert_eq!(instructions.len(), 2, "Should generate Compare + Not");
}
/// Phase 92 P4 Level 2: Test body-local variable resolution
///
/// This test verifies that conditions can reference body-local variables
/// (e.g., `ch == '\\'` in escape sequence patterns).
///
/// Variable resolution priority:
/// 1. ConditionEnv (loop parameters, captured variables)
/// 2. LoopBodyLocalEnv (body-local variables like `ch`)
#[test]
fn test_body_local_variable_resolution() {
// Setup ConditionEnv with loop variable
let mut env = ConditionEnv::new();
env.insert("i".to_string(), ValueId(100));
// Setup LoopBodyLocalEnv with body-local variable
let mut body_local_env = LoopBodyLocalEnv::new();
body_local_env.insert("ch".to_string(), ValueId(200));
let mut value_counter = 300u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: ch == "\\"
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "ch".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String("\\".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
};
// Phase 92 P2-2: Use lower_condition_to_joinir with body_local_env
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
assert!(
result.is_ok(),
"Body-local variable resolution should succeed"
);
let (cond_value, instructions) = result.unwrap();
// Should have: Const("\\"), Compare(ch == "\\")
assert_eq!(
instructions.len(),
2,
"Should generate Const + Compare for body-local variable"
);
// Verify the comparison uses the body-local variable's ValueId(200)
if let Some(JoinInst::Compute(MirLikeInst::Compare { lhs, .. })) = instructions.get(1) {
assert_eq!(
*lhs,
ValueId(200),
"Compare should use body-local variable ValueId(200)"
);
} else {
panic!("Expected Compare instruction at position 1");
}
assert!(cond_value.0 >= 300, "Result should use newly allocated ValueId");
}
/// Phase 92 P4 Level 2: Test variable resolution priority (ConditionEnv takes precedence)
///
/// When a variable exists in both ConditionEnv and LoopBodyLocalEnv,
/// ConditionEnv should take priority.
#[test]
fn test_variable_resolution_priority() {
// Setup both environments with overlapping variable "x"
let mut env = ConditionEnv::new();
env.insert("x".to_string(), ValueId(100)); // ConditionEnv priority
let mut body_local_env = LoopBodyLocalEnv::new();
body_local_env.insert("x".to_string(), ValueId(200)); // Should be shadowed
let mut value_counter = 300u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: x == 42
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(42),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
assert!(result.is_ok(), "Variable resolution should succeed");
let (_cond_value, instructions) = result.unwrap();
// Verify the comparison uses ConditionEnv's ValueId(100), not LoopBodyLocalEnv's ValueId(200)
if let Some(JoinInst::Compute(MirLikeInst::Compare { lhs, .. })) = instructions.get(1) {
assert_eq!(
*lhs,
ValueId(100),
"ConditionEnv should take priority over LoopBodyLocalEnv"
);
} else {
panic!("Expected Compare instruction at position 1");
}
}
/// Phase 92 P4 Level 2: Test error handling for undefined variables
///
/// Variables not found in either environment should produce clear error messages.
#[test]
fn test_undefined_variable_error() {
let env = ConditionEnv::new();
let body_local_env = LoopBodyLocalEnv::new();
let mut value_counter = 300u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: undefined_var == 42
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "undefined_var".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(42),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env));
assert!(result.is_err(), "Undefined variable should fail");
let err = result.unwrap_err();
assert!(
err.contains("undefined_var"),
"Error message should mention the undefined variable name"
);
assert!(
err.contains("not found"),
"Error message should indicate variable was not found"
);
}
}