feat(joinir): Phase 170-D-impl-2 Minimal analysis logic with condition_var_analyzer
Implement variable extraction and scope classification with Box separation: New module: - src/mir/loop_pattern_detection/condition_var_analyzer.rs (270 lines) Public API (pure functions): - extract_all_variables(): Recursive AST traversal for variable collection * Handles: Variable, UnaryOp, BinaryOp, MethodCall, FieldAccess, Index, If * Deduplicates via HashSet automatically * Returns all variable names found in expression - is_outer_scope_variable(): Scope classification heuristic * Phase 170-D simplified: checks pinned set and header-only definitions * Conservative: defaults to LoopBodyLocal if uncertain * Returns true only for definitely outer-scope variables Integration (LoopConditionScopeBox.analyze()): - Delegated to condition_var_analyzer functions - Maintains original 3-level classification (LoopParam / OuterLocal / LoopBodyLocal) - Cleaner separation: analyzer = pure logic, Box = orchestration Test coverage: - 12 unit tests in condition_var_analyzer * Variable extraction: single, multiple, nested, deduped * Unary/Binary operations * Literal handling * Scope classification with mocked LoopScopeShape * Pinned variable detection * Header-only and multi-block definitions Architecture improvements: - Pure functions enable independent testing and reuse - Fail-Fast principle: conservative scope classification - Phase 170-D design: simple heuristics sufficient for initial detection Build: ✅ Passed with no errors 🤖 Generated with Claude Code Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -91,6 +91,14 @@ impl LoopConditionScopeBox {
|
||||
/// # Returns
|
||||
///
|
||||
/// LoopConditionScope with classified variables
|
||||
///
|
||||
/// # Algorithm
|
||||
///
|
||||
/// 1. Extract all variables from condition AST nodes using condition_var_analyzer
|
||||
/// 2. Classify each variable:
|
||||
/// - If matches loop_param_name → LoopParam
|
||||
/// - Else if in outer scope (via condition_var_analyzer) → OuterLocal
|
||||
/// - Else → LoopBodyLocal (conservative default)
|
||||
pub fn analyze(
|
||||
loop_param_name: &str,
|
||||
condition_nodes: &[&ASTNode],
|
||||
@ -99,16 +107,17 @@ impl LoopConditionScopeBox {
|
||||
let mut result = LoopConditionScope::new();
|
||||
let mut found_vars = HashSet::new();
|
||||
|
||||
// Extract variable names from all condition nodes
|
||||
// Phase 170-D-impl-2: Use condition_var_analyzer for extraction
|
||||
for node in condition_nodes {
|
||||
Self::extract_vars(node, &mut found_vars);
|
||||
let vars = super::condition_var_analyzer::extract_all_variables(node);
|
||||
found_vars.extend(vars);
|
||||
}
|
||||
|
||||
// Classify each variable
|
||||
for var_name in found_vars {
|
||||
let var_scope = if var_name == loop_param_name {
|
||||
CondVarScope::LoopParam
|
||||
} else if Self::is_outer_local(&var_name, scope) {
|
||||
} else if super::condition_var_analyzer::is_outer_scope_variable(&var_name, scope) {
|
||||
CondVarScope::OuterLocal
|
||||
} else {
|
||||
// Default: assume it's loop-body-local if not identified as outer
|
||||
@ -120,68 +129,6 @@ impl LoopConditionScopeBox {
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Recursively extract variable names from an AST node
|
||||
fn extract_vars(node: &ASTNode, vars: &mut HashSet<String>) {
|
||||
match node {
|
||||
ASTNode::Variable { name, .. } => {
|
||||
vars.insert(name.clone());
|
||||
}
|
||||
ASTNode::UnaryOp { operand, .. } => {
|
||||
Self::extract_vars(operand, vars);
|
||||
}
|
||||
ASTNode::BinaryOp {
|
||||
left, right, ..
|
||||
} => {
|
||||
Self::extract_vars(left, vars);
|
||||
Self::extract_vars(right, vars);
|
||||
}
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
Self::extract_vars(object, vars);
|
||||
for arg in arguments {
|
||||
Self::extract_vars(arg, vars);
|
||||
}
|
||||
}
|
||||
ASTNode::FieldAccess { object, .. } => {
|
||||
Self::extract_vars(object, vars);
|
||||
}
|
||||
ASTNode::Index { target, index, .. } => {
|
||||
Self::extract_vars(target, vars);
|
||||
Self::extract_vars(index, vars);
|
||||
}
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
Self::extract_vars(condition, vars);
|
||||
for stmt in then_body {
|
||||
Self::extract_vars(stmt, vars);
|
||||
}
|
||||
if let Some(else_body) = else_body {
|
||||
for stmt in else_body {
|
||||
Self::extract_vars(stmt, vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {} // Skip other node types for now
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a variable is from outer scope
|
||||
///
|
||||
/// Phase 170-D: Simplified heuristic - just check if LoopScopeShape exists
|
||||
/// and has external variable definitions. Full implementation would track
|
||||
/// variable_definitions in the LoopScopeShape.
|
||||
fn is_outer_local(_var_name: &str, _scope: Option<&LoopScopeShape>) -> bool {
|
||||
// Phase 170-D: Simplified implementation
|
||||
// Real implementation would check variable_definitions from LoopScopeShape
|
||||
// For now, we rely on the default fallback to LoopBodyLocal
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user