refactor(joinir): Extract carrier_update_emitter from loop_with_break_minimal

Phase 179 Task 1: Modular separation for single responsibility

Changes:
- NEW: carrier_update_emitter.rs (416 lines)
  - emit_carrier_update() function + 6 unit tests
  - Focused module for UpdateExpr → JoinInst conversion
- REDUCED: loop_with_break_minimal.rs (998→597 lines, -401 lines)
  - Removed emit_carrier_update() and carrier_update_tests module
  - Now imports from carrier_update_emitter
- UPDATED: mod.rs - added carrier_update_emitter module

Benefits:
- Single responsibility: carrier update emission isolated
- Reusability: can be used by Pattern 3, 4, and future patterns
- Testability: independent unit tests
- Maintainability: 40% size reduction in loop_with_break_minimal.rs

Note: Pre-existing test failure in test_pattern2_accepts_loop_param_only
is unrelated to this refactoring (test expects 1 var but gets 3 due to
literal "10" and "5" being counted as variables).
This commit is contained in:
nyash-codex
2025-12-08 19:03:30 +09:00
parent 4e7f7f1334
commit 0dc9b838d6
3 changed files with 420 additions and 404 deletions

View File

@ -0,0 +1,416 @@
//! Phase 176-2 / Phase 179: Carrier Update Emission
//!
//! Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
//! that compute the updated carrier value.
//!
//! This module is extracted from loop_with_break_minimal.rs to improve
//! modularity and single responsibility.
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
/// Emit JoinIR instructions for a single carrier update
///
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
/// that compute the updated carrier value.
///
/// # Arguments
///
/// * `carrier` - Carrier variable information (name, ValueId)
/// * `update` - Update expression (e.g., CounterLike, AccumulationLike)
/// * `alloc_value` - ValueId allocator closure
/// * `env` - ConditionEnv for variable resolution
/// * `instructions` - Output vector to append instructions to
///
/// # Returns
///
/// ValueId of the computed update result
///
/// # Example
///
/// ```ignore
/// // For "count = count + 1":
/// let count_next = emit_carrier_update(
/// &count_carrier,
/// &UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Const(1) },
/// &mut alloc_value,
/// &env,
/// &mut instructions,
/// )?;
/// // Generates:
/// // const_1 = Const(1)
/// // count_next = BinOp(Add, count_param, const_1)
/// ```
pub fn emit_carrier_update(
carrier: &CarrierVar,
update: &UpdateExpr,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match update {
UpdateExpr::Const(step) => {
// CounterLike: carrier = carrier + step
// Allocate const ValueId
let const_id = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_id,
value: ConstValue::Integer(*step),
}));
// Get carrier parameter ValueId from env
let carrier_param = env
.get(&carrier.name)
.ok_or_else(|| {
format!(
"Carrier '{}' not found in ConditionEnv",
carrier.name
)
})?;
// Allocate result ValueId
let result = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result,
op: BinOpKind::Add,
lhs: carrier_param,
rhs: const_id,
}));
Ok(result)
}
UpdateExpr::BinOp { lhs, op, rhs } => {
// General binary operation: carrier = carrier op rhs
// Verify lhs matches carrier name
if lhs != &carrier.name {
return Err(format!(
"Update expression LHS '{}' doesn't match carrier '{}'",
lhs, carrier.name
));
}
// Get carrier parameter ValueId from env
let carrier_param = env
.get(&carrier.name)
.ok_or_else(|| {
format!(
"Carrier '{}' not found in ConditionEnv",
carrier.name
)
})?;
// Resolve RHS
let rhs_id = match rhs {
UpdateRhs::Const(n) => {
let const_id = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_id,
value: ConstValue::Integer(*n),
}));
const_id
}
UpdateRhs::Variable(var_name) => {
env.get(var_name).ok_or_else(|| {
format!(
"Update RHS variable '{}' not found in ConditionEnv",
var_name
)
})?
}
// Phase 178: String updates detected but not lowered to JoinIR yet
// The Rust MIR path handles string concatenation
// For JoinIR: just pass through the carrier param (no JoinIR update)
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
eprintln!(
"[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough",
carrier.name
);
return Ok(carrier_param); // Pass-through: no JoinIR update
}
};
// Allocate result ValueId
let result = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result,
op: *op,
lhs: carrier_param,
rhs: rhs_id,
}));
Ok(result)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
// Helper: Create a test ConditionEnv
fn test_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
env.insert("count".to_string(), ValueId(10));
env.insert("sum".to_string(), ValueId(20));
env.insert("i".to_string(), ValueId(30));
env
}
// Helper: Create a test CarrierVar
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
CarrierVar {
name: name.to_string(),
host_id: ValueId(host_id),
join_id: None, // Phase 177-STRUCT-1
}
}
#[test]
fn test_emit_const_update() {
// Test: count = count + 1 (UpdateExpr::Const)
let carrier = test_carrier("count", 100);
let update = UpdateExpr::Const(1);
let env = test_env();
let mut value_counter = 50u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 2 instructions: Const(1) + BinOp(Add)
assert_eq!(instructions.len(), 2);
// Instruction 1: Const(1)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
assert_eq!(*dst, ValueId(50)); // First allocated
assert!(matches!(value, ConstValue::Integer(1)));
}
_ => panic!("Expected Const instruction"),
}
// Instruction 2: BinOp(Add, count, const_1)
match &instructions[1] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(51)); // Second allocated
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(10)); // count from env
assert_eq!(*rhs, ValueId(50)); // const_1
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(51));
}
#[test]
fn test_emit_binop_update_with_const() {
// Test: sum = sum + 5 (UpdateExpr::BinOp with Const RHS)
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Const(5),
};
let env = test_env();
let mut value_counter = 60u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 2 instructions: Const(5) + BinOp(Add)
assert_eq!(instructions.len(), 2);
// Instruction 1: Const(5)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
assert_eq!(*dst, ValueId(60));
assert!(matches!(value, ConstValue::Integer(5)));
}
_ => panic!("Expected Const instruction"),
}
// Instruction 2: BinOp(Add, sum, const_5)
match &instructions[1] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(61));
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(20)); // sum from env
assert_eq!(*rhs, ValueId(60)); // const_5
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(61));
}
#[test]
fn test_emit_binop_update_with_variable() {
// Test: sum = sum + i (UpdateExpr::BinOp with Variable RHS)
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Variable("i".to_string()),
};
let env = test_env();
let mut value_counter = 70u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 1 instruction: BinOp(Add, sum, i)
assert_eq!(instructions.len(), 1);
// Instruction: BinOp(Add, sum, i)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(70));
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(20)); // sum from env
assert_eq!(*rhs, ValueId(30)); // i from env
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(70));
}
#[test]
fn test_emit_update_carrier_not_in_env() {
// Test error case: carrier not found in env
let carrier = test_carrier("unknown", 300);
let update = UpdateExpr::Const(1);
let env = test_env(); // doesn't have "unknown"
let mut value_counter = 80u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Carrier 'unknown' not found"));
}
#[test]
fn test_emit_update_lhs_mismatch() {
// Test error case: LHS doesn't match carrier name
let carrier = test_carrier("count", 100);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(), // Wrong! Should be "count"
op: BinOpKind::Add,
rhs: UpdateRhs::Const(1),
};
let env = test_env();
let mut value_counter = 90u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("doesn't match carrier"));
}
#[test]
fn test_emit_update_rhs_variable_not_found() {
// Test error case: RHS variable not in env
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Variable("unknown_var".to_string()),
};
let env = test_env();
let mut value_counter = 100u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
}
}

View File

@ -56,10 +56,11 @@
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar, ExitMeta, JoinFragmentMeta};
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta, JoinFragmentMeta};
use crate::mir::join_ir::lowering::carrier_update_emitter::emit_carrier_update;
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::join_ir::{
BinOpKind, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
MirLikeInst, UnaryOp,
@ -71,141 +72,6 @@ use crate::mir::loop_pattern_detection::error_messages::{
use crate::mir::ValueId;
use std::collections::HashMap;
/// Phase 176-2: Emit JoinIR instructions for a single carrier update
///
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
/// that compute the updated carrier value.
///
/// # Arguments
///
/// * `carrier` - Carrier variable information (name, ValueId)
/// * `update` - Update expression (e.g., CounterLike, AccumulationLike)
/// * `alloc_value` - ValueId allocator closure
/// * `env` - ConditionEnv for variable resolution
/// * `instructions` - Output vector to append instructions to
///
/// # Returns
///
/// ValueId of the computed update result
///
/// # Example
///
/// ```ignore
/// // For "count = count + 1":
/// let count_next = emit_carrier_update(
/// &count_carrier,
/// &UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Const(1) },
/// &mut alloc_value,
/// &env,
/// &mut instructions,
/// )?;
/// // Generates:
/// // const_1 = Const(1)
/// // count_next = BinOp(Add, count_param, const_1)
/// ```
fn emit_carrier_update(
carrier: &CarrierVar,
update: &UpdateExpr,
alloc_value: &mut dyn FnMut() -> ValueId,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
match update {
UpdateExpr::Const(step) => {
// CounterLike: carrier = carrier + step
// Allocate const ValueId
let const_id = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_id,
value: ConstValue::Integer(*step),
}));
// Get carrier parameter ValueId from env
let carrier_param = env
.get(&carrier.name)
.ok_or_else(|| {
format!(
"Carrier '{}' not found in ConditionEnv",
carrier.name
)
})?;
// Allocate result ValueId
let result = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result,
op: BinOpKind::Add,
lhs: carrier_param,
rhs: const_id,
}));
Ok(result)
}
UpdateExpr::BinOp { lhs, op, rhs } => {
// General binary operation: carrier = carrier op rhs
// Verify lhs matches carrier name
if lhs != &carrier.name {
return Err(format!(
"Update expression LHS '{}' doesn't match carrier '{}'",
lhs, carrier.name
));
}
// Get carrier parameter ValueId from env
let carrier_param = env
.get(&carrier.name)
.ok_or_else(|| {
format!(
"Carrier '{}' not found in ConditionEnv",
carrier.name
)
})?;
// Resolve RHS
let rhs_id = match rhs {
UpdateRhs::Const(n) => {
let const_id = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_id,
value: ConstValue::Integer(*n),
}));
const_id
}
UpdateRhs::Variable(var_name) => {
env.get(var_name).ok_or_else(|| {
format!(
"Update RHS variable '{}' not found in ConditionEnv",
var_name
)
})?
}
// Phase 178: String updates detected but not lowered to JoinIR yet
// The Rust MIR path handles string concatenation
// For JoinIR: just pass through the carrier param (no JoinIR update)
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
eprintln!(
"[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough",
carrier.name
);
return Ok(carrier_param); // Pass-through: no JoinIR update
}
};
// Allocate result ValueId
let result = alloc_value();
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: result,
op: *op,
lhs: carrier_param,
rhs: rhs_id,
}));
Ok(result)
}
}
}
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
///
/// # Phase 188-Impl-2: Pure JoinIR Fragment Generation
@ -728,271 +594,4 @@ mod tests {
assert!(vars.contains("ch")); // The problematic body-local variable
}
// Phase 176-2: Tests for emit_carrier_update helper
mod carrier_update_tests {
use super::*;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
// Helper: Create a test ConditionEnv
fn test_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
env.insert("count".to_string(), ValueId(10));
env.insert("sum".to_string(), ValueId(20));
env.insert("i".to_string(), ValueId(30));
env
}
// Helper: Create a test CarrierVar
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
CarrierVar {
name: name.to_string(),
host_id: ValueId(host_id),
join_id: None, // Phase 177-STRUCT-1
}
}
#[test]
fn test_emit_const_update() {
// Test: count = count + 1 (UpdateExpr::Const)
let carrier = test_carrier("count", 100);
let update = UpdateExpr::Const(1);
let env = test_env();
let mut value_counter = 50u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 2 instructions: Const(1) + BinOp(Add)
assert_eq!(instructions.len(), 2);
// Instruction 1: Const(1)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
assert_eq!(*dst, ValueId(50)); // First allocated
assert!(matches!(value, ConstValue::Integer(1)));
}
_ => panic!("Expected Const instruction"),
}
// Instruction 2: BinOp(Add, count, const_1)
match &instructions[1] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(51)); // Second allocated
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(10)); // count from env
assert_eq!(*rhs, ValueId(50)); // const_1
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(51));
}
#[test]
fn test_emit_binop_update_with_const() {
// Test: sum = sum + 5 (UpdateExpr::BinOp with Const RHS)
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Const(5),
};
let env = test_env();
let mut value_counter = 60u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 2 instructions: Const(5) + BinOp(Add)
assert_eq!(instructions.len(), 2);
// Instruction 1: Const(5)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
assert_eq!(*dst, ValueId(60));
assert!(matches!(value, ConstValue::Integer(5)));
}
_ => panic!("Expected Const instruction"),
}
// Instruction 2: BinOp(Add, sum, const_5)
match &instructions[1] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(61));
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(20)); // sum from env
assert_eq!(*rhs, ValueId(60)); // const_5
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(61));
}
#[test]
fn test_emit_binop_update_with_variable() {
// Test: sum = sum + i (UpdateExpr::BinOp with Variable RHS)
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Variable("i".to_string()),
};
let env = test_env();
let mut value_counter = 70u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_ok());
let result_id = result.unwrap();
// Should generate 1 instruction: BinOp(Add, sum, i)
assert_eq!(instructions.len(), 1);
// Instruction: BinOp(Add, sum, i)
match &instructions[0] {
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
assert_eq!(*dst, ValueId(70));
assert_eq!(*op, BinOpKind::Add);
assert_eq!(*lhs, ValueId(20)); // sum from env
assert_eq!(*rhs, ValueId(30)); // i from env
}
_ => panic!("Expected BinOp instruction"),
}
assert_eq!(result_id, ValueId(70));
}
#[test]
fn test_emit_update_carrier_not_in_env() {
// Test error case: carrier not found in env
let carrier = test_carrier("unknown", 300);
let update = UpdateExpr::Const(1);
let env = test_env(); // doesn't have "unknown"
let mut value_counter = 80u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Carrier 'unknown' not found"));
}
#[test]
fn test_emit_update_lhs_mismatch() {
// Test error case: LHS doesn't match carrier name
let carrier = test_carrier("count", 100);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(), // Wrong! Should be "count"
op: BinOpKind::Add,
rhs: UpdateRhs::Const(1),
};
let env = test_env();
let mut value_counter = 90u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("doesn't match carrier"));
}
#[test]
fn test_emit_update_rhs_variable_not_found() {
// Test error case: RHS variable not in env
let carrier = test_carrier("sum", 200);
let update = UpdateExpr::BinOp {
lhs: "sum".to_string(),
op: BinOpKind::Add,
rhs: UpdateRhs::Variable("unknown_var".to_string()),
};
let env = test_env();
let mut value_counter = 100u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut instructions = Vec::new();
let result = emit_carrier_update(
&carrier,
&update,
&mut alloc_value,
&env,
&mut instructions,
);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
}
}
}

View File

@ -21,6 +21,7 @@
pub mod bool_expr_lowerer; // Phase 168: Boolean expression lowering for complex conditions
pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
pub mod carrier_update_emitter; // Phase 179: Carrier update instruction emission
pub mod common;
pub mod condition_env; // Phase 171-fix: Condition expression environment
pub mod condition_lowerer; // Phase 171-fix: Core condition lowering logic