diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 2e36f63a..afcc5cb4 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -42,6 +42,19 @@ - **if-sum 統合**: extract_loop_condition() を正規化ベースに変更 - **テスト**: phase222_if_cond_left_literal_min.hako → RC=2 達成 - **制約解決**: Phase 221 の "if condition pattern" 制約を解消 +- **Phase 223 完了** ✅: LoopBodyLocal Condition Promotion(条件昇格システム) + - **Phase 223-1 完了** ✅: 包括的棚卸(Category A: 6 patterns, Category B: 1, Category C: 2) + - **Phase 223-2 完了** ✅: API レベル設計(LoopBodyCondPromoter Box, P0: Pattern4/_skip_whitespace 向け) + - 既存箱との役割整理(LoopConditionScopeBox, LoopBodyCarrierPromoter, TrimLoopLowerer) + - Promotion API 定義(ConditionPromotionRequest/Result) + - Pattern4 統合方針(promotion-first, Fail-Fast fallback) + - **Phase 223-3 完了** ✅: 実装完了(LoopBodyCondPromoter 抽出, Pattern4 統合, E2E テスト) + - `LoopBodyCondPromoter` Box 実装(`loop_body_cond_promoter.rs`) + - `extract_continue_condition()`: body 内 if 文から continue 条件を抽出 + - Pattern4 への統合: LoopBodyLocal 昇格成功時に lowering 続行(以前は Fail-Fast) + - E2E テスト: `apps/tests/phase223_p4_skip_whitespace_min.hako`(昇格成功 → Pattern4 lowering 続行確認) + - 5 unit tests PASS、ビルド成功、ドキュメント更新完了 + - **残課題**: JoinIR Trim lowering(Phase 172+)で完全な RC 正確性を実現する必要あり ### 2. JsonParser / Trim / selfhost への適用状況 diff --git a/apps/tests/phase223_p4_skip_whitespace_min.hako b/apps/tests/phase223_p4_skip_whitespace_min.hako new file mode 100644 index 00000000..69dd7517 --- /dev/null +++ b/apps/tests/phase223_p4_skip_whitespace_min.hako @@ -0,0 +1,39 @@ +// Phase 223-3 E2E Test: Pattern4 skip_whitespace minimal test +// +// Tests that LoopBodyLocal in continue condition can be promoted +// and Pattern4 lowering continues successfully (no Fail-Fast). +// +// Pattern: Category A-3 (_skip_whitespace) +// - Single LoopBodyLocal (ch) in continue condition +// - Definition: local ch = s.substring(...) +// - Condition: ch == " " || ch == "\t" || ch == "\n" || ch == "\r" +// - Action: pos = pos + 1; continue +// +// Phase 223-3 Achievement: +// - Previously: Fail-Fast with "[cf_loop/pattern4] Cannot promote LoopBodyLocal" +// - Now: Promotion succeeds, Pattern4 lowering continues +// +// NOTE: Full correct execution (RC=2) requires Phase 172+ JoinIR Trim lowering. +// This test verifies promotion works, not final execution correctness. + +static box SkipWhitespaceP4Test { + main() { + local s = " ab" + local pos = 0 + local n = 4 + + loop(pos < n) { + local ch = s.substring(pos, pos + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + pos = pos + 1 + continue + } + break + } + + // Phase 223-3: Promotion success allows lowering to proceed + // Expected final value after Phase 172+: pos = 2 + print(pos) + return pos + } +} diff --git a/docs/development/current/main/PHASE_223_SUMMARY.md b/docs/development/current/main/PHASE_223_SUMMARY.md new file mode 100644 index 00000000..7a57e524 --- /dev/null +++ b/docs/development/current/main/PHASE_223_SUMMARY.md @@ -0,0 +1,291 @@ +# Phase 223: LoopBodyLocal Condition Promotion - Summary + +## Overview + +Phase 223 addresses the "LoopBodyLocal in condition" constraint that blocks JsonParser loops (discovered in Phase 221). This phase enables Pattern2/Pattern4 to handle loops where loop-body-local variables appear in break/continue conditions. + +--- + +## Phase Breakdown + +### Phase 223-1: Comprehensive Inventory ✅ COMPLETE + +**Deliverable**: `/docs/development/current/main/phase223-loopbodylocal-condition-inventory.md` + +**Key Findings**: +- **Category A** (Safe for Promotion): 6 patterns + - A-1/A-2: Trim leading/trailing (✅ already handled by TrimLoopHelper) + - **A-3: Skip whitespace (Pattern 4)** - **P0 target** ⚠️ needs Pattern4 support + - A-4: Digit detection (cascading LoopBodyLocal) - P1 candidate + - A-5: String comparison (multi-variable) - P2 candidate + - A-6: atoi range check (complex) - P2 candidate +- **Category B** (Fail-Fast Maintained): 1 pattern (complex nested conditions) +- **Category C** (Body-Only): 2 patterns (not blocked, already working) + +**Priority Classification**: +- **P0**: Category A-3 (skip_whitespace) - critical for JsonParser +- **P1**: Category A-4 (cascading LoopBodyLocal) - high priority +- **P2**: Category A-5/A-6 (multi-variable, complex) - future extension + +--- + +### Phase 223-2: API-Level Design ✅ COMPLETE + +**Deliverable**: `/docs/development/current/main/phase223-loopbodylocal-condition-design.md` + +**Key Design Decisions**: + +1. **New Box: LoopBodyCondPromoter** (thin coordinator) + - **Location**: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` (to be created in Phase 223-3) + - **Role**: Unified API for Pattern2/Pattern4 condition promotion + - **Delegation**: Reuses existing `LoopBodyCarrierPromoter` for detection logic + - **Output**: Metadata only (no code generation) + +2. **API Signature**: + ```rust + pub struct ConditionPromotionRequest<'a> { + pub loop_param_name: &'a str, + pub cond_scope: &'a LoopConditionScope, + pub scope_shape: Option<&'a LoopScopeShape>, + pub break_cond: Option<&'a ASTNode>, + pub continue_cond: Option<&'a ASTNode>, + pub loop_body: &'a [ASTNode], + } + + pub enum ConditionPromotionResult { + Promoted { carrier_info, promoted_var, carrier_name }, + CannotPromote { reason, vars }, + } + + impl LoopBodyCondPromoter { + pub fn try_promote_for_condition(req: ConditionPromotionRequest) + -> ConditionPromotionResult; + } + ``` + +3. **Pattern4 Integration Strategy**: + - **Current**: Immediate Fail-Fast when `has_loop_body_local() == true` + - **Future** (Phase 223-3): + ```rust + if loop_cond_scope.has_loop_body_local() { + match LoopBodyCondPromoter::try_promote_for_condition(req) { + Promoted { carrier_info, .. } => { + // Merge carrier, continue Pattern4 lowering + } + CannotPromote { .. } => { + // Fail-Fast (same as current) + } + } + } + ``` + +4. **P0 Constraints** (strict): + - Single LoopBodyLocal variable only (e.g., `ch`) + - Must match existing Trim pattern (substring + equality chain) + - No cascading dependencies (A-4: `ch` + `digit_pos` → rejected) + - No multi-variable patterns (A-5: `ch_s` + `ch_lit` → rejected) + +5. **Box Role Matrix**: + +| Box | Detection | Metadata | Code Gen | Integration | +|-----|-----------|----------|----------|-------------| +| **LoopConditionScopeBox** | ✅ Classify vars | ❌ | ❌ | Pattern2/4 | +| **LoopBodyCarrierPromoter** | ✅ Trim pattern | ✅ TrimPatternInfo | ❌ | TrimLoopLowerer | +| **TrimLoopLowerer** | ❌ (delegates) | ✅ CarrierInfo | ✅ MIR emission | Pattern2 only | +| **LoopBodyCondPromoter** (新) | ❌ (delegates) | ✅ CarrierInfo | ❌ | **Pattern2/4** | + +**Design Principle**: Single Responsibility +- **Detection**: LoopBodyCarrierPromoter +- **Metadata**: TrimPatternInfo, CarrierInfo +- **Code Generation**: Pattern-specific lowerers (TrimLoopLowerer for P2, Pattern4 lowerer for P4) +- **Coordination**: LoopBodyCondPromoter (thin wrapper) + +--- + +### Phase 223-3: Implementation (PLANNED) + +**Estimated Deliverables**: + +1. **LoopBodyCondPromoter implementation** (~50-80 lines) + - Extract promotion logic from TrimLoopLowerer + - Add P0 constraint checking (single var, simple pattern) + - Delegate to LoopBodyCarrierPromoter + +2. **Pattern4 integration** (+30-40 lines) + - Call LoopBodyCondPromoter before Fail-Fast + - Merge promoted carrier into existing CarrierInfo + - Continue with Pattern4 lowering if promotion succeeds + +3. **Unit tests** (5-7 test cases) + - P0 promotion success (Category A-3: skip_whitespace) + - Cascading Fail-Fast (Category A-4: ch + digit_pos) + - Complex pattern Fail-Fast (Category B-1: nested if) + - Multi-variable Fail-Fast (Category A-5: ch_s + ch_lit) + +4. **E2E test** (1 file) + - `apps/tests/phase223_p4_skip_whitespace.hako` + - Pattern: _skip_whitespace with continue + - Expected: Pattern4 lowering + MIR execution success + +**Total Estimated Size**: +150-200 lines (net) + +--- + +## Documentation Updates + +### CURRENT_TASK.md ✅ UPDATED + +Added Phase 223 summary: +``` +- **Phase 223 進行中**: LoopBodyLocal Condition Promotion(条件昇格システム) + - **Phase 223-1 完了** ✅: 包括的棚卸(Category A: 6 patterns, Category B: 1, Category C: 2) + - **Phase 223-2 完了** ✅: API レベル設計(LoopBodyCondPromoter Box, P0: Pattern4/_skip_whitespace 向け) + - **Phase 223-3 予定**: 実装(LoopBodyCondPromoter 抽出, Pattern4 統合, E2E テスト) +``` + +### joinir-architecture-overview.md ✅ UPDATED + +Added LoopBodyCondPromoter to Section 2.2 (条件式ライン): +``` +- **LoopBodyCondPromoter(Phase 223-2 設計完了)** + - ファイル: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs`(Phase 223-3 で実装予定) + - 責務: ループ条件に出てくる LoopBodyLocal を carrier に昇格する統一 API + - 設計原則: Thin coordinator, Pattern-agnostic, Fail-Fast + - 入出力: ConditionPromotionRequest → ConditionPromotionResult + - 使用元: Pattern4 (promotion-first), Pattern2 (TrimLoopLowerer 経由) +``` + +--- + +## Impact Analysis + +### Immediate Impact (P0 - Phase 223-3) + +**Unblocks**: +- `apps/tests/parser_box_minimal.hako` (skip_ws method) +- `tools/hako_shared/json_parser.hako` (_skip_whitespace) + +**Pattern Coverage**: +- Category A-1/A-2: Already working (TrimLoopHelper) +- **Category A-3: WILL WORK** (Phase 223-3 target) ✨ +- Category A-4/A-5/A-6: Still blocked (P1/P2 future work) + +**JsonParser Loop Coverage**: +- Before Phase 223: 7/13 loops (54%) +- After Phase 223-3 (P0): **8/13 loops (62%)** (+1 loop: _skip_whitespace Pattern4 variant) + +--- + +### Future Extensions (P1/P2) + +**P1: Cascading LoopBodyLocal** (Category A-4) +- Pattern: `local ch = ...; local digit_pos = digits.indexOf(ch); if digit_pos < 0 { break }` +- Solution: Promote only leaf variable (`digit_pos` → `is_digit`) +- Requires: Dependency analysis in LoopBodyCarrierPromoter + +**P2: Multi-Variable Patterns** (Category A-5, A-6) +- Pattern: `local ch_s = ...; local ch_lit = ...; if ch_s != ch_lit { break }` +- Solution: Promote to single carrier (`chars_match`) +- Requires: Multi-variable carrier initialization + +**Non-Goals**: +- Category B (Complex patterns): Continue to Fail-Fast +- Nested if with reassignment +- Method call chains + +--- + +## Test Strategy + +### Phase 223-3 Tests + +1. **Unit Tests** (5-7 cases): + - `test_p0_skip_whitespace_promotion()`: Category A-3 success + - `test_cascading_fail_fast()`: Category A-4 rejection (P0 constraint) + - `test_multi_variable_fail_fast()`: Category A-5 rejection + - `test_complex_pattern_fail_fast()`: Category B-1 rejection + - `test_pattern2_integration()`: Verify existing TrimLoopLowerer still works + +2. **E2E Tests** (1 file): + - `apps/tests/phase223_p4_skip_whitespace.hako` + - Expected output: Correct whitespace skipping behavior + - Verification: MIR execution returns expected result + +3. **Regression Tests**: + - All Phase 220-222 tests must continue to pass + - Existing TrimLoopHelper tests (Phase 171-176) must pass + +--- + +## Success Criteria + +### Phase 223-2 ✅ COMPLETE + +- [x] Design document created (`phase223-loopbodylocal-condition-design.md`) +- [x] API types defined (ConditionPromotionRequest/Result) +- [x] Box roles clarified (LoopBodyCondPromoter vs existing boxes) +- [x] Pattern4 integration strategy documented +- [x] P0 constraints specified (single var, Trim pattern only) +- [x] CURRENT_TASK.md updated +- [x] joinir-architecture-overview.md updated + +### Phase 223-3 (PLANNED) + +- [ ] LoopBodyCondPromoter implementation (~50-80 lines) +- [ ] Pattern4 integration (+30-40 lines) +- [ ] Unit tests (5-7 cases) all passing +- [ ] E2E test passing (phase223_p4_skip_whitespace.hako) +- [ ] No regressions in Phase 220-222 tests +- [ ] TrimLoopHelper tests continue to pass + +--- + +## Files Modified/Created + +### Phase 223-1 +- Created: `docs/development/current/main/phase223-loopbodylocal-condition-inventory.md` + +### Phase 223-2 +- Created: `docs/development/current/main/phase223-loopbodylocal-condition-design.md` +- Updated: `CURRENT_TASK.md` +- Updated: `docs/development/current/main/joinir-architecture-overview.md` +- Created: `docs/development/current/main/PHASE_223_SUMMARY.md` (this file) + +### Phase 223-3 (Planned) +- Create: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` (~80 lines) +- Modify: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` (+40 lines) +- Create: `apps/tests/phase223_p4_skip_whitespace.hako` (E2E test) +- Create: Unit test module in `loop_body_cond_promoter.rs` (~100 lines tests) + +--- + +## Relation to Overall JoinIR Roadmap + +**Phase 223** is part of the **JsonParser実戦投入ライン** (Phase 220-225): + +``` +Phase 220: ConditionEnv統合 ✅ +Phase 221: 実戦投入・制約整理 ✅ +Phase 222: If Condition正規化 ✅ +Phase 223: LoopBodyLocal Condition Promotion ← 現在地 + ├─ Phase 223-1: Inventory ✅ + ├─ Phase 223-2: Design ✅ (完了) + └─ Phase 223-3: Implementation (次) +Phase 224: P1 Cascading LoopBodyLocal (計画中) +Phase 225: P2 Multi-Variable Patterns (計画中) +``` + +**Overall Goal**: Enable all JsonParser loops to use JoinIR Pattern1-4 infrastructure + +**Current Coverage**: +- Before Phase 223: 7/13 loops (54%) +- After Phase 223-3 (P0): 8/13 loops (62%) +- After Phase 224 (P1): 10/13 loops (77% estimated) +- After Phase 225 (P2): 11/13 loops (85% estimated) +- Remaining 2 loops: Category B (complex, may remain Fail-Fast) + +--- + +## Revision History + +- **2025-12-10**: Phase 223-2 design complete, summary document created diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 8cc41d36..f74456df 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -308,6 +308,27 @@ Local Region (1000+): - Phase 174 で `_parse_string` 最小化版(終端クォート検出)でも動作確認済み。 - → 空白文字以外の文字比較ループにも対応可能(TrimLoopHelper の汎用性実証)。 +- **LoopBodyCondPromoter(Phase 223-3 実装完了)** + - ファイル: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` + - 責務: + - ループ条件(header/break/continue)に出てくる LoopBodyLocal を carrier に昇格する統一 API。 + - P0(Category A-3: _skip_whitespace): 単一変数の Trim パターンのみ対応。 + - Pattern 2/Pattern 4 両対応の薄いコーディネーター箱(detection は LoopBodyCarrierPromoter に委譲)。 + - Phase 223-3 実装内容: + - `extract_continue_condition()`: body 内の if 文から continue 条件を抽出。 + - `try_promote_for_condition()`: LoopBodyCarrierPromoter を使った昇格処理。 + - Pattern4 への統合完了: LoopBodyLocal 条件の昇格成功時に lowering を続行(以前は Fail-Fast)。 + - 設計原則: + - **Thin coordinator**: LoopBodyCarrierPromoter に promotion ロジックを委譲し、metadata のみ返す。 + - **Pattern-agnostic**: Pattern2 (break) / Pattern4 (continue) の統一入口として機能。 + - **Fail-Fast**: 複雑パターン(Category B: 多段 if / method chain)は引き続き reject。 + - 入出力: + - 入力: `ConditionPromotionRequest`(loop_param_name, cond_scope, break_cond/continue_cond, loop_body) + - 出力: `ConditionPromotionResult::Promoted { carrier_info, promoted_var, carrier_name }` または `CannotPromote { reason, vars }` + - 使用元(Phase 223-3 実装時): + - Pattern4: promotion-first(昇格試行 → 成功なら CarrierInfo merge → 失敗なら Fail-Fast) + - Pattern2: 既存 TrimLoopLowerer 経由で間接利用(将来的に統一可能) + - **ContinueBranchNormalizer / LoopUpdateAnalyzer** - ファイル: - `src/mir/join_ir/lowering/continue_branch_normalizer.rs` diff --git a/docs/development/current/main/phase223-loopbodylocal-condition-design.md b/docs/development/current/main/phase223-loopbodylocal-condition-design.md new file mode 100644 index 00000000..68441e8e --- /dev/null +++ b/docs/development/current/main/phase223-loopbodylocal-condition-design.md @@ -0,0 +1,650 @@ +# Phase 223-2: LoopBodyLocal Condition Promotion Design + +## Purpose + +This document defines the **API-level design** for promoting LoopBodyLocal variables used in loop conditions (header, break, continue) to bool carriers. This enables Pattern2/Pattern4 to handle loops like JsonParser's `_skip_whitespace` which currently Fail-Fast. + +**Scope**: Design-only phase. No implementation. Pure API definitions and integration strategy. + +**P0 Target**: Category A-3 (_skip_whitespace pattern with continue) + +--- + +## 1. Problem Statement + +### 1.1 Current State + +**Fail-Fast Location**: Pattern2/Pattern4 lowerers detect LoopBodyLocal in conditions and reject: + +```rust +// src/mir/join_ir/lowering/loop_with_break_minimal.rs +if loop_cond_scope.has_loop_body_local() { + let body_local_names = extract_body_local_names(&loop_cond_scope.vars); + return Err(format_unsupported_condition_error("pattern2", &body_local_names)); +} +``` + +**Example Pattern (Category A-3)**: +```hako +loop(i < n) { + local ch = src.substring(i, i + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + i = i + 1 + continue + } + break +} +``` + +**Current Behavior**: Fail-Fast with `[joinir/freeze] Pattern4: unsupported condition variables (LoopBodyLocal): ch` + +**Desired Behavior**: Promote `ch` to bool carrier `is_whitespace`, continue with Pattern4 lowering + +--- + +### 1.2 Existing Infrastructure + +| Box | Responsibility | Location | +|-----|---------------|----------| +| **LoopConditionScopeBox** | Classify variables as LoopParam/OuterLocal/LoopBodyLocal | `loop_condition_scope.rs` | +| **LoopBodyCarrierPromoter** | Promote LoopBodyLocal to bool carrier (Trim/P5) | `loop_body_carrier_promoter.rs` | +| **TrimLoopLowerer** | Orchestrate Trim pattern detection + lowering | `trim_loop_lowering.rs` | + +**Current Use Case**: Pattern2 (with break) + Trim pattern +- Detects `local ch = s.substring(...)` + `ch == " " || ...` +- Promotes `ch` to `is_whitespace` carrier +- Replaces break condition with `!is_whitespace` + +**Gap**: Pattern4 (with continue) does not integrate with TrimLoopLowerer + +--- + +## 2. Existing Box Roles + +### 2.1 LoopConditionScopeBox + +**File**: `src/mir/loop_pattern_detection/loop_condition_scope.rs` + +**Responsibility**: +- Analyze condition AST (header, break, continue) +- Classify each variable by scope: + - `LoopParam`: Loop parameter (e.g., `i` in `loop(i < len)`) + - `OuterLocal`: Pre-existing variable from outer scope + - `LoopBodyLocal`: Variable defined inside loop body + +**API**: +```rust +pub struct LoopConditionScope { + pub vars: Vec, +} + +impl LoopConditionScope { + pub fn has_loop_body_local(&self) -> bool; + pub fn var_names(&self) -> HashSet; +} +``` + +**Usage in Pattern2/4**: +```rust +let loop_cond_scope = LoopConditionScopeBox::analyze(break_cond, &scope); +if loop_cond_scope.has_loop_body_local() { + // Current: Fail-Fast + // Future: Try promotion +} +``` + +--- + +### 2.2 LoopBodyCarrierPromoter + +**File**: `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs` + +**Responsibility**: +- Detect Trim-like patterns (substring + equality chain) +- Generate `TrimPatternInfo` with carrier name and initialization logic +- Convert to `CarrierInfo` with placeholder ValueId(0) + +**API**: +```rust +pub struct PromotionRequest<'a> { + pub scope: &'a LoopScopeShape, + pub cond_scope: &'a LoopConditionScope, + pub break_cond: Option<&'a ASTNode>, + pub loop_body: &'a [ASTNode], +} + +pub enum PromotionResult { + Promoted { trim_info: TrimPatternInfo }, + CannotPromote { reason: String, vars: Vec }, +} + +impl LoopBodyCarrierPromoter { + pub fn try_promote(req: PromotionRequest) -> PromotionResult; +} +``` + +**Current Limitation**: Designed for Pattern2 (break), not Pattern4 (continue) + +--- + +### 2.3 TrimLoopLowerer + +**File**: `src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs` + +**Responsibility**: +- Orchestrate Trim pattern detection (via LoopBodyCarrierPromoter) +- Filter condition-only LoopBodyLocal (Phase 183) +- Generate carrier initialization code (substring extraction + bool expression) +- Build ConditionBinding for promoted carrier + +**API**: +```rust +pub struct TrimLoweringResult { + pub condition: ASTNode, // Replaced break condition + pub carrier_info: CarrierInfo, // Updated with promoted carrier + pub condition_bindings: Vec, +} + +impl TrimLoopLowerer { + pub fn try_lower_trim_like_loop( + builder: &mut MirBuilder, + scope: &LoopScopeShape, + loop_cond: &ASTNode, + break_cond: &ASTNode, + body: &[ASTNode], + loop_var_name: &str, + carrier_info: CarrierInfo, + alloc_join_value: &mut dyn FnMut() -> ValueId, + ) -> Result, String>; +} +``` + +**Current Integration**: Pattern2 only +**Gap**: Pattern4 does not call TrimLoopLowerer + +--- + +## 3. New API Design: Condition Promotion for Pattern4 + +### 3.1 Design Philosophy + +**Principle**: Minimal new code. Reuse existing Trim infrastructure with thin adapter. + +**Options Considered**: + +| Option | Pros | Cons | Decision | +|--------|------|------|----------| +| **A**: New `ConditionPromotionBox` | Clean separation | Duplication with TrimLoopLowerer | ❌ Rejected | +| **B**: Extend `TrimLoopLowerer` with `for_condition()` | Reuse existing logic | Name becomes misleading | ❌ Rejected | +| **C**: Extract `LoopBodyCondPromoter` as shared base | Single responsibility | Refactoring required | ✅ **Selected** | + +**Selected Approach (Option C)**: +- **Phase 223-2 (Design)**: Define `LoopBodyCondPromoter` API (name + types) +- **Phase 223-3 (Impl)**: Extract from `TrimLoopLowerer`, integrate into Pattern4 + +--- + +### 3.2 API Definition: LoopBodyCondPromoter + +**New File**: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` (design-only, no implementation yet) + +```rust +/// Phase 223-2: LoopBodyLocal condition promotion coordinator +/// +/// Handles promotion of LoopBodyLocal variables used in loop conditions +/// to bool carriers. Supports Pattern2 (break), Pattern4 (continue), +/// and future patterns. +/// +/// ## Responsibilities +/// +/// - Detect safe promotion patterns (Category A-3 from phase223-loopbodylocal-condition-inventory.md) +/// - Coordinate with LoopBodyCarrierPromoter for actual promotion logic +/// - Provide uniform API for Pattern2/Pattern4 integration +/// +/// ## Design Principle +/// +/// This is a **thin coordinator** that reuses existing boxes: +/// - LoopBodyCarrierPromoter: Promotion logic (Trim pattern detection) +/// - TrimLoopHelper: Pattern-specific metadata +/// - ConditionEnvBuilder: Binding generation +pub struct LoopBodyCondPromoter; + +/// Promotion request for condition variables +/// +/// Unified API for Pattern2 (break) and Pattern4 (continue) +pub struct ConditionPromotionRequest<'a> { + /// Loop parameter name (e.g., "i") + pub loop_param_name: &'a str, + + /// Condition scope analysis result + pub cond_scope: &'a LoopConditionScope, + + /// Loop structure metadata + pub scope_shape: Option<&'a LoopScopeShape>, + + /// Break condition AST (Pattern2: Some, Pattern4: None) + pub break_cond: Option<&'a ASTNode>, + + /// Continue condition AST (Pattern4: Some, Pattern2: None) + pub continue_cond: Option<&'a ASTNode>, + + /// Loop body statements + pub loop_body: &'a [ASTNode], +} + +/// Promotion result +pub enum ConditionPromotionResult { + /// Promotion successful + Promoted { + /// Carrier metadata (from TrimLoopHelper) + carrier_info: CarrierInfo, + + /// Variable name that was promoted (e.g., "ch") + promoted_var: String, + + /// Promoted carrier name (e.g., "is_whitespace") + carrier_name: String, + }, + + /// Cannot promote (Fail-Fast) + CannotPromote { + /// Human-readable reason + reason: String, + + /// List of problematic LoopBodyLocal variables + vars: Vec, + }, +} + +impl LoopBodyCondPromoter { + /// Try to promote LoopBodyLocal variables in conditions + /// + /// Phase 223-2 Design: API signature only + /// Phase 223-3 Implementation: Extract from TrimLoopLowerer + /// + /// ## P0 Requirements (Category A-3) + /// + /// - Single LoopBodyLocal variable (e.g., `ch`) + /// - Definition: `local ch = s.substring(...)` or similar + /// - Condition: Simple equality chain (e.g., `ch == " " || ch == "\t"`) + /// - Pattern: Identical to existing Trim pattern + /// + /// ## Algorithm (Delegated to LoopBodyCarrierPromoter) + /// + /// 1. Extract LoopBodyLocal names from cond_scope + /// 2. Build PromotionRequest for LoopBodyCarrierPromoter + /// 3. Call LoopBodyCarrierPromoter::try_promote() + /// 4. Convert PromotionResult to ConditionPromotionResult + /// 5. Return result (Promoted or CannotPromote) + /// + /// ## Differences from TrimLoopLowerer + /// + /// - TrimLoopLowerer: Full lowering pipeline (detection + code generation) + /// - LoopBodyCondPromoter: Detection + metadata only (no code generation) + /// + /// This allows Pattern4 to handle code generation differently than Pattern2 + /// while sharing the same promotion logic. + pub fn try_promote_for_condition( + req: ConditionPromotionRequest + ) -> ConditionPromotionResult { + // Phase 223-3 implementation: + // 1. Check P0 constraints (single LoopBodyLocal, simple pattern) + // 2. Delegate to LoopBodyCarrierPromoter::try_promote() + // 3. Convert result to ConditionPromotionResult + unimplemented!("Phase 223-3") + } +} +``` + +--- + +### 3.3 P0 Minimum Requirements + +**Target Pattern**: Category A-3 (_skip_whitespace) + +```hako +loop(i < n) { + local ch = src.substring(i, i + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + i = i + 1 + continue + } + break +} +``` + +**Detection Criteria** (must all be true): + +1. **Single LoopBodyLocal**: Exactly one LoopBodyLocal variable (e.g., `ch`) +2. **Substring Definition**: `local ch = s.substring(i, i+1)` or equivalent +3. **Simple Condition**: Equality chain (`ch == "lit" || ch == "lit2" || ...`) +4. **No Reassignment**: Variable is not reassigned inside loop body +5. **Trim Pattern Match**: Must match existing TrimLoopHelper pattern + +**Fail-Fast Cases** (Phase 223-2 will not handle): + +- Multiple LoopBodyLocal variables (Category A-5: string comparison) +- Cascading LoopBodyLocal (Category A-4: `ch` + `digit_pos`) +- Complex expressions (Category B-1: nested if with reassignment) +- Method call chains (Category B-2) + +--- + +## 4. Integration Strategy: Pattern4 Usage + +### 4.1 Current Pattern4 Fail-Fast Point + +**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` + +**Current Code**: +```rust +let loop_cond_scope = LoopConditionScopeBox::analyze( + Some(&continue_cond), + &pipeline_ctx.loop_scope, +); + +if loop_cond_scope.has_loop_body_local() { + return Err(format!( + "[joinir/freeze] Pattern4: unsupported condition variables (LoopBodyLocal): {}", + extract_body_local_names(&loop_cond_scope.vars).join(", ") + )); +} +``` + +--- + +### 4.2 Future Pattern4 Integration (Phase 223-3) + +**Proposed Change** (design-only, not implemented): + +```rust +// Phase 223-2 Design: Integration point +let loop_cond_scope = LoopConditionScopeBox::analyze( + Some(&continue_cond), + &pipeline_ctx.loop_scope, +); + +if loop_cond_scope.has_loop_body_local() { + // Phase 223-3: Try promotion before Fail-Fast + use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ + LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult + }; + + let promotion_req = ConditionPromotionRequest { + loop_param_name: &pipeline_ctx.loop_var_name, + cond_scope: &loop_cond_scope, + scope_shape: Some(&pipeline_ctx.loop_scope), + break_cond: None, // Pattern4: no break condition + continue_cond: Some(&continue_cond), + loop_body: &body, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { + ConditionPromotionResult::Promoted { carrier_info, promoted_var, carrier_name } => { + // Success: Merge promoted carrier into existing carrier_info + let updated_carrier_info = pipeline_ctx.carrier_info + .merge_from(&carrier_info) + .map_err(|e| format!("[joinir/freeze] Pattern4: carrier merge failed: {}", e))?; + + // Update pipeline_ctx with new carrier_info + pipeline_ctx.carrier_info = updated_carrier_info; + + // Continue with Pattern4 lowering (carrier now includes promoted bool) + } + ConditionPromotionResult::CannotPromote { reason, vars } => { + // Fail-Fast: Same as current behavior + return Err(format!( + "[joinir/freeze] Pattern4: unsupported condition variables (LoopBodyLocal): {} ({})", + vars.join(", "), + reason + )); + } + } +} +``` + +**Key Points**: + +1. **Promotion First**: Try promotion before Fail-Fast +2. **Merge Carriers**: Use existing `CarrierInfo::merge_from()` API +3. **Fail-Fast Fallback**: If promotion fails, maintain current behavior +4. **No Code Generation**: `LoopBodyCondPromoter` only provides metadata, Pattern4 lowerer handles JoinIR emission + +--- + +### 4.3 Code Generation Differences: Pattern2 vs Pattern4 + +| Aspect | Pattern2 (break) | Pattern4 (continue) | +|--------|------------------|---------------------| +| **Condition Usage** | `if !is_carrier { break }` | `if is_carrier { continue }` | +| **Carrier Initialization** | Before loop entry | Before loop entry (same) | +| **Carrier Update** | End of loop body | End of loop body (same) | +| **JoinIR Structure** | Break to exit block | Continue to header block | + +**Design Note**: TrimLoopLowerer currently generates `!is_carrier` for break. Pattern4 will use `is_carrier` directly (no negation). + +--- + +## 5. Relationship to Existing Boxes + +### 5.1 Box Hierarchy + +``` +LoopBodyCondPromoter (新規 - Phase 223-2 design) + ├── Delegates to: LoopBodyCarrierPromoter (既存) + │ ├── Uses: TrimPatternInfo (既存) + │ └── Uses: TrimLoopHelper (既存) + └── Used by: + ├── Pattern2 (via TrimLoopLowerer - 既存) + └── Pattern4 (direct call - Phase 223-3 実装) +``` + +### 5.2 Responsibility Matrix + +| Box | Detection | Metadata | Code Gen | Integration | +|-----|-----------|----------|----------|-------------| +| **LoopConditionScopeBox** | ✅ Classify vars | ❌ | ❌ | Pattern2/4 | +| **LoopBodyCarrierPromoter** | ✅ Trim pattern | ✅ TrimPatternInfo | ❌ | TrimLoopLowerer | +| **TrimLoopLowerer** | ❌ (delegates) | ✅ CarrierInfo | ✅ MIR emission | Pattern2 only | +| **LoopBodyCondPromoter** (新) | ❌ (delegates) | ✅ CarrierInfo | ❌ | Pattern2/4 | + +**Design Principle**: Single Responsibility +- **Detection**: LoopBodyCarrierPromoter +- **Metadata**: TrimPatternInfo, CarrierInfo +- **Code Generation**: Pattern-specific lowerers (TrimLoopLowerer for P2, Pattern4 lowerer for P4) +- **Coordination**: LoopBodyCondPromoter (thin wrapper) + +--- + +## 6. Phase 223-3 Implementation Hints + +### 6.1 Extraction Strategy + +**Source**: `TrimLoopLowerer::try_lower_trim_like_loop()` + +**Extract to**: `LoopBodyCondPromoter::try_promote_for_condition()` + +**Extraction Boundary**: + +```rust +// KEEP in TrimLoopLowerer (Pattern2-specific): +// - carrier initialization code generation (emit_trim_carrier_init) +// - condition replacement (build_negated_carrier_condition) +// - ConditionBinding construction + +// MOVE to LoopBodyCondPromoter (pattern-agnostic): +// - LoopBodyCarrierPromoter::try_promote() call +// - PromotionResult → ConditionPromotionResult conversion +// - P0 constraint checking (single var, simple pattern) +``` + +**Expected Line Count**: +- LoopBodyCondPromoter: ~50-80 lines (thin wrapper) +- TrimLoopLowerer: No change (continues to use new API internally) +- Pattern4: +30-40 lines (integration point) + +--- + +### 6.2 Test Strategy + +**Phase 223-2**: No tests (design-only) + +**Phase 223-3**: Unit tests for LoopBodyCondPromoter + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_p0_skip_whitespace_promotion() { + // Category A-3: Single LoopBodyLocal, simple equality chain + let req = ConditionPromotionRequest { + // ... build request for _skip_whitespace pattern + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::Promoted { carrier_name, .. } => { + assert_eq!(carrier_name, "is_whitespace"); + } + _ => panic!("Expected promotion success"), + } + } + + #[test] + fn test_cascading_loopbodylocal_fail_fast() { + // Category A-4: Cascading (ch + digit_pos) should Fail-Fast in P0 + let req = ConditionPromotionRequest { + // ... build request for cascading pattern + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::CannotPromote { reason, vars } => { + assert!(reason.contains("multiple LoopBodyLocal")); + assert_eq!(vars.len(), 2); // ch + digit_pos + } + _ => panic!("Expected Fail-Fast"), + } + } +} +``` + +**E2E Test** (Phase 223-3): +- File: `apps/tests/phase223_p4_skip_whitespace.hako` +- Content: Category A-3 pattern (skip_whitespace with continue) +- Expected: Pattern4 lowering succeeds, MIR execution returns correct result + +--- + +## 7. Design Constraints + +### 7.1 P0 Constraints (Strict) + +1. **Single LoopBodyLocal Only**: Exactly one variable in condition +2. **Trim Pattern Only**: Must match existing TrimLoopHelper detection +3. **No Cascading**: No dependencies between LoopBodyLocal variables +4. **No Multi-Break**: Only one break/continue condition (not both) + +**Rationale**: P0 focuses on unblocking _skip_whitespace (Category A-3), which is identical to Trim pattern except for continue vs break. + +--- + +### 7.2 Future Extensions (P1/P2) + +**P1 Extensions** (Phase 224+): +- **Cascading LoopBodyLocal** (Category A-4): + - `local ch = s.substring(...); local pos = digits.indexOf(ch)` + - Promote only leaf variable (`pos` → `is_digit`) + - Requires dependency analysis in LoopBodyCarrierPromoter + +**P2 Extensions** (Phase 225+): +- **Multi-Variable Patterns** (Category A-5): + - `local ch_s = ...; local ch_lit = ...; if ch_s != ch_lit` + - Promote to single carrier (`chars_match`) + - Requires multi-variable carrier initialization + +**Non-Goals**: +- **Category B** (Complex patterns): Continue to Fail-Fast +- **Nested if with reassignment**: Too complex for safe promotion +- **Method call chains**: Side effects cannot be promoted + +--- + +## 8. Documentation Updates + +### 8.1 CURRENT_TASK.md + +**Add to "最近まとまった大きな塊" section**: + +```markdown +- **Phase 223 LoopBodyLocal Condition Promotion (進行中)** + - Phase 223-1: 棚卸完了 (6 Category A patterns, 1 Category B, 2 Category C) + - Phase 223-2: 設計完了 (LoopBodyCondPromoter API, P0 _skip_whitespace 対応) + - Phase 223-3: 実装予定 (Pattern4 統合, E2E テスト) +``` + +--- + +### 8.2 joinir-architecture-overview.md + +**Add to Section 2.2 (条件式ライン)**: + +```markdown +- **LoopBodyCondPromoter(Phase 223-2 設計完了)** + - ファイル: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` + - 責務: + - ループ条件(header/break/continue)に出てくる LoopBodyLocal を carrier に昇格。 + - P0(Category A-3: _skip_whitespace): 単一変数の Trim パターンのみ対応。 + - Pattern2/Pattern4 の統一 API として機能(既存 TrimLoopLowerer から抽出予定)。 + - 設計原則: + - Thin coordinator: LoopBodyCarrierPromoter に detection を委譲、metadata のみ返す。 + - Pattern-agnostic: Pattern2 (break) / Pattern4 (continue) 両対応。 + - Fail-Fast: 複雑パターン(Category B)は引き続き reject。 +``` + +--- + +## 9. Summary: Design Decisions + +| Decision | Rationale | +|----------|-----------| +| **Reuse existing Trim infrastructure** | Avoid duplication, leverage proven Pattern2 logic | +| **Thin coordinator pattern** | LoopBodyCondPromoter delegates to LoopBodyCarrierPromoter | +| **P0: Category A-3 only** | Unblock _skip_whitespace, simplest safe pattern | +| **No code generation in coordinator** | Pattern-specific lowerers handle JoinIR emission | +| **Fail-Fast for complex patterns** | Category B continues to reject (safety first) | +| **Extract from TrimLoopLowerer** | Pattern2 continues to work, Pattern4 gains promotion | + +--- + +## 10. Next Phase: Phase 223-3 Implementation + +**Deliverables**: + +1. **LoopBodyCondPromoter implementation** (~50-80 lines) + - File: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs` + - Extract promotion logic from TrimLoopLowerer + - Add P0 constraint checking + +2. **Pattern4 integration** (+30-40 lines) + - File: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` + - Call LoopBodyCondPromoter before Fail-Fast + - Merge promoted carrier into existing CarrierInfo + +3. **Unit tests** (5-7 test cases) + - P0 promotion success (Category A-3) + - Cascading Fail-Fast (Category A-4) + - Complex pattern Fail-Fast (Category B-1) + +4. **E2E test** (1 file) + - File: `apps/tests/phase223_p4_skip_whitespace.hako` + - Pattern: _skip_whitespace with continue + - Expected: Pattern4 lowering + MIR execution success + +**Estimated Size**: +150-200 lines total (net) + +--- + +## Revision History + +- **2025-12-10**: Phase 223-2 design document created (API-only, no implementation) diff --git a/docs/development/current/main/phase223-loopbodylocal-condition-inventory.md b/docs/development/current/main/phase223-loopbodylocal-condition-inventory.md new file mode 100644 index 00000000..9552e9ad --- /dev/null +++ b/docs/development/current/main/phase223-loopbodylocal-condition-inventory.md @@ -0,0 +1,443 @@ +# Phase 223-1: LoopBodyLocal in Condition - Comprehensive Inventory + +## Purpose + +This document inventories all loops that are currently **blocked** by the LoopConditionScopeBox Fail-Fast mechanism because they have `LoopBodyLocal` variables appearing in loop conditions (header, break, or continue). + +The goal is to: +1. Identify **safe patterns** (Trim/JsonParser-style) that can be promoted to carriers +2. Identify **complex patterns** that should continue to Fail-Fast +3. Provide design input for Phase 223-2 carrier promotion system + +## Detection Methodology + +### Rust-side Detection + +**Fail-Fast Location**: `src/mir/join_ir/lowering/loop_with_break_minimal.rs` + +```rust +if loop_cond_scope.has_loop_body_local() { + let body_local_names = extract_body_local_names(&loop_cond_scope.vars); + return Err(format_unsupported_condition_error("pattern2", &body_local_names)); +} +``` + +**Also checked in**: +- Pattern 4 (with continue): `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` +- TrimLoopLowering: `src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs` (tries promotion first) + +### .hako File Patterns + +Search patterns used: +```bash +# Pattern 1: local variable followed by loop +rg "local\s+\w+.*loop\(" apps/tests tools/hako_shared + +# Pattern 2: substring/indexOf assignments (common in parsers) +rg "local\s+\w+\s*=.*substring|local\s+\w+\s*=.*indexOf" apps/tests tools/hako_shared + +# Pattern 3: Loop conditions with character comparison +grep -r "loop.*ch.*==" apps/tests tools/hako_shared +``` + +--- + +## Category A: Safe Trim/JsonParser Patterns (昇格候補) + +These patterns are **safe for carrier promotion** because: +- LoopBodyLocal is a simple value extraction (substring/indexOf) +- Condition is a simple boolean expression (equality/comparison) +- No complex control flow in the extraction +- Carrier update is straightforward + +### Pattern A-1: Trim Leading Whitespace + +**Example**: `tools/hako_shared/json_parser.hako` (line 330-336) + +```hako +loop(start < end) { + local ch = s.substring(start, start+1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + start = start + 1 + } else { + break + } +} +``` + +**LoopBodyLocal**: `ch` (String) +**Definition**: `local ch = s.substring(start, start+1)` +**Condition Usage**: Break condition uses `ch` in OR chain: `ch == " " || ch == "\t" || ch == "\n" || ch == "\r"` +**Promotion Target**: `is_whitespace` (bool carrier) +**Status**: ✅ **Already handled by TrimLoopHelper** (Phase 171-C) +**Carrier Initialization**: `is_whitespace = (ch == " " || ch == "\t" || ch == "\n" || ch == "\r")` +**Carrier Update**: Same as initialization (at end of loop body) + +--- + +### Pattern A-2: Trim Trailing Whitespace + +**Example**: `tools/hako_shared/json_parser.hako` (line 340-346) + +```hako +loop(end > start) { + local ch = s.substring(end-1, end) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + end = end - 1 + } else { + break + } +} +``` + +**LoopBodyLocal**: `ch` (String) +**Definition**: `local ch = s.substring(end-1, end)` (note: backward indexing) +**Condition Usage**: Break condition uses `ch` in OR chain +**Promotion Target**: `is_whitespace` (bool carrier) +**Status**: ✅ **Already handled by TrimLoopHelper** (Phase 171-C) + +--- + +### Pattern A-3: Skip Whitespace (Parser Pattern) + +**Example**: `apps/tests/parser_box_minimal.hako` (line 30-41) + +```hako +loop(i < n) { + local ch = src.substring(i, i + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + i = i + 1 + continue + } + break +} +``` + +**LoopBodyLocal**: `ch` (String) +**Definition**: `local ch = src.substring(i, i + 1)` +**Condition Usage**: Continue condition uses `ch` in OR chain +**Promotion Target**: `is_whitespace` (bool carrier) +**Status**: ⚠️ **Pattern 4 (with continue)** - needs promotion support +**Notes**: Uses `continue` instead of `break` - currently blocked in Pattern 4 + +--- + +### Pattern A-4: JsonParser Number Parsing (Digit Detection) + +**Example**: `tools/hako_shared/json_parser.hako` (line 121-133) + +```hako +local digits = "0123456789" +loop(p < s.length()) { + local ch = s.substring(p, p+1) + local digit_pos = digits.indexOf(ch) + + // Exit condition: non-digit character found + if digit_pos < 0 { + break + } + + // Continue parsing: digit found + num_str = num_str + ch + p = p + 1 +} +``` + +**LoopBodyLocal**: `digit_pos` (Integer) +**Definition**: `local digit_pos = digits.indexOf(ch)` (depends on another LoopBodyLocal `ch`) +**Condition Usage**: Break condition `if digit_pos < 0` +**Promotion Target**: `is_digit` (bool carrier) +**Status**: ⚠️ **More complex** - depends on TWO LoopBodyLocal variables (`ch` and `digit_pos`) +**Notes**: This is the **cascading LoopBodyLocal pattern** - needs special handling + +--- + +### Pattern A-5: Simple Character Comparison Loop + +**Example**: `apps/tests/phase182_p1_match_literal.hako` (line 356-364) + +```hako +loop(i < len) { + local ch_s = s.substring(pos + i, pos + i + 1) + local ch_lit = literal.substring(i, i + 1) + if ch_s != ch_lit { + print("Result: NOMATCH") + return 0 + } + i = i + 1 +} +``` + +**LoopBodyLocal**: `ch_s` and `ch_lit` (both String) +**Definition**: Two substring extractions +**Condition Usage**: Break condition `if ch_s != ch_lit` +**Promotion Target**: `chars_match` (bool carrier) +**Status**: ⚠️ **Multiple LoopBodyLocal** - needs multi-variable promotion +**Notes**: This is a **string comparison pattern** - safe but needs multi-var support + +--- + +### Pattern A-6: Parser atoi with Range Check + +**Example**: `apps/tests/parser_box_minimal.hako` (line 20-25) and `tools/hako_shared/json_parser.hako` (line 453-460) + +```hako +loop(i < n) { + local ch = s.substring(i, i+1) + if ch < "0" || ch > "9" { break } + local pos = digits.indexOf(ch) + if pos < 0 { break } + v = v * 10 + pos + i = i + 1 +} +``` + +**LoopBodyLocal**: `ch` (String), `pos` (Integer) +**Definition**: +- `local ch = s.substring(i, i+1)` +- `local pos = digits.indexOf(ch)` +**Condition Usage**: +- Break condition 1: `ch < "0" || ch > "9"` (range check) +- Break condition 2: `pos < 0` (indexOf check) +**Promotion Target**: `is_digit` (bool carrier) +**Status**: ⚠️ **Cascading + Multiple Break Conditions** - complex but safe +**Notes**: Two break conditions using different LoopBodyLocal variables + +--- + +## Category B: Complex Patterns (Fail-Fast 維持) + +These patterns should **continue to Fail-Fast** because: +- Multiple complex LoopBodyLocal dependencies +- Nested method calls in conditions +- Complex control flow that cannot be safely promoted + +### Pattern B-1: Nested If with LoopBodyLocal + +**Example**: `apps/tests/minimal_ssa_bug_loop.hako` + +```hako +loop(i < n) { + local line = src.substring(i, i + 10) + + // Problem pattern: nested conditions with reassignment + immediate use + if line.length() > 0 { + line = line.substring(0, 5) // reassign line + if line.length() > 0 && line.substring(0, 1) == "u" { // immediate use + // ... + } + } + i = i + 1 +} +``` + +**LoopBodyLocal**: `line` (String) +**Why Fail-Fast**: +- LoopBodyLocal is **reassigned** inside the loop body +- **Nested conditions** with method calls (`line.length()`, `line.substring()`) +- Cannot create a simple bool carrier - would need complex state tracking + +--- + +### Pattern B-2: Method Call Chain in Condition + +**Example**: Hypothetical (not found in current codebase) + +```hako +loop(i < n) { + local item = array.get(i) + if item.process().isValid() { + // ... + } + i = i + 1 +} +``` + +**Why Fail-Fast**: +- Method call chain makes carrier initialization complex +- Side effects in `process()` cannot be promoted + +--- + +## Category C: Body-Only LoopBodyLocal (不要 - Already Handled) + +These patterns have LoopBodyLocal variables that **only appear in the loop body**, NOT in conditions. They **do not trigger Fail-Fast**. + +### Pattern C-1: Body-Only Computation + +**Example**: `apps/tests/phase183_body_only_loopbodylocal.hako` + +```hako +loop(i < 5) { + // Body-only LoopBodyLocal: temp is computed but never appears in any condition + local temp = i * 2 + + // Break condition doesn't use temp - only uses outer variable i + if i == 3 { + break + } + + result = result + temp + i = i + 1 +} +``` + +**LoopBodyLocal**: `temp` (Integer) +**Condition Usage**: **NONE** - only used in body expression `result = result + temp` +**Status**: ✅ **No Fail-Fast** - these are already handled correctly + +--- + +### Pattern C-2: Body-Local Update Variable + +**Example**: `apps/tests/phase184_body_local_with_break.hako` + +```hako +loop(i < 10) { + local temp = i * 3 // Body-local variable + sum = sum + temp // Use body-local in update expression + + if (sum >= 15) { // Break condition uses 'sum', NOT 'temp' + break + } + + i = i + 1 +} +``` + +**LoopBodyLocal**: `temp` (Integer) +**Condition Usage**: **NONE** - break condition uses `sum`, not `temp` +**Status**: ✅ **No Fail-Fast** - already handled correctly + +--- + +## Summary Statistics + +| Category | Count | Description | +|----------|-------|-------------| +| **Category A** (Safe for Promotion) | 6 patterns | Trim/JsonParser-style, simple boolean extraction | +| **Category B** (Fail-Fast Maintained) | 1 pattern | Complex nested conditions, reassignment | +| **Category C** (Body-Only, Not Blocked) | 2 patterns | LoopBodyLocal only in body, not in conditions | + +### Category A Breakdown + +| Pattern | Status | Complexity | Priority | +|---------|--------|-----------|----------| +| A-1: Trim Leading | ✅ **Handled** (TrimLoopHelper) | Simple | - | +| A-2: Trim Trailing | ✅ **Handled** (TrimLoopHelper) | Simple | - | +| A-3: Skip Whitespace (Pattern 4) | ⚠️ **Needs Pattern 4 Support** | Simple | **P0** | +| A-4: Digit Detection (Cascading) | ⚠️ **Cascading LoopBodyLocal** | Medium | **P1** | +| A-5: String Comparison | ⚠️ **Multi-Variable** | Medium | P2 | +| A-6: atoi Range Check | ⚠️ **Cascading + Multi-Break** | High | P2 | + +--- + +## Key Insights for Phase 223-2 Design + +### 1. **Simple Trim Pattern is Solved** ✅ + +Phase 171-C's TrimLoopHelper already handles patterns A-1 and A-2 successfully. This is the foundation to build on. + +### 2. **Pattern 4 (with continue) Needs Promotion Support** ⚠️ **P0** + +Pattern A-3 (skip_whitespace) is a **critical blocker** for JsonParser. It's the same as Trim pattern but uses `continue` instead of `break`. + +**Action Required**: Extend LoopBodyCarrierPromoter to support Pattern 4 (Phase 223-2-P0). + +### 3. **Cascading LoopBodyLocal is Common** ⚠️ **P1** + +Pattern A-4 shows a **cascading dependency**: +```hako +local ch = s.substring(p, p+1) // First LoopBodyLocal +local digit_pos = digits.indexOf(ch) // Second LoopBodyLocal (depends on ch) +``` + +**Design Question**: +- Promote both to carriers? (`ch_carrier`, `is_digit_carrier`) +- Or only promote the "leaf" variable (`is_digit_carrier`)? + +**Recommendation**: Promote only the **leaf** variable that appears in conditions. In Pattern A-4, only `digit_pos` appears in the break condition (`if digit_pos < 0`), so promote that. + +### 4. **Multi-Variable Patterns Need Special Handling** (P2) + +Pattern A-5 (string comparison) uses TWO LoopBodyLocal variables in the same condition. This is less common but should be supported eventually. + +### 5. **Fail-Fast for Complex Patterns is Correct** ✅ + +Pattern B-1 (nested if with reassignment) correctly Fail-Fasts. These patterns are too complex for safe carrier promotion. + +--- + +## Next Steps (Phase 223-2) + +### Phase 223-2-P0: Pattern 4 Promotion (Critical) + +**Goal**: Enable `skip_whitespace` pattern (A-3) by supporting carrier promotion in Pattern 4 (with continue). + +**Files to Modify**: +- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` (add promotion logic) +- `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs` (extend to handle continue) + +**Test Case**: `apps/tests/parser_box_minimal.hako` (skip_ws method) + +--- + +### Phase 223-2-P1: Cascading LoopBodyLocal (High Priority) + +**Goal**: Enable JsonParser number parsing (Pattern A-4) by promoting leaf variables in cascading dependencies. + +**Design**: +1. Detect cascading pattern: `local ch = ...; local digit_pos = indexOf(ch)` +2. Identify leaf variable: `digit_pos` (appears in condition) +3. Promote leaf variable to carrier: `is_digit` (bool) +4. Initialize carrier: `is_digit = (digit_pos >= 0)` +5. Update carrier: Same as initialization (at end of loop) + +**Test Case**: `tools/hako_shared/json_parser.hako` (_parse_number method) + +--- + +### Phase 223-2-P2: Multi-Variable Patterns (Lower Priority) + +**Goal**: Enable string comparison pattern (A-5) by promoting multiple variables. + +**Design**: TBD (after P0/P1 experience) + +--- + +## Appendix: Full File Locations + +### Trim Patterns (Already Handled) + +- ✅ `tools/hako_shared/json_parser.hako:330-336` (_trim leading) +- ✅ `tools/hako_shared/json_parser.hako:340-346` (_trim trailing) + +### Pattern 4 (Needs P0) + +- ⚠️ `apps/tests/parser_box_minimal.hako:30-41` (skip_ws with continue) +- ⚠️ `tools/hako_shared/json_parser.hako:310-321` (_skip_whitespace) + +### Cascading LoopBodyLocal (Needs P1) + +- ⚠️ `tools/hako_shared/json_parser.hako:121-133` (_parse_number digit detection) +- ⚠️ `tools/hako_shared/json_parser.hako:453-460` (_atoi digit parsing) +- ⚠️ `apps/tests/parser_box_minimal.hako:20-25` (to_int) + +### Multi-Variable (Needs P2) + +- ⚠️ `apps/tests/phase182_p1_match_literal.hako:356-364` (_match_literal) + +### Complex (Fail-Fast Maintained) + +- ✅ `apps/tests/minimal_ssa_bug_loop.hako` (nested if with reassignment) + +### Body-Only (Not Blocked) + +- ✅ `apps/tests/phase183_body_only_loopbodylocal.hako` (temp variable) +- ✅ `apps/tests/phase184_body_local_with_break.hako` (temp in update) + +--- + +## Revision History + +- **2025-12-10**: Phase 223-1 initial inventory created diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs index 186b56d5..14fc505d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs @@ -209,64 +209,74 @@ impl MirBuilder { // Phase 195: Use unified trace trace::trace().varmap("pattern4_start", &self.variable_map); - // Phase 171-C-3: LoopBodyCarrierPromoter integration - // Check if LoopConditionScopeBox detects LoopBodyLocal variables, and attempt promotion + // Phase 223-3: LoopBodyCondPromoter integration + // Check for LoopBodyLocal in continue condition and attempt promotion { use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; - use crate::mir::loop_pattern_detection::loop_body_carrier_promoter::{ - LoopBodyCarrierPromoter, PromotionRequest, PromotionResult, + use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ + LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult, + }; + + // Phase 223-3: Extract continue condition from body (if present) + // This handles skip_whitespace pattern: if ch == " " || ... { continue } + let continue_cond = LoopBodyCondPromoter::extract_continue_condition(body_to_analyze); + + // Analyze both header condition and continue condition for LoopBodyLocal + let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond { + vec![condition, cont_cond] + } else { + vec![condition] }; - // First check: Does the condition reference LoopBodyLocal variables? let cond_scope = LoopConditionScopeBox::analyze( &loop_var_name, - &[condition], + &conditions_to_analyze, Some(&scope), ); if cond_scope.has_loop_body_local() { - // Phase 171-C-3: Try promotion - // Pattern 4 has no break condition - let request = PromotionRequest { - scope: &scope, + // Phase 223-3: Try promotion using LoopBodyCondPromoter + let promotion_req = ConditionPromotionRequest { + loop_param_name: &loop_var_name, cond_scope: &cond_scope, - break_cond: None, // Pattern 4 doesn't have break + scope_shape: Some(&scope), + break_cond: None, // Pattern 4 has no break + continue_cond, loop_body: body_to_analyze, }; - match LoopBodyCarrierPromoter::try_promote(&request) { - PromotionResult::Promoted { trim_info } => { + match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { + ConditionPromotionResult::Promoted { + carrier_info: promoted_carrier, + promoted_var, + carrier_name, + } => { eprintln!( - "[pattern4/promoter] LoopBodyLocal '{}' promoted to carrier '{}'", - trim_info.var_name, trim_info.carrier_name + "[pattern4/cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'", + promoted_var, carrier_name ); - // Phase 171-C-4: Convert to CarrierInfo and merge - let promoted_carrier = trim_info.to_carrier_info(); + // Phase 223-3: Merge promoted carrier into existing CarrierInfo carrier_info.merge_from(&promoted_carrier); eprintln!( - "[pattern4/promoter] Phase 171-C-4: Merged carrier '{}' into CarrierInfo (total carriers: {})", - trim_info.carrier_name, + "[pattern4/cond_promoter] Merged carrier '{}' into CarrierInfo (total carriers: {})", + carrier_name, carrier_info.carrier_count() ); - // Phase 171-impl-Trim: Check if this is a safe Trim pattern + // Phase 223-3: Check if this is a safe Trim pattern if let Some(helper) = carrier_info.trim_helper() { if helper.is_safe_trim() { - eprintln!("[pattern4/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction"); - eprintln!("[pattern4/trim] Carrier: '{}', original var: '{}', whitespace chars: {:?}", - helper.carrier_name, helper.original_var, helper.whitespace_chars); - - // Phase 171-impl-Trim: Validation successful! - // Phase 172+ will implement the actual JoinIR generation for Trim patterns - // For now, return an informative message that the pattern is recognized but not yet lowered - return Err(format!( - "[cf_loop/pattern4] ✅ Trim pattern validation successful! \ - Carrier '{}' ready for Phase 172 implementation. \ - (Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)", - helper.carrier_name - )); + eprintln!( + "[pattern4/cond_promoter] Safe Trim/skip_whitespace pattern detected" + ); + eprintln!( + "[pattern4/cond_promoter] Carrier: '{}', original var: '{}', whitespace chars: {:?}", + helper.carrier_name, helper.original_var, helper.whitespace_chars + ); + // Phase 223-3: Continue with Pattern4 lowering + // (fall through to normal lowering path) } else { return Err(format!( "[cf_loop/pattern4] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", @@ -274,15 +284,12 @@ impl MirBuilder { helper.whitespace_count() )); } - } else { - return Err(format!( - "[cf_loop/pattern4] Promoted but no TrimLoopHelper attached (carrier: '{}')", - trim_info.carrier_name - )); } + // Phase 223-3: Continue with normal Pattern4 lowering + // The carrier has been promoted and merged, lowering can proceed } - PromotionResult::CannotPromote { reason, vars } => { - // Phase 171-C-3: Fail-Fast on promotion failure + ConditionPromotionResult::CannotPromote { reason, vars } => { + // Phase 223-3: Fail-Fast on promotion failure return Err(format!( "[cf_loop/pattern4] Cannot promote LoopBodyLocal variables {:?}: {}", vars, reason diff --git a/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs b/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs new file mode 100644 index 00000000..58dca7f4 --- /dev/null +++ b/src/mir/loop_pattern_detection/loop_body_cond_promoter.rs @@ -0,0 +1,455 @@ +//! Phase 223-3: LoopBodyCondPromoter Box +//! +//! Handles promotion of LoopBodyLocal variables used in loop conditions +//! to bool carriers. Supports Pattern2 (break), Pattern4 (continue), +//! and future patterns. +//! +//! ## Responsibilities +//! +//! - Detect safe promotion patterns (Category A-3 from phase223-loopbodylocal-condition-inventory.md) +//! - Coordinate with LoopBodyCarrierPromoter for actual promotion logic +//! - Provide uniform API for Pattern2/Pattern4 integration +//! +//! ## Design Principle +//! +//! This is a **thin coordinator** that reuses existing boxes: +//! - LoopBodyCarrierPromoter: Promotion logic (Trim pattern detection) +//! - TrimLoopHelper: Pattern-specific metadata +//! - ConditionEnvBuilder: Binding generation +//! +//! ## P0 Requirements (Category A-3) +//! +//! - Single LoopBodyLocal variable (e.g., `ch`) +//! - Definition: `local ch = s.substring(...)` or similar +//! - Condition: Simple equality chain (e.g., `ch == " " || ch == "\t"`) +//! - Pattern: Identical to existing Trim pattern + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::loop_pattern_detection::loop_body_carrier_promoter::{ + LoopBodyCarrierPromoter, PromotionRequest, PromotionResult, +}; +use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScope; + +/// Promotion request for condition variables +/// +/// Unified API for Pattern2 (break) and Pattern4 (continue) +pub struct ConditionPromotionRequest<'a> { + /// Loop parameter name (e.g., "i") + pub loop_param_name: &'a str, + + /// Condition scope analysis result + pub cond_scope: &'a LoopConditionScope, + + /// Loop structure metadata (crate-internal) + pub(crate) scope_shape: Option<&'a LoopScopeShape>, + + /// Break condition AST (Pattern2: Some, Pattern4: None) + pub break_cond: Option<&'a ASTNode>, + + /// Continue condition AST (Pattern4: Some, Pattern2: None) + pub continue_cond: Option<&'a ASTNode>, + + /// Loop body statements + pub loop_body: &'a [ASTNode], +} + +/// Promotion result +pub enum ConditionPromotionResult { + /// Promotion successful + Promoted { + /// Carrier metadata (from TrimLoopHelper) + carrier_info: CarrierInfo, + + /// Variable name that was promoted (e.g., "ch") + promoted_var: String, + + /// Promoted carrier name (e.g., "is_whitespace") + carrier_name: String, + }, + + /// Cannot promote (Fail-Fast) + CannotPromote { + /// Human-readable reason + reason: String, + + /// List of problematic LoopBodyLocal variables + vars: Vec, + }, +} + +/// Phase 223-3: LoopBodyCondPromoter Box +/// +/// Coordinates LoopBodyLocal condition promotion for Pattern2/Pattern4. +pub struct LoopBodyCondPromoter; + +impl LoopBodyCondPromoter { + /// Extract continue condition from loop body + /// + /// Finds the first if statement with continue in then-branch and returns its condition. + /// This is used for Pattern4 skip_whitespace pattern detection. + /// + /// # Pattern + /// + /// ```nyash + /// loop(i < n) { + /// local ch = s.substring(...) + /// if ch == " " || ch == "\t" { // ← This condition is returned + /// i = i + 1 + /// continue + /// } + /// break + /// } + /// ``` + /// + /// # Returns + /// + /// The condition AST if found, None otherwise + pub fn extract_continue_condition(body: &[ASTNode]) -> Option<&ASTNode> { + for stmt in body { + if let ASTNode::If { + condition, + then_body, + .. + } = stmt + { + // Check if then_body contains continue + if Self::contains_continue(then_body) { + return Some(condition.as_ref()); + } + } + } + None + } + + /// Check if statements contain a continue statement + fn contains_continue(stmts: &[ASTNode]) -> bool { + for stmt in stmts { + match stmt { + ASTNode::Continue { .. } => return true, + ASTNode::If { + then_body, + else_body, + .. + } => { + if Self::contains_continue(then_body) { + return true; + } + if let Some(else_stmts) = else_body { + if Self::contains_continue(else_stmts) { + return true; + } + } + } + _ => {} + } + } + false + } +} + +impl LoopBodyCondPromoter { + /// Try to promote LoopBodyLocal variables in conditions + /// + /// ## P0 Requirements (Category A-3) + /// + /// - Single LoopBodyLocal variable (e.g., `ch`) + /// - Definition: `local ch = s.substring(...)` or similar + /// - Condition: Simple equality chain (e.g., `ch == " " || ch == "\t"`) + /// - Pattern: Identical to existing Trim pattern + /// + /// ## Algorithm (Delegated to LoopBodyCarrierPromoter) + /// + /// 1. Extract LoopBodyLocal names from cond_scope + /// 2. Build PromotionRequest for LoopBodyCarrierPromoter + /// 3. Call LoopBodyCarrierPromoter::try_promote() + /// 4. Convert PromotionResult to ConditionPromotionResult + /// 5. Return result (Promoted or CannotPromote) + /// + /// ## Differences from TrimLoopLowerer + /// + /// - TrimLoopLowerer: Full lowering pipeline (detection + code generation) + /// - LoopBodyCondPromoter: Detection + metadata only (no code generation) + pub fn try_promote_for_condition(req: ConditionPromotionRequest) -> ConditionPromotionResult { + // P0 constraint: Need LoopScopeShape for LoopBodyCarrierPromoter + let scope_shape = match req.scope_shape { + Some(s) => s, + None => { + return ConditionPromotionResult::CannotPromote { + reason: "No LoopScopeShape provided".to_string(), + vars: vec![], + }; + } + }; + + // Determine which condition to use for break_cond in LoopBodyCarrierPromoter + // Pattern2: break_cond + // Pattern4: continue_cond (use as break_cond for Trim pattern detection) + let condition_for_promotion = req.break_cond.or(req.continue_cond); + + // Build request for LoopBodyCarrierPromoter + let promotion_request = PromotionRequest { + scope: scope_shape, + cond_scope: req.cond_scope, + break_cond: condition_for_promotion, + loop_body: req.loop_body, + }; + + // Delegate to existing LoopBodyCarrierPromoter + match LoopBodyCarrierPromoter::try_promote(&promotion_request) { + PromotionResult::Promoted { trim_info } => { + eprintln!( + "[cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'", + trim_info.var_name, trim_info.carrier_name + ); + + // Convert TrimPatternInfo to CarrierInfo + let carrier_info = trim_info.to_carrier_info(); + + ConditionPromotionResult::Promoted { + carrier_info, + promoted_var: trim_info.var_name, + carrier_name: trim_info.carrier_name, + } + } + PromotionResult::CannotPromote { reason, vars } => { + eprintln!( + "[cond_promoter] Cannot promote LoopBodyLocal variables {:?}: {}", + vars, reason + ); + + ConditionPromotionResult::CannotPromote { reason, vars } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{BinaryOperator, LiteralValue, Span}; + use crate::mir::loop_pattern_detection::loop_condition_scope::{ + CondVarScope, LoopConditionScope, + }; + use crate::mir::BasicBlockId; + use std::collections::{BTreeMap, BTreeSet}; + + fn minimal_scope() -> LoopScopeShape { + LoopScopeShape { + header: BasicBlockId(0), + body: BasicBlockId(1), + latch: BasicBlockId(2), + exit: BasicBlockId(3), + pinned: BTreeSet::new(), + carriers: BTreeSet::new(), + body_locals: BTreeSet::new(), + exit_live: BTreeSet::new(), + progress_carrier: None, + variable_definitions: BTreeMap::new(), + } + } + + fn cond_scope_with_body_local(var_name: &str) -> LoopConditionScope { + let mut scope = LoopConditionScope::new(); + scope.add_var(var_name.to_string(), CondVarScope::LoopBodyLocal); + scope + } + + // Helper: Create a Variable node + fn var_node(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + // Helper: Create a String literal node + fn str_literal(s: &str) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::String(s.to_string()), + span: Span::unknown(), + } + } + + // Helper: Create an equality comparison (var == literal) + fn eq_cmp(var_name: &str, literal: &str) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(var_node(var_name)), + right: Box::new(str_literal(literal)), + span: Span::unknown(), + } + } + + // Helper: Create an Or expression + fn or_expr(left: ASTNode, right: ASTNode) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Or, + left: Box::new(left), + right: Box::new(right), + span: Span::unknown(), + } + } + + // Helper: Create a MethodCall node + fn method_call(object: &str, method: &str) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(var_node(object)), + method: method.to_string(), + arguments: vec![], + span: Span::unknown(), + } + } + + // Helper: Create an Assignment node + fn assignment(target: &str, value: ASTNode) -> ASTNode { + ASTNode::Assignment { + target: Box::new(var_node(target)), + value: Box::new(value), + span: Span::unknown(), + } + } + + #[test] + fn test_cond_promoter_no_scope_shape() { + let cond_scope = LoopConditionScope::new(); + + let req = ConditionPromotionRequest { + loop_param_name: "i", + cond_scope: &cond_scope, + scope_shape: None, // No scope shape + break_cond: None, + continue_cond: None, + loop_body: &[], + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::CannotPromote { reason, .. } => { + assert!(reason.contains("No LoopScopeShape")); + } + _ => panic!("Expected CannotPromote when no scope shape provided"), + } + } + + #[test] + fn test_cond_promoter_no_body_locals() { + let scope_shape = minimal_scope(); + let cond_scope = LoopConditionScope::new(); // Empty, no LoopBodyLocal + + let req = ConditionPromotionRequest { + loop_param_name: "i", + cond_scope: &cond_scope, + scope_shape: Some(&scope_shape), + break_cond: None, + continue_cond: None, + loop_body: &[], + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::CannotPromote { vars, .. } => { + assert!(vars.is_empty()); + } + _ => panic!("Expected CannotPromote when no LoopBodyLocal variables"), + } + } + + #[test] + fn test_cond_promoter_skip_whitespace_pattern() { + // Full Trim/skip_whitespace pattern test (Category A-3): + // - LoopBodyLocal: ch + // - Definition: ch = s.substring(...) + // - Continue condition: ch == " " || ch == "\t" + + let scope_shape = minimal_scope(); + let cond_scope = cond_scope_with_body_local("ch"); + + let loop_body = vec![assignment("ch", method_call("s", "substring"))]; + + let continue_cond = or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")); + + let req = ConditionPromotionRequest { + loop_param_name: "i", + cond_scope: &cond_scope, + scope_shape: Some(&scope_shape), + break_cond: None, + continue_cond: Some(&continue_cond), + loop_body: &loop_body, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::Promoted { + promoted_var, + carrier_name, + carrier_info, + } => { + assert_eq!(promoted_var, "ch"); + assert_eq!(carrier_name, "is_ch_match"); + // CarrierInfo should have trim_helper attached + assert!(carrier_info.trim_helper.is_some()); + } + ConditionPromotionResult::CannotPromote { reason, .. } => { + panic!("Expected Promoted, got CannotPromote: {}", reason); + } + } + } + + #[test] + fn test_cond_promoter_break_condition() { + // Pattern2 style: break_cond instead of continue_cond + + let scope_shape = minimal_scope(); + let cond_scope = cond_scope_with_body_local("ch"); + + let loop_body = vec![assignment("ch", method_call("s", "substring"))]; + + let break_cond = or_expr(eq_cmp("ch", " "), eq_cmp("ch", "\t")); + + let req = ConditionPromotionRequest { + loop_param_name: "i", + cond_scope: &cond_scope, + scope_shape: Some(&scope_shape), + break_cond: Some(&break_cond), + continue_cond: None, + loop_body: &loop_body, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::Promoted { promoted_var, .. } => { + assert_eq!(promoted_var, "ch"); + } + ConditionPromotionResult::CannotPromote { reason, .. } => { + panic!("Expected Promoted, got CannotPromote: {}", reason); + } + } + } + + #[test] + fn test_cond_promoter_non_substring_pattern() { + // Non-substring method call should NOT be promoted + let scope_shape = minimal_scope(); + let cond_scope = cond_scope_with_body_local("ch"); + + // ch = s.length() (not substring) + let loop_body = vec![assignment("ch", method_call("s", "length"))]; + + let continue_cond = eq_cmp("ch", "5"); // Some comparison + + let req = ConditionPromotionRequest { + loop_param_name: "i", + cond_scope: &cond_scope, + scope_shape: Some(&scope_shape), + break_cond: None, + continue_cond: Some(&continue_cond), + loop_body: &loop_body, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(req) { + ConditionPromotionResult::CannotPromote { vars, reason } => { + // Should fail because it's not a substring pattern + assert!(vars.contains(&"ch".to_string()) || reason.contains("Trim pattern")); + } + _ => panic!("Expected CannotPromote for non-substring pattern"), + } + } +} diff --git a/src/mir/loop_pattern_detection/mod.rs b/src/mir/loop_pattern_detection/mod.rs index 62fd49ff..a5def4f7 100644 --- a/src/mir/loop_pattern_detection/mod.rs +++ b/src/mir/loop_pattern_detection/mod.rs @@ -778,6 +778,9 @@ pub mod error_messages; // Phase 171-C: LoopBodyLocal Carrier Promotion pub mod loop_body_carrier_promoter; +// Phase 223-3: LoopBodyLocal Condition Promotion (for Pattern4) +pub mod loop_body_cond_promoter; + // Phase 171-C-5: Trim Pattern Helper pub mod trim_loop_helper; pub use trim_loop_helper::TrimLoopHelper;