Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini
This commit is contained in:
@ -251,9 +251,7 @@ mod tests {
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
assert!(BreakConditionAnalyzer::has_break_in_else_clause(&[
|
||||
if_stmt
|
||||
]));
|
||||
assert!(BreakConditionAnalyzer::has_break_in_else_clause(&[if_stmt]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -448,9 +446,7 @@ mod tests {
|
||||
|
||||
// Should be wrapped in UnaryOp::Not
|
||||
if let ASTNode::UnaryOp {
|
||||
operator,
|
||||
operand,
|
||||
..
|
||||
operator, operand, ..
|
||||
} = negated
|
||||
{
|
||||
assert!(matches!(operator, UnaryOperator::Not));
|
||||
|
||||
@ -124,10 +124,7 @@ pub fn extract_all_variables(node: &ASTNode) -> HashSet<String> {
|
||||
/// Future versions may include:
|
||||
/// - Dominance tree analysis
|
||||
/// - More sophisticated scope inference
|
||||
pub(crate) fn is_outer_scope_variable(
|
||||
var_name: &str,
|
||||
scope: Option<&LoopScopeShape>,
|
||||
) -> bool {
|
||||
pub(crate) fn is_outer_scope_variable(var_name: &str, scope: Option<&LoopScopeShape>) -> bool {
|
||||
match scope {
|
||||
// No scope information: be conservative but *not* over‑strict.
|
||||
// We treat unknown as body-local only when we have a LoopScopeShape
|
||||
@ -162,7 +159,10 @@ pub(crate) fn is_outer_scope_variable(
|
||||
// ...
|
||||
// i = i + 1 (latch)
|
||||
// }
|
||||
if def_blocks.iter().all(|b| *b == scope.header || *b == scope.latch) {
|
||||
if def_blocks
|
||||
.iter()
|
||||
.all(|b| *b == scope.header || *b == scope.latch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -423,7 +423,10 @@ mod tests {
|
||||
|
||||
let vars = extract_all_variables(&literal_node);
|
||||
|
||||
assert!(vars.is_empty(), "Literal-only condition should extract no variables");
|
||||
assert!(
|
||||
vars.is_empty(),
|
||||
"Literal-only condition should extract no variables"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -453,14 +456,16 @@ mod tests {
|
||||
};
|
||||
|
||||
// Phase 170-ultrathink: header+latch ONLY → OuterLocal (carrier variable)
|
||||
assert!(is_outer_scope_variable("i", Some(&scope)),
|
||||
"Carrier variable (header+latch only) should be classified as OuterLocal");
|
||||
assert!(
|
||||
is_outer_scope_variable("i", Some(&scope)),
|
||||
"Carrier variable (header+latch only) should be classified as OuterLocal"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scope_priority_in_add_var() {
|
||||
// Test Phase 170-ultrathink scope priority system
|
||||
use super::super::loop_condition_scope::{LoopConditionScope, CondVarScope};
|
||||
use super::super::loop_condition_scope::{CondVarScope, LoopConditionScope};
|
||||
|
||||
let mut scope = LoopConditionScope::new();
|
||||
|
||||
@ -472,19 +477,28 @@ mod tests {
|
||||
// Add same variable as OuterLocal (more restrictive)
|
||||
scope.add_var("x".to_string(), CondVarScope::OuterLocal);
|
||||
assert_eq!(scope.vars.len(), 1, "Should not duplicate variable");
|
||||
assert_eq!(scope.vars[0].scope, CondVarScope::OuterLocal,
|
||||
"Should upgrade to more restrictive OuterLocal");
|
||||
assert_eq!(
|
||||
scope.vars[0].scope,
|
||||
CondVarScope::OuterLocal,
|
||||
"Should upgrade to more restrictive OuterLocal"
|
||||
);
|
||||
|
||||
// Try to downgrade to LoopBodyLocal (should be rejected)
|
||||
scope.add_var("x".to_string(), CondVarScope::LoopBodyLocal);
|
||||
assert_eq!(scope.vars.len(), 1);
|
||||
assert_eq!(scope.vars[0].scope, CondVarScope::OuterLocal,
|
||||
"Should NOT downgrade from OuterLocal to LoopBodyLocal");
|
||||
assert_eq!(
|
||||
scope.vars[0].scope,
|
||||
CondVarScope::OuterLocal,
|
||||
"Should NOT downgrade from OuterLocal to LoopBodyLocal"
|
||||
);
|
||||
|
||||
// Add same variable as LoopParam (most restrictive)
|
||||
scope.add_var("x".to_string(), CondVarScope::LoopParam);
|
||||
assert_eq!(scope.vars.len(), 1);
|
||||
assert_eq!(scope.vars[0].scope, CondVarScope::LoopParam,
|
||||
"Should upgrade to most restrictive LoopParam");
|
||||
assert_eq!(
|
||||
scope.vars[0].scope,
|
||||
CondVarScope::LoopParam,
|
||||
"Should upgrade to most restrictive LoopParam"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,10 @@ pub fn format_unsupported_condition_error(
|
||||
Consider using Pattern 5+ for complex loop conditions.",
|
||||
pattern_name,
|
||||
body_local_names,
|
||||
pattern_name.chars().filter(|c| c.is_numeric()).collect::<String>()
|
||||
pattern_name
|
||||
.chars()
|
||||
.filter(|c| c.is_numeric())
|
||||
.collect::<String>()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -30,9 +30,9 @@
|
||||
//! Phase 200-A creates the infrastructure to capture such variables.
|
||||
//! Phase 200-B will implement the actual detection logic.
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// A variable captured from function scope for use in loop conditions/body.
|
||||
@ -155,7 +155,9 @@ pub(crate) fn analyze_captured_vars(
|
||||
Some(idx) => idx,
|
||||
None => {
|
||||
if debug {
|
||||
eprintln!("[capture/debug] Loop not found in function body, returning empty CapturedEnv");
|
||||
eprintln!(
|
||||
"[capture/debug] Loop not found in function body, returning empty CapturedEnv"
|
||||
);
|
||||
}
|
||||
return CapturedEnv::new();
|
||||
}
|
||||
@ -169,7 +171,10 @@ pub(crate) fn analyze_captured_vars(
|
||||
let pre_loop_locals = collect_local_declarations(&fn_body[..loop_index]);
|
||||
|
||||
if debug {
|
||||
eprintln!("[capture/debug] Found {} pre-loop local declarations", pre_loop_locals.len());
|
||||
eprintln!(
|
||||
"[capture/debug] Found {} pre-loop local declarations",
|
||||
pre_loop_locals.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut env = CapturedEnv::new();
|
||||
@ -230,7 +235,10 @@ pub(crate) fn analyze_captured_vars(
|
||||
// Note: We don't have access to variable_map here, so we use a placeholder ValueId
|
||||
// The actual host_id will be resolved in ConditionEnvBuilder
|
||||
if debug {
|
||||
eprintln!("[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", name);
|
||||
eprintln!(
|
||||
"[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
env.add_var(CapturedVar {
|
||||
@ -241,7 +249,8 @@ pub(crate) fn analyze_captured_vars(
|
||||
}
|
||||
|
||||
if debug {
|
||||
eprintln!("[capture/result] Captured {} variables: {:?}",
|
||||
eprintln!(
|
||||
"[capture/result] Captured {} variables: {:?}",
|
||||
env.vars.len(),
|
||||
env.vars.iter().map(|v| &v.name).collect::<Vec<_>>()
|
||||
);
|
||||
@ -301,7 +310,10 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
};
|
||||
|
||||
if debug {
|
||||
eprintln!("[capture/debug] Found {} pre-loop local declarations", pre_loop_locals.len());
|
||||
eprintln!(
|
||||
"[capture/debug] Found {} pre-loop local declarations",
|
||||
pre_loop_locals.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut env = CapturedEnv::new();
|
||||
@ -360,7 +372,10 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
|
||||
// All checks passed: add to CapturedEnv
|
||||
if debug {
|
||||
eprintln!("[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv", name);
|
||||
eprintln!(
|
||||
"[capture/accept] '{}': ALL CHECKS PASSED, adding to CapturedEnv",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
env.add_var(CapturedVar {
|
||||
@ -374,8 +389,10 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
let names_in_loop = collect_names_in_loop_parts(loop_condition, loop_body);
|
||||
|
||||
// pre-loop local names (already processed above)
|
||||
let pre_loop_local_names: BTreeSet<String> =
|
||||
pre_loop_locals.iter().map(|(name, _)| name.clone()).collect();
|
||||
let pre_loop_local_names: BTreeSet<String> = pre_loop_locals
|
||||
.iter()
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
|
||||
// Check each variable used in loop
|
||||
for name in names_in_loop {
|
||||
@ -402,7 +419,10 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
|
||||
// This is a function parameter-like variable - add to CapturedEnv
|
||||
if debug {
|
||||
eprintln!("[capture/param/accept] '{}': function parameter used in loop", name);
|
||||
eprintln!(
|
||||
"[capture/param/accept] '{}': function parameter used in loop",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
env.add_var(CapturedVar {
|
||||
@ -413,7 +433,8 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
}
|
||||
|
||||
if debug {
|
||||
eprintln!("[capture/result] Captured {} variables: {:?}",
|
||||
eprintln!(
|
||||
"[capture/result] Captured {} variables: {:?}",
|
||||
env.vars.len(),
|
||||
env.vars.iter().map(|v| &v.name).collect::<Vec<_>>()
|
||||
);
|
||||
@ -428,9 +449,9 @@ pub(crate) fn analyze_captured_vars_v2(
|
||||
#[allow(dead_code)]
|
||||
fn find_stmt_index(fn_body: &[ASTNode], loop_ast: &ASTNode) -> Option<usize> {
|
||||
// Compare by pointer address (same AST node instance)
|
||||
fn_body.iter().position(|stmt| {
|
||||
std::ptr::eq(stmt as *const ASTNode, loop_ast as *const ASTNode)
|
||||
})
|
||||
fn_body
|
||||
.iter()
|
||||
.position(|stmt| std::ptr::eq(stmt as *const ASTNode, loop_ast as *const ASTNode))
|
||||
}
|
||||
|
||||
/// Phase 200-C: Find loop index by structure matching (condition + body comparison)
|
||||
@ -443,7 +464,10 @@ fn find_loop_index_by_structure(
|
||||
target_body: &[ASTNode],
|
||||
) -> Option<usize> {
|
||||
for (idx, stmt) in fn_body.iter().enumerate() {
|
||||
if let ASTNode::Loop { condition, body, .. } = stmt {
|
||||
if let ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} = stmt
|
||||
{
|
||||
// Compare condition and body by structure
|
||||
if ast_matches(condition, target_condition) && body_matches(body, target_body) {
|
||||
return Some(idx);
|
||||
@ -476,7 +500,12 @@ fn collect_local_declarations(stmts: &[ASTNode]) -> Vec<(String, Option<Box<ASTN
|
||||
let mut locals = Vec::new();
|
||||
|
||||
for stmt in stmts {
|
||||
if let ASTNode::Local { variables, initial_values, .. } = stmt {
|
||||
if let ASTNode::Local {
|
||||
variables,
|
||||
initial_values,
|
||||
..
|
||||
} = stmt
|
||||
{
|
||||
// Local declaration can have multiple variables (e.g., local a, b, c)
|
||||
for (i, name) in variables.iter().enumerate() {
|
||||
let init_expr = initial_values.get(i).and_then(|opt| opt.clone());
|
||||
@ -528,29 +557,43 @@ fn is_reassigned_in_fn(fn_body: &[ASTNode], name: &str) -> bool {
|
||||
}
|
||||
|
||||
// Grouped assignment expression: (x = expr)
|
||||
ASTNode::GroupedAssignmentExpr { lhs, rhs, .. } => {
|
||||
lhs == name || check_node(rhs, name)
|
||||
}
|
||||
ASTNode::GroupedAssignmentExpr { lhs, rhs, .. } => lhs == name || check_node(rhs, name),
|
||||
|
||||
// Recursive cases
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
check_node(condition, name)
|
||||
|| then_body.iter().any(|n| check_node(n, name))
|
||||
|| else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_node(n, name)))
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map_or(false, |body| body.iter().any(|n| check_node(n, name)))
|
||||
}
|
||||
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
check_node(condition, name) || body.iter().any(|n| check_node(n, name))
|
||||
}
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => check_node(condition, name) || body.iter().any(|n| check_node(n, name)),
|
||||
|
||||
ASTNode::While { condition, body, .. } => {
|
||||
check_node(condition, name) || body.iter().any(|n| check_node(n, name))
|
||||
}
|
||||
ASTNode::While {
|
||||
condition, body, ..
|
||||
} => check_node(condition, name) || body.iter().any(|n| check_node(n, name)),
|
||||
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
..
|
||||
} => {
|
||||
try_body.iter().any(|n| check_node(n, name))
|
||||
|| catch_clauses.iter().any(|clause| clause.body.iter().any(|n| check_node(n, name)))
|
||||
|| finally_body.as_ref().map_or(false, |body| body.iter().any(|n| check_node(n, name)))
|
||||
|| catch_clauses
|
||||
.iter()
|
||||
.any(|clause| clause.body.iter().any(|n| check_node(n, name)))
|
||||
|| finally_body
|
||||
.as_ref()
|
||||
.map_or(false, |body| body.iter().any(|n| check_node(n, name)))
|
||||
}
|
||||
|
||||
ASTNode::UnaryOp { operand, .. } => check_node(operand, name),
|
||||
@ -559,9 +602,9 @@ fn is_reassigned_in_fn(fn_body: &[ASTNode], name: &str) -> bool {
|
||||
check_node(left, name) || check_node(right, name)
|
||||
}
|
||||
|
||||
ASTNode::MethodCall { object, arguments, .. } => {
|
||||
check_node(object, name) || arguments.iter().any(|arg| check_node(arg, name))
|
||||
}
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => check_node(object, name) || arguments.iter().any(|arg| check_node(arg, name)),
|
||||
|
||||
ASTNode::FunctionCall { arguments, .. } => {
|
||||
arguments.iter().any(|arg| check_node(arg, name))
|
||||
@ -573,9 +616,7 @@ fn is_reassigned_in_fn(fn_body: &[ASTNode], name: &str) -> bool {
|
||||
check_node(target, name) || check_node(index, name)
|
||||
}
|
||||
|
||||
ASTNode::Return { value, .. } => {
|
||||
value.as_ref().map_or(false, |v| check_node(v, name))
|
||||
}
|
||||
ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_node(v, name)),
|
||||
|
||||
ASTNode::Local { .. } => {
|
||||
// Local declarations are not reassignments
|
||||
@ -598,14 +639,21 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool {
|
||||
match node {
|
||||
ASTNode::Variable { name: var_name, .. } => var_name == name,
|
||||
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
check_usage(condition, name) || body.iter().any(|n| check_usage(n, name))
|
||||
}
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)),
|
||||
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
check_usage(condition, name)
|
||||
|| then_body.iter().any(|n| check_usage(n, name))
|
||||
|| else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_usage(n, name)))
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map_or(false, |body| body.iter().any(|n| check_usage(n, name)))
|
||||
}
|
||||
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
@ -618,9 +666,9 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool {
|
||||
check_usage(left, name) || check_usage(right, name)
|
||||
}
|
||||
|
||||
ASTNode::MethodCall { object, arguments, .. } => {
|
||||
check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name))
|
||||
}
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)),
|
||||
|
||||
ASTNode::FunctionCall { arguments, .. } => {
|
||||
arguments.iter().any(|arg| check_usage(arg, name))
|
||||
@ -632,15 +680,11 @@ fn is_used_in_loop(loop_ast: &ASTNode, name: &str) -> bool {
|
||||
check_usage(target, name) || check_usage(index, name)
|
||||
}
|
||||
|
||||
ASTNode::Return { value, .. } => {
|
||||
value.as_ref().map_or(false, |v| check_usage(v, name))
|
||||
}
|
||||
ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_usage(v, name)),
|
||||
|
||||
ASTNode::Local { initial_values, .. } => {
|
||||
initial_values.iter().any(|opt| {
|
||||
opt.as_ref().map_or(false, |init| check_usage(init, name))
|
||||
})
|
||||
}
|
||||
ASTNode::Local { initial_values, .. } => initial_values
|
||||
.iter()
|
||||
.any(|opt| opt.as_ref().map_or(false, |init| check_usage(init, name))),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -657,14 +701,21 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b
|
||||
match node {
|
||||
ASTNode::Variable { name: var_name, .. } => var_name == name,
|
||||
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
check_usage(condition, name) || body.iter().any(|n| check_usage(n, name))
|
||||
}
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => check_usage(condition, name) || body.iter().any(|n| check_usage(n, name)),
|
||||
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
check_usage(condition, name)
|
||||
|| then_body.iter().any(|n| check_usage(n, name))
|
||||
|| else_body.as_ref().map_or(false, |body| body.iter().any(|n| check_usage(n, name)))
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map_or(false, |body| body.iter().any(|n| check_usage(n, name)))
|
||||
}
|
||||
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
@ -677,9 +728,9 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b
|
||||
check_usage(left, name) || check_usage(right, name)
|
||||
}
|
||||
|
||||
ASTNode::MethodCall { object, arguments, .. } => {
|
||||
check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name))
|
||||
}
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => check_usage(object, name) || arguments.iter().any(|arg| check_usage(arg, name)),
|
||||
|
||||
ASTNode::FunctionCall { arguments, .. } => {
|
||||
arguments.iter().any(|arg| check_usage(arg, name))
|
||||
@ -691,15 +742,11 @@ fn is_used_in_loop_parts(condition: &ASTNode, body: &[ASTNode], name: &str) -> b
|
||||
check_usage(target, name) || check_usage(index, name)
|
||||
}
|
||||
|
||||
ASTNode::Return { value, .. } => {
|
||||
value.as_ref().map_or(false, |v| check_usage(v, name))
|
||||
}
|
||||
ASTNode::Return { value, .. } => value.as_ref().map_or(false, |v| check_usage(v, name)),
|
||||
|
||||
ASTNode::Local { initial_values, .. } => {
|
||||
initial_values.iter().any(|opt| {
|
||||
opt.as_ref().map_or(false, |init| check_usage(init, name))
|
||||
})
|
||||
}
|
||||
ASTNode::Local { initial_values, .. } => initial_values
|
||||
.iter()
|
||||
.any(|opt| opt.as_ref().map_or(false, |init| check_usage(init, name))),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -718,7 +765,12 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe
|
||||
ASTNode::Variable { name, .. } => {
|
||||
acc.insert(name.clone());
|
||||
}
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
collect(condition, acc);
|
||||
for stmt in then_body {
|
||||
collect(stmt, acc);
|
||||
@ -736,14 +788,19 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe
|
||||
ASTNode::UnaryOp { operand, .. } => {
|
||||
collect(operand, acc);
|
||||
}
|
||||
ASTNode::Return { value: Some(operand), .. } => {
|
||||
ASTNode::Return {
|
||||
value: Some(operand),
|
||||
..
|
||||
} => {
|
||||
collect(operand, acc);
|
||||
}
|
||||
ASTNode::BinaryOp { left, right, .. } => {
|
||||
collect(left, acc);
|
||||
collect(right, acc);
|
||||
}
|
||||
ASTNode::MethodCall { object, arguments, .. } => {
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
collect(object, acc);
|
||||
for arg in arguments {
|
||||
collect(arg, acc);
|
||||
@ -768,7 +825,9 @@ fn collect_names_in_loop_parts(condition: &ASTNode, body: &[ASTNode]) -> BTreeSe
|
||||
collect(target, acc);
|
||||
collect(index, acc);
|
||||
}
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => {
|
||||
collect(condition, acc);
|
||||
for stmt in body {
|
||||
collect(stmt, acc);
|
||||
@ -890,8 +949,8 @@ mod tests {
|
||||
|
||||
let fn_body = vec![digits_decl, loop_node.clone()];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -982,8 +1041,8 @@ mod tests {
|
||||
|
||||
let fn_body = vec![digits_decl, reassignment, loop_node.clone()];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1040,8 +1099,8 @@ mod tests {
|
||||
|
||||
let fn_body = vec![loop_node.clone(), digits_decl];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1119,8 +1178,8 @@ mod tests {
|
||||
|
||||
let fn_body = vec![result_decl, loop_node.clone()];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1173,14 +1232,14 @@ mod tests {
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
body: vec![], // empty body, no usage of digits
|
||||
body: vec![], // empty body, no usage of digits
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let fn_body = vec![digits_decl, loop_node.clone()];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1205,7 +1264,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_capture_function_param_used_in_condition() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator};
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
// Simulate: fn parse_number(s, p, len) { loop(p < len) { ... } }
|
||||
// Expected: 'len' should be captured (used in condition, not reassigned)
|
||||
@ -1223,30 +1282,28 @@ mod tests {
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
let body = vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
let body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "p".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1274,7 +1331,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_capture_function_param_used_in_method_call() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator};
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
// Simulate: fn parse_number(s, p) { loop(p < s.length()) { ch = s.charAt(p) } }
|
||||
// Expected: 's' should be captured (used in condition and body, not reassigned)
|
||||
@ -1335,8 +1392,8 @@ mod tests {
|
||||
},
|
||||
];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1364,7 +1421,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_capture_function_param_reassigned_rejected() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator};
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
// Simulate: fn bad_func(x) { x = 5; loop(x < 10) { x = x + 1 } }
|
||||
// Expected: 'x' should NOT be captured (reassigned in function)
|
||||
@ -1382,45 +1439,41 @@ mod tests {
|
||||
span: Span::unknown(),
|
||||
});
|
||||
|
||||
let body = vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
let body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
// fn_body includes reassignment before loop
|
||||
let fn_body = vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(5),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
let fn_body = vec![ASTNode::Assignment {
|
||||
target: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}),
|
||||
value: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(5),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
@ -1444,7 +1497,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_capture_mixed_locals_and_params() {
|
||||
use crate::ast::{ASTNode, LiteralValue, Span, BinaryOperator};
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
// Simulate: fn parse(s, len) { local digits = "0123"; loop(p < len) { ch = digits.indexOf(...); s.charAt(...) } }
|
||||
// Expected: 'len', 's', and 'digits' should all be captured
|
||||
@ -1498,19 +1551,17 @@ mod tests {
|
||||
];
|
||||
|
||||
// fn_body includes local declaration before loop
|
||||
let fn_body = vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["digits".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::String("0123".to_string()),
|
||||
span: Span::unknown(),
|
||||
}))],
|
||||
let fn_body = vec![ASTNode::Local {
|
||||
variables: vec!["digits".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::String("0123".to_string()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
use std::collections::{BTreeSet, BTreeMap};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
let scope = crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape {
|
||||
header: BasicBlockId(0),
|
||||
|
||||
@ -68,16 +68,16 @@ impl TrimPatternInfo {
|
||||
/// - The actual host ValueId will be assigned during merge_joinir_mir_blocks
|
||||
/// - JoinInlineBoundary will handle the boundary mapping
|
||||
pub fn to_carrier_info(&self) -> crate::mir::join_ir::lowering::carrier_info::CarrierInfo {
|
||||
use super::trim_loop_helper::TrimLoopHelper;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::ValueId;
|
||||
use super::trim_loop_helper::TrimLoopHelper;
|
||||
|
||||
// Phase 171-C-4/5: Create CarrierInfo with promoted carrier as loop variable
|
||||
// and attach TrimLoopHelper for future lowering
|
||||
let mut carrier_info = CarrierInfo::with_carriers(
|
||||
self.carrier_name.clone(), // "is_ch_match" becomes the loop variable
|
||||
ValueId(0), // Placeholder (will be remapped)
|
||||
vec![], // No additional carriers
|
||||
self.carrier_name.clone(), // "is_ch_match" becomes the loop variable
|
||||
ValueId(0), // Placeholder (will be remapped)
|
||||
vec![], // No additional carriers
|
||||
);
|
||||
|
||||
// Phase 171-C-5: Attach TrimLoopHelper for pattern-specific lowering logic
|
||||
@ -85,7 +85,9 @@ impl TrimPatternInfo {
|
||||
|
||||
// Phase 229: Record promoted variable (no need for condition_aliases)
|
||||
// Dynamic resolution uses promoted_loopbodylocals + naming convention
|
||||
carrier_info.promoted_loopbodylocals.push(self.var_name.clone());
|
||||
carrier_info
|
||||
.promoted_loopbodylocals
|
||||
.push(self.var_name.clone());
|
||||
|
||||
carrier_info
|
||||
}
|
||||
@ -104,7 +106,7 @@ pub enum PromotionResult {
|
||||
/// 昇格不可: 理由を説明
|
||||
CannotPromote {
|
||||
reason: String,
|
||||
vars: Vec<String>, // 問題の LoopBodyLocal
|
||||
vars: Vec<String>, // 問題の LoopBodyLocal
|
||||
},
|
||||
}
|
||||
|
||||
@ -125,7 +127,10 @@ impl LoopBodyCarrierPromoter {
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope;
|
||||
|
||||
// 1. LoopBodyLocal を抽出
|
||||
let body_locals: Vec<&String> = request.cond_scope.vars.iter()
|
||||
let body_locals: Vec<&String> = request
|
||||
.cond_scope
|
||||
.vars
|
||||
.iter()
|
||||
.filter(|v| v.scope == CondVarScope::LoopBodyLocal)
|
||||
.map(|v| &v.name)
|
||||
.collect();
|
||||
@ -230,7 +235,11 @@ impl LoopBodyCarrierPromoter {
|
||||
}
|
||||
|
||||
// If: then_body と else_body を探索
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
for stmt in then_body {
|
||||
worklist.push(stmt);
|
||||
}
|
||||
@ -242,7 +251,9 @@ impl LoopBodyCarrierPromoter {
|
||||
}
|
||||
|
||||
// Loop: body を探索(ネストループ)
|
||||
ASTNode::Loop { body: loop_body, .. } => {
|
||||
ASTNode::Loop {
|
||||
body: loop_body, ..
|
||||
} => {
|
||||
for stmt in loop_body {
|
||||
worklist.push(stmt);
|
||||
}
|
||||
@ -250,7 +261,11 @@ impl LoopBodyCarrierPromoter {
|
||||
|
||||
// Phase 171-impl-Trim: Handle Local with initial values
|
||||
// local ch = s.substring(...)
|
||||
ASTNode::Local { variables, initial_values, .. } if initial_values.len() == variables.len() => {
|
||||
ASTNode::Local {
|
||||
variables,
|
||||
initial_values,
|
||||
..
|
||||
} if initial_values.len() == variables.len() => {
|
||||
for (i, var) in variables.iter().enumerate() {
|
||||
if var == var_name {
|
||||
if let Some(Some(init_expr)) = initial_values.get(i) {
|
||||
@ -303,7 +318,12 @@ impl LoopBodyCarrierPromoter {
|
||||
while let Some(node) = worklist.pop() {
|
||||
match node {
|
||||
// BinaryOp: Or で分岐、Eq で比較
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
match operator {
|
||||
// Or: 両側を探索
|
||||
BinaryOperator::Or => {
|
||||
@ -317,7 +337,11 @@ impl LoopBodyCarrierPromoter {
|
||||
if let ASTNode::Variable { name, .. } = left.as_ref() {
|
||||
if name == var_name {
|
||||
// right が String リテラル
|
||||
if let ASTNode::Literal { value: LiteralValue::String(s), .. } = right.as_ref() {
|
||||
if let ASTNode::Literal {
|
||||
value: LiteralValue::String(s),
|
||||
..
|
||||
} = right.as_ref()
|
||||
{
|
||||
result.push(s.clone());
|
||||
}
|
||||
}
|
||||
@ -325,7 +349,11 @@ impl LoopBodyCarrierPromoter {
|
||||
// right が Variable で var_name に一致(逆順)
|
||||
if let ASTNode::Variable { name, .. } = right.as_ref() {
|
||||
if name == var_name {
|
||||
if let ASTNode::Literal { value: LiteralValue::String(s), .. } = left.as_ref() {
|
||||
if let ASTNode::Literal {
|
||||
value: LiteralValue::String(s),
|
||||
..
|
||||
} = left.as_ref()
|
||||
{
|
||||
result.push(s.clone());
|
||||
}
|
||||
}
|
||||
@ -353,10 +381,10 @@ impl LoopBodyCarrierPromoter {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Span;
|
||||
use crate::mir::BasicBlockId;
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::{
|
||||
CondVarScope, LoopConditionScope,
|
||||
};
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
fn minimal_scope() -> LoopScopeShape {
|
||||
@ -489,9 +517,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_find_definition_in_body_simple() {
|
||||
// Test: local ch = s.substring(...)
|
||||
let body = vec![
|
||||
assignment("ch", method_call("s", "substring")),
|
||||
];
|
||||
let body = vec![assignment("ch", method_call("s", "substring"))];
|
||||
|
||||
let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch");
|
||||
|
||||
@ -507,20 +533,19 @@ mod tests {
|
||||
#[test]
|
||||
fn test_find_definition_in_body_nested_if() {
|
||||
// Test: Definition inside if-else block
|
||||
let body = vec![
|
||||
ASTNode::If {
|
||||
condition: Box::new(var_node("flag")),
|
||||
then_body: vec![
|
||||
assignment("ch", method_call("s", "substring")),
|
||||
],
|
||||
else_body: None,
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
let body = vec![ASTNode::If {
|
||||
condition: Box::new(var_node("flag")),
|
||||
then_body: vec![assignment("ch", method_call("s", "substring"))],
|
||||
else_body: None,
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch");
|
||||
|
||||
assert!(result.is_some(), "Definition should be found inside if block");
|
||||
assert!(
|
||||
result.is_some(),
|
||||
"Definition should be found inside if block"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -528,8 +553,12 @@ mod tests {
|
||||
let substring_call = method_call("s", "substring");
|
||||
let other_call = method_call("s", "length");
|
||||
|
||||
assert!(LoopBodyCarrierPromoter::is_substring_method_call(&substring_call));
|
||||
assert!(!LoopBodyCarrierPromoter::is_substring_method_call(&other_call));
|
||||
assert!(LoopBodyCarrierPromoter::is_substring_method_call(
|
||||
&substring_call
|
||||
));
|
||||
assert!(!LoopBodyCarrierPromoter::is_substring_method_call(
|
||||
&other_call
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -547,10 +576,7 @@ mod tests {
|
||||
fn test_extract_equality_literals_or_chain() {
|
||||
// Test: ch == " " || ch == "\t" || ch == "\n"
|
||||
let cond = or_expr(
|
||||
or_expr(
|
||||
eq_cmp("ch", " "),
|
||||
eq_cmp("ch", "\t"),
|
||||
),
|
||||
or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")),
|
||||
eq_cmp("ch", "\n"),
|
||||
);
|
||||
|
||||
@ -569,7 +595,10 @@ mod tests {
|
||||
|
||||
let result = LoopBodyCarrierPromoter::extract_equality_literals(&cond, "ch");
|
||||
|
||||
assert!(result.is_empty(), "Should not extract literals for wrong variable");
|
||||
assert!(
|
||||
result.is_empty(),
|
||||
"Should not extract literals for wrong variable"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -582,14 +611,9 @@ mod tests {
|
||||
let scope = minimal_scope();
|
||||
let cond_scope = cond_scope_with_body_local("ch");
|
||||
|
||||
let loop_body = vec![
|
||||
assignment("ch", method_call("s", "substring")),
|
||||
];
|
||||
let loop_body = vec![assignment("ch", method_call("s", "substring"))];
|
||||
|
||||
let break_cond = or_expr(
|
||||
eq_cmp("ch", " "),
|
||||
eq_cmp("ch", "\t"),
|
||||
);
|
||||
let break_cond = or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t"));
|
||||
|
||||
let request = PromotionRequest {
|
||||
scope: &scope,
|
||||
@ -620,19 +644,11 @@ mod tests {
|
||||
let scope = minimal_scope();
|
||||
let cond_scope = cond_scope_with_body_local("ch");
|
||||
|
||||
let loop_body = vec![
|
||||
assignment("ch", method_call("s", "substring")),
|
||||
];
|
||||
let loop_body = vec![assignment("ch", method_call("s", "substring"))];
|
||||
|
||||
let break_cond = or_expr(
|
||||
or_expr(
|
||||
eq_cmp("ch", " "),
|
||||
eq_cmp("ch", "\t"),
|
||||
),
|
||||
or_expr(
|
||||
eq_cmp("ch", "\n"),
|
||||
eq_cmp("ch", "\r"),
|
||||
),
|
||||
or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")),
|
||||
or_expr(eq_cmp("ch", "\n"), eq_cmp("ch", "\r")),
|
||||
);
|
||||
|
||||
let request = PromotionRequest {
|
||||
|
||||
@ -149,7 +149,7 @@ impl LoopBodyCondPromoter {
|
||||
/// - LoopBodyCondPromoter: Detection + metadata only (no code generation)
|
||||
pub fn try_promote_for_condition(req: ConditionPromotionRequest) -> ConditionPromotionResult {
|
||||
use crate::mir::loop_pattern_detection::loop_body_digitpos_promoter::{
|
||||
DigitPosPromotionRequest, DigitPosPromotionResult, DigitPosPromoter,
|
||||
DigitPosPromoter, DigitPosPromotionRequest, DigitPosPromotionResult,
|
||||
};
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope;
|
||||
|
||||
|
||||
@ -187,19 +187,21 @@ impl DigitPosPromoter {
|
||||
let bool_carrier_name = format!("is_{}", var_in_cond);
|
||||
// Extract the base name for integer carrier (e.g., "digit_pos" → "digit")
|
||||
let base_name = if var_in_cond.ends_with("_pos") {
|
||||
&var_in_cond[..var_in_cond.len() - 4] // Remove "_pos" suffix
|
||||
&var_in_cond[..var_in_cond.len() - 4] // Remove "_pos" suffix
|
||||
} else {
|
||||
var_in_cond.as_str()
|
||||
};
|
||||
let int_carrier_name = format!("{}_value", base_name);
|
||||
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::carrier_info::{
|
||||
CarrierInit, CarrierRole, CarrierVar,
|
||||
};
|
||||
|
||||
// Boolean carrier (condition-only, for break)
|
||||
let promoted_carrier_bool = CarrierVar {
|
||||
name: bool_carrier_name.clone(),
|
||||
host_id: ValueId(0), // Placeholder (will be remapped)
|
||||
join_id: None, // Will be allocated later
|
||||
join_id: None, // Will be allocated later
|
||||
role: CarrierRole::ConditionOnly, // Phase 227: DigitPos is condition-only
|
||||
init: CarrierInit::BoolConst(false), // Phase 228: Initialize with false
|
||||
};
|
||||
@ -208,7 +210,7 @@ impl DigitPosPromoter {
|
||||
let promoted_carrier_int = CarrierVar {
|
||||
name: int_carrier_name.clone(),
|
||||
host_id: ValueId(0), // Placeholder (loop-local; no host slot)
|
||||
join_id: None, // Will be allocated later
|
||||
join_id: None, // Will be allocated later
|
||||
role: CarrierRole::LoopState, // Phase 247-EX: LoopState for accumulation
|
||||
init: CarrierInit::LoopLocalZero, // Derived in-loop carrier (no host binding)
|
||||
};
|
||||
@ -216,13 +218,15 @@ impl DigitPosPromoter {
|
||||
// Create CarrierInfo with a dummy loop_var_name (will be ignored during merge)
|
||||
let mut carrier_info = CarrierInfo::with_carriers(
|
||||
"__dummy_loop_var__".to_string(), // Placeholder, not used
|
||||
ValueId(0), // Placeholder
|
||||
ValueId(0), // Placeholder
|
||||
vec![promoted_carrier_bool, promoted_carrier_int],
|
||||
);
|
||||
|
||||
// Phase 229: Record promoted variable (no need for condition_aliases)
|
||||
// Dynamic resolution uses promoted_loopbodylocals + naming convention
|
||||
carrier_info.promoted_loopbodylocals.push(var_in_cond.clone());
|
||||
carrier_info
|
||||
.promoted_loopbodylocals
|
||||
.push(var_in_cond.clone());
|
||||
|
||||
eprintln!(
|
||||
"[digitpos_promoter] Phase 247-EX: A-4 DigitPos pattern promoted: {} → {} (bool) + {} (i64)",
|
||||
@ -335,9 +339,7 @@ impl DigitPosPromoter {
|
||||
/// Handles: `if digit_pos < 0`, `if digit_pos >= 0`, etc.
|
||||
fn extract_comparison_var(cond: &ASTNode) -> Option<String> {
|
||||
match cond {
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, ..
|
||||
} => {
|
||||
ASTNode::BinaryOp { operator, left, .. } => {
|
||||
// Check if it's a comparison operator (not equality)
|
||||
match operator {
|
||||
BinaryOperator::Less
|
||||
|
||||
@ -46,7 +46,9 @@ impl LoopConditionScope {
|
||||
|
||||
/// 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)
|
||||
self.vars
|
||||
.iter()
|
||||
.any(|v| v.scope == CondVarScope::LoopBodyLocal)
|
||||
}
|
||||
|
||||
/// Check if all variables in this scope are in the allowed set
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
//!
|
||||
//! Reference: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/design.md
|
||||
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
|
||||
// ============================================================================
|
||||
// Pattern Classification System (Phase 194+)
|
||||
@ -142,7 +142,8 @@ pub struct LoopFeatures {
|
||||
/// Contains UpdateKind (CounterLike/AccumulationLike/Other) for each carrier.
|
||||
/// Used by CaseALoweringShape for more precise shape detection.
|
||||
/// None if carrier names are not available.
|
||||
pub update_summary: Option<crate::mir::join_ir::lowering::loop_update_summary::LoopUpdateSummary>,
|
||||
pub update_summary:
|
||||
Option<crate::mir::join_ir::lowering::loop_update_summary::LoopUpdateSummary>,
|
||||
}
|
||||
|
||||
impl LoopFeatures {
|
||||
@ -196,7 +197,10 @@ impl LoopFeatures {
|
||||
///
|
||||
/// # Returns
|
||||
/// * `LoopFeatures` - Feature vector for pattern classification
|
||||
pub(crate) fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeShape>) -> LoopFeatures {
|
||||
pub(crate) fn extract_features(
|
||||
loop_form: &LoopForm,
|
||||
scope: Option<&LoopScopeShape>,
|
||||
) -> LoopFeatures {
|
||||
// Phase 194: Basic feature extraction from LoopForm
|
||||
let has_break = !loop_form.break_targets.is_empty();
|
||||
let has_continue = !loop_form.continue_targets.is_empty();
|
||||
@ -219,7 +223,9 @@ pub(crate) fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeSha
|
||||
// Note: carriers is BTreeSet<String>, so each item is already a String
|
||||
let update_summary = scope.map(|s| {
|
||||
let carrier_names: Vec<String> = s.carriers.iter().cloned().collect();
|
||||
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(&carrier_names)
|
||||
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(
|
||||
&carrier_names,
|
||||
)
|
||||
});
|
||||
|
||||
LoopFeatures {
|
||||
@ -275,7 +281,11 @@ pub fn classify(features: &LoopFeatures) -> LoopPatternKind {
|
||||
|
||||
// Pattern 3: If-PHI (check before Pattern 1)
|
||||
// Phase 212.5: Structural if detection - route to P3 if has_if && carrier_count >= 1
|
||||
if features.has_if && features.carrier_count >= 1 && !features.has_break && !features.has_continue {
|
||||
if features.has_if
|
||||
&& features.carrier_count >= 1
|
||||
&& !features.has_break
|
||||
&& !features.has_continue
|
||||
{
|
||||
return LoopPatternKind::Pattern3IfPhi;
|
||||
}
|
||||
|
||||
@ -326,10 +336,7 @@ pub fn classify_with_diagnosis(features: &LoopFeatures) -> (LoopPatternKind, Str
|
||||
"Simple while loop with no special control flow".to_string()
|
||||
}
|
||||
LoopPatternKind::Unknown => {
|
||||
format!(
|
||||
"Unknown pattern: {}",
|
||||
features.debug_stats()
|
||||
)
|
||||
format!("Unknown pattern: {}", features.debug_stats())
|
||||
}
|
||||
};
|
||||
|
||||
@ -666,8 +673,8 @@ fn has_simple_condition(_loop_form: &LoopForm) -> bool {
|
||||
mod tests;
|
||||
|
||||
// Phase 170-D: Loop Condition Scope Analysis Boxes
|
||||
pub mod loop_condition_scope;
|
||||
pub mod condition_var_analyzer;
|
||||
pub mod loop_condition_scope;
|
||||
|
||||
// Phase 170-ultrathink: Error Message Utilities
|
||||
pub mod error_messages;
|
||||
|
||||
@ -40,8 +40,15 @@ fn assignment(target: ASTNode, value: ASTNode) -> ASTNode {
|
||||
fn has_continue(node: &ASTNode) -> bool {
|
||||
match node {
|
||||
ASTNode::Continue { .. } => true,
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
then_body.iter().any(has_continue) || else_body.as_ref().map_or(false, |b| b.iter().any(has_continue))
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
then_body.iter().any(has_continue)
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map_or(false, |b| b.iter().any(has_continue))
|
||||
}
|
||||
ASTNode::Loop { body, .. } => body.iter().any(has_continue),
|
||||
_ => false,
|
||||
@ -51,8 +58,15 @@ fn has_continue(node: &ASTNode) -> bool {
|
||||
fn has_break(node: &ASTNode) -> bool {
|
||||
match node {
|
||||
ASTNode::Break { .. } => true,
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
then_body.iter().any(has_break) || else_body.as_ref().map_or(false, |b| b.iter().any(has_break))
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
then_body.iter().any(has_break)
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map_or(false, |b| b.iter().any(has_break))
|
||||
}
|
||||
ASTNode::Loop { body, .. } => body.iter().any(has_break),
|
||||
_ => false,
|
||||
@ -69,7 +83,11 @@ fn carrier_count(body: &[ASTNode]) -> usize {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::Assignment { .. } => c += 1,
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
c += count(then_body);
|
||||
if let Some(else_body) = else_body {
|
||||
c += count(else_body);
|
||||
@ -80,7 +98,11 @@ fn carrier_count(body: &[ASTNode]) -> usize {
|
||||
}
|
||||
c
|
||||
}
|
||||
if count(body) > 0 { 1 } else { 0 }
|
||||
if count(body) > 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_body(body: &[ASTNode]) -> LoopPatternKind {
|
||||
@ -102,7 +124,10 @@ fn classify_body(body: &[ASTNode]) -> LoopPatternKind {
|
||||
#[test]
|
||||
fn pattern1_simple_while_is_detected() {
|
||||
// loop(i < len) { i = i + 1 }
|
||||
let body = vec![assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1)))];
|
||||
let body = vec![assignment(
|
||||
var("i"),
|
||||
bin(BinaryOperator::Add, var("i"), lit_i(1)),
|
||||
)];
|
||||
let kind = classify_body(&body);
|
||||
assert_eq!(kind, LoopPatternKind::Pattern1SimpleWhile);
|
||||
}
|
||||
@ -211,7 +236,7 @@ fn test_atoi_loop_classified_as_pattern2() {
|
||||
let mul_expr = bin(BinaryOperator::Multiply, var("result"), lit_i(10));
|
||||
let result_update = assignment(
|
||||
var("result"),
|
||||
bin(BinaryOperator::Add, mul_expr, var("digit_pos"))
|
||||
bin(BinaryOperator::Add, mul_expr, var("digit_pos")),
|
||||
);
|
||||
|
||||
// i = i + 1
|
||||
@ -229,6 +254,9 @@ fn test_atoi_loop_classified_as_pattern2() {
|
||||
];
|
||||
|
||||
let kind = classify_body(&body);
|
||||
assert_eq!(kind, LoopPatternKind::Pattern2Break,
|
||||
"_atoi loop should be classified as Pattern2 (Break) due to if-break structure");
|
||||
assert_eq!(
|
||||
kind,
|
||||
LoopPatternKind::Pattern2Break,
|
||||
"_atoi loop should be classified as Pattern2 (Break) due to if-break structure"
|
||||
);
|
||||
}
|
||||
|
||||
@ -275,7 +275,12 @@ mod tests {
|
||||
let helper = TrimLoopHelper {
|
||||
original_var: "ch".to_string(),
|
||||
carrier_name: "is_whitespace".to_string(),
|
||||
whitespace_chars: vec![" ".to_string(), "\t".to_string(), "\n".to_string(), "\r".to_string()],
|
||||
whitespace_chars: vec![
|
||||
" ".to_string(),
|
||||
"\t".to_string(),
|
||||
"\n".to_string(),
|
||||
"\r".to_string(),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(helper.whitespace_count(), 4);
|
||||
|
||||
Reference in New Issue
Block a user