Files
hakorune/docs/development/current/main/phase223-loopbodylocal-condition-inventory.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

14 KiB
Raw Blame History

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

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:

# 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)

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)

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)

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)

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)

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)

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

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)

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

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

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:

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 Status: Active
    Scope: LoopBodyLocal condition 在庫JoinIR/ExprLowerer ライン)