Files
hakorune/src/mir/loop_pattern_detection/loop_condition_scope.rs

239 lines
7.7 KiB
Rust
Raw Normal View History

//! 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<CondVarInfo>,
}
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<String> {
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<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)]
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);
}
}