feat(joinir): Phase 171-fix ConditionEnv/ConditionBinding architecture
Proper HOST↔JoinIR ValueId separation for condition variables: - Add ConditionEnv struct (name → JoinIR-local ValueId mapping) - Add ConditionBinding struct (HOST/JoinIR ValueId pairs) - Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map - Update Pattern2 lowerer to build ConditionEnv and ConditionBindings - Extend JoinInlineBoundary with condition_bindings field - Update BoundaryInjector to inject Copy instructions for condition variables This fixes the undefined ValueId errors where HOST ValueIds were being used directly in JoinIR instructions. Programs now execute (RC: 0), though loop variable exit values still need Phase 172 work. Key invariants established: 1. JoinIR uses ONLY JoinIR-local ValueIds 2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary 3. condition_to_joinir NEVER accesses builder.variable_map 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal file
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal file
@ -0,0 +1,596 @@
|
||||
//! Phase 169: JoinIR Condition Lowering Helper
|
||||
//!
|
||||
//! This module provides AST → JoinIR condition lowering for loop patterns.
|
||||
//! Unlike BoolExprLowerer (which generates MIR), this generates JoinIR instructions.
|
||||
//!
|
||||
//! ## Design Philosophy
|
||||
//!
|
||||
//! **Separation of Concerns**:
|
||||
//! - BoolExprLowerer: AST → MIR (for regular control flow)
|
||||
//! - condition_to_joinir: AST → JoinIR (for loop lowerers)
|
||||
//!
|
||||
//! This dual approach maintains clean boundaries:
|
||||
//! - Loop lowerers work in JoinIR space (pure functional transformation)
|
||||
//! - Regular control flow uses MIR space (stateful builder)
|
||||
//!
|
||||
//! ## Usage Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! let mut value_counter = 0u32;
|
||||
//! let mut alloc_value = || {
|
||||
//! let id = ValueId(value_counter);
|
||||
//! value_counter += 1;
|
||||
//! id
|
||||
//! };
|
||||
//!
|
||||
//! // Lower condition: i < end
|
||||
//! let (cond_value, cond_insts) = lower_condition_to_joinir(
|
||||
//! condition_ast,
|
||||
//! &mut alloc_value,
|
||||
//! builder,
|
||||
//! )?;
|
||||
//!
|
||||
//! // cond_value: ValueId holding boolean result
|
||||
//! // cond_insts: Vec<JoinInst::Compute> to evaluate condition
|
||||
//! ```
|
||||
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
|
||||
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Phase 171-fix: Environment for condition expression lowering
|
||||
/// Maps variable names to JoinIR-local ValueIds
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConditionEnv {
|
||||
pub name_to_join: HashMap<String, ValueId>,
|
||||
}
|
||||
|
||||
impl ConditionEnv {
|
||||
pub fn new() -> Self {
|
||||
Self { name_to_join: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, name: String, join_id: ValueId) {
|
||||
self.name_to_join.insert(name, join_id);
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<ValueId> {
|
||||
self.name_to_join.get(name).copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 171-fix: Binding between HOST and JoinIR ValueIds for condition variables
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConditionBinding {
|
||||
pub name: String,
|
||||
pub host_value: ValueId,
|
||||
pub join_value: ValueId,
|
||||
}
|
||||
|
||||
/// Lower an AST condition to JoinIR instructions
|
||||
///
|
||||
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cond_ast` - AST node representing the boolean condition
|
||||
/// * `alloc_value` - ValueId allocator function
|
||||
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds)
|
||||
///
|
||||
/// # 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
|
||||
pub fn lower_condition_to_joinir<F>(
|
||||
cond_ast: &ASTNode,
|
||||
alloc_value: &mut F,
|
||||
env: &ConditionEnv,
|
||||
) -> Result<(ValueId, Vec<JoinInst>), String>
|
||||
where
|
||||
F: FnMut() -> ValueId,
|
||||
{
|
||||
let mut instructions = Vec::new();
|
||||
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
|
||||
Ok((result_value, instructions))
|
||||
}
|
||||
|
||||
/// Recursive helper for condition lowering
|
||||
///
|
||||
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
|
||||
fn lower_condition_recursive<F>(
|
||||
cond_ast: &ASTNode,
|
||||
alloc_value: &mut F,
|
||||
env: &ConditionEnv,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String>
|
||||
where
|
||||
F: FnMut() -> ValueId,
|
||||
{
|
||||
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 left and right sides
|
||||
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, 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)
|
||||
}
|
||||
BinaryOperator::And => {
|
||||
// Logical AND: evaluate both sides and combine
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit BinOp And instruction
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst,
|
||||
op: BinOpKind::And,
|
||||
lhs,
|
||||
rhs,
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
BinaryOperator::Or => {
|
||||
// Logical OR: evaluate both sides and combine
|
||||
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
|
||||
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit BinOp Or instruction
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst,
|
||||
op: BinOpKind::Or,
|
||||
lhs,
|
||||
rhs,
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
_ => {
|
||||
// Other operators (arithmetic, etc.)
|
||||
Err(format!(
|
||||
"Unsupported binary operator in condition: {:?}",
|
||||
operator
|
||||
))
|
||||
}
|
||||
},
|
||||
|
||||
// Unary NOT operator
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => {
|
||||
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
|
||||
let dst = alloc_value();
|
||||
|
||||
// Emit UnaryOp Not instruction
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst,
|
||||
op: crate::mir::join_ir::UnaryOp::Not,
|
||||
operand: operand_val,
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
// Variables - resolve from ConditionEnv (Phase 171-fix)
|
||||
ASTNode::Variable { name, .. } => {
|
||||
// Look up variable in ConditionEnv (JoinIR-local ValueIds)
|
||||
env.get(name)
|
||||
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name))
|
||||
}
|
||||
|
||||
// Literals - emit as constants
|
||||
ASTNode::Literal { value, .. } => {
|
||||
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(_) => {
|
||||
// Float literals not supported in JoinIR ConstValue yet
|
||||
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)
|
||||
}
|
||||
|
||||
_ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower a value expression (for comparison operands, etc.)
|
||||
///
|
||||
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
|
||||
///
|
||||
/// This handles the common case where we need to evaluate a simple value
|
||||
/// (variable or literal) as part of a comparison.
|
||||
fn lower_value_expression<F>(
|
||||
expr: &ASTNode,
|
||||
alloc_value: &mut F,
|
||||
env: &ConditionEnv,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String>
|
||||
where
|
||||
F: FnMut() -> ValueId,
|
||||
{
|
||||
match expr {
|
||||
// Variables - look up in ConditionEnv (Phase 171-fix)
|
||||
ASTNode::Variable { name, .. } => env
|
||||
.get(name)
|
||||
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name)),
|
||||
|
||||
// Literals - emit as constants
|
||||
ASTNode::Literal { value, .. } => {
|
||||
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(_) => {
|
||||
// Float literals not supported in JoinIR ConstValue yet
|
||||
return Err("Float literals not supported in JoinIR value expressions yet".to_string());
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("Unsupported literal type: {:?}", value));
|
||||
}
|
||||
};
|
||||
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst,
|
||||
value: const_value,
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
// Binary operations (for arithmetic in conditions like i + 1 < n)
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
|
||||
let rhs = lower_value_expression(right, alloc_value, 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)
|
||||
}
|
||||
|
||||
_ => Err(format!("Unsupported expression in value context: {:?}", expr)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract all variable names used in a condition AST (Phase 171)
|
||||
///
|
||||
/// This helper recursively traverses the condition AST and collects all
|
||||
/// unique variable names. Used to determine which variables need to be
|
||||
/// available in JoinIR scope.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cond_ast` - AST node representing the condition
|
||||
/// * `exclude_vars` - Variable names to exclude (e.g., loop parameters already registered)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Sorted vector of unique variable names found in the condition
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // For condition: start < end && i < len
|
||||
/// let vars = extract_condition_variables(
|
||||
/// condition_ast,
|
||||
/// &["i".to_string()], // Exclude loop variable 'i'
|
||||
/// );
|
||||
/// // Result: ["end", "len", "start"] (sorted, 'i' excluded)
|
||||
/// ```
|
||||
pub fn extract_condition_variables(
|
||||
cond_ast: &ASTNode,
|
||||
exclude_vars: &[String],
|
||||
) -> Vec<String> {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
let mut all_vars = BTreeSet::new();
|
||||
collect_variables_recursive(cond_ast, &mut all_vars);
|
||||
|
||||
// Filter out excluded variables and return sorted list
|
||||
all_vars.into_iter()
|
||||
.filter(|name| !exclude_vars.contains(name))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Recursive helper to collect variable names
|
||||
fn collect_variables_recursive(ast: &ASTNode, vars: &mut std::collections::BTreeSet<String>) {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
vars.insert(name.clone());
|
||||
}
|
||||
ASTNode::BinaryOp { left, right, .. } => {
|
||||
collect_variables_recursive(left, vars);
|
||||
collect_variables_recursive(right, vars);
|
||||
}
|
||||
ASTNode::UnaryOp { operand, .. } => {
|
||||
collect_variables_recursive(operand, vars);
|
||||
}
|
||||
ASTNode::Literal { .. } => {
|
||||
// Literals have no variables
|
||||
}
|
||||
_ => {
|
||||
// Other AST nodes not expected in conditions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
/// Phase 171-fix: 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(&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(&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(&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_extract_condition_variables_simple() {
|
||||
// AST: start < end
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "end".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let vars = extract_condition_variables(&ast, &[]);
|
||||
assert_eq!(vars, vec!["end", "start"]); // Sorted order
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_condition_variables_with_exclude() {
|
||||
// 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 vars = extract_condition_variables(&ast, &["i".to_string()]);
|
||||
assert_eq!(vars, vec!["end"]); // 'i' excluded
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_condition_variables_complex() {
|
||||
// AST: start < end && i < len
|
||||
let ast = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::And,
|
||||
left: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "end".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: 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: "len".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let vars = extract_condition_variables(&ast, &["i".to_string()]);
|
||||
assert_eq!(vars, vec!["end", "len", "start"]); // Sorted, 'i' excluded
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user