Files
hakorune/docs/development/current/main/phase223-loopbodylocal-condition-design.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

653 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
- **LoopBodyCondPromoterPhase 223-2 設計完了)**
- ファイル: `src/mir/loop_pattern_detection/loop_body_cond_promoter.rs`
- 責務:
- ループ条件header/break/continueに出てくる LoopBodyLocal を carrier に昇格。
- P0Category 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)
Status: Active
Scope: LoopBodyLocal condition 設計JoinIR/ExprLowerer ライン)