docs(joinir): Phase 232-239 documentation and ExprLowerer refinements

Documentation:
- Move completion reports to docs/archive/reports/
- Add phase232-238 design/inventory documents
- Update joinir-architecture-overview.md
- Add doc-status-policy.md

Code refinements:
- ExprLowerer: condition catalog improvements
- ScopeManager: boundary clarifications
- CarrierInfo: cleanup

🤖 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 00:21:29 +09:00
parent 13a676d406
commit 448bf3d8c5
44 changed files with 1372 additions and 79 deletions

View File

@ -19,15 +19,15 @@
- **Pattern2 統合**: break 条件の **pre-validation** として ExprLowerer を試行fallback 完備)
**成果**:
- ✅ 全 Pattern2 テスト PASS890/897 tests passing
- ✅ 全 Pattern2 テスト PASS最新: cargo test --release で 894/897 緑、残りは array_filter 系 3 件のみ
- ✅ ExprLowerer が簡単な条件式(`i >= 5` など)を正常に検証
- ✅ 複雑なパターンは UnsupportedNode エラーで legacy path へ fallbackFail-Safe 設計)
- ✅ 箱化・モジュール化の原則に準拠ScopeManager は trait、ExprLowerer は再利用可能)
**次のステップ**:
- Phase 232: ExprLowerer を Pattern1/Pattern3 にも拡大(検証専用)
- Phase 233: ExprLowerer で実際の lowering を置き換えfallback 削除
- Phase 234+: MethodCall / NewBox など General context 対応
- Phase 232 ✅: 7 FAIL の棚卸しP0/P1/P2 分類)完了
- Phase 233: loop_update_summary テストを AST ベース API に揃えて Phase 219 以降の設計に追随(テスト負債の解消
- Phase 234+: MethodCall / NewBox など General context 対応ExprLowerer 拡張案)
### 1. JoinIR ループ基盤の状態
@ -89,6 +89,13 @@
- **Phase 223228 リファクタ調査**: ConditionAlias 冗長性や ConditionOnly フィルタ重複を棚卸し、phase223-228-refactoring-opportunities.md / phase229-action-plan.md に統合。
- **Phase 229**: digit_pos P2 ラインを「インフラ完成」で固定し、数値ロジックは次フェーズへ送るRC=0 は仕様)。
- **Phase 230**: ExprLowerer/ScopeManager 設計docs のみで、条件式/Init/Update lowering を統合するためのインターフェース設計)。
- **Phase 232**: 残り 7 FAIL を「箱 / パターン / レイヤ」ごとに棚卸しし、P0/P1/P2 に分類して次フェーズcore 修正かパターン拡張か)を選びやすくする設計フェーズ(基本 docs のみ、ArrayExtBox.filter 系 3 件を P2 に残す整理)。
- **Phase 233**: loop_update_summary テストを AST ベース API に刷新し、deprecated wrapper 依存の FAIL を解消7→3 FAIL
- **Phase 234**: ArrayExtBox.filter 系 3 FAIL について、P3 if-PHI で正式サポートするか当面 Fail-Fast として残すかを docs ベースで決める設計フェーズ(実装は後続 Phase 24x 以降に送る)。
- **Phase 235**: ExprLowerer/ScopeManager のユニットテストを追加しつつ、Pattern2 の break 条件に validation-only で組み込み、既存の condition lowering と挙動が変わらないことを確認するパイロットフェーズ。
- **Phase 236-EX**: Pattern2 break 条件の lowering を ExprLowerer/ScopeManager 経由の本番経路に切り替え、全 E2E テストの挙動RC/ログ)が従来と一致するかを確認するフェーズ(差分が出た場合は docs に記録して設計見直しに回す)。
- **Phase 237-EX**: JsonParser/selfhost のループ条件・break/continue 条件を棚卸しし、ExprLowerer/ScopeManager で優先対応すべきパターンをカタログ化する設計フェーズ(コード変更なし)。
- **Phase 238-EX**: ExprLowerer/ScopeManager/ConditionEnv/LoopBodyLocalEnv/UpdateEnv の責務と参照範囲を文書化し、「誰がどこまで見てよいか」を SSOT として固定するガイドライン整備フェーズ(コード変更なし)。***
### 2. JsonParser / Trim / selfhost への適用状況

View File

@ -132,3 +132,4 @@ Phase 61-5.2 で作成した If PHI 関数表に削減優先度P1/P2/P3
- Phase 61-2: [if_phi_spec.rs](../../../src/mir/join_ir/lowering/if_phi_spec.rs)
- Phase 61-3: [if_in_loop/mod.rs](../../../src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs)
- PHI Inventory: [PHI_BOX_INVENTORY.md](../../../docs/private/roadmap2/phases/phase-30-final-joinir-world/PHI_BOX_INVENTORY.md)
Status: Historical

View File

@ -129,3 +129,4 @@ Phase 61-3 で JoinIR 経路による If-in-Loop PHI 生成が完全動作した
### 合計削減見込み: 76行
### 期待成果: JoinIR SSOT 確立、PhiBuilderBox 観察コード完全削除
Status: Historical

View File

@ -111,3 +111,4 @@ Phase 61-5.3 の優先度表から、Phase 61-6If PHI JoinIR 化 第2弾
- PHI Inventory: [PHI_BOX_INVENTORY.md](../../../docs/private/roadmap2/phases/phase-30-final-joinir-world/PHI_BOX_INVENTORY.md)
- If PHI Context: [if_phi_context.rs](../../../src/mir/join_ir/lowering/if_phi_context.rs)
- If PHI Spec: [if_phi_spec.rs](../../../src/mir/join_ir/lowering/if_phi_spec.rs)
Status: Historical

View File

@ -432,3 +432,4 @@ Phase 84-2 の CopyTypePropagator により 12件 → 9件に削減成功。
- **GroupC**: await 特殊1件→ 暫定対応で解決可能
Phase 84-3/4/5 の実装により、**Case D を完全解決** できる見込み。
Status: Historical

View File

@ -388,3 +388,4 @@ NYASH_PHI_FALLBACK_DISABLED=1 cargo test --release --lib 2>&1 | grep "Case D"
cargo test --release --lib
# 期待: test result: ok
```
Status: Historical

View File

@ -209,3 +209,4 @@ cargo test --release --lib
- [CopyTypePropagator 実装](../../../src/mir/phi_core/copy_type_propagator.rs)
- [GenericTypeResolver](../../../src/mir/join_ir/lowering/generic_type_resolver.rs)
- [lifecycle.rs 統合箇所](../../../src/mir/builder/lifecycle.rs:371)
Status: Historical

View File

@ -118,3 +118,4 @@ RUST_BACKTRACE=1 cargo test --release --lib <test_name>
- [詳細調査](./phase84-2-case-d-investigation.md)
- [サマリー](./phase84-2-summary.md)
- [CopyTypePropagator 実装](../../../src/mir/phi_core/copy_type_propagator.rs)
Status: Historical

View File

@ -518,3 +518,4 @@ NYASH_PHI_FALLBACK_DISABLED=1 cargo test --release --lib 2>&1 | grep "Case D" |
**総削減見込み**:
- 12件初期→ 0件Phase 84-5 完了時)
- **100%削減達成!**
Status: Historical

View File

@ -206,3 +206,4 @@ NYASH_PHI_FALLBACK_DISABLED=1 cargo test --release --lib mir_lowering_of_qmark_p
**総削減見込み**:
- 12件初期→ 0件Phase 84-5 完了時)
- **100%削減達成へ!**
Status: Historical

View File

@ -408,3 +408,4 @@ cargo test --release --lib 2>&1 | grep "infer_type_from_phi_fallback"
- ✅ 型推論システムの完全箱化達成
**次のステップ**: Phase 84-5if_phi.rs 完全削除)
Status: Historical

View File

@ -373,3 +373,4 @@ pub fn resolve_from_phi_recursive(
**期待される最終結果**:
- Case D: 24件 → **0-2件**90-100%解決)
- テスト成功率: 471/523 (90%) → **519-521/523 (99-100%)**
Status: Historical

View File

@ -235,3 +235,4 @@ Phase 84-3/4/5 の実装により、**Case D を完全解決** できる見込
---
**次のアクション**: Phase 84-3 の Edge Copy 追跡 PHI 型推論を実装
Status: Historical

View File

@ -182,3 +182,4 @@ pub fn emit_integer(b: &mut MirBuilder, val: i64) -> ValueId {
完全な分析レポートは以下を参照:
- [phase84-case-d-detailed-analysis.md](./phase84-case-d-detailed-analysis.md)
Status: Historical

View File

@ -425,3 +425,4 @@ Phase 84 プロジェクトは完全達成しました!🎉
**完了日時**: 2025-12-02
**実装者**: Claude (Phase 84-5)
**Git Commit**: (次のコミットで記録)
Status: Historical

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 166 Validation Report: JsonParserBox Unit Test with BoolExprLowerer
**Date**: 2025-12-06 (Updated: 2025-12-07 Phase 170)

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 170: JsonParserBox JoinIR Preparation & Re-validation - Completion Report
**Date**: 2025-12-07

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 171: JoinIR → MIR ValueId Boundary Mapping Fix - Completion Report
**Date**: 2025-12-07

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 176-1: Pattern2 Limitation Investigation - Completion Report
**Date**: 2025-12-08

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 176: Pattern2 Multi-Carrier Lowering - 完了レポート
**日付**: 2025-12-08

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 182 Completion Report: JsonParser Simple Loop Implementation (P2/P1 Verification)
**Date**: 2025-12-08

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 185 Completion Report: Body-local Pattern2 Integration (Partial)
**Date**: 2025-12-09

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 33-17: JoinIR Modularization - Final Report
## Executive Summary

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 84-3: PhiTypeResolver 実装完了報告書
**日付**: 2025-12-02

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# Phase 84-4: BoxCall型情報登録 完了報告書
**日付**: 2025-12-02

View File

@ -1,3 +1,5 @@
Status: VerificationReport, Historical
# selfhost_exe_stageb.sh End-to-End Verification Report
**Date**: 2025-11-11

View File

@ -27,7 +27,9 @@
- **進行状況**: `current/CURRENT_TASK.md`
- **開発計画**: `roadmap/phases/`
- **技術提案**: `proposals/`
- **ドキュメントのステータス/入口ポリシー**: `doc-status-policy.md`
- **JoinIR / Selfhost の入口**: `current/main/01-JoinIR-Selfhost-INDEX.md`
## 📝 注意事項
このディレクトリの内容は開発中であり、頻繁に変更されます。
安定した仕様は`reference/`を参照してください。
安定した仕様は`reference/`を参照してください。

View File

@ -16,6 +16,8 @@
## JoinIR / Selfhost 関連の入口
- 「JoinIR / Selfhost まわりで、まずどのドキュメントを読むべきか」は
`docs/development/current/main/01-JoinIR-Selfhost-INDEX.md` を入口として使ってね。
- JoinIR 全体のアーキテクチャと箱の関係は
`docs/development/current/main/joinir-architecture-overview.md`参照してね
- selfhost / .hako 側から JoinIR を使うときも、この設計を SSOT として運用する方針だよ。
`docs/development/current/main/joinir-architecture-overview.md` SSOT として参照するよ
- selfhost / .hako 側から JoinIR を使うときも、この JoinIR 設計を前提にして設計・実装する方針だよ。

View File

@ -0,0 +1,71 @@
# JoinIR / Selfhost INDEX読み始めガイド
Status: Active
Scope: JoinIR と SelfhostStageB/Stage1/Stage3に関する「最初に読むべき現役ドキュメント」だけを集約した入口。
このファイルは、JoinIR と Selfhost ラインの主戦場をすばやく把握するためのインデックスだよ。
歴史メモや詳細な Phase 文書に飛ぶ前に、まずここに載っている現役ドキュメントから辿っていくことを想定しているよ。
---
## 1. まず全体像だけ掴みたいとき
- JoinIR 全体像SSOT
- `docs/development/current/main/joinir-architecture-overview.md`
- Selfhost / StageB〜3 の代表フロー
- `docs/development/current/main/selfhost_stage3_expected_flow.md`
- 「いまどこまで進んでいるか」の現状サマリ
- `docs/development/current/main/10-Now.md`
- 「JoinIR / Loop / If ライン」
- 「JsonParser / Selfhost depth2 ライン」
---
## 2. JoinIR をこれから触る人向け
JoinIR の箱構造と責務、ループ/if の lowering パターンを把握したいときの読み順だよ。
1. JoinIR の基本設計SSOT
- `docs/development/current/main/joinir-architecture-overview.md`
2. ループパターン空間とパターン番号の意味
- `docs/development/current/main/loop_pattern_space.md`
3. Boundary / ExitLine / Carrier の具体パターン
- `docs/development/current/main/joinir-boundary-builder-pattern.md`
4. 代表的な Phase 文書(現役ラインとの接点だけ絞ったもの)
- `docs/development/current/main/phase33-16-INDEX.md`
- `docs/development/current/main/phase33-17-joinir-modularization-analysis.md`
- `docs/development/current/main/phase183-selfhost-depth2-joinir-status.md`
Phase 文書は歴史や検証ログも含むので、「JoinIR の現役設計を確認した上で、必要なときだけ掘る」という前提で読んでね。
---
## 3. SelfhostStageB / Stage1 / Stage3を触る人向け
自己ホストコンパイラのフローや実行手順、Ny Executor ラインの計画を押さえたいときの読み順だよ。
1. Selfhost 全体フローStageB / Stage1 / Stage3 と JSON v0
- `docs/development/current/main/selfhost_stage3_expected_flow.md`
2. 実行手順・クイックスタート
- `docs/development/selfhosting/quickstart.md`
- `docs/development/testing/selfhost_exe_stageb_quick_guide.md`
3. Ny ExecutorNy で MIR(JSON v0) を実行)のロードマップ
- `docs/development/roadmap/selfhosting-ny-executor.md`
4. Stage3 / depth2 関連で「現役」として参照する Phase 文書
- `docs/development/current/main/phase150_selfhost_stage3_depth1_baseline.md`
- `docs/development/current/main/phase150_selfhost_stage3_depth1_results.md`
- `docs/development/current/main/phase183-selfhost-depth2-joinir-status.md`
- `docs/development/current/main/phase120_selfhost_stable_paths.md`
---
## 4. 迷ったときの読み分けガイド
- JoinIR の箱や契約で迷っているとき
- → 2章の 1〜3 をこの順番で読む。
- Selfhost のビルド / 実行フローで迷っているとき
- → 3章の 1〜3 をこの順番で読む。
- 「この Phase 文書は現役か?」で迷ったとき
- → まず `docs/development/current/main/10-Now.md`
`docs/development/current/main/30-Backlog.md` を確認し、そこで名前が挙がっている Phase 文書を優先して読んでね。

View File

@ -367,7 +367,6 @@ Local Region (1000+):
- **Two-tier promotion**: Step1 で A-3 Trim 試行 → 失敗なら Step2 で A-4 DigitPos 試行 → 両方失敗で Fail-Fast。
- **DigitPosPromoter 統合**: cascading indexOf パターンsubstring → indexOf → comparisonの昇格をサポート。
- **Unit test 完全成功**: 6/6 PASSpromoter 自体は完璧動作)。
- **Phase 224-D**: ConditionAlias/CarrierInfo との連携により、昇格済み LoopBodyLocal 名(`digit_pos` 等)を ConditionEnv から見えるようにブリッジ。
- **残りの制約**: body-local init の MethodCall`substring` 等)の lowering は Phase 193/224-B/C のスコープ外で、今後の Phase で対応。
- 設計原則:
- **Thin coordinator**: 専門 PromoterLoopBodyCarrierPromoter / DigitPosPromoterに昇格ロジックを委譲。
@ -405,7 +404,7 @@ Local Region (1000+):
- **単体テスト**: 5/5 PASShappy path, wrong operator/variable/constant, non-binary-op
- **E2E テスト**: `phase2235_p2_digit_pos_min.hako` で型エラー解消確認。
- **回帰テスト**: digitpos (11 tests), trim (32 tests) 全て PASS。
- **digit_pos 正規化ライン**: DigitPosPromoter + ConditionAlias + DigitPosConditionNormalizer で `digit_pos < 0` を bool キャリア `is_digit_pos` ベースの条件(`!is_digit_pos`)に直してから ConditionEnv / BoolExprLowerer へ渡す。
- **digit_pos 正規化ライン**: DigitPosPromoter + CarrierInfo.promoted_loopbodylocals + DigitPosConditionNormalizer で `digit_pos < 0` を bool キャリア `is_digit_pos` ベースの条件(`!is_digit_pos`)に直してから ConditionEnv / ConditionPatternBox / ExprLowerer 系の条件 lowering に渡す。
- 参考:
- 設計ドキュメント: `docs/development/current/main/phase224-digitpos-condition-normalizer.md`
- 実装サマリ: `docs/development/current/main/PHASE_224_SUMMARY.md`
@ -799,6 +798,27 @@ Phase 210221 で「数値ループif-sum」を実戦投入し、JoinIR イ
- **Phase 230ExprLowerer / ScopeManager 設計フェーズ)**
- 目標: 条件式 / init 式 / carrier 更新式の lowering を将来ひとつの ExprLowerer + ScopeManager に統合できるよう、既存の散在する lowering/API/Env を設計レベルで整理する(このフェーズではコード変更なし)。
- **Phase 233loop_update_summary テスト刷新フェーズ)**
- 目標: deprecated `analyze_loop_updates()` 依存のテストを廃止し、`analyze_loop_updates_from_ast()` に揃えたユニットで if-sum 判定ラインの期待値を固定する。
- **Phase 232Failing Tests の棚卸しフェーズ)**
- 目標: `cargo test --release` で残っている 7 件の FAIL を「どの箱 / どのパターン / どのレイヤ」の問題かで整理し、P0/P1/P2 に分類して次フェーズのターゲットを決めるcore バグを見つけるフェーズではなく、未対応領域の可視化フェーズ)。
- **Phase 234ArrayFilter / Pattern3 設計フェーズ)**
- 目標: ArrayExtBox.filter 系 3 テストを P3 if-PHI の正式対象に含めるか、当面は PoC 領域として Fail-Fast を仕様として維持するかを docs ベースで決める(コード変更なし)。
- **Phase 235ExprLowerer / ScopeManager パイロット実装 & テスト)**
- 目標: ExprLowerer/ScopeManager の Condition 文脈を小さな範囲でユニットテストしつつ、Pattern2 の break 条件で validation-only の経路として動かし、既存の legacy lowering と挙動が一致することを確認する(本番 lowering の置き換えは後続フェーズ)。
- **Phase 236-EXExprLowerer / ScopeManager 本番導入 - Pattern2 break 条件)**
- 目標: Pattern2 の break 条件 lowering を ExprLowerer/ScopeManager 経由の本番経路に切り替え、既存の condition_to_joinir ベース実装と RC / JoinIR 構造 / ログ挙動が一致することを確認する(影響範囲は Pattern2 break 条件のみに限定)。
- **Phase 237-EXExprLowerer 条件パターン棚卸しフェーズ)**
- 目標: JsonParser / selfhost のループ条件・break/continue 条件をカタログ化し、ExprLowerer/ScopeManager で優先的に扱うパターンと後回しにするパターンを SSOT 化する(コード変更なし)。
- **Phase 238-EXExprLowerer / ScopeManager Scope Boundaries**
- 目標: ExprLowerer / ScopeManager / ConditionEnv / LoopBodyLocalEnv / UpdateEnv の責務と参照範囲を文書化し、「誰がどこまで見てよいか」を SSOT として固定する(コード変更なし、ガイドライン整備)。***
---
## 5. selfhost / .hako JoinIR Frontend との関係

View File

@ -162,3 +162,11 @@ Phase 230 の時点では「どの箱をどこに収めるか」をざっくり
結論として Phase 230 は、JoinIR ラインにおける「式」と「スコープ」の SSOT を見据えた
**設計フェーズ(ドキュメントのみ)** として完了させるイメージだよ。
補足Phase 231 時点の実装状況メモ):
- `src/mir/join_ir/lowering/expr_lowerer.rs` / `scope_manager.rs` には、Phase 231 のパイロット実装が入っている。
- コンテキストは `ExprContext::Condition` のみサポート。
- 実装は ScopeManager → ConditionEnv を構築してから、既存の `lower_condition_to_joinir` に委譲する薄いラッパー。
- `pattern2_with_break.rs` では **バリデーション専用(結果は捨てて、実際の lowering は従来経路)の呼び出し**になっている。
- 本ドキュメントは「最終的に目指す ExprLowerer/ScopeManager 像」の設計であり、Phase 231 の実装はあくまで
その前段階のパイロットという位置づけだよ(実装が完全にこの API 図のとおりになっているわけではない点に注意)。

View File

@ -0,0 +1,190 @@
# Phase 232: Failing Tests Inventory & Classification
このドキュメントは、2025-12 時点の `cargo test --release` で残っている 7 件の FAIL を
「どの箱/どのパターン/どのレイヤの問題か」で棚卸しするフェーズ用のメモだよ。
---
## 1. 失敗テスト一覧Task 232-1
ベースコマンド:
```bash
cargo test --release
```
このセッションでの FAIL は次の 7 件だったよ(当時: 890 passed / 7 failed / 64 ignored
**Update (Phase 233)**: `loop_update_summary` の 4 件を AST ベーステストに刷新して PASS 化し、いまは **894 passed / 3 failed / 64 ignored** だよ(残りは array_filter 系 3 件のみ)。
### 1.1 loop_update_summary 系ユニットテスト4 件 → Phase 233 で解消済み)
対象ファイル: `src/mir/join_ir/lowering/loop_update_summary.rs`
1. `mir::join_ir::lowering::loop_update_summary::tests::test_analyze_mixed`
- パターン: if-sum / Counter+Accumulation 検出ロジックP3 if-sum 用の分析箱)。
- 失敗種類: **期待値 mismatch名前ベースヒューリスティック vs 新 AST ベース実装)**
- 現行実装 `analyze_loop_updates()` は deprecated だが、テストはまだ古いヒューリスティック前提。
- 備考: 実運用では `analyze_loop_updates_from_ast()` が主経路Phase 219
2. `mir::join_ir::lowering::loop_update_summary::tests::test_analyze_single_counter`
- パターン: CounterOnly単一カウンタ判定。
- 失敗種類: **期待値 mismatchdeprecated API の挙動とテスト期待のズレ)**
- 備考: `analyze_loop_updates()` 内で kind を全て AccumulationLike にしているため、テストの CounterLike 期待と合わない。
3. `mir::join_ir::lowering::loop_update_summary::tests::test_is_simple_if_sum_pattern_basic`
- パターン: P3 if-sum の「i + sum」基本パターン検出。
- 失敗種類: **期待値 mismatchis_simple_if_sum_pattern の入力を deprecated wrapper で生成している)**
- 備考: 本番 if-sum 経路は Phase 219 以降、AST ベースの update 検出に移行済み。
4. `mir::join_ir::lowering::loop_update_summary::tests::test_is_simple_if_sum_pattern_with_count`
- パターン: P3 if-sum の「i + sum + count」マルチキャリアパターン。
- 失敗種類: **期待値 mismatch上と同様、テスト専用の古い summary 生成ロジック)**
Update (Phase 233): 上記 14 はすべて `analyze_loop_updates_from_ast()` を直接呼ぶ AST フィクスチャベースのテストに置き換え、`cargo test --release` で PASS になったよ。
### 1.2 ArrayExtBox.filter まわりの joinir mainline テスト3 件)
対象ファイル: `src/tests/joinir/mainline_phase49.rs`
5. `tests::joinir::mainline_phase49::phase49_joinir_array_filter_smoke`
- パターン: P3 if-phi / Array filter ループ(実戦寄り PoC
- 失敗種類: **[joinir/freeze] 系エラー → accumulator `'sum' not found in variable_map` で panic**
- メッセージ: `"[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map"`
- 備考: JoinIR mainline に route したあと、Pattern3 PoC lowerer が `sum` を carrier として見つけられず Fail-Fast。
6. `tests::joinir::mainline_phase49::phase49_joinir_array_filter_fallback`
- パターン: 同じ ArrayExtBox.filter の fallback 経路Structure-only / legacy PoC の確認用)。
- 失敗種類: **テスト側期待値「fallback compile should succeed」 vs 実際は Fail-Fast エラー**
- つまり「今は array_filter の JoinIR 対応をまだ本番とみなさない」状態をどう扱うかのポリシーの問題。
7. `tests::joinir::mainline_phase49::phase49_joinir_array_filter_ab_comparison`
- パターン: Array filter の route A/B 比較テスト。
- 失敗種類: **Route A compile should succeed → 同じ `'sum' not found` エラーで失敗**
- 備考: いずれも core バグというより「ArrayExtBox.filter の JoinIR 適用方針が途中」のフェーズ感のテストだよ。
---
## 2. 箱・レイヤごとの分類Task 232-2
ここでは「どのラインの箱から見た問題か」をざっくり割り当てるよ。
### 2.1 JoinIR core ライン
- ArrayExtBox.filter 系(テスト 57
- 主に Pattern3 if-phi / carrier detection / variable_map との橋渡しライン。
- エラー文言から見えるのは:
- `Accumulator variable 'sum' not found in variable_map`
-**LoopPattern + CarrierInfo + ExitLine の間の「carrier 定義/再接続」が足りない**
- ただし、これは「内部の SSOT が崩れている」というより、PoC lowerer と本線 lowerer が混在している歴史的ライン。
### 2.2 LoopPattern ライン
- loop_update_summary 系(テスト 14
- P3 if-sum / CaseA パターン検出の一部。
- 現行本番経路:
- AST ベースの `analyze_loop_updates_from_ast()` P3 if-sum 実装Phase 212/213/220
- テストが見ているのは:
- deprecated な `analyze_loop_updates()`(名前ベース)と、それにぶら下がる is_simple_if_sum_pattern の挙動。
- 位置づけとしては **LoopPattern (分析箱) の「旧 API テスト」**
### 2.3 表現ラインConditionEnv / ExprLowerer / MethodCallLowerer / LoopBodyLocalEnv
- 今回の 7 件の FAIL は、直接にはこの表現レイヤにはかかっていない:
- Type error / SSA-undef / alias 解決の失敗ではなく、
- 「古い分析 API のテスト期待値のまま」
- 「PoC lowerer の carrier 認識が足りない」
といったレベルの問題に収まっている。
- つまり Phase 232 時点では、ExprLowerer/ScopeManager まわりの **新実装が原因の FAIL はゼロ** という整理でよさそうだよ。
---
## 3. P0/P1/P2 分類Task 232-3
ここでは、次フェーズでどう扱うかの優先度分類をするよ。
### 3.1 P0: すぐ直したい JoinIR core バグ
- **今回は P0 該当なし** として扱うのがよさそう。
- 理由:
- SSA-undef / PHI 破損 / ValueId 衝突 / ExitLine 契約違反のような「core 崩壊系」の FAIL は含まれていない。
- ArrayExtBox.filter 系も「PoC lowerer の制約に当たって Fail-Fast」が明示的に出ているだけで、
既存の JsonParser / if-sum ラインを壊しているわけではない。
### 3.2 P1: パターン拡張で解決できるもの
候補としては次の 2 グループだよPhase 232 時点の整理; その後の更新を併記するね):
1. **loop_update_summary ユニット(テスト 14** → Phase 233 で解消済み
- 実際の本番 if-sum ラインは AST ベースで動いているので、
- これらのテストを「deprecated wrapper の振る舞いテスト」ではなく、
- `analyze_loop_updates_from_ast()` ベースのテストに差し替える
ことで自然に緑にできる。
- つまり「core バグ」ではなく **テストの更新/仕様の追随** で解消可能。
- **Phase 233 で実施済みPASS 化)**。
2. **ArrayExtBox.filter 系(テスト 57**
- Pattern3 if-phi / Accumulation パターンとして見れば、
- `sum` を carrier として認識する
- legacy PoC lowerer のエラー文言を **「意図的な Fail-Fast」か「サポートする方向」か** 決める
といった「パターン拡張 or ポリシー明確化」で解決可能。
- ただし、こちらは実戦 ArrayExtBox.filter の仕様をどうしたいかに依存するので、
先に loop_update_summary 側を P1 で触るほうがスコープが小さくて良さそう。
### 3.3 P2: 当面 Fail-Fast に残すもの
Phase 234 の結論:
- **ArrayExtBox.filter 系 3 テスト57は P2 に固定**(意図した Fail-Fast / 将来拡張候補)。
- 理由:
- ArrayFilter は「BoxCall ベース Accumulationout.push」であり、数値 if-sum ラインとは別系統の設計が必要。
- JsonParser / selfhost / ExprLowerer など、既に進行中のコアラインより優先して手を入れる理由がまだ弱い。
- 現状の `[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map` は、「P3 if-PHI の外側にある PoC 領域」を示すラベルとして有用。
- したがって Phase 234 では:
- コード変更は行わず、
- `phase234-array-filter-design.md` に ArrayFilter パターンと P3 if-PHI との関係、将来フェーズでの拡張案だけを整理する。
一方、loop_update_summary ユニット14は:
- P1「次にテストを更新するときに一緒に触る」として扱うのが妥当だよ。
- 本番経路は既に `analyze_loop_updates_from_ast()` を使っており、
deprecated wrapper の仕様を保つ必要は低い。
---
## 4. 次フェーズ候補Task 232-4
Phase 232 の整理を前提にした「次の軸」の候補だけメモしておくね(コードはまだ書かない)。
- **Phase 233P1: 分析箱テストのアップデート)**
- ねらい:
- `loop_update_summary.rs` のテスト 14 を `analyze_loop_updates_from_ast()` ベースに更新し、
実戦 if-sum ラインとテスト仕様を揃える。
- やることの例:
- AST フィクスチャ(簡単な if-sum ループ)を直接組んで `analyze_loop_updates_from_ast()` をテスト。
- deprecated `analyze_loop_updates()` のテストは「歴史メモ」扱いに移すか、最小限だけ残す。
- **Phase 234P1: ArrayExtBox.filter ラインの設計再検討)**
- ねらい:
- ArrayExtBox.filter を「P3 if-phi + Accumulation pattern」として本当に JoinIR で扱うかどうかを設計レベルで決める。
- やることの例:
- phase49 の mainline テストが期待しているシナリオを整理route A/B、fallback の位置づけ)。
- Pattern3 if-sum / array filter / map/filter 系の位置づけを `loop_pattern_space.md` に追記。
- まだコードには触らず、「やるならこういう箱に分ける」という案まで。
- **Phase 235P2: B 系パターンの扱い方の設計だけ)**
- ねらい:
- いまは Fail-Fast している B 系パターン(複雑ネスト / 多段メソッドチェーンなど)を
「今後も Fail-Fast のままにするのか」「別レイヤの箱で扱うか」を整理する。
- やることの例:
- `[joinir/freeze]` 系メッセージを洗い出して、B 系パターンの代表例を docs にまとめる。
- async / catch / filter/map 連鎖などを、JoinIR 本体ではなく上位レイヤに逃がす設計案を書くだけに留める。
---
## 5. まとめ
- 現状の 7 FAIL は:
- 4 件: **loop_update_summary の deprecated API テスト**Phase 233 で AST ベースに刷新し PASS 化済み)。
- 3 件: **ArrayExtBox.filter の PoC パターン3 lowerer に対する挙動確認テスト**(意図的 Fail-Fast に近い) ← 現在残っている FAIL。
- JoinIR corePHI/ValueId/ExitLine/SSAとしての致命的な崩れは含まれていないので、
Phase 232 の結論としては「**今の FAIL は “どこをまだやっていないか” を教えてくれるラベル**」とみなしてよさそうだよ。

View File

@ -0,0 +1,47 @@
# Phase 233: loop_update_summary テスト刷新
目的: deprecated な `analyze_loop_updates()` 依存の期待値を捨てて、Phase 219 で本番化した `analyze_loop_updates_from_ast()` に合わせてユニットテストを組み直すよ。ロジック本体は既に AST ベースで動いているので、テスト側の追随だけを行うフェーズだよ。
---
## 1. 旧 API (`analyze_loop_updates`) の前提
- 入力: carrier 名リストだけRHS や AST 構造を見ない)。
- ヒューリスティック: すべて AccumulationLike 扱い(名前ベースの Counter 推定は撤廃済み)。
- テストで見ていたもの:
- `test_analyze_single_counter` / `test_analyze_mixed`: 名前だけで Counter/Accumulation を判断する期待。
- `test_is_simple_if_sum_pattern_*`: deprecated wrapper で組んだ summary をそのまま `is_simple_if_sum_pattern` に渡していた。
- 問題: 本番 if-sum ラインは AST ベースに移行済みのため、これらのテストは現実の入力と乖離して FAIL していた。
## 2. 新 API (`analyze_loop_updates_from_ast`) の前提
- 入力: carrier 名リスト + ループ body の AST。
- 処理:
- `extract_assigned_variables` で実際に LHS に現れた変数だけを対象にする。
- `find_assignment_rhs` で RHS を拾い、`classify_update_kind_from_rhs` で CounterLike / AccumulationLike を決定。
- ループインデックスっぽい名前i/j/k/idx/index/pos/nだけは Counter 扱いに固定。
- 実運用:
- PatternPipelineContext から if-sum 判定に使われる唯一の経路。
- Phantom carrier 解消Phase 219の一環として既に配線済み。
## 3. テストケースの扱い(棚卸し結果)
- `test_analyze_single_counter`**AST ベースに組み直す**
- ループ body に `i = i + 1` を置き、`has_single_counter()` が真になることだけを確認。
- `test_analyze_accumulation`**AST ベースに置き換え**
- `sum = sum + i` のような RHS を与え、非 index 名が AccumulationLike になることを確認。
- `test_analyze_mixed`**Counter + Accumulation の AST で再構成**
- `i = i + 1``sum = sum + i` の 2 本で、counter=1 / accumulation=1 を確認。
- `test_is_simple_if_sum_pattern_basic`**if-body に accumulator 更新を置いた AST で検証**
- `if (cond) { sum = sum + i }` + `i = i + 1` の組み合わせで true を確認。
- `test_is_simple_if_sum_pattern_with_count`**accumulator 2 本の AST で検証**
- `sum` / `count` 両方が AccumulationLike になり、2 本まで許容する条件を確認。
- 旧 wrapper への期待は残さず、どうしても残す場合は `#[ignore]` で歴史テストにする方針。
---
## 4. 期待する着地
- ユニットテストは `analyze_loop_updates_from_ast()` を直接呼ぶ形に揃える。
- `is_simple_if_sum_pattern` は AST 由来の summary を入力として検証するだけにする(パターン検出ロジック本体の契約をテスト)。
- Phase 232 時点の FAIL4 件)は、テスト刷新で 0 件にする。***

View File

@ -0,0 +1,137 @@
# Phase 234: ArrayFilter / Pattern3 設計フェーズ
目的: ArrayExtBox.filter 系 3 テストPhase 49-4の役割と期待値を整理し、Pattern3 if-PHI として正式サポートするか、当面は Fail-Fast を仕様として維持するかを設計レベルで決めるフェーズだよ(コード変更なし)。
---
## 1. ArrayExtBox.filter テスト仕様Task 234-1
対象: `src/tests/joinir/mainline_phase49.rs`
### 1.1 phase49_joinir_array_filter_smoke
- 目的:
- dev フラグ `HAKO_JOINIR_ARRAY_FILTER_MAIN=1` で JoinIR Frontend mainline を経由したときに、ArrayExtBox.filter のコンパイルがクラッシュしないことを確認する。
- filter 実装の形:
- `filter(arr, pred)``out: ArrayBox`, `i`, `n` をローカルとして持つ。
- ループ本体:
- `loop(i < n) {`
- `v = arr.get(i)`
- `if pred(v) { out.push(v) }`
- `i = i + 1`
- `return out`
- 期待しているのは:
- 「Route B (JoinIR Frontend mainline) で compile が `Ok` を返すこと」(実行結果まではテストしていない)。
### 1.2 phase49_joinir_array_filter_fallback
- 目的:
- dev フラグ OFF`HAKO_JOINIR_ARRAY_FILTER_MAIN` 未設定のときは、従来のJoinIR mainline を通らない)経路でコンパイルが成功することを確認する。
- filter 実装の形:
- ソースは smoke と同一(同じ filter 実装)。
- 期待しているのは:
- dev フラグ OFF の Route A が「従来経路で compile `Ok`」になること。
- JoinIR mainline を切った状態での後方互換性チェック。
### 1.3 phase49_joinir_array_filter_ab_comparison
- 目的:
- ArrayExtBox.filter に対して Route Alegacyと Route BJoinIR Frontendを A/B 比較し、両経路とも compile だけは成功しつつ、生成される MIR ブロック数をログで観察する。
- filter 実装の形:
- ソースは前二つと同一。
- 期待しているのは:
- Route A: `HAKO_JOINIR_ARRAY_FILTER_MAIN` OFF で compile `Ok`
- Route B: `HAKO_JOINIR_ARRAY_FILTER_MAIN=1` で compile `Ok`
- 実行結果ではなく、コンパイル成功と大まかなブロック構造(ブロック数)の健全性を確認するテスト。
---
## 2. 既存 Pattern / Box との対応付けTask 234-2
### 2.1 パターン構造の分類
- loop 形:
- `loop(i < n) { ... i = i + 1 }` → ループ条件 `i < n` + カウンタ更新 `i = i + 1` は既存の Pattern2break/Pattern3if-PHIと同型。
- 条件分岐:
- `if pred(v) { out.push(v) }`
- `pred` はラムダ(関数)で、ループ内から呼び出される。
- `out.push(v)` は ArrayBox への蓄積Accumulationだが、ループの「キャリア」としては `out` をどう扱うかが問題になる。
- 変数役割(ざっくり):
- ループインデックス: `i`(既存 CounterLike
- ループ境界: `n`captured const 相当)
- 出力配列: `out`Array 型 accumulator
- 一時変数: `v`body-local
### 2.2 Pattern3 If-PHI との関係
- 既存の P3 if-sum:
- `if (cond) { sum = sum + x }` という「条件付き Accumulation」を、Counter (`i`) + Accumulator (`sum`) の 2 キャリアで扱う。
- LoopUpdateSummary + CarrierInfo + ExitLine で Counter/Accumulator を JoinIR の PHI / Exit に接続する。
- ArrayFilter の if:
- `if pred(v) { out.push(v) }` は、`sum = sum + x` の配列版filter 型 Accumulationに近い。
- ただし:
- Accumulator の更新が「BoxCallpush」であり、純粋な数値加算ではない。
- `out` はループ外でも使われるオブジェクト(ループ終了後に returnで、スカラ accumulator と扱いが異なる。
- まとめ:
- **構造的には P3 if-PHI と似ているが、更新が BoxCall ベースであり、現在の数値 if-sum パイプラインとは別系統の扱いが必要**。
### 2.3 既存箱でどこまで表現可能か
- PatternPipelineContext / CarrierInfo / ExitLine だけで無理なく扱えるか?
- ループキャリアとして `out` を登録し、ExitLine で `out` を JoinIR の戻り値に接続すること自体は可能そう。
- しかし、現行の P3 if-sum は「スカラ accumulator の PHI 合成」を前提にしており、配列の push を Accumulation として静的に扱うための契約SideEffect・Alias・BoxCall 許容範囲)が決まっていない。
- ラムダ内のループかどうか?
- 今回の ArrayExtBox.filter 実装は「ラムダの中にループがある」のではなく、「filter 本体にループがあり、ラムダ `pred` をコールする」形。
- したがって「ラムダ内のループ」という意味での特別扱いは不要だが、「Box メソッドとして公開されるループ」という観点では selfhost/JsonParser とは少し違うレイヤ。
---
## 3. 方針決定Task 234-3
ここでは **Phase 234 時点での方針** を決めるだけで、コードはまだ触らないよ。
### Option A: P3 If-PHI の正式対象としてサポートする
- やることのイメージ(将来フェーズで):
- P3 if-sum パイプラインを一般化して「BoxCall ベースの Accumulationpush など)」も扱えるようにする。
- CarrierInfo に「配列 accumulator」を追加し、ExitLine で `out` を JoinIR 経由で返り値に接続。
- Pattern3 lowerer に「ArrayFilter パターン」用のサブ箱を足すか、既存 Accumulation 判定を拡張する。
- 必要な投資:
- BoxCall の side-effect モデル(純粋な Accumulation として扱って安全か)を整理する。
- JsonParser / selfhost よりも優先する理由が必要現状は数値JsonParser ラインが主戦場)。
### Option B: 当面は Fail-Fast を仕様として維持するPhase 234 の結論)
- ここまでの整理から見えること:
- 現在の FAIL は `[cf_loop/pattern3] Accumulator variable 'sum' not found in variable_map` という **PoC lowerer の制約に当たっての Fail-Fast** であり、JoinIR corePHI/ValueId/ExitLineの崩れではない。
- JsonParser / selfhost / ExprLowerer ラインがまだ道半ばで、P3 if-sum も「数値 accumulator」を中心に設計されている。
- ArrayFilter のような「BoxCall ベースの Accumulation」を安全に一般化するには、BoxCall/side-effect モデルと LoopPattern の交差設計が必要で、Phase 234 の守備範囲を超える。
- したがって Phase 234 の結論としては:
- **ArrayExtBox.filter 系 3 テストは、当面「P3 if-PHI の外側にある PoC 領域」として Fail-Fast を仕様として維持する**。
- 将来フェーズ(例: Phase 24xで「Box ベース Accumulation / ArrayFilter パターン」を正式に設計するまでは、
- 今のエラーメッセージと FAIL は「未対応領域を示すラベル」として残す。
---
## 4. FAIL の位置づけ更新Task 234-4
- Phase 232 では array_filter 3 件を P1/P2 候補として挙げていたが、Phase 234 の結論に従って:
- **分類: P2意図的 Fail-Fast / 将来拡張候補)**
- ラベル: 「ArrayExtBox.filter / BoxCall ベース Accumulation パターン(将来の P3 拡張候補)」。
- `phase232-failing-tests-inventory.md` では:
- loop_update_summary 系 4 件は Phase 233 で解消済み。
- 残る array_filter 3 件は「Phase 234 の結論により、当面は Fail-Fast を仕様として維持する P2」として位置づける。
---
## 5. 次フェーズ候補(メモ)
- Phase 24x: ArrayFilter / BoxCall Accumulation の設計フェーズ
- ねらい:
- P3 if-PHI の「accumulator」概念を、数値 + 配列BoxCall pushまで拡張できるか検討する。
- 具体案:
- CarrierInfo に「Box ベース accumulator」を追加するかどうか。
- LoopPatternSpace に「filter パターン」を追加するか、既存 P3 のバリエーションとして扱うか。
- JsonParser / selfhost で類似パターンが出てきたときに、ArrayFilter と同じ箱で扱えるか評価する。
このフェーズPhase 234では、ここまでの設計メモだけに留めて、実装やテスト期待値の変更は行わないよ。***

View File

@ -0,0 +1,63 @@
# Phase 236-EX: ExprLowerer/ScopeManager 本番導入Pattern2 条件)
目的: Phase 231/235 でパイロット実装・テストしてきた ExprLowerer/ScopeManager を、Pattern2 の break 条件 lowering に実際に使ってみて、既存の condition_to_joinir ベース経路と挙動が一致することを確認するフェーズだよ。影響範囲は Pattern2 の「break 条件」だけに限定する。
---
## 1. 現状の経路Task 236-1 メモ)
### 1.1 関連ファイル
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
- Pattern 2 のルーティングと、`lower_loop_with_break_minimal` 呼び出しの入口。
- Phase 231 で ExprLowerer による **validation-only** 呼び出しが挿入されている。
- `src/mir/join_ir/lowering/loop_with_break_minimal.rs`
- Pattern2 本体の JoinIR ライン。
- `lower_loop_with_break_minimal()` の中で、`condition_to_joinir::lower_condition_to_joinir` を用いて
- ループ条件
- break 条件
の両方を JoinIR に lower している。
- `src/mir/join_ir/lowering/expr_lowerer.rs`
- Phase 231/235: ExprLowerer 本体。ScopeManager から ConditionEnv を組み立てて `condition_lowerer::lower_condition_to_joinir` に委譲する薄い箱。
- `src/mir/join_ir/lowering/scope_manager.rs`
- Phase 231: `Pattern2ScopeManager` 実装。ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo を束ねて、名前→ValueId / VarScopeKind を返す。
- `src/mir/join_ir/lowering/condition_to_joinir.rs`
- 既存の condition lowering オーケストレータ。`ConditionEnv` + `lower_condition_to_joinir` + 変数抽出をまとめた API。
### 1.2 現在の break 条件 lowering の流れ
1. `pattern2_with_break.rs` で:
- LoopScopeShape や LoopBodyLocalEnv を構築。
- ConditionEnvBuilder v2 相当で `env: ConditionEnv` を用意。
- `carrier_info` / `carrier_updates` など Pattern2 メタ情報を集約。
2. Phase 231 追加分:
- `Pattern2ScopeManager` を上記 env + body_local_env + captured_env + carrier_info から組み立てる。
- ExprLowerer を `ExprContext::Condition` で生成し、`effective_break_condition`**一度 lower してみるだけ**(結果 ValueId は捨てる)。
- UnsupportedNode / VariableNotFound などはログ出力にとどめ、実際の lowering には関与しない。
3. 本番 lowering:
- `lower_loop_with_break_minimal(...)` を呼び出し、
- その内部で `condition_to_joinir::lower_condition_to_joinir` により
- ループ条件 AST
- break 条件 AST
をそれぞれ `env: ConditionEnv` を使って JoinIR 命令列 & ValueId に変換している。
### 1.3 Phase 236 の狙い
- `loop_with_break_minimal.rs` 内の break 条件 lowering を、
- 「ConditionEnv → lower_condition_to_joinir」から、
- 「ScopeManager + ExprLowerer内部は既存 lower_condition_to_joinir を利用)」に置き換える。
- ループ条件(自然終了条件)の lowering は従来どおり condition_to_joinir 経路のまま保持し、差分を break 条件のみに限定する。
---
## 2. 次フェーズとのつながりPhase 237-EX 予告)
- Phase 237-EX では JsonParser/selfhost の条件パターンを棚卸しし、ExprLowerer/ScopeManager が今後どの条件を優先的に扱うかのカタログを作る予定だよ。
- 参照: `phase237-exprlowerer-condition-catalog.md`(条件パターン一覧と ExprLowerer サポート状況の SSOT
---
## 3. その先Phase 238-EX との接続)
- Phase 238-EX で Scope/Env の境界ルールを文書化し、ExprLowerer/ScopeManager/ConditionEnv/LoopBodyLocalEnv/UpdateEnv の責務を固定する予定。
- 参照: `phase238-exprlowerer-scope-boundaries.md`(境界ルールと将来のガード案)。***

View File

@ -0,0 +1,109 @@
# Phase 237-EX: ExprLowerer 条件パターンカタログJsonParser / selfhost
目的: JsonParser/selfhost に存在するループ条件・break/continue 条件を棚卸しし、ExprLowerer/ScopeManager で必ず扱いたいパターンと後回しにしてよいパターンを整理する。コード変更は行わず、設計 SSOT としてのカタログを作るフェーズだよ。
---
## 1. 対象範囲
- **JsonParser**: `tools/hako_shared/json_parser.hako` の主要ループ_parse_number / _parse_string / _parse_array / _parse_object / _skip_whitespace / _trim / _match_literal / _atoi
- **selfhost 代表ループ**: `apps/tests/phase190_atoi_impl.hako`, `apps/tests/phase190_parse_number_impl.hako`Stage-3/コンパイラ本体に類似する最小パターン)。
- 既存設計メモ: `phase230-expr-lowerer-design.md`, `phase230-expr-lowering-inventory.md`, `phase236-exprlowerer-integration.md`, `joinir-architecture-overview.md`
---
## 2. 条件パターン一覧(サマリ)
| ID | Source | Function/Loop | Pattern (P1P5) | Position | AST Pattern | Uses | ExprLowerer Support (P236時点) | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| JP-01 | json_parser.hako | _parse_number | P2 | header | `p < s.length()` | LoopParam + MethodCall(length) | PARTIAL | MethodCall(length)はBoolExprLowererで許容済み。ExprLowererではMethodCall未対応のため NO に近い扱い。 |
| JP-02 | json_parser.hako | _parse_number | P2 | break | `digit_pos < 0` | LoopBodyLocal(digit_pos) + OuterLocal(digits) | YES | Phase 223236 で昇格済み (ConditionOnly carrier経由)。 |
| JP-03 | json_parser.hako | _parse_string | P2-ish (early return) | break/return | `p < s.length()` / `ch == "\"" -> return` | LoopParam + MethodCall(substring/length) | PARTIAL | returnで抜ける構造。ExprLowererはMethodCall未対応なので当面 ConditionEnv 経由。 |
| JP-04 | json_parser.hako | _parse_array | P4 | header | `p < s.length()` | LoopParam + MethodCall(length) | PARTIAL | continue/returnを含む。MethodCall未対応。 |
| JP-05 | json_parser.hako | _parse_array | P4 | break/return | `ch == "]"` / `ch == ","` | LoopBodyLocal(ch) + MethodCall(substring) | PARTIAL | substring + equality。ExprLowererにMethodCall対応が必要。 |
| JP-06 | json_parser.hako | _parse_object | P4 | header | `p < s.length()` | LoopParam + MethodCall(length) | PARTIAL | 上と同様。 |
| JP-07 | json_parser.hako | _skip_whitespace | P4 | continue | `p < s.length()` + whitespace 判定 | LoopParam + MethodCall(substring) | PARTIAL | 文字比較のみ。MethodCall(substring)が鍵。 |
| JP-08 | json_parser.hako | _trim (leading) | P4 | continue | `start < end` + whitespace 判定 | LoopBodyLocal(start/end) + MethodCall(substring) | PARTIAL | 先頭/末尾トリム。 |
| JP-09 | json_parser.hako | _match_literal | P2 | header | `i < len` | LoopParam + OuterLocal(len) | YES | 純スカラ比較のみ。 |
| JP-10 | json_parser.hako | _atoi | P2 | header | `i < n` | LoopParam + OuterLocal(n) | YES | 純スカラ比較。 |
| SH-01 | phase190_atoi_impl.hako | main loop | P2 | header | `i < 10` | LoopParam | YES | ExprLowerer 条件対応済み。 |
| SH-02 | phase190_atoi_impl.hako | break | `i >= 3` | LoopParam | YES | 比較のみ。 |
| SH-03 | phase190_parse_number_impl.hako | header | `i < 10` | LoopParam | YES | 比較のみ。 |
| SH-04 | phase190_parse_number_impl.hako | break | `i > 3` | LoopParam | YES | 比較のみ。 |
※ Support 列の解釈: YES = Phase 236 までの ExprLowerer/condition_to_joinir で扱えるか実績あり、PARTIAL = BoolExprLowerer/ConditionEnv では対応しているが ExprLowerer で MethodCall 等が未実装、NO = まだ扱っていない。
---
## 3. パターン詳細JsonParser
- **_parse_number**
- header: `loop(p < s.length())` — MethodCall(length) を含む。現在は ConditionEnv + condition_to_joinir で処理、ExprLowererは MethodCall 未対応なので PARTIAL。
- break: `if digit_pos < 0 { break }` — digit_pos は body-local → ConditionOnly carrier として昇格済み。ExprLowerer 対応可。
- continue path: digit が見つかったら `p = p + 1` で次イテレーション。
- **_parse_string**
- header: `loop(p < s.length())` — MethodCall(length)。
- break/return: `if ch == "\"" { return ... }` / escape 判定など。MethodCall(substring) + equality の組み合わせ。
- **_parse_array**
- header: `loop(p < s.length())` — MethodCall(length)。
- 内部: `if ch == "]" { return ... }`, `if ch == "," { ...; continue }` — substring + equality。
- **_parse_object**
- header: `loop(p < s.length())` — MethodCall(length)。
- 内部: `if ch == "}" { return ... }` / `if ch == "," { continue }` — substring + equality。
- **_skip_whitespace**
- header: `loop(p < s.length())`
- continue: whitespace 判定 (`" " || "\t" || "\n" || "\r"`) → continue、そうでなければ break 相当。
- **_trim**
- leading trim: `loop(start < end)` + whitespace 判定 → continue/break。
- trailing trim: `loop(end > start)` + whitespace 判定 → continue/break。
- **_match_literal**
- header: `loop(i < len)`、body: 文字比較 + break。
- ExprLowerer で既に扱える純スカラ比較。
- **_atoi**
- header: `loop(i < n)`
- break: 非 digit を見つけたら break。
- 文字比較 + indexOf(digit) などが絡むが、条件自体は比較のみ。
---
## 4. パターン詳細selfhost 代表)
- **phase190_atoi_impl.hako**
- header: `loop(i < 10)`、break: `if i >= 3 { break }`
- 純スカラ比較のみ。ExprLowerer 本番経路で扱える。
- **phase190_parse_number_impl.hako**
- header: `loop(i < 10)`、break: `if i > 3 { break }`
- 純スカラ比較のみ。ExprLowerer 本番経路で扱える。
- selfhost コンパイラ本体の Stage-3 ループは JsonParser と同型/サブセットであることが多く、上記 JP パターンに準拠する想定。必要に応じて SH-xx を追加する。)
---
## 5. ExprLowerer/ScopeManager へのマッピング
- **YESPhase 236 時点で処理可能)**
- スカラ比較のみの header/break: `i < 10`, `i > 3`, `digit_pos < 0`(昇格済み ConditionOnly carrier
- `_match_literal`, `_atoi` のループ条件・break 条件のような純スカラ比較。
- **PARTIAL**
- MethodCall を含む header 条件: `p < s.length()`length 呼び出し)、`ch == s.substring(...)` など。
- substring/indexOf を含む break/continue 条件: `_parse_array/_object/_string` 内の `ch == "]"` / `ch == ","` 等。
- → ExprLowerer で MethodCall (length/substring/indexOf) を許可する拡張が必要。
- **NO未対応**
- return ベースでループを抜けるパターン_parse_string のように return で終了するケースは、ExprLowerer 以前に LoopPattern 側での扱いを要検討。
- 文字列操作が複合的に絡む条件escape 処理など)も当面は ConditionEnv + legacy lowerer 維持を推奨。
---
## 6. 次フェーズ候補(実装案のメモ)
- 候補 A: Pattern2 header 条件の MethodCall(length) を ExprLowerer で扱うJP-01/03/04/06 の header を統一)。
- 候補 B: substring + equality のシンプル条件を ExprLowerer に許可し、_parse_array/_object の `ch == "]"` / `ch == ","` を扱う。
- 候補 C: selfhost の P2 ループheader/breakを ExprLowerer 本番経路に順次寄せて、実戦ループのカバレッジを測定する。
- 候補 D: return ベースの終了条件を LoopPattern 側でどこまで扱うか設計し、ExprLowerer への影響範囲を決める。
---
## 7. 完了の定義Phase 237-EX
- JsonParser 11 ループの主要条件header/break/continue/if-guardが JP-xx 行としてカタログに掲載されている。
- selfhost 代表ループ 3〜5 例SH-xxが掲載されている。
- 各行に ExprLowerer Support (YES/PARTIAL/NO) と簡単な Notes が入っており、今後どの箱を触ればよいか判断できる。***

View File

@ -0,0 +1,60 @@
# Phase 238-EX: ExprLowerer/ScopeManager Scope Boundaries
目的: ExprLowerer / ScopeManager / ConditionEnv / LoopBodyLocalEnv / UpdateEnv の責務と参照範囲を文章で固定し、「誰がどこまで見てよいか」を SSOT 化する。コード変更は行わず、ガイドラインと境界ルールを整備するフェーズだよ。
---
## 1. 対象コンポーネント
- ExprLowerer条件式 lowering 箱)
- ScopeManager名前解決の窓口
- ConditionEnv条件式用の名前→ValueId マップ)
- LoopBodyLocalEnvbody-local init 専用の環境)
- UpdateEnvcarrier 更新専用の環境)
---
## 2. 境界ルール(原則)
- **名前解決は ScopeManager 経由のみ**: ExprLowerer は ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo に直接触らない。ScopeManager の lookup/scope_of を唯一の窓口とする。
- **条件式から UpdateEnv へは触らない**: header/break/continue 条件は carrier 更新専用の UpdateEnv にアクセスしない。更新式は UpdateEnv / LoopBodyLocalInitLowerer に限定。
- **ConditionEnv は「条件で参照する値のみ」**: loop var / carriers / ConditionOnly / captured const など、条件式で参照する JoinIR ValueId のみを持ち、body-local を直接含めない(昇格する場合は ScopeManager+CarrierInfo 経由)。
- **LoopBodyLocalEnv は init 専用**: body 内 local 定義の init 式だけを扱い、条件式の参照は ScopeManager が仲介する(条件側から直接参照しない)。
- **Fail-Fast / by-name 禁止**: ExprLowerer/ScopeManager は「名前ヒューリスティック」や silent fallback を行わず、Unsupported/NotFound は明示エラー(上位で Fail-Fast ポリシーに従う)。
---
## 3. JsonParser/selfhost 条件パターンへの対応237 カタログとの橋渡し)
- **優先対応YES/PARTIAL で拾いたいもの)**
- P2/P4 header の単純比較 (`i < n`, `p < s.length()`): LoopParam + OuterLocal/Captured/MethodCall(length)。ScopeManager で loop var / captured const を解決し、MethodCall 対応は将来 ExprLowerer 拡張で吸収。
- P2 break 条件 `digit_pos < 0`: body-local 昇格済み (ConditionOnly carrier)。ScopeManager が `digit_pos``is_digit_pos` を解決。
- substring + equality の単純分岐(`ch == "]"` など): LoopBodyLocal + MethodCall(substring)。MethodCallLowerer 経由で ExprLowerer へ委譲する拡張が必要。
- **当面後回し**
- return ベースの終了_parse_string のように return で抜ける): LoopPattern 側の設計が先。ExprLowerer は条件式評価に限定。
- 複合的な文字列操作escape 処理など): legacy ConditionEnv +専用 lowerer 維持。
ScopeManager が解決する名前の例:
- LoopParam: `i`, `p`, `start`, `end`
- Captured/OuterLocal: `len`, `n`, `digits`
- Promoted LoopBodyLocal: `digit_pos``is_digit_pos`
- BodyLocal条件で参照する場合: `ch`, `temp` などLoopBodyLocalEnv → ScopeManager 経由)
---
## 4. LAYER_GUARD / ガード実装案(構造メモ)
- 将来的に `src/mir/join_ir/lowering/LAYER_GUARD.rs` 相当で、以下を静的に検知する案を検討:
- ExprLowerer が ConditionEnv / LoopBodyLocalEnv を直接 import していないか。
- ScopeManager が UpdateEnv を参照していないか。
- condition_to_joinir から ScopeManager をバイパスするパスが残っていないか。
- 既存の AGENTS 原則by-name ハードコード禁止 / Fail-Fast / 構造優先)を lint 的に守る仕組みの叩き台にする。
---
## 5. 次フェーズ候補(実装 TODO のメモ)
- ExprLowerer に MethodCall(length/substring/indexOf) の条件用 lowering を追加(カタログ JP-01/03/04/06/08/07 対応)。
- ScopeManager のガード強化(条件式から UpdateEnv へのアクセス禁止を型/モジュール境界で表現)。
- ConditionEnv 構築を ScopeManager 中心に巻き直すConditionEnvBuilder v2 を ScopeManager-front に寄せる)。
- LAYER_GUARD 的な静的チェックの導入検討。***

View File

@ -0,0 +1,68 @@
# Development Docs Status & Scope Policy
このファイルは、開発ドキュメント(特に `docs/development/` 配下)が増えたときに
「どれが現役の設計か」「どれが歴史メモか」「どれを入口に読むべきか」を迷わないようにするための運用ルールだよ。
## 1. ステータス分類
開発ドキュメントの先頭付近に、次のいずれか(または組み合わせ)のステータスを明示することを推奨するよ。
- `Status: SSOT`
- 単一情報源Single Source of Truth。同じ内容を別ファイルに複製しない前提の設計ドキュメント。
- 変更があったらまずここを更新し、Phase 文書などは補足とする。
- `Status: Active`
- いまの開発ラインで直接参照される現役ドキュメント。
- 設計・実装の前提として読むことを想定。
- `Status: Historical`
- 過去フェーズのメモや調査ログ。読み物としては有用だが、設計の SSOT ではない。
- 先頭付近に「現行仕様は <path> を参照」と追記できるとさらに親切。
- `Status: VerificationReport`
- スモーク/ゴールデン/自動テストの実行レポート。
- 仕様そのものではなく、「いつ・何を確認したか」の記録。
完全移行は必須ではないけれど、新しく書くドキュメントや編集するタイミングで
少しずつステータスを追記していく運用を想定しているよ。
## 2. スコープと入口の書き方
ステータスに加えて、「このドキュメントは何の入口なのか」を 13 行で明示しておくと迷いにくくなるよ。
推奨フォーマットの例:
```markdown
Status: SSOT, Active
Scope: JoinIR ライン全体Loop/If/Boundaryの箱と契約の設計を横串で定義する。
See also: docs/development/current/main/01-JoinIR-Selfhost-INDEX.md
```
- `Scope:` では「このファイルを読んだら何が分かるか」を 1 文で書く。
- `See also:` では関連する INDEX や補助ドキュメントにリンクする(必要な場合だけで OK
## 3. JoinIR / Selfhost まわりの現時点の整理
特に問い合わせが多い JoinIR / Selfhost については、次を入口として扱うよ。
- トピック別 INDEX
- `docs/development/current/main/01-JoinIR-Selfhost-INDEX.md`
- JoinIR SSOT
- `docs/development/current/main/joinir-architecture-overview.md`Status: SSOT, Active 想定)
- Selfhost StageB/Stage1/Stage3 フロー
- `docs/development/current/main/selfhost_stage3_expected_flow.md`Status: Active 想定)
これらの詳細な読み順や関連ドキュメントは、INDEX 側に集約していく方針だよ。
## 4. 重複ドキュメントの扱い方
「同じようなことが複数のファイルに書かれている」場合は、次の順番で整理するのがおすすめだよ。
1. **SSOT を決める**
- 設計として参照されるべき 1 ファイルを選び、`Status: SSOT` を付ける。
2. **他ファイルは役割を限定する**
- 例: `Status: Historical` または `Status: VerificationReport` を付ける。
- 冒頭に「現行仕様は <SSOT パス> を参照」と追記する。
3. **新しい情報は SSOT に寄せる**
- フェーズメモから仕様が昇格したら、SSOT 側を先に更新し、メモ側にはリンクだけを残す。
いきなり全ファイルを一括変換するのではなく、
「触るタイミングでステータスとスコープを少しずつ整える」方針で進めると安全だよ。

View File

@ -0,0 +1,10 @@
# 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 ヒューリスティックや静かなフォールバックは禁止。
詳しい境界ルールは `docs/development/current/main/phase238-exprlowerer-scope-boundaries.md` を参照してね。***

View File

@ -406,6 +406,54 @@ impl CarrierInfo {
pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> {
self.trim_helper.as_ref()
}
/// Phase 229/231: Resolve promoted LoopBodyLocal name to carrier JoinIR ValueId
///
/// This helper centralizes the naming convention for promoted variables so that
/// ScopeManager 実装がそれぞれ命名規約を再実装しなくて済むようにするよ。
///
/// 命名規約:
/// - DigitPos パターン: `"var"` → `"is_var"`(例: "digit_pos" → "is_digit_pos"
/// - Trim パターン : `"var"` → `"is_var_match"`(例: "ch" → "is_ch_match"
///
/// # Arguments
///
/// * `original_name` - 元の LoopBodyLocal 名(例: "digit_pos"
///
/// # Returns
///
/// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合
/// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
if !self.promoted_loopbodylocals.contains(&original_name.to_string()) {
return None;
}
let candidates = [
format!("is_{}", original_name), // DigitPos pattern
format!("is_{}_match", original_name), // Trim pattern
];
for carrier_name in &candidates {
// loop_var 自身が ConditionOnly carrier として扱われるケースは現状ほぼないが、
// 将来の拡張に備えて loop_var_name も一応チェックしておく。
if carrier_name == &self.loop_var_name {
if let Some(carrier) = self.carriers.iter().find(|c| c.name == self.loop_var_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
if let Some(carrier) = self.carriers.iter().find(|c| c.name == *carrier_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
None
}
}
/// Exit metadata returned by lowerers

View File

@ -17,6 +17,7 @@
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
use crate::mir::ValueId;
use crate::mir::join_ir::{JoinInst, MirLikeInst, BinOpKind, UnaryOp as JoinUnaryOp};
use crate::mir::builder::MirBuilder;
use super::scope_manager::ScopeManager;
use super::condition_lowerer::lower_condition_to_joinir;
@ -108,6 +109,12 @@ pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
/// Debug flag (inherited from caller)
debug: bool,
/// Last lowered instruction sequence (for testing/inspection)
///
/// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
/// instructions are emitted for supported patterns. Productionコードからは未使用。
last_instructions: Vec<JoinInst>,
}
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
@ -124,6 +131,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
context,
builder,
debug: false,
last_instructions: Vec::new(),
}
}
@ -133,6 +141,14 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
self
}
/// Take the last lowered instruction sequence (mainly for tests)
///
/// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
/// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
std::mem::take(&mut self.last_instructions)
}
/// Lower an expression to JoinIR ValueId
///
/// Phase 231: This is the main entry point. Currently delegates to
@ -184,12 +200,15 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
id
};
let (result_value, _instructions) = lower_condition_to_joinir(
let (result_value, instructions) = lower_condition_to_joinir(
ast,
&mut alloc_value,
&condition_env,
).map_err(|e| ExprLoweringError::LoweringError(e))?;
// Phase 235: 保存しておき、テストから観察できるようにする
self.last_instructions = instructions;
if self.debug {
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
}
@ -292,7 +311,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
mod tests {
use super::*;
use crate::ast::{Span, LiteralValue};
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
@ -301,6 +320,41 @@ mod tests {
MirBuilder::new()
}
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(),
}
}
fn not(expr: ASTNode) -> ASTNode {
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(expr),
span: span(),
}
}
#[test]
fn test_expr_lowerer_simple_comparison() {
let mut condition_env = ConditionEnv::new();
@ -451,4 +505,226 @@ mod tests {
};
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
}
// Phase 235: Additional patterns for condition lowering
fn make_basic_scope() -> Pattern2ScopeManager<'static> {
// NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
// テスト専用なので許容する。
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(1));
condition_env.insert("j".to_string(), ValueId(2));
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
Pattern2ScopeManager {
condition_env: condition_env_ref,
loop_body_local_env: None,
captured_env: None,
carrier_info: carrier_ref,
}
}
fn assert_has_compare(instructions: &[JoinInst]) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::Compare { .. }
))),
"Expected at least one Compare instruction, got {:?}",
instructions
);
}
fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
)),
"Expected at least one BinOp {:?}, got {:?}",
op,
instructions
);
}
fn assert_has_not(instructions: &[JoinInst]) {
assert!(
instructions.iter().any(|inst| matches!(
inst,
JoinInst::Compute(MirLikeInst::UnaryOp { op: JoinUnaryOp::Not, .. }
))),
"Expected at least one UnaryOp::Not, got {:?}",
instructions
);
}
#[test]
fn test_expr_lowerer_var_less_literal_generates_compare() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i < 10
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i < 10 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_literal_less_var_generates_compare() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// 0 < i
let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "0 < i should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_greater_than_between_vars() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i > j
let ast = bin(BinaryOperator::Greater, var("i"), var("j"));
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i > j should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_and_combination() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// i > 0 && j < 5
let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
let ast = bin(BinaryOperator::And, left, right);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
assert_has_binop(&instructions, BinOpKind::And);
}
#[test]
fn test_expr_lowerer_not_of_comparison() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// !(i < 10)
let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
let ast = not(inner);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "!(i < 10) should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(!instructions.is_empty(), "instructions should not be empty");
assert_has_compare(&instructions);
assert_has_not(&instructions);
}
#[test]
fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
let mut condition_env = ConditionEnv::new();
condition_env.insert("digit_pos".to_string(), ValueId(10));
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: None,
carrier_info: &carrier_info,
};
let mut builder = create_test_builder();
// digit_pos < 0
let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
assert!(
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
"digit_pos < 0 should be supported in Pattern2 break condition"
);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(result.is_ok(), "digit_pos < 0 should lower successfully");
let instructions = expr_lowerer.take_last_instructions();
assert!(
!instructions.is_empty(),
"instructions for digit_pos < 0 should not be empty"
);
assert_has_compare(&instructions);
}
#[test]
fn test_expr_lowerer_pattern2_break_methodcall_is_unsupported() {
let scope = make_basic_scope();
let mut builder = create_test_builder();
// s.length() (MethodCall) is not supported for Pattern2 break condition
let ast = ASTNode::MethodCall {
object: Box::new(var("s")),
method: "length".to_string(),
arguments: vec![],
span: span(),
};
assert!(
!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
"MethodCall should be rejected for Pattern2 break condition"
);
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
let result = expr_lowerer.lower(&ast);
assert!(
matches!(result, Err(ExprLoweringError::UnsupportedNode(_))),
"MethodCall should fail-fast with UnsupportedNode"
);
}
}

View File

@ -379,6 +379,7 @@ pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
#[test]
fn test_update_kind_name() {
@ -387,91 +388,174 @@ mod tests {
assert_eq!(UpdateKind::Other.name(), "Other");
}
// NOTE: Phase 222 - test_typical_index_names commented out
// Function is_typical_index_name() was removed in earlier phase
// #[test]
// fn test_typical_index_names() {
// assert!(is_typical_index_name("i"));
// assert!(is_typical_index_name("idx"));
// assert!(is_typical_index_name("pos"));
// assert!(!is_typical_index_name("result"));
// assert!(!is_typical_index_name("sum"));
// }
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 add(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(lhs),
right: Box::new(rhs),
span: span(),
}
}
fn assign(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Assignment {
target: Box::new(var(name)),
value: Box::new(value),
span: span(),
}
}
fn if_with_updates(
condition: ASTNode,
then_body: Vec<ASTNode>,
else_body: Option<Vec<ASTNode>>,
) -> ASTNode {
ASTNode::If {
condition: Box::new(condition),
then_body,
else_body,
span: span(),
}
}
#[test]
fn test_analyze_single_counter() {
fn test_analyze_single_counter_from_ast() {
let names = vec!["i".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.has_single_counter());
assert!(!summary.has_accumulation());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 0);
}
#[test]
fn test_analyze_accumulation() {
let names = vec!["result".to_string()];
let summary = analyze_loop_updates(&names);
fn test_analyze_accumulation_from_ast() {
let names = vec!["sum".to_string()];
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.has_single_counter());
assert!(summary.has_accumulation());
assert_eq!(summary.counter_count(), 0);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_analyze_mixed() {
fn test_analyze_mixed_from_ast() {
let names = vec!["i".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
assign("sum", add(var("sum"), var("i"))),
assign("i", add(var("i"), lit_i(1))),
];
assert!(!summary.has_single_counter()); // 2つあるので false
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.has_single_counter());
assert!(summary.has_accumulation());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 1);
}
// Phase 213 tests for is_simple_if_sum_pattern
#[test]
fn test_is_simple_if_sum_pattern_basic() {
// phase212_if_sum_min.hako pattern: i (counter) + sum (accumulator)
fn test_is_simple_if_sum_pattern_basic_ast() {
let names = vec!["i".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
if_with_updates(
var("cond"),
vec![assign("sum", add(var("sum"), var("i")))],
None,
),
assign("i", add(var("i"), lit_i(1))),
];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_is_simple_if_sum_pattern_with_count() {
// Phase 195 pattern: i (counter) + sum + count (2 accumulators)
fn test_is_simple_if_sum_pattern_with_count_ast() {
let names = vec!["i".to_string(), "sum".to_string(), "count".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
if_with_updates(
var("cond"),
vec![
assign("sum", add(var("sum"), var("i"))),
assign("count", add(var("count"), lit_i(1))),
],
None,
),
assign("i", add(var("i"), lit_i(1))),
];
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 2);
}
#[test]
fn test_is_simple_if_sum_pattern_no_accumulator() {
// Only counter, no accumulator
fn test_is_simple_if_sum_pattern_no_accumulator_ast() {
let names = vec!["i".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
assert!(!summary.is_simple_if_sum_pattern()); // No accumulator
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 1);
assert_eq!(summary.accumulation_count(), 0);
}
#[test]
fn test_is_simple_if_sum_pattern_no_counter() {
// Only accumulator, no counter
fn test_is_simple_if_sum_pattern_no_counter_ast() {
let names = vec!["sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
assert!(!summary.is_simple_if_sum_pattern()); // No counter
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 0);
assert_eq!(summary.accumulation_count(), 1);
}
#[test]
fn test_is_simple_if_sum_pattern_multiple_counters() {
// Multiple counters (not supported)
fn test_is_simple_if_sum_pattern_multiple_counters_ast() {
let names = vec!["i".to_string(), "j".to_string(), "sum".to_string()];
let summary = analyze_loop_updates(&names);
let loop_body = vec![
assign("i", add(var("i"), lit_i(1))),
assign("j", add(var("j"), lit_i(1))),
assign("sum", add(var("sum"), var("i"))),
];
assert!(!summary.is_simple_if_sum_pattern()); // 2 counters
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
assert!(!summary.is_simple_if_sum_pattern());
assert_eq!(summary.counter_count(), 2);
assert_eq!(summary.accumulation_count(), 1);
}
}

View File

@ -251,12 +251,57 @@ pub(crate) fn lower_loop_with_break_minimal(
// After condition lowering, allocate remaining ValueIds
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
// Phase 170-B: Lower break condition using condition_to_joinir (no hardcoding!)
let (break_cond_value, mut break_cond_instructions) = lower_condition_to_joinir(
break_condition,
&mut alloc_value,
env,
)?;
// Phase 170-B / Phase 236-EX: Lower break condition
//
// - ループ条件: 既存どおり ConditionEnv + lower_condition_to_joinir 経路を使用
// - break 条件: Phase 236 から ExprLowerer/ScopeManager 経由に切り替え
//
// ExprLowerer 自体は内部で lower_condition_to_joinir を呼び出すため、JoinIR レベルの命令列は
// 従来経路と構造的に同等になることを期待している。
let (break_cond_value, mut break_cond_instructions) = {
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::builder::MirBuilder;
// Phase 236-EX: ExprLowerer は MirBuilder 参照を要求するが、
// Pattern2 の JoinIR lowering では既存の JoinInst バッファと ValueId allocator を直接使っている。
// ここでは MirBuilder は ExprLowerer 内での API 互換のためだけに使い、
// 実際の JoinIR 命令は lower_condition_to_joinir が返すものを採用する。
let mut dummy_builder = MirBuilder::new();
// Build minimal ScopeManager view for break condition lowering.
let empty_body_env = LoopBodyLocalEnv::new();
let empty_captured_env = CapturedEnv::new();
let scope_manager = Pattern2ScopeManager {
condition_env: env,
loop_body_local_env: Some(&empty_body_env),
captured_env: Some(&empty_captured_env),
carrier_info,
};
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
let value_id = match expr_lowerer.lower(break_condition) {
Ok(v) => v,
Err(ExprLoweringError::UnsupportedNode(msg)) => {
return Err(format!(
"[joinir/pattern2] ExprLowerer does not support break condition node: {}",
msg
));
}
Err(e) => {
return Err(format!(
"[joinir/pattern2] ExprLowerer failed to lower break condition: {}",
e
));
}
};
let instructions = expr_lowerer.take_last_instructions();
(value_id, instructions)
};
let _const_1 = alloc_value(); // Increment constant
let _i_next = alloc_value(); // i + 1

View File

@ -146,31 +146,9 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
}
}
// 4. Promoted LoopBodyLocal → Carrier lookup
// If this variable was promoted, try to find its carrier equivalent
if self.carrier_info.promoted_loopbodylocals.contains(&name.to_string()) {
// Try naming conventions
for carrier_name in &[
format!("is_{}", name), // DigitPos pattern
format!("is_{}_match", name), // Trim pattern
] {
// Check if it's the loop variable (unlikely but possible)
if carrier_name == &self.carrier_info.loop_var_name {
if let Some(id) = self.condition_env.get(&self.carrier_info.loop_var_name) {
return Some(id);
}
}
// Otherwise check carriers
if let Some(carrier) = self.carrier_info.carriers.iter().find(|c| c.name == *carrier_name) {
if let Some(join_id) = carrier.join_id {
return Some(join_id);
}
}
}
}
None
// 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約)
self.carrier_info
.resolve_promoted_join_id(name)
}
fn scope_of(&self, name: &str) -> Option<VarScopeKind> {
@ -323,4 +301,36 @@ mod tests {
assert_eq!(scope.lookup("temp"), Some(ValueId(200)));
assert_eq!(scope.scope_of("temp"), Some(VarScopeKind::LoopBodyLocal));
}
#[test]
fn test_pattern2_scope_manager_captured() {
let mut condition_env = ConditionEnv::new();
condition_env.insert("i".to_string(), ValueId(100));
condition_env.insert("len".to_string(), ValueId(201));
let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new();
captured_env.add_var(CapturedVar {
name: "len".to_string(),
host_id: ValueId(42),
is_immutable: true,
});
let carrier_info = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(1),
carriers: vec![],
trim_helper: None,
promoted_loopbodylocals: vec![],
};
let scope = Pattern2ScopeManager {
condition_env: &condition_env,
loop_body_local_env: None,
captured_env: Some(&captured_env),
carrier_info: &carrier_info,
};
assert_eq!(scope.lookup("len"), Some(ValueId(201)));
assert_eq!(scope.scope_of("len"), Some(VarScopeKind::Captured));
}
}