feat(joinir): Phase 223-3 - LoopBodyCondPromoter implementation
Implements LoopBodyLocal condition promotion for Pattern4/continue patterns.
Previously, loops with LoopBodyLocal in conditions would Fail-Fast.
Now, safe Trim/skip_whitespace patterns (Category A-3) are promoted and lowering continues.
## Implementation
- **LoopBodyCondPromoter Box** (loop_body_cond_promoter.rs)
- extract_continue_condition(): Extract if-condition from continue branches
- try_promote_for_condition(): Thin wrapper delegating to LoopBodyCarrierPromoter
- ConditionPromotionRequest/Result API for Pattern2/4 integration
- **Pattern4 Integration** (pattern4_with_continue.rs)
- Analyze both header condition + continue condition
- On promotion success: merge carrier_info → continue lowering
- On promotion failure: Fail-Fast with clear error message
- **Unit Tests** (5 tests, all PASS)
- test_cond_promoter_skip_whitespace_pattern
- test_cond_promoter_break_condition
- test_cond_promoter_non_substring_pattern
- test_cond_promoter_no_scope_shape
- test_cond_promoter_no_body_locals
- **E2E Test** (phase223_p4_skip_whitespace_min.hako)
- Category A-3 pattern: local ch = s.substring(...); if ch == " " { continue }
- Verifies promotion succeeds and Pattern4 lowering proceeds
## Documentation
- joinir-architecture-overview.md: Updated LoopBodyCondPromoter section (Phase 223-3 完了)
- CURRENT_TASK.md: Added Phase 223-3 completion summary
- PHASE_223_SUMMARY.md: Phase overview and status tracking
- phase223-loopbodylocal-condition-*.md: Design docs and inventory
## Achievement
- **Before**: LoopBodyLocal in condition → Fail-Fast
- **Now**: Trim/skip_whitespace patterns promoted → Pattern4 lowering continues
- **Remaining**: Phase 172+ JoinIR Trim lowering for complete RC correctness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -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 への適用状況
|
||||
|
||||
|
||||
39
apps/tests/phase223_p4_skip_whitespace_min.hako
Normal file
39
apps/tests/phase223_p4_skip_whitespace_min.hako
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
291
docs/development/current/main/PHASE_223_SUMMARY.md
Normal file
291
docs/development/current/main/PHASE_223_SUMMARY.md
Normal file
@ -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
|
||||
@ -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`
|
||||
|
||||
@ -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<CondVarInfo>,
|
||||
}
|
||||
|
||||
impl LoopConditionScope {
|
||||
pub fn has_loop_body_local(&self) -> bool;
|
||||
pub fn var_names(&self) -> HashSet<String>;
|
||||
}
|
||||
```
|
||||
|
||||
**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<String> },
|
||||
}
|
||||
|
||||
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<ConditionBinding>,
|
||||
}
|
||||
|
||||
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<Option<TrimLoweringResult>, 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<String>,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
455
src/mir/loop_pattern_detection/loop_body_cond_promoter.rs
Normal file
455
src/mir/loop_pattern_detection/loop_body_cond_promoter.rs
Normal file
@ -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<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user