//! Phase 170-D: Loop Condition Scope Analysis Box //! //! Analyzes which variables appear in loop conditions (header, break, continue) //! and classifies them by their scope: //! - LoopParam: The loop parameter itself (e.g., 'i' in loop(i < 10)) //! - OuterLocal: Variables from outer scope (pre-existing before loop) //! - LoopBodyLocal: Variables defined inside the loop body //! //! This Box enables Pattern2/4 to determine if they can handle a given loop's //! condition expressions, providing clear Fail-Fast when conditions reference //! unsupported loop-body variables. use crate::ast::ASTNode; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use std::collections::HashSet; /// Scope classification for a condition variable #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CondVarScope { /// The loop parameter itself (e.g., 'i' in loop(i < 10)) LoopParam, /// A variable from outer scope, defined before the loop OuterLocal, /// A variable defined inside the loop body LoopBodyLocal, } /// Information about a single condition variable #[derive(Debug, Clone)] pub struct CondVarInfo { pub name: String, pub scope: CondVarScope, } /// Analysis result for all variables in loop conditions #[derive(Debug, Clone)] pub struct LoopConditionScope { pub vars: Vec, } impl LoopConditionScope { /// Create a new empty condition scope pub fn new() -> Self { LoopConditionScope { vars: Vec::new() } } /// Check if this scope contains any loop-body-local variables pub fn has_loop_body_local(&self) -> bool { self.vars.iter().any(|v| v.scope == CondVarScope::LoopBodyLocal) } /// Check if all variables in this scope are in the allowed set pub fn all_in(&self, allowed: &[CondVarScope]) -> bool { self.vars.iter().all(|v| allowed.contains(&v.scope)) } /// Get all variable names as a set pub fn var_names(&self) -> HashSet { self.vars.iter().map(|v| v.name.clone()).collect() } /// Add a variable to this scope (avoiding duplicates by name) pub fn add_var(&mut self, name: String, scope: CondVarScope) { // Check if variable already exists if !self.vars.iter().any(|v| v.name == name) { self.vars.push(CondVarInfo { name, scope }); } } } impl Default for LoopConditionScope { fn default() -> Self { Self::new() } } /// Phase 170-D: Loop Condition Scope Analysis Box /// /// This is the main analyzer that determines variable scopes for condition expressions. pub struct LoopConditionScopeBox; impl LoopConditionScopeBox { /// Analyze condition variable scopes for a loop /// /// # Arguments /// /// * `loop_param_name` - Name of the loop parameter (e.g., "i" in loop(i < 10)) /// * `condition_nodes` - Array of AST nodes containing conditions to analyze /// * `scope` - LoopScopeShape with information about variable definitions /// /// # Returns /// /// LoopConditionScope with classified variables pub fn analyze( loop_param_name: &str, condition_nodes: &[&ASTNode], scope: Option<&LoopScopeShape>, ) -> LoopConditionScope { let mut result = LoopConditionScope::new(); let mut found_vars = HashSet::new(); // Extract variable names from all condition nodes for node in condition_nodes { Self::extract_vars(node, &mut found_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) { CondVarScope::OuterLocal } else { // Default: assume it's loop-body-local if not identified as outer CondVarScope::LoopBodyLocal }; result.add_var(var_name, var_scope); } result } /// Recursively extract variable names from an AST node fn extract_vars(node: &ASTNode, vars: &mut HashSet) { 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)] mod tests { use super::*; #[test] fn test_condition_scope_new() { let scope = LoopConditionScope::new(); assert!(scope.vars.is_empty()); assert!(!scope.has_loop_body_local()); } #[test] fn test_add_var() { let mut scope = LoopConditionScope::new(); scope.add_var("i".to_string(), CondVarScope::LoopParam); scope.add_var("end".to_string(), CondVarScope::OuterLocal); assert_eq!(scope.vars.len(), 2); assert!(!scope.has_loop_body_local()); } #[test] fn test_has_loop_body_local() { let mut scope = LoopConditionScope::new(); scope.add_var("i".to_string(), CondVarScope::LoopParam); scope.add_var("ch".to_string(), CondVarScope::LoopBodyLocal); assert!(scope.has_loop_body_local()); } #[test] fn test_all_in() { let mut scope = LoopConditionScope::new(); scope.add_var("i".to_string(), CondVarScope::LoopParam); scope.add_var("end".to_string(), CondVarScope::OuterLocal); assert!(scope.all_in(&[CondVarScope::LoopParam, CondVarScope::OuterLocal])); assert!(!scope.all_in(&[CondVarScope::LoopParam])); } #[test] fn test_var_names() { let mut scope = LoopConditionScope::new(); scope.add_var("i".to_string(), CondVarScope::LoopParam); scope.add_var("end".to_string(), CondVarScope::OuterLocal); let names = scope.var_names(); assert!(names.contains("i")); assert!(names.contains("end")); assert_eq!(names.len(), 2); } }