Fix infinite loop in Pattern6 (nested loop minimal) caused by main→loop_step overwriting k_inner_exit→loop_step latch values. Root cause: JoinIR main entry block was incorrectly treated as BackEdge, causing it to overwrite the correct latch incoming values set by the true back edge (k_inner_exit → loop_step). Solution: - Restrict latch recording to TailCallKind::BackEdge only - Treat only MAIN's entry block as entry-like (not loop_step's entry block) - Add debug_assert! to detect double latch set in future Refactoring: - Extract latch recording to latch_incoming_recorder module (SSOT) - Add boundary.loop_header_func_name for explicit header identification - Strengthen tail_call_classifier with is_source_entry_like parameter Tests: apps/tests/phase1883_nested_minimal.hako → RC:9 (was infinite loop) Smoke: 154/154 PASS, no regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
JoinIR Lowering (ExprLowerer / ScopeManager / Envs)
このディレクトリは JoinIR lowering の中でも、条件式や環境まわりの箱(ExprLowerer, ScopeManager, ConditionEnv, LoopBodyLocalEnv, UpdateEnv など)を扱う層だよ。コードを触るときは、以下の最小ルールを守ってね。
- ExprLowerer は ScopeManager 経由のみ で名前解決する。ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo に直接触らない。
- 条件式から UpdateEnv を参照しない。UpdateEnv はキャリア更新専用で、header/break/continue 条件は ScopeManager→ConditionEnv で完結させる。
- ConditionEnv は「条件で参照する JoinIR ValueId だけ」を持つ。body-local を直接入れず、必要なら昇格+ScopeManager に解決を任せる。
- Fail-Fast 原則: Unsupported/NotFound は明示エラーにして、by-name ヒューリスティックや静かなフォールバックは禁止。
名前解決の境界(SSOT)
このディレクトリの ScopeManager は「JoinIR lowering の中で」名前を ValueId に解決するための箱だよ。
同じ “名前” でも、MIR 側の束縛寿命とは問題が違うので混ぜない。
- MIR(SSA/束縛寿命):
src/mir/builder/vars/*が{...}のレキシカルスコープとlocalのシャドウイングを管理する。 - JoinIR lowering(この層):
ScopeManagerがConditionEnv/LoopBodyLocalEnv/CapturedEnv/CarrierInfoを束ねて解決順序を固定する。 - 解析箱:
LoopConditionScopeBoxは「条件が参照して良いスコープか」を判定する箱で、名前解決そのものはしない。
詳しい境界ルールは docs/development/current/main/phase238-exprlowerer-scope-boundaries.md を参照してね。***