feat(joinir): Phase 245C - Function parameter capture + test fix

Extend CapturedEnv to include function parameters used in loop conditions,
enabling ExprLowerer to resolve variables like `s` in `loop(p < s.length())`.

Phase 245C changes:
- function_scope_capture.rs: Add collect_names_in_loop_parts() helper
- function_scope_capture.rs: Extend analyze_captured_vars_v2() with param capture logic
- function_scope_capture.rs: Add 4 new comprehensive tests

Test fix:
- expr_lowerer/ast_support.rs: Accept all MethodCall nodes for syntax support
  (validation happens during lowering in MethodCallLowerer)

Problem solved: "Variable not found: s" errors in loop conditions

Test results: 924/924 PASS (+13 from baseline 911)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-11 13:13:08 +09:00
parent 00ecddbbc9
commit d4597dacfa
40 changed files with 2386 additions and 1046 deletions

View File

@ -57,6 +57,17 @@ pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext)
return false;
}
// Debug: show routing decision when requested
if ctx.debug {
trace::trace().debug(
"pattern2/can_lower",
&format!(
"pattern_kind={:?}, break_count={}, continue_count={}",
ctx.pattern_kind, ctx.features.break_count, ctx.features.continue_count
),
);
}
// Phase 188/Refactor: Use common carrier update validation
// Extracts loop variable for dummy carrier creation (not used but required by API)
let loop_var_name = match builder.extract_loop_variable_from_condition(ctx.condition) {
@ -533,7 +544,7 @@ impl MirBuilder {
// This is a VALIDATION-ONLY step. We check if ExprLowerer can handle the condition,
// but still use the existing proven lowering path. Future phases will replace actual lowering.
{
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, ScopeManager};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::join_ir::lowering::expr_lowerer::{ExprLowerer, ExprContext, ExprLoweringError};
let scope_manager = Pattern2ScopeManager {
@ -651,3 +662,81 @@ impl MirBuilder {
Ok(Some(void_val))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinaryOperator, LiteralValue, Span};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::loop_pattern_detection::LoopPatternKind;
fn span() -> Span {
Span::unknown()
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: span(),
}
}
fn lit_i(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: span(),
}
}
fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: op,
left: Box::new(left),
right: Box::new(right),
span: span(),
}
}
#[test]
fn parse_number_like_loop_is_routed_to_pattern2() {
let condition = bin(BinaryOperator::Less, var("p"), var("len"));
let break_cond = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
let body = vec![
ASTNode::If {
condition: Box::new(break_cond),
then_body: vec![ASTNode::Break { span: span() }],
else_body: None,
span: span(),
},
ASTNode::Assignment {
target: Box::new(var("p")),
value: Box::new(bin(
BinaryOperator::Add,
var("p"),
lit_i(1),
)),
span: span(),
},
// num_str = num_str + ch (string append is allowed by CommonPatternInitializer)
ASTNode::Assignment {
target: Box::new(var("num_str")),
value: Box::new(bin(
BinaryOperator::Add,
var("num_str"),
var("ch"),
)),
span: span(),
},
];
let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true);
let builder = MirBuilder::new();
assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern2Break);
assert!(
can_lower(&builder, &ctx),
"Pattern2 lowerer should accept JsonParser-like break loop"
);
}
}

View File

@ -127,9 +127,15 @@ impl MirBuilder {
&mut join_value_space,
)?;
eprintln!("[pattern3/if-sum] Phase 220-D: ConditionEnv has {} bindings", condition_bindings.len());
trace::trace().debug(
"pattern3/if-sum",
&format!("ConditionEnv bindings = {}", condition_bindings.len()),
);
for binding in &condition_bindings {
eprintln!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value);
trace::trace().debug(
"pattern3/if-sum",
&format!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value),
);
}
// Call AST-based if-sum lowerer with ConditionEnv

View File

@ -360,6 +360,7 @@ mod tests {
use crate::ast::{BinaryOperator, LiteralValue, Span};
// Helper: Create a simple condition (i < 10)
#[allow(dead_code)]
fn test_condition(var_name: &str) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Less,