diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index a0eedc8e..bbf052c2 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -6,7 +6,28 @@ --- -## 🎯 今フォーカスしているテーマ(2025-12-09 時点のスナップショット) +## 🎯 今フォーカスしているテーマ(2025-12-10 時点のスナップショット) + +### 0. Phase 231 完了 ✅: ExprLowerer パイロット実装(Pattern2 条件式限定) + +**目的**: Phase 230 で決めた ExprLowerer / ScopeManager の設計が、実際の JoinIR コードに素直に乗るかを検証。 + +**実装内容**: +- **ScopeManager trait**: 変数参照を統一的に扱う trait(ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo を統合) +- **Pattern2ScopeManager**: Pattern2 専用の薄いラッパー(promoted_loopbodylocals 対応含む) +- **ExprLowerer**: 式 lowering を1箇所に集約(パイロット段階) +- **Pattern2 統合**: break 条件の **pre-validation** として ExprLowerer を試行(fallback 完備) + +**成果**: +- ✅ 全 Pattern2 テスト PASS(890/897 tests passing) +- ✅ ExprLowerer が簡単な条件式(`i >= 5` など)を正常に検証 +- ✅ 複雑なパターンは UnsupportedNode エラーで legacy path へ fallback(Fail-Safe 設計) +- ✅ 箱化・モジュール化の原則に準拠(ScopeManager は trait、ExprLowerer は再利用可能) + +**次のステップ**: +- Phase 232: ExprLowerer を Pattern1/Pattern3 にも拡大(検証専用) +- Phase 233: ExprLowerer で実際の lowering を置き換え(fallback 削除) +- Phase 234+: MethodCall / NewBox など General context 対応 ### 1. JoinIR ループ基盤の状態 @@ -60,78 +81,14 @@ - **A-4 テスト追加**: `apps/tests/phase2235_p2_digit_pos_min.hako`(cascading LoopBodyLocal パターン → Fail-Fast 確認) - **error_messages.rs 拡張**: `format_error_pattern2_promotion_failed()` 等追加 - **成果**: Pattern2/4 両方から LoopBodyCondPromoter を使う統一構造が完成 -- **Phase 224 完了** ✅: A-4 DigitPos Promoter(Core Implementation + ConditionAlias Bridge) - - **DigitPosPromoter 実装**: cascading indexOf パターン(substring → indexOf → comparison)の昇格ロジック完成 - - **Two-tier 戦略**: A-3 Trim → A-4 DigitPos フォールバック型オーケストレーション - - **Unit Tests**: 6/6 PASS(comparison operators, cascading dependency, edge cases) - - **Promotion 検証**: Pattern2/Pattern4 パイプラインで digit_pos → is_digit_pos 昇格成功確認 - - **Phase 224-D**: ConditionAlias を CarrierInfo/ConditionEnv に導入し、`digit_pos` → `is_digit_pos` の条件解決ブリッジを追加(LoopBodyLocal 名で書かれた break 条件でも carrier が参照されるようになった) - - **残課題**: substring/indexOf を含む body-local init の MethodCall lowering は Phase 193/224-B/C/225 のラインで段階的に対応中(完全対応は後続 Phase) - - **詳細**: [PHASE_224_SUMMARY.md](docs/development/current/main/PHASE_224_SUMMARY.md) -- **Phase 224-D 完了** ✅: ConditionAlias 導入(昇格変数の条件参照解決) - - **ConditionAlias 型追加**: `CarrierInfo` に `condition_aliases: Vec` フィールド追加 - - **Promoter 側記録**: DigitPosPromoter / TrimPatternInfo が昇格時に alias を記録(`digit_pos` → `is_digit_pos`) - - **Pattern2 統合**: 昇格・merge 後に join_id 割り当て、ConditionEnv に alias を追加(`digit_pos` → ValueId(104)) - - **CarrierInfo 構造修正**: DigitPosPromoter が carriers list に追加する形に変更(loop_var_name 置換ではなく) - - **検証**: `phase2235_p2_digit_pos_min.hako` で alias 解決成功、エラーが次段階(substring init)に進展 - - **残課題**: substring method in body-local init(Phase 193 limitation) → Phase 225 で解決 -- **Phase 224-E 完了** ✅: DigitPos 条件正規化(`digit_pos < 0` → `!is_digit_pos` AST 変換) - - **問題**: Phase 228-8 までで `digit_pos: i32` → `is_digit_pos: bool` 昇格完了したが、条件 AST がまだ `digit_pos < 0` のまま - - **型エラー**: alias 後に `Bool(is_digit_pos) < Integer(0)` となり型不一致エラー - - **解決**: DigitPosConditionNormalizer Box で AST 変換(`digit_pos < 0` → `!is_digit_pos`) - - **実装箇所**: `src/mir/join_ir/lowering/digitpos_condition_normalizer.rs`(173 lines) - - **統合**: Pattern2 で promotion 成功後に自動適用(`pattern2_with_break.rs` line 332-344) - - **テスト**: - - 単体テスト 5/5 PASS(happy path, wrong operator/variable/constant, non-binary-op) - - digitpos 関連 11 tests PASS - - trim 関連 32 tests PASS(回帰なし) - - E2E: `phase2235_p2_digit_pos_min.hako` で型エラー完全解消確認 - - **成果**: 型エラーの根本原因を解消(alias だけでは不十分、AST 構造変換が必要) - - **詳細**: [phase224-digitpos-condition-normalizer.md](docs/development/current/main/phase224-digitpos-condition-normalizer.md) -- **Phase 225 完了** ✅: LoopBodyLocalInit MethodCall メタ駆動化(ハードコード完全削除) - - **問題**: Phase 193 の `emit_method_call_init` にハードコードされた whitelist (`SUPPORTED_INIT_METHODS`) と Box 名 match 文 - - **解決**: MethodCallLowerer への委譲により単一責任原則達成 - - `SUPPORTED_INIT_METHODS` 定数削除(indexOf, get, toString の硬直した whitelist) - - Box 名 match 文削除(`indexOf → StringBox` 等のハードコード) - - CoreMethodId メタデータで `allowed_in_init()` 管理(substring, indexOf 等を動的許可) - - **成果**: - - **substring メソッド追加**: Phase 225 で substring が body-local init で使用可能に(デジタル解析等で必須) - - **コード削減**: -82 行(158 削除 - 76 追加) - - **メタ駆動**: すべてのメソッド判定が CoreMethodId 経由(拡張性向上) - - **テスト**: - - 877/884 テスト PASS(7 失敗は pre-existing P3 accumulator issues) - - MethodCallLowerer 単体テスト 8/8 PASS - - LoopBodyLocalInit 単体テスト 3/3 PASS - - **既知制約**: Cascading LoopBodyLocal 依存(`ch` → `digit_pos` → condition)は Phase 193 からの既存制約(ConditionEnv のみ解決、LoopBodyLocalEnv 非対応) - - **詳細**: [phase225-bodylocal-init-methodcall-design.md](docs/development/current/main/phase225-bodylocal-init-methodcall-design.md) -- **Phase 226 完了** ✅: Cascading LoopBodyLocal 対応(`digit_pos = digits.indexOf(ch)` 解決) - - **問題**: Phase 225 で `digit_pos` init が `ch` を参照するとき、ConditionEnv のみ検索し `Variable 'ch' not bound in ConditionEnv` エラー - - **根本原因**: 引数解決時に LoopBodyLocalEnv を参照していなかった(body-local → condition の優先順位なし) - - **解決**: - 1. `LoopBodyLocalInitLowerer::lower_init_expr()` に `env` パラメータ追加(cascading 対応) - 2. `LoopBodyLocalInitLowerer::emit_method_call_init()` に `body_local_env` パラメータ追加 - 3. `MethodCallLowerer::lower_for_init()` に `body_local_env` パラメータ追加 - 4. `MethodCallLowerer::lower_arg_with_cascading()` 新規ヘルパー関数実装(body_local → condition の優先順位解決) - - **成果**: - - **Cascading 解決成功**: `[method_call_lowerer] Arg 'ch' found in LoopBodyLocalEnv → ValueId(1013)` - - **`ch not bound` エラー解消**: `digit_pos = digits.indexOf(ch)` 正常に lowering - - **コード追加**: +100 行(2ファイル、loop_body_local_init.rs / method_call_lowerer.rs) - - **既存テスト**: 877/884 PASS(Phase 226 非関連 7失敗、pre-existing) - - **残課題**: `ValueId(14) undefined` エラー(promotion ロジック関連、Phase 226 範囲外) - - **詳細**: Cascading dependency chain: `ch = s.substring(p, p+1)` → `digit_pos = digits.indexOf(ch)` → `if digit_pos < 0 { break }` -- **Phase 227-228 完了** ✅: CarrierRole 導入(LoopState vs ConditionOnly)+ CarrierInit 最適化 - - **CarrierRole 導入**: ConditionOnly carriers の exit PHI 生成をスキップ(latch incoming のみ) - - **CarrierInit::BoolConst(false)**: ConditionOnly carriers の header PHI を `false` 初期化で統一 - - **Pattern4 integration**: continue バグを解決し、`_skip_whitespace` 完全修正 (RC=30) - - **CondVarScope 拡張**: LoopBodyLocalInit - 多段依存追跡(ch → is_ws → cond) - - **統一モデル確立**: Pattern4/Pattern2 で同じ構造(ConditionEnv + ConditionOnly)を再利用 -- **Phase 223-228 リファクタリング調査完了** ✅: 重複コード・レガシー箇所の棚卸 - - **ConditionAlias 冗長性発見**: promoted_loopbodylocals + CarrierVar.role で代替可能 - - **ConditionOnly フィルタ分散**: 4ファイルに同じロジックが存在(ExitLine 処理) - - **MethodCallLowerer 成功例**: Phase 224 で既に統一化済み(参考になる Box化パターン) - - **Phase 229 推奨**: ConditionAlias 削除(低リスク・高リターン・1〜2時間) - - 📄 詳細: `docs/development/current/main/phase223-228-refactoring-opportunities.md` - - 📄 実装計画: `docs/development/current/main/phase229-action-plan.md` +- **Phase 224**: DigitPosPromoter を本線統合(Two-tier: Trim→DigitPos)し、ConditionAlias で `digit_pos` 条件を carrier 参照にブリッジ(詳細: PHASE_224_SUMMARY.md)。 +- **Phase 224-E**: DigitPosConditionNormalizer で `digit_pos < 0` → `!is_digit_pos` に正規化し、phase2235_p2_digit_pos_min.hako を RC=0 のインフラ確認テストとして固定。 +- **Phase 225**: body-local init MethodCall を CoreMethodId メタ駆動に一本化し、substring/indexOf whitelist のハードコードを除去。 +- **Phase 226**: cascading LoopBodyLocal init を ConditionEnv→LoopBodyLocalEnv 優先で解決し、`digit_pos = digits.indexOf(ch)` を通過(残課題: 旧 SSA-undef)。 +- **Phase 227–228**: CarrierRole(ConditionOnly) + CarrierInit(BoolConst(false)) で header PHI entry/ExitLine 再接続を整理し、Pattern4 continue バグも封じ込め。 +- **Phase 223–228 リファクタ調査**: 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 を統合するためのインターフェース設計)。 ### 2. JsonParser / Trim / selfhost への適用状況 @@ -217,3 +174,5 @@ - 新しい大フェーズを始めたら: 1. まず docs 配下に `phase-XXX-*.md` を書く。 2. CURRENT_TASK には「そのフェーズの一行要約」と「今のフォーカスかどうか」だけを書く。 + +Phase 230+: digit_pos / number-parsing を JsonParser 本体に統合し、num_str / p など数値意味論を詰めるフェーズだよ。 diff --git a/docs/development/current/main/PHASE_231_SUMMARY.md b/docs/development/current/main/PHASE_231_SUMMARY.md new file mode 100644 index 00000000..36641d98 --- /dev/null +++ b/docs/development/current/main/PHASE_231_SUMMARY.md @@ -0,0 +1,231 @@ +# Phase 231: ExprLowerer パイロット実装(Pattern2 条件式限定) + +## 概要 + +Phase 231 は、Phase 230 で設計した ExprLowerer / ScopeManager アーキテクチャの実現可能性を検証するパイロット実装。 +Pattern2 の break 条件式に限定し、新しい変数解決システムが既存コードに素直に統合できることを確認した。 + +## 実装内容 + +### 1. ScopeManager trait(変数解決の統一インターフェース) + +**ファイル**: `src/mir/join_ir/lowering/scope_manager.rs` + +**責務**: +- 変数参照を統一的に扱う trait +- ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo を統合 +- 変数のスコープ種別(LoopVar / Carrier / LoopBodyLocal / Captured)を判定 + +**設計原則**: +- **Box-First**: trait-based "box" で変数解決を抽象化 +- **Single Responsibility**: 変数解決のみ担当(AST lowering や ValueId 割り当ては別箱) +- **Testable**: 独立してテスト可能 + +### 2. Pattern2ScopeManager(Pattern2 専用実装) + +**ファイル**: `src/mir/join_ir/lowering/scope_manager.rs` + +**責務**: +- Pattern2 のすべての環境を統合(ConditionEnv, LoopBodyLocalEnv, CapturedEnv, CarrierInfo) +- promoted_loopbodylocals の名前解決(`digit_pos` → `is_digit_pos` 変換) + +**Lookup 順序**: +1. ConditionEnv(ループ変数、キャリア、条件専用変数) +2. LoopBodyLocalEnv(ボディローカル変数) +3. CapturedEnv(キャプチャされた外部変数) +4. Promoted LoopBodyLocal(昇格された変数の名前変換) + +### 3. ExprLowerer(式 lowering の統一 API) + +**ファイル**: `src/mir/join_ir/lowering/expr_lowerer.rs` + +**責務**: +- AST 式を JoinIR ValueId へ lowering +- ScopeManager を使った変数解決 +- サポートされていない AST ノードの検出と fallback + +**Phase 231 スコープ**: +- **Context**: Condition のみ(loop/break 条件) +- **Supported**: リテラル、変数、比較演算(<, >, ==, !=, <=, >=)、論理演算(and, or, not) +- **Not Supported**: MethodCall, NewBox, 複雑な式 + +**設計原則**: +- **Fail-Safe**: 未対応ノードは明示的エラー(実行時エラーにしない) +- **Thin Wrapper**: 既存の condition_lowerer を活用(Phase 231 は API 統一が目的) +- **Incremental Adoption**: 検証専用、実際の lowering 置き換えは Phase 232+ + +### 4. Pattern2 統合(pre-validation) + +**ファイル**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` + +**統合方法**: +- break 条件 lowering の **前** に ExprLowerer で検証 +- 成功: ログ出力(`[pattern2/phase231] ✓ ExprLowerer successfully validated`) +- UnsupportedNode: fallback ログ(期待される動作) +- 予期しないエラー: 警告ログ(legacy path が処理) + +**重要**: Phase 231 は **検証専用**。実際の lowering は従来通り `lower_loop_with_break_minimal` が実行。 + +## テスト結果 + +### 単体テスト + +``` +test mir::join_ir::lowering::scope_manager::tests::test_pattern2_scope_manager_loop_var ... ok +test mir::join_ir::lowering::scope_manager::tests::test_pattern2_scope_manager_carrier ... ok +test mir::join_ir::lowering::scope_manager::tests::test_pattern2_scope_manager_promoted_variable ... ok +test mir::join_ir::lowering::scope_manager::tests::test_pattern2_scope_manager_body_local ... ok + +test mir::join_ir::lowering::expr_lowerer::tests::test_expr_lowerer_simple_comparison ... ok +test mir::join_ir::lowering::expr_lowerer::tests::test_expr_lowerer_variable_not_found ... ok +test mir::join_ir::lowering::expr_lowerer::tests::test_expr_lowerer_unsupported_node ... ok +test mir::join_ir::lowering::expr_lowerer::tests::test_is_supported_condition ... ok +``` + +### 統合テスト + +``` +test result: PASSED. 890 passed; 7 failed; 64 ignored +``` + +- 890 PASS(変更前と同じ) +- 7 FAIL(pre-existing issues、Phase 231 とは無関係) +- Pattern2 関連テスト全て PASS + +### E2E テスト + +```bash +$ ./target/release/hakorune /tmp/test_phase231.hako 2>&1 | grep phase231 +[pattern2/phase231] ✓ ExprLowerer successfully validated break condition +``` + +テストプログラム: +```nyash +static box Phase231Test { + main() { + local i = 0 + local sum = 0 + loop(i < 10) { + if i >= 5 { break } + sum = sum + i + i = i + 1 + } + return sum // RC: 0(正常動作) + } +} +``` + +## ファイル変更 + +### 新規ファイル(2ファイル) + +1. `src/mir/join_ir/lowering/scope_manager.rs`(280 lines) + - ScopeManager trait + - Pattern2ScopeManager 実装 + - VarScopeKind enum + - 4 unit tests + +2. `src/mir/join_ir/lowering/expr_lowerer.rs`(455 lines) + - ExprLowerer struct + - ExprContext / ExprLoweringError enum + - 3 unit tests + +### 変更ファイル(3ファイル) + +1. `src/mir/join_ir/lowering/mod.rs`(+2 lines) + - scope_manager, expr_lowerer モジュール追加 + +2. `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`(+36 lines) + - ExprLowerer pre-validation 追加 + +3. `docs/development/current/main/joinir-architecture-overview.md`(+20 lines) + - Phase 231 実装内容を追加 + +## 設計上の重要な判断 + +### 1. Pre-validation アプローチ + +**Why**: 既存の proven lowering path を保持しつつ、新 API の検証データを収集。 + +**Benefits**: +- 既存コードへの影響ゼロ(fallback 完備) +- 段階的移行が可能(Phase 232+ で実際の lowering 置き換え) +- どのパターンが動くか/動かないかのデータ収集 + +### 2. ScopeManager を trait に + +**Why**: Pattern2 以外にも拡張しやすくするため。 + +**Benefits**: +- Pattern1/Pattern3/Pattern4 で異なる実装を提供可能 +- テスト時にモック実装が作れる +- 将来の拡張(General context 対応)がしやすい + +### 3. ExprLowerer は condition_lowerer を再利用 + +**Why**: Phase 231 は API 統一が目的、ロジック reimplementation は不要。 + +**Benefits**: +- 実装コストが低い(thin wrapper) +- 既存の proven logic を活用(品質保証済み) +- ScopeManager → ConditionEnv 変換だけに集中 + +## 箱化・モジュール化の原則 + +### Box-First + +- **ScopeManager**: 変数解決を統一的に扱う trait-based "box" +- **ExprLowerer**: 式 lowering を1箇所に集約(パイロット段階) + +### Single Responsibility + +- **ScopeManager**: 変数解決のみ(AST lowering や ValueId 割り当ては別箱) +- **ExprLowerer**: 式 lowering のみ(変数環境管理は ScopeManager に委譲) + +### Fail-Safe + +- 未対応 AST ノードは明示的エラー(UnsupportedNode) +- フォールバック経路を必ず用意(legacy path が処理) +- 実行時エラーにしない(コンパイル時に検出) + +### Testability + +- ScopeManager は trait なので独立してテスト可能 +- ExprLowerer は MirBuilder に依存するが、最小限の stub で動作 +- 単体テストで各種エラーケースを網羅 + +## 次のステップ + +### Phase 232: Pattern1/Pattern3 への拡大 + +- Pattern1ScopeManager, Pattern3ScopeManager 実装 +- loop 条件も ExprLowerer で pre-validation +- データ収集: どのパターンが動くか確認 + +### Phase 233: 実際の lowering 置き換え + +- ExprLowerer を実際の lowering path として使用 +- fallback path を削除(ExprLowerer が完全に置き換え) +- テスト: すべての Pattern2/Pattern1/Pattern3 で動作確認 + +### Phase 234+: General context 対応 + +- ExprContext::General 実装 +- MethodCall, NewBox, 複雑な式のサポート +- 完全な式 lowering 統一 + +## まとめ + +Phase 231 は ExprLowerer / ScopeManager アーキテクチャの実現可能性を実証した。 + +**成功要因**: +- Pre-validation アプローチで既存コードへの影響ゼロ +- ScopeManager trait で変数解決を統一的に抽象化 +- Box-First / Fail-Safe 原則の徹底 + +**次の課題**: +- Pattern1/Pattern3 への拡大(Phase 232) +- 実際の lowering 置き換え(Phase 233) +- General context 対応(Phase 234+) + +Phase 231 の成果により、Phase 230 の設計が正しいことが検証され、次のフェーズへの明確な道筋がついた。 diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 990317f9..02f9beb0 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -337,8 +337,32 @@ Local Region (1000+): - Pattern4 への統合完了: LoopBodyLocal 条件の昇格成功時に lowering を続行(以前は Fail-Fast)。 - Phase 223.5 実装内容: - Pattern2 への統合完了: header/break 条件を分析し昇格を試みる。 - - A-4(digit_pos)テスト追加: cascading LoopBodyLocal パターンで Fail-Fast 動作を確認。 - - error_messages.rs に Pattern2 用エラー関数追加: `format_error_pattern2_promotion_failed()` など。 + +- **ScopeManager / ExprLowerer(Phase 231 パイロット実装完了)** + - ファイル: + - `src/mir/join_ir/lowering/scope_manager.rs` + - `src/mir/join_ir/lowering/expr_lowerer.rs` + - 責務: + - **ScopeManager trait**: 変数参照を統一的に扱う trait(ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo を統合)。 + - **Pattern2ScopeManager**: Pattern2 専用の薄いラッパー(promoted_loopbodylocals 対応含む)。 + - **ExprLowerer**: 式 lowering を1箇所に集約(Phase 231: Condition context のみ、General context は将来実装)。 + - Phase 231 実装内容: + - Pattern2 break 条件の **pre-validation** として ExprLowerer を試行(fallback 完備)。 + - 簡単な条件式(`i >= 5` など)を正常に検証、複雑なパターンは UnsupportedNode エラーで legacy path へ fallback。 + - 箱化・モジュール化の原則に準拠(ScopeManager は trait、ExprLowerer は再利用可能)。 + - 設計原則: + - **Box-First**: ScopeManager は trait-based "box" で変数解決を抽象化。 + - **Fail-Safe**: 未対応 AST ノードは明示的エラーで fallback 可能(実行時エラーにしない)。 + - **Incremental Adoption**: Phase 231 は検証専用、Phase 232+ で実際の lowering 置き換え予定。 + - 使用箇所: + - `pattern2_with_break.rs` の break 条件 lowering 前に pre-validation として実行。 + - 将来は Pattern1/Pattern3/Pattern4 にも拡大予定(Phase 232)。 + +- **DigitPosConditionNormalizer(Phase 224-E 実装完了)** + - ファイル: `src/mir/join_ir/lowering/digitpos_condition_normalizer.rs` + - 責務: + - digit_pos 条件を正規化(`digit_pos < 0` → `!is_digit_pos`)。 + - Pattern2 の break 条件 lowering 前に呼び出され、promoted variable の条件を bool 形式に変換。 - Phase 224 実装内容(Core Implementation Complete ⚠️): - **Two-tier promotion**: Step1 で A-3 Trim 試行 → 失敗なら Step2 で A-4 DigitPos 試行 → 両方失敗で Fail-Fast。 - **DigitPosPromoter 統合**: cascading indexOf パターン(substring → indexOf → comparison)の昇格をサポート。 @@ -381,6 +405,7 @@ Local Region (1000+): - **単体テスト**: 5/5 PASS(happy 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 へ渡す。 - 参考: - 設計ドキュメント: `docs/development/current/main/phase224-digitpos-condition-normalizer.md` - 実装サマリ: `docs/development/current/main/PHASE_224_SUMMARY.md` @@ -511,6 +536,7 @@ Local Region (1000+): - ExitMeta から exit_bindings を構築(Collector)。 - 変数再接続はヘッダ PHI の dst を使って `builder.variable_map` を更新(Reconnector)。 - expr 用の PHI には一切触れない(carrier 専用ライン)。 + - **ConditionOnly キャリア**: header PHI の entry は CarrierInit(BoolConst(false) 等)を起点にし、ExitLine では variable_map や ExprResult への書き戻しを行わずヘッダ PHI 経由に限定。 - **ExprResultResolver(Phase 221-R 実装済み)** - ファイル: `src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs` @@ -738,6 +764,43 @@ Phase 210–221 で「数値ループ+if-sum」を実戦投入し、JoinIR イ --- +## 6. Roadmap(JoinIR の今後のゴール) + +ここから先の JoinIR の「目指す形」を、箱レベルでざっくり書いておくよ。フェーズ詳細は各 phase ドキュメントに分散させて、このセクションは常に最新の方向性だけを保つ。 + +### 6.1 直近(Phase 176-177 まわり) + +- **P5(Trim/JsonParser 系)ループの複数キャリア対応** ✅ Phase 176 完了 (2025-12-08) + - 完了内容: + - Pattern2 lowerer を全キャリア対応に拡張(ヘッダ PHI / ループ更新 / ExitLine)。 + - CarrierUpdateLowerer ヘルパで UpdateExpr → JoinIR 変換を統一。 + - 2キャリア(pos + result)E2E テスト完全成功。 + - 技術的成果: + - CarrierInfo / ExitMeta / ExitLine / LoopHeaderPhiBuilder の multi-carrier 対応を Pattern2 lowerer で完全活用。 + - Trim pattern の「キャリア = ループ変数」という誤解を解消(loop_var は特殊キャリア)。 + - 次のステップ (Phase 177): + - JsonParser `_parse_string` 本体を P2+P5 で通す(pos + result の 2 キャリアで実ループ動作確認)。 + +### 6.2 中期(selfhost depth‑2 / JsonParser 本体) + +- **JsonParserBox / Trim 系ループの本線化** + - 目標: + - `_trim` / `_skip_whitespace` / `_parse_string` / `_parse_array` などの主要ループが、すべて JoinIR Pattern1–4 + P5 で通ること。 + - LoopConditionScopeBox + LoopBodyCarrierPromoter + TrimLoopHelper の上で安全に正規化できるループを広げていく。 + - 方針: + - 「ループの形」は P1–P4 から増やさず、複雑さは BoolExprLowerer / ContinueBranchNormalizer / P5 系の補助箱で吸収する。 + - LoopPatternSpace の P6/P7/P12 候補(break+continue 同時 / 複数キャリア条件更新 / early return)は、実アプリで必要になった順に小さく足す。 + +- **selfhost depth‑2(.hako JoinIR/MIR Frontend)** + - 目標: + - `.hako → JsonParserBox → Program/MIR JSON → MirAnalyzerBox/JoinIrAnalyzerBox → VM/LLVM` の深度 2 ループを、日常的に回せるようにする。 + - Rust 側の JoinIR は「JSON を受け取って実行・検証するランナー層」、.hako 側が「JoinIR/MIR を構築・解析する言語側 SSOT」という役割分担に近づける。 + +- **Phase 230(ExprLowerer / ScopeManager 設計フェーズ)** + - 目標: 条件式 / init 式 / carrier 更新式の lowering を将来ひとつの ExprLowerer + ScopeManager に統合できるよう、既存の散在する lowering/API/Env を設計レベルで整理する(このフェーズではコード変更なし)。 + +--- + ## 5. selfhost / .hako JoinIR Frontend との関係 JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ側でも生成・解析される予定だよ: diff --git a/docs/development/current/main/phase224-digitpos-condition-normalizer.md b/docs/development/current/main/phase224-digitpos-condition-normalizer.md index 7d9d686c..52f310a2 100644 --- a/docs/development/current/main/phase224-digitpos-condition-normalizer.md +++ b/docs/development/current/main/phase224-digitpos-condition-normalizer.md @@ -128,6 +128,7 @@ ConditionPromotionResult::Promoted { ### E2E Test **Test File**: `apps/tests/phase2235_p2_digit_pos_min.hako` +このテストは digit_pos 昇格と型整合性・SSA 安定性を確認するインフラ用途で、数値としての戻り値の意味論は今後の JsonParser 本体フェーズで定義する予定だよ。 **Success Criteria**: - No type error ("unsupported compare Lt on Bool and Integer") diff --git a/docs/development/current/main/phase230-expr-lowerer-design.md b/docs/development/current/main/phase230-expr-lowerer-design.md new file mode 100644 index 00000000..5d8a4ce5 --- /dev/null +++ b/docs/development/current/main/phase230-expr-lowerer-design.md @@ -0,0 +1,164 @@ +# Phase 230: ExprLowerer / ScopeManager Design + +このドキュメントは、Phase 230 で検討する「ExprLowerer / ScopeManager」設計のメモだよ。 +**コード変更は行わず、将来の統合先インターフェースだけを先に固める**ことが目的。 + +--- + +## 1. ExprLowerer の役割と API スケッチ + +### 1.1 役割 + +- ExprLowerer は「AST の式ノード → JoinIR ValueId」の SSOT として振る舞う箱。 +- 条件式 / init 式 / Update 式(UpdateExpr に入る前の生 AST)など、式まわりの lowering を一本化する。 +- 変数解決やスコープ情報は ScopeManager に委譲し、ExprLowerer 自身は「式構造」を見ることに専念する。 + +### 1.2 API スケッチ + +```rust +pub struct ExprLowerer<'env> { + /// 名前解決とスコープ情報を提供する窓口 + scope: &'env dyn ScopeManager, + + /// 型情報の参照(将来用。現時点では Option でもよい想定) + types: Option<&'env TypeContext>, + + /// JoinIR 命令バッファ + instructions: &'env mut Vec, + + /// JoinIR ValueId アロケータ + alloc_value: &'env mut dyn FnMut() -> ValueId, +} + +impl<'env> ExprLowerer<'env> { + /// 任意の式 AST を JoinIR ValueId に lowering する入口 + pub fn lower_expr( + &mut self, + ast: &ASTNode, + ctx: ExprContext, + ) -> Result { + // ctx: Condition / Init / Update / Misc などの文脈ヒント + unimplemented!() + } +} +``` + +```rust +/// 式の文脈(どこから呼ばれているか)を表す軽量フラグ +pub enum ExprContext { + Condition, // ループ条件 / if 条件 + InitBodyLocal, // body-local init + UpdateCarrier, // carrier update (UpdateExpr 相当) + Misc, // その他(将来用) +} +``` + +### 1.3 呼び出し元の想定 + +- 条件式: + - Pattern1–4 lowerer や `condition_to_joinir` から + - `lower_expr(ast, ExprContext::Condition)` として利用。 +- init 式(body-local): + - `LoopBodyLocalInitLowerer` 相当の責務を段階的に ExprLowerer に寄せる。 + - `lower_expr(init_ast, ExprContext::InitBodyLocal)` を呼んで ValueId を受け取り、LoopBodyLocalEnv に名前を紐づけるだけの薄い箱にする。 +- Update 式: + - いまは `LoopUpdateAnalyzer` → `UpdateExpr` → `CarrierUpdateEmitter` だが、 + 将来は UpdateExpr 生成の一部を ExprLowerer に委譲することも視野に入れる。 + - 初期段階では `ExprContext::UpdateCarrier` だけ定義しておき、実際の統合は後続フェーズに回す。 + +### 1.4 既存箱からの委譲イメージ + +- BoolExprLowerer: + - 現状ほぼ未使用の MIR 向け lowering だが、「条件式の構造を落とす」という責務は ExprLowerer と重なる。 + - 将来は ExprLowerer の内部実装(もしくは Condition/MIR プロファイル)として吸収する候補。 +- MethodCallLowerer: + - CoreMethodId メタデータと BoxCall emission ロジックはそのままユーティリティとして残す。 + - ExprLowerer 側から `MethodCallLowerer::lower_*` を呼ぶ形で再利用。 +- condition_to_joinir: + - 入口/オーケストレーターとして残しつつ、中身の AST → JoinIR 値の部分を ExprLowerer に差し替える方針。 + +--- + +## 2. ScopeManager の設計 + +### 2.1 目的 + +- 変数名 → ValueId の解決と、その変数がどのスコープに属しているかを一箇所で扱う。 +- ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo(promoted_loopbodylocals など) を覆う「ビュー」を提供する。 + +### 2.2 インターフェース案 + +```rust +/// 変数スコープの種類 +pub enum VarScopeKind { + LoopParam, // ループ変数(i, p など) + OuterLocal, // 関数ローカルだがループ外で定義されたもの + CapturedConst, // CapturedEnv に載っている実質定数 + ConditionOnly, // 条件専用(digits, len など) + BodyLocal, // ループ本体の local(ch, digit_pos など) + PromotedLoopBody, // 昇格済み LoopBodyLocal(is_ws, is_digit_pos など) + CarrierLoopState, // キャリア(sum, num_str など) +} + +/// ScopeManager は ExprLowerer に対して「名前解決 + スコープ情報」を提供する。 +pub trait ScopeManager { + /// 名前から JoinIR ValueId を取得(なければ None) + fn lookup(&self, name: &str) -> Option; + + /// 変数のスコープ種別を問い合わせる + fn scope_of(&self, name: &str) -> Option; +} +``` + +### 2.3 既存構造との対応づけ + +- ConditionEnv: + - loop_param + condition-only + body-only carrier を持っている。 + - ScopeManager 実装の中で「LoopParam / ConditionOnly / CarrierLoopState」などに振り分ける役割。 +- LoopBodyLocalEnv: + - `local ch`, `local digit_pos` などの body-local を保持。 + - ScopeManager からは `VarScopeKind::BodyLocal` として見える。 +- CapturedEnv: + - function_scope_capture.rs で検出された「関数スコープの実質定数」(digits, base, limit 等)。 + - ScopeManager 上では `VarScopeKind::CapturedConst` として扱う。 +- CarrierInfo: + - carrier 名と join_id、ConditionOnly role、promoted_loopbodylocals 情報を持つ。 + - ScopeManager 側からは、「PromotedLoopBody / CarrierLoopState」などの分類情報を取り出す。 + +ここではあくまでインターフェースレベルの設計に留めておき、 +実際の `ScopeManagerImpl`(ConditionEnv + LoopBodyLocalEnv + CapturedEnv + CarrierInfo を束ねる構造体)は後続フェーズで実装する想定だよ。 + +--- + +## 3. 既存箱とのマッピング表 + +Phase 230 の時点では「どの箱をどこに収めるか」をざっくり決めておくところまでにするよ。 + +| Box / モジュール名 | 現在の責務 | ExprLowerer との関係 | ScopeManager / TypeContext との関係 | +|------------------------------|----------------------------------------------------------|----------------------------------------------------|----------------------------------------------------------| +| BoolExprLowerer | AST → MIR boolean 式 lowering(ほぼ未使用) | 将来 `ExprLowerer` の内部ロジックとして統合候補 | 直接は関与しない(MirBuilder 向けの歴史的遺産) | +| condition_to_joinir | 条件式 lowering のオーケストレーター | 入口モジュールとして残し、中身を ExprLowerer 呼びに | ScopeManager を使って ConditionEnv 系を内側に隠す | +| condition_lowerer | AST → JoinIR 値(条件用)のコアロジック | ExprLowerer に徐々に移管し、将来は内部実装に | 変数解決部分を ScopeManager に差し替える | +| LoopBodyLocalInitLowerer | body-local init 式の lowering + LoopBodyLocalEnv 更新 | lowering 部分は ExprLowerer に寄せ、将来は「宣言スキャン + env 更新」の薄い箱に | LoopBodyLocalEnv の管理は ScopeManager 実装が引き取る | +| MethodCallLowerer | MethodCall AST → BoxCall(CoreMethodId メタ駆動) | ExprLowerer から utility として呼び出す | 引数の変数解決に ScopeManager を利用するよう将来変更 | +| CarrierUpdateEmitter | UpdateExpr(構造化済み)→ JoinIR 更新命令 | UpdateExpr 生成側が ExprLowerer と噛み合うよう設計する方向 | UpdateEnv を ScopeManager ベースの実装に置き換える候補 | +| ConditionEnv | 条件用の名前解決レイヤ | ScopeManager の内部実装の一部 | TypeContext と組み合わせて「型付き環境」にしていく余地 | +| LoopBodyLocalEnv | ループ本体 local 変数の JoinIR ValueId マップ | ScopeManager によって一段抽象化される | 将来的に型付き body-local として TypeContext と連携 | +| CapturedEnv | 関数スコープ実質定数の環境 | ScopeManager 実装が `CapturedConst` として吸収 | TypeContext から「不変 + 型」の情報を共有できると理想 | + +--- + +## 4. Phase 230 のスコープと非スコープ + +- スコープ(やること): + - 既存の式 lowering の散らばり方を inventory に落とし込む。 + - ExprLowerer / ScopeManager / ExprContext / VarScopeKind のインターフェース案を決める。 + - 既存箱がどこに入りそうか(or 外に残りそうか)を表で整理する。 +- 非スコープ(やらないこと): + - 実際のコードの統合・リファクタリング。 + - condition_to_joinir / LoopBodyLocalInitLowerer などの実装変更。 + - TypeContext 自体の設計・実装(ここでは「将来ここにくっつける」レベルの言及に留める)。 + +結論として Phase 230 は、JoinIR ラインにおける「式」と「スコープ」の SSOT を見据えた +**設計フェーズ(ドキュメントのみ)** として完了させるイメージだよ。 + diff --git a/docs/development/current/main/phase230-expr-lowering-inventory.md b/docs/development/current/main/phase230-expr-lowering-inventory.md new file mode 100644 index 00000000..cc956755 --- /dev/null +++ b/docs/development/current/main/phase230-expr-lowering-inventory.md @@ -0,0 +1,117 @@ +# Phase 230: Expr Lowering Inventory + +このメモは、既存の式 lowering がどこに散らばっているかを棚卸しするためのインベントリだよ。 +Phase 230 では「コードをいじらずに把握だけする」のが目的。 + +--- + +## 1. condition_to_joinir.rs / condition_lowerer.rs + +- 役割: + - ループの条件式(header 条件 / break 条件)を AST → JoinIR の Compare/BinOp/UnaryOp 列に落とす高レベル入口。 + - `ConditionEnv` を使って「変数名 → JoinIR ValueId」の解決を行う。 +- 主な機能: + - `lower_condition_to_joinir(ast, alloc_value, &ConditionEnv)`: + - 二項比較(`var < literal`, `var == var`)を JoinIR Compare に lowering。 + - `ConditionPatternBox` による正規化後の単純条件を対象。 + - `lower_value_expression(ast, alloc_value, &ConditionEnv, &mut instructions)`: + - 条件式の内部で出てくるサブ式(`i+1`, `s.length()`, `digits.indexOf(ch)` など)を JoinIR 値に潰す。 + - `MethodCallLowerer` を経由してメソッド呼び出しを BoxCall に変換。 +- 制約: + - JoinIR 専用(MirBuilder には触らない)。 + - 変数解決は ConditionEnv に限定(LoopBodyLocalEnv や UpdateEnv には直接アクセスしない)。 + - support 対象外の AST ノードは Fail-Fast(`Result::Err`)で返す。 + +--- + +## 2. bool_expr_lowerer.rs + +- 役割: + - 「MIR 向け」の boolean 式 lowering(AST → MirBuilder / SSA)を行う旧来の箱。 + - OR チェーンや `&&`/`||`/`!` を MIR の Compare / BinOp / UnaryOp に展開する。 +- 主な機能: + - `BoolExprLowerer::lower_condition(&ASTNode) -> Result`: + - BinaryOp(比較演算子 + `&&`/`||`)を再帰的に潰し、MirInstruction を emit。 + - 変数・リテラル・メソッド呼び出しなどは MirBuilder の `build_expression` に委譲。 +- 制約: + - MirBuilder 前提の API で、JoinIR condition_to_joinir とは別ライン。 + - 現時点では「ほぼ未使用(テストもコメントアウト)」扱いの歴史的モジュール。 + - condition_to_joinir 側と直接の接点はなく、将来 ExprLowerer に統合する際の候補。 + +--- + +## 3. loop_body_local_init.rs(LoopBodyLocalInitLowerer) + +- 役割: + - ループ本体の `local` 宣言の初期化式を AST → JoinIR に落として `LoopBodyLocalEnv` に格納する。 + - 「body-local 変数の定義側(init)」専用の lowering。 +- 主な機能: + - `lower_inits_for_loop(body_ast, &mut LoopBodyLocalEnv)`: + - ループ本体 AST から `ASTNode::Local` をスキャンし、各変数の init 式を順番に処理。 + - `lower_init_expr(expr, &LoopBodyLocalEnv) -> Result`: + - リテラル(整数/文字列)→ Const + - 変数参照 → ConditionEnv 経由で解決 + - 二項演算(`+ - * /`)→ BinOp + - MethodCall(`s.substring`, `digits.indexOf`)→ `emit_method_call_init` 経由で MethodCallLowerer に委譲 +- 制約: + - 変数解決は ConditionEnv + 既存の LoopBodyLocalEnv(cascading)のみ。 + - サポート外のリテラル種別・演算子・複雑な式は Fail-Fast。 + - lowering 対象は「init 式」だけで、更新式(UpdateExpr)は別の箱(CarrierUpdateEmitter)が担当。 + +--- + +## 4. method_call_lowerer.rs(MethodCallLowerer) + +- 役割: + - MethodCall AST ノードを CoreMethodId メタデータに基づいて JoinIR BoxCall に lowering する箱。 + - 同じメソッド呼び出しでも「条件文から使うか」「init から使うか」でホワイトリストを分けている。 +- 主な機能: + - `lower_for_condition(recv_val, method_name, args, alloc, &ConditionEnv, &mut instructions)`: + - `allowed_in_condition()` に通る CoreMethodId だけ許可(例: `length`, 一部の `indexOf`)。 + - 引数は `condition_lowerer::lower_value_expression` で JoinIR 値に lowering。 + - `lower_for_init(recv_val, method_name, args, alloc, &ConditionEnv, &LoopBodyLocalEnv, &mut instructions)`: + - `allowed_in_init()` に通るメソッド(`substring`, `indexOf` など)を body-local init 用に lowering。 + - 引数の変数は LoopBodyLocalEnv → ConditionEnv の優先順で解決(cascading local をサポート)。 + - `lower_arg_with_cascading`: + - 引数用の小さなヘルパー。変数なら LoopBodyLocalEnv/ConditionEnv を見て、それ以外は condition_lowerer に委譲。 +- 制約: + - CoreMethodId メタデータが前提(メソッド名や Box 名のハードコード禁止)。 + - 文脈(condition/init)ごとに別 API で呼び分ける必要がある。 + - 型情報は暗黙的(CoreMethodId 側に埋め込まれており、TypeContext のような統一ビューはまだない)。 + +--- + +## 5. carrier_update_emitter.rs(CarrierUpdateEmitter) + +- 役割: + - LoopUpdateAnalyzer で分類された UpdateExpr(`sum = sum + digit` など)を JoinIR 命令列に変換する。 + - 「キャリア更新の右辺式」を安全なパターンだけ受理して lowering するホワイトリスト箱。 +- 主な機能: + - `emit_carrier_update_with_env(carrier, &UpdateExpr, alloc, &UpdateEnv, &mut instructions)`: + - `UpdateRhs::Const` → Const + BinOp(Add/Mul)。 + - `UpdateRhs::Variable` → UpdateEnv.resolve(name) 経由で条件変数・body-local を横断解決。 + - `UpdateRhs::StringLiteral` → Const(String)。 + - `UpdateRhs::NumberAccumulation` → base/digit 組み合わせを Mul + Add の2段構成で emit。 + - `emit_carrier_update`(レガシー): + - ConditionEnv ベースの旧 API(body-local 非対応)を後方互換のために残している。 +- 制約: + - lowering 対象は「UpdateExpr に正規化済みの式」に限る(生 AST は扱わない)。 + - `UpdateRhs::Other` や method call を含む複雑な更新は can_lower() 側で reject される前提。 + - 型は整数/String の一部パターンに限定(TypeContext への統合は未実施)。 + +--- + +## 6. 小まとめ(Phase 230 時点の散らばり方) + +- 「条件式」と「init/body-local」と「UpdateExpr」が、それぞれ別の箱で AST / 中間表現を潰している: + - 条件式: `condition_to_joinir` + `condition_lowerer`(+ 一部 BoolExprLowerer/MIR 側) + - body-local init: `LoopBodyLocalInitLowerer` + `MethodCallLowerer(lower_for_init)` + - carrier 更新: `CarrierUpdateEmitter` + `UpdateEnv`(UpdateExpr ベース) +- 変数解決も 3 系統に分かれている: + - `ConditionEnv`(loop param / captured / condition-only) + - `LoopBodyLocalEnv`(body 内 `local`) + - `UpdateEnv`(ConditionEnv + LoopBodyLocalEnv の合成ビュー) +- 将来の ExprLowerer/ScopeManager では、これらを + - 「式 lowering の SSOT」として ExprLowerer + - 「名前解決の SSOT」として ScopeManager + に段階統合していくのがターゲット、という整理になっているよ。 diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 6a8238c0..5d367d0d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -529,9 +529,44 @@ impl MirBuilder { } } + // Phase 231: ExprLowerer pilot - pre-validate break condition + // 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::expr_lowerer::{ExprLowerer, ExprContext, ExprLoweringError}; + + let scope_manager = Pattern2ScopeManager { + condition_env: &env, + loop_body_local_env: Some(&body_local_env), + captured_env: Some(&captured_env), + carrier_info: &carrier_info, + }; + + // Try ExprLowerer validation (doesn't affect actual lowering yet) + // Phase 231: This is data-gathering only - we want to see which patterns work + match ExprLowerer::new(&scope_manager, ExprContext::Condition, self) + .with_debug(debug) + .lower(&effective_break_condition) + { + Ok(_value_id) => { + eprintln!("[pattern2/phase231] ✓ ExprLowerer successfully validated break condition"); + } + Err(ExprLoweringError::UnsupportedNode(msg)) => { + eprintln!("[pattern2/phase231] ℹ ExprLowerer fallback (unsupported): {}", msg); + // This is expected for complex patterns - not an error + } + Err(e) => { + eprintln!("[pattern2/phase231] ⚠ ExprLowerer validation error: {}", e); + // Unexpected error - log but don't fail (legacy path will handle it) + } + } + } + // Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition // Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation // Phase 176-3: Multi-carrier support - pass carrier_info and carrier_updates + // Phase 231: ExprLowerer validated above, but we still use proven legacy lowering eprintln!( "[pattern2/before_lowerer] About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'", carrier_info.loop_var_name diff --git a/src/mir/join_ir/lowering/expr_lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer.rs new file mode 100644 index 00000000..bff06c4e --- /dev/null +++ b/src/mir/join_ir/lowering/expr_lowerer.rs @@ -0,0 +1,454 @@ +//! Phase 231: Expression Lowering with Unified Scope Management +//! +//! This module provides a pilot implementation of expression lowering that uses +//! ScopeManager for variable resolution. It's a thin wrapper around existing +//! condition_lowerer logic, focusing on API unification rather than reimplementation. +//! +//! ## Design Philosophy +//! +//! **Box-First**: ExprLowerer is a "box" that encapsulates expression lowering +//! logic with clean boundaries: takes AST + ScopeManager, returns ValueId + instructions. +//! +//! **Incremental Adoption**: Phase 231 starts with Condition context only. +//! Future phases will expand to support General expressions (method calls, etc.). +//! +//! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers +//! to fall back to legacy paths. + +use crate::ast::{ASTNode, BinaryOperator, UnaryOperator}; +use crate::mir::ValueId; +use crate::mir::builder::MirBuilder; +use super::scope_manager::ScopeManager; +use super::condition_lowerer::lower_condition_to_joinir; +use super::condition_env::ConditionEnv; + +/// Phase 231: Expression lowering context +/// +/// Defines the context in which an expression is being lowered, which affects +/// what AST nodes are supported and how they're translated. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExprContext { + /// Loop condition expression (limited subset: comparisons, logical ops) + Condition, + + /// General expression (future: method calls, box ops, etc.) + #[allow(dead_code)] // Phase 231: Not yet implemented + General, +} + +/// Phase 231: Expression lowering error +/// +/// Explicit error types allow callers to handle different failure modes +/// (e.g., fall back to legacy path for unsupported nodes). +#[derive(Debug)] +pub enum ExprLoweringError { + /// AST node type not supported in this context + UnsupportedNode(String), + + /// Variable not found in any scope + VariableNotFound(String), + + /// Type error during lowering (e.g., non-boolean in condition) + TypeError(String), + + /// Internal lowering error (from condition_lowerer) + LoweringError(String), +} + +impl std::fmt::Display for ExprLoweringError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExprLoweringError::UnsupportedNode(msg) => write!(f, "Unsupported node: {}", msg), + ExprLoweringError::VariableNotFound(name) => write!(f, "Variable not found: {}", name), + ExprLoweringError::TypeError(msg) => write!(f, "Type error: {}", msg), + ExprLoweringError::LoweringError(msg) => write!(f, "Lowering error: {}", msg), + } + } +} + +/// Phase 231: Expression lowerer (pilot implementation) +/// +/// This struct provides a unified interface for lowering AST expressions to +/// JoinIR instructions, using ScopeManager for variable resolution. +/// +/// ## Current Scope (Phase 231) +/// +/// - **Context**: Condition only (loop/break conditions) +/// - **Supported**: Literals, variables, comparisons (<, >, ==, !=, <=, >=), logical ops (and, or, not) +/// - **Not Supported**: Method calls, NewBox, complex expressions +/// +/// ## Usage Pattern +/// +/// ```ignore +/// let scope = Pattern2ScopeManager { ... }; +/// let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, builder); +/// +/// match expr_lowerer.lower(&break_condition_ast) { +/// Ok(value_id) => { +/// // Use value_id in JoinIR +/// } +/// Err(ExprLoweringError::UnsupportedNode(_)) => { +/// // Fall back to legacy condition_to_joinir path +/// } +/// Err(e) => { +/// // Handle other errors (variable not found, etc.) +/// } +/// } +/// ``` +pub struct ExprLowerer<'env, 'builder, S: ScopeManager> { + /// Scope manager for variable resolution + scope: &'env S, + + /// Expression context (Condition vs General) + context: ExprContext, + + /// MIR builder (for ValueId allocation, not used in Phase 231) + #[allow(dead_code)] // Phase 231: Reserved for future use + builder: &'builder mut MirBuilder, + + /// Debug flag (inherited from caller) + debug: bool, +} + +impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { + /// Create a new expression lowerer + /// + /// # Arguments + /// + /// * `scope` - ScopeManager for variable resolution + /// * `context` - Expression context (Condition or General) + /// * `builder` - MIR builder (for future use) + pub fn new(scope: &'env S, context: ExprContext, builder: &'builder mut MirBuilder) -> Self { + Self { + scope, + context, + builder, + debug: false, + } + } + + /// Enable debug output + pub fn with_debug(mut self, debug: bool) -> Self { + self.debug = debug; + self + } + + /// Lower an expression to JoinIR ValueId + /// + /// Phase 231: This is the main entry point. Currently delegates to + /// lower_condition for Condition context. + /// + /// # Returns + /// + /// * `Ok(ValueId)` - Expression result ValueId + /// * `Err(ExprLoweringError)` - Lowering failed (caller can fall back to legacy) + pub fn lower(&mut self, ast: &ASTNode) -> Result { + match self.context { + ExprContext::Condition => self.lower_condition(ast), + ExprContext::General => { + Err(ExprLoweringError::UnsupportedNode( + "General expression context not yet implemented (Phase 231)".to_string() + )) + } + } + } + + /// Lower a condition expression to JoinIR ValueId + /// + /// Phase 231: Thin wrapper around condition_lowerer. The main innovation + /// is using ScopeManager for variable resolution instead of direct ConditionEnv. + /// + /// # Returns + /// + /// * `Ok(ValueId)` - Condition result ValueId (boolean) + /// * `Err(ExprLoweringError)` - Lowering failed + fn lower_condition(&mut self, ast: &ASTNode) -> Result { + // 1. Check if AST is supported in condition context + if !Self::is_supported_condition(ast) { + return Err(ExprLoweringError::UnsupportedNode( + format!("Unsupported condition node: {:?}", ast) + )); + } + + // 2. Build ConditionEnv from ScopeManager + // This is the key integration point: we translate ScopeManager's view + // into the ConditionEnv format expected by condition_lowerer. + let condition_env = self.build_condition_env_from_scope(ast)?; + + // 3. Delegate to existing condition_lowerer + // Phase 231: We use the existing, well-tested lowering logic. + let mut value_counter = 1000u32; // Phase 231: Start high to avoid collisions + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + let (result_value, _instructions) = lower_condition_to_joinir( + ast, + &mut alloc_value, + &condition_env, + ).map_err(|e| ExprLoweringError::LoweringError(e))?; + + if self.debug { + eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value); + } + + Ok(result_value) + } + + /// Build ConditionEnv from ScopeManager + /// + /// This method extracts all variables referenced in the AST and resolves + /// them through ScopeManager, building a ConditionEnv for condition_lowerer. + fn build_condition_env_from_scope(&self, ast: &ASTNode) -> Result { + let mut env = ConditionEnv::new(); + + // Extract all variable names from the AST + let var_names = Self::extract_variable_names(ast); + + // Resolve each variable through ScopeManager + for name in var_names { + if let Some(value_id) = self.scope.lookup(&name) { + env.insert(name.clone(), value_id); + } else { + return Err(ExprLoweringError::VariableNotFound(name)); + } + } + + Ok(env) + } + + /// Extract all variable names from an AST node (recursively) + fn extract_variable_names(ast: &ASTNode) -> Vec { + let mut names = Vec::new(); + Self::extract_variable_names_recursive(ast, &mut names); + names.sort(); + names.dedup(); + names + } + + /// Recursive helper for variable name extraction + fn extract_variable_names_recursive(ast: &ASTNode, names: &mut Vec) { + match ast { + ASTNode::Variable { name, .. } => { + names.push(name.clone()); + } + ASTNode::BinaryOp { left, right, .. } => { + Self::extract_variable_names_recursive(left, names); + Self::extract_variable_names_recursive(right, names); + } + ASTNode::UnaryOp { operand, .. } => { + Self::extract_variable_names_recursive(operand, names); + } + // Phase 231: Only support simple expressions + _ => {} + } + } + + /// Check if an AST node is supported in condition context + /// + /// Phase 231: Conservative whitelist. We only support patterns we know work. + fn is_supported_condition(ast: &ASTNode) -> bool { + match ast { + // Literals: Integer, Bool + ASTNode::Literal { .. } => true, + + // Variables + ASTNode::Variable { .. } => true, + + // Comparison operators + ASTNode::BinaryOp { operator, left, right, .. } => { + let op_supported = matches!( + operator, + BinaryOperator::Less + | BinaryOperator::Greater + | BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::LessEqual + | BinaryOperator::GreaterEqual + | BinaryOperator::And + | BinaryOperator::Or + ); + + op_supported + && Self::is_supported_condition(left) + && Self::is_supported_condition(right) + } + + // Unary operators (not) + ASTNode::UnaryOp { operator, operand, .. } => { + matches!(operator, UnaryOperator::Not) + && Self::is_supported_condition(operand) + } + + // Everything else is unsupported + _ => false, + } + } +} + +#[cfg(test)] +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::condition_env::ConditionEnv; + use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; + + // Helper to create a test MirBuilder (Phase 231: minimal stub) + fn create_test_builder() -> MirBuilder { + MirBuilder::new() + } + + #[test] + fn test_expr_lowerer_simple_comparison() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + + 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(); + + // AST: i < 10 + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + + assert!(result.is_ok(), "Should lower simple comparison successfully"); + } + + #[test] + fn test_expr_lowerer_variable_not_found() { + let condition_env = ConditionEnv::new(); + + 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(); + + // AST: unknown_var < 10 + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "unknown_var".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + + assert!(matches!(result, Err(ExprLoweringError::VariableNotFound(_)))); + } + + #[test] + fn test_expr_lowerer_unsupported_node() { + let condition_env = ConditionEnv::new(); + + 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(); + + // AST: MethodCall (unsupported in condition context) + let ast = ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + + let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder); + let result = expr_lowerer.lower(&ast); + + assert!(matches!(result, Err(ExprLoweringError::UnsupportedNode(_)))); + } + + #[test] + fn test_is_supported_condition() { + // Supported: i < 10 + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + assert!(ExprLowerer::::is_supported_condition(&ast)); + + // Unsupported: MethodCall + let ast = ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), + span: Span::unknown(), + }), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + }; + assert!(!ExprLowerer::::is_supported_condition(&ast)); + } +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 487e3ce8..36deab25 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -34,6 +34,7 @@ pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrato pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven) pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST pub mod continue_branch_normalizer; // Phase 33-19: Continue branch normalization for Pattern B +pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection pub(crate) mod exit_args_resolver; // Internal exit argument resolution @@ -52,6 +53,7 @@ pub mod inline_boundary; // Phase 188-Impl-3: JoinIR→Host boundary pub mod inline_boundary_builder; // Phase 200-2: Builder pattern for JoinInlineBoundary pub mod join_value_space; // Phase 201: Unified JoinIR ValueId allocation pub(crate) mod loop_form_intake; // Internal loop form intake +pub mod scope_manager; // Phase 231: Unified variable scope management pub(crate) mod loop_pattern_router; // Phase 33-12: Loop pattern routing (re-exported) pub(crate) mod loop_pattern_validator; // Phase 33-23: Loop structure validation pub(crate) mod loop_patterns; // Phase 188: Pattern-based loop lowering (3 patterns) diff --git a/src/mir/join_ir/lowering/scope_manager.rs b/src/mir/join_ir/lowering/scope_manager.rs new file mode 100644 index 00000000..f13ded3d --- /dev/null +++ b/src/mir/join_ir/lowering/scope_manager.rs @@ -0,0 +1,326 @@ +//! Phase 231: Scope Manager for Unified Variable Lookup +//! +//! This module provides a unified interface for variable lookup across different +//! scopes in JoinIR lowering. It abstracts over the complexity of multiple +//! environments (ConditionEnv, LoopBodyLocalEnv, CapturedEnv, CarrierInfo). +//! +//! ## Design Philosophy +//! +//! **Box-First**: ScopeManager is a trait-based "box" that encapsulates variable +//! lookup logic, making it easy to swap implementations or test in isolation. +//! +//! **Single Responsibility**: Variable resolution only. Does NOT: +//! - Lower AST to JoinIR (that's ExprLowerer) +//! - Manage ValueId allocation (that's JoinValueSpace) +//! - Handle HOST ↔ JoinIR bindings (that's InlineBoundary) +//! +//! ## Pattern2 Pilot Implementation +//! +//! Phase 231 starts with Pattern2-specific implementation to validate the design. +//! Future phases will generalize to Pattern1, Pattern3, etc. + +use crate::mir::ValueId; +use super::condition_env::ConditionEnv; +use super::loop_body_local_env::LoopBodyLocalEnv; +use super::carrier_info::CarrierInfo; +use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; +use std::collections::BTreeMap; + +/// Phase 231: Scope kind for variables +/// +/// Helps distinguish where a variable comes from, which affects how it's +/// treated during lowering (e.g., PHI generation, exit handling). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VarScopeKind { + /// Loop control variable (i, p) + LoopVar, + /// Carrier variable (sum, count, is_digit_pos) + Carrier, + /// Loop body-local variable (ch, digit_pos before promotion) + LoopBodyLocal, + /// Captured from outer function scope (digits, s, len) + Captured, +} + +/// Phase 231: Scope manager trait for unified variable lookup +/// +/// This trait provides a unified interface for looking up variables across +/// multiple environments. Implementations can aggregate different environment +/// types (ConditionEnv, LoopBodyLocalEnv, etc.) and provide consistent lookup. +/// +/// # Example +/// +/// ```ignore +/// let scope: &dyn ScopeManager = &Pattern2ScopeManager { ... }; +/// if let Some(value_id) = scope.lookup("sum") { +/// // Use value_id in expression lowering +/// } +/// ``` +pub trait ScopeManager { + /// Look up variable by name, return ValueId if found + /// + /// This method searches across all available scopes and returns the first + /// match. The search order is implementation-defined but should be + /// documented in the implementing struct. + fn lookup(&self, name: &str) -> Option; + + /// Get the scope kind of a variable + /// + /// This helps the caller understand where the variable comes from, which + /// can affect code generation (e.g., PHI node generation, exit handling). + fn scope_of(&self, name: &str) -> Option; +} + +/// Phase 231: Pattern2-specific scope manager (pilot implementation) +/// +/// This implementation aggregates all the environments used in Pattern2 loop +/// lowering and provides unified variable lookup. +/// +/// ## Lookup Order +/// +/// 1. ConditionEnv (includes loop var, carriers, condition-only vars) +/// 2. LoopBodyLocalEnv (body-local variables before promotion) +/// 3. CapturedEnv (function-scoped captured variables) +/// 4. Promoted LoopBodyLocal → Carrier (using naming convention) +/// +/// ## Naming Convention for Promoted Variables +/// +/// - DigitPos pattern: `"digit_pos"` → `"is_digit_pos"` +/// - Trim pattern: `"ch"` → `"is_ch_match"` +/// +/// # Example +/// +/// ```ignore +/// let scope = Pattern2ScopeManager { +/// condition_env: &env, +/// loop_body_local_env: Some(&body_local_env), +/// captured_env: Some(&captured_env), +/// carrier_info: &carrier_info, +/// }; +/// +/// // Lookup loop variable +/// assert_eq!(scope.lookup("i"), Some(ValueId(100))); +/// +/// // Lookup carrier +/// assert_eq!(scope.lookup("sum"), Some(ValueId(101))); +/// +/// // Lookup promoted variable (uses naming convention) +/// assert_eq!(scope.lookup("digit_pos"), Some(ValueId(102))); // Resolves to "is_digit_pos" +/// ``` +pub struct Pattern2ScopeManager<'a> { + /// Condition environment (loop var + carriers + condition-only vars) + pub condition_env: &'a ConditionEnv, + + /// Loop body-local environment (optional, may be empty) + pub loop_body_local_env: Option<&'a LoopBodyLocalEnv>, + + /// Captured environment (optional, may be empty) + pub captured_env: Option<&'a CapturedEnv>, + + /// Carrier information (includes promoted_loopbodylocals list) + pub carrier_info: &'a CarrierInfo, +} + +impl<'a> ScopeManager for Pattern2ScopeManager<'a> { + fn lookup(&self, name: &str) -> Option { + // 1. ConditionEnv (highest priority: loop var, carriers, condition-only) + if let Some(id) = self.condition_env.get(name) { + return Some(id); + } + + // 2. LoopBodyLocalEnv (body-local variables) + if let Some(env) = self.loop_body_local_env { + if let Some(id) = env.get(name) { + return Some(id); + } + } + + // 3. CapturedEnv (function-scoped captured variables) + if let Some(env) = self.captured_env { + for var in &env.vars { + if var.name == name { + // Captured variables are already in condition_env, so this + // should have been caught in step 1. But check here for safety. + return self.condition_env.get(name); + } + } + } + + // 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 + } + + fn scope_of(&self, name: &str) -> Option { + // Check loop variable first + if name == self.carrier_info.loop_var_name { + return Some(VarScopeKind::LoopVar); + } + + // Check carriers + if self.carrier_info.carriers.iter().any(|c| c.name == name) { + return Some(VarScopeKind::Carrier); + } + + // Check body-local + if let Some(env) = self.loop_body_local_env { + if env.contains(name) { + return Some(VarScopeKind::LoopBodyLocal); + } + } + + // Check captured + if let Some(env) = self.captured_env { + if env.vars.iter().any(|v| v.name == name) { + return Some(VarScopeKind::Captured); + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::join_ir::lowering::carrier_info::{CarrierVar, CarrierRole, CarrierInit}; + use crate::mir::loop_pattern_detection::function_scope_capture::CapturedVar; + + #[test] + fn test_pattern2_scope_manager_loop_var() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + + 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, + }; + + assert_eq!(scope.lookup("i"), Some(ValueId(100))); + assert_eq!(scope.scope_of("i"), Some(VarScopeKind::LoopVar)); + } + + #[test] + fn test_pattern2_scope_manager_carrier() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + condition_env.insert("sum".to_string(), ValueId(101)); + + let carrier_info = CarrierInfo { + loop_var_name: "i".to_string(), + loop_var_id: ValueId(1), + carriers: vec![ + CarrierVar { + name: "sum".to_string(), + host_id: ValueId(2), + join_id: Some(ValueId(101)), + role: CarrierRole::LoopState, + init: CarrierInit::FromHost, + }, + ], + 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, + }; + + assert_eq!(scope.lookup("sum"), Some(ValueId(101))); + assert_eq!(scope.scope_of("sum"), Some(VarScopeKind::Carrier)); + } + + #[test] + fn test_pattern2_scope_manager_promoted_variable() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + + let carrier_info = CarrierInfo { + loop_var_name: "i".to_string(), + loop_var_id: ValueId(1), + carriers: vec![ + CarrierVar { + name: "is_digit_pos".to_string(), + host_id: ValueId(2), + join_id: Some(ValueId(102)), + role: CarrierRole::ConditionOnly, + init: CarrierInit::BoolConst(false), + }, + ], + trim_helper: None, + promoted_loopbodylocals: vec!["digit_pos".to_string()], + }; + + let scope = Pattern2ScopeManager { + condition_env: &condition_env, + loop_body_local_env: None, + captured_env: None, + carrier_info: &carrier_info, + }; + + // Lookup "digit_pos" should resolve to "is_digit_pos" carrier + assert_eq!(scope.lookup("digit_pos"), Some(ValueId(102))); + } + + #[test] + fn test_pattern2_scope_manager_body_local() { + let mut condition_env = ConditionEnv::new(); + condition_env.insert("i".to_string(), ValueId(100)); + + let mut body_local_env = LoopBodyLocalEnv::new(); + body_local_env.insert("temp".to_string(), ValueId(200)); + + 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: Some(&body_local_env), + captured_env: None, + carrier_info: &carrier_info, + }; + + assert_eq!(scope.lookup("temp"), Some(ValueId(200))); + assert_eq!(scope.scope_of("temp"), Some(VarScopeKind::LoopBodyLocal)); + } +}