feat(joinir): phase 100 P1 - pinned local analyzer and wiring into CapturedEnv

- Implement PinnedLocalAnalyzer box to identify pinned loop-outer locals
  * Pure AST analysis (no MIR dependencies)
  * Detects locals: defined before loop, referenced in loop, NOT assigned in loop
  * 5 unit tests covering all edge cases (no assignment, assigned, empty body, etc.)
- Integrate PinnedLocalAnalyzer into pattern2_with_break.rs
  * Call analyzer with loop body AST and candidate locals from variable_map
  * Wire pinned locals into CapturedEnv with CapturedKind::Pinned
  * Fail-Fast on host_id lookup failure or analyzer errors
- Update loop_body_local_init.rs resolver to search CapturedEnv
  * New search order: LoopBodyLocalEnv → ConditionEnv → CapturedEnv
  * Access via cond_env.captured (already integrated in ConditionEnv)
  * Updated error message to show full search order
- All existing tests pass (1101 passed, 1 unrelated failure)
- Smoke tests verified: phase96_json_loader_next_non_ws_vm, phase94_p5b_escape_e2e

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-17 05:32:35 +09:00
parent 0c7ea21cac
commit 0661e92225
4 changed files with 386 additions and 2 deletions

View File

@ -134,6 +134,56 @@ fn prepare_pattern2_inputs(
}
}
// Phase 100 P1-3: Pinned Local Analysis (Judgment Box)
// Analyze loop body AST to identify pinned locals (read-only loop-outer locals)
let mut captured_env = captured_env; // Make mutable for pinned insertions
// Collect candidate locals from variable_map (all variables defined before loop)
let candidate_locals: std::collections::BTreeSet<String> =
builder.variable_ctx.variable_map.keys().cloned().collect();
if !candidate_locals.is_empty() {
use crate::mir::loop_pattern_detection::pinned_local_analyzer::analyze_pinned_locals;
match analyze_pinned_locals(body, &candidate_locals) {
Ok(pinned_names) => {
if verbose && !pinned_names.is_empty() {
log.log(
"phase100_p1",
format!("Detected {} pinned locals", pinned_names.len()),
);
}
// Wire pinned locals into CapturedEnv
for pinned_name in pinned_names {
// Look up host ValueId from variable_map
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&pinned_name) {
if verbose {
log.log(
"phase100_p1",
format!(
"Wiring pinned local '{}' with host_id={:?}",
pinned_name, host_id
),
);
}
captured_env.insert_pinned(pinned_name, host_id);
} else {
// Fail-Fast: host_id not found
return Err(format!(
"Pinned local '{}' not found in variable_map (internal error)",
pinned_name
));
}
}
}
Err(e) => {
// Fail-Fast: analyzer error
return Err(format!("Pinned local analysis failed: {}", e));
}
}
}
// Value space + condition env
let mut join_value_space = JoinValueSpace::new();
let (mut env, mut condition_bindings, _loop_var_join_id) =