feat(joinir): Phase 171-C-2 Trim pattern detection in LoopBodyCarrierPromoter
Implements the Trim pattern detection logic for carrier promotion:
- find_definition_in_body(): Iterative AST traversal to locate variable definitions
- is_substring_method_call(): Detects substring() method calls
- extract_equality_literals(): Extracts string literals from OR chains (ch == " " || ch == "\t")
- TrimPatternInfo: Captures detected pattern details for carrier promotion
This enables Pattern 5 to detect trim-style loops:
```hako
loop(start < end) {
local ch = s.substring(start, start+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
start = start + 1
} else {
break
}
}
```
Unit tests cover:
- Simple and nested definition detection
- substring method call detection
- Single and chained equality literal extraction
- Full Trim pattern detection with 2-4 whitespace characters
Next: Phase 171-C-3 integration with Pattern 2/4 routing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
4727
CURRENT_TASK.md
4727
CURRENT_TASK.md
File diff suppressed because it is too large
Load Diff
@ -91,12 +91,50 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
|||||||
- `ConditionEnv` 経由で「変数名 → JoinIR ValueId」のみを見る。
|
- `ConditionEnv` 経由で「変数名 → JoinIR ValueId」のみを見る。
|
||||||
- host 側の ValueId は `ConditionBinding { name, host_value, join_value }` として JoinInlineBoundary に記録する。
|
- host 側の ValueId は `ConditionBinding { name, host_value, join_value }` として JoinInlineBoundary に記録する。
|
||||||
|
|
||||||
- **LoopConditionScopeBox(設計中)**
|
- **LoopConditionScopeBox(Phase 170-D 実装済み)**
|
||||||
- 予定ファイル: `src/mir/loop_pattern_detection/loop_condition_scope.rs` 等
|
- ファイル: `src/mir/loop_pattern_detection/loop_condition_scope.rs`
|
||||||
- 責務:
|
- 責務:
|
||||||
- 条件式に登場する変数が、ループパラメータ(LoopParam)/ループ外ローカル(OuterLocal)/ループ本体ローカル(LoopBodyLocal)のどれかを分類する。
|
- 条件式に登場する変数が、ループパラメータ(LoopParam)/ループ外ローカル(OuterLocal)/ループ本体ローカル(LoopBodyLocal)のどれかを分類する。
|
||||||
- Pattern2/4 が「対応してよい条件のスコープ」を判定するための箱。
|
- Pattern2/4 が「対応してよい条件のスコープ」を判定するための箱。
|
||||||
- ループ本体ローカルを条件に含む高度なパターンは、将来の Pattern5+ で扱う設計とし、現状は Fail‑Fast で明示的に弾く。
|
- ループ本体ローカルを条件に含む高度なパターンは、**LoopBodyCarrierPromoter(Phase 171)** で carrier に昇格させる。
|
||||||
|
- **Bug Fix(2025-12-07)**:
|
||||||
|
- 関数パラメータが LoopBodyLocal と誤分類される問題を修正。
|
||||||
|
- `condition_var_analyzer.rs` の `is_outer_scope_variable()` で、`variable_definitions` に含まれない変数を OuterLocal とする。
|
||||||
|
- これにより JsonParserBox などの関数パラメータを含むループが正しく動作。
|
||||||
|
|
||||||
|
- **LoopBodyCarrierPromoter(Phase 171-C-1 実装中)**
|
||||||
|
- ファイル: `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||||||
|
- 責務:
|
||||||
|
- LoopBodyLocal 変数を carrier に昇格させ、Pattern 2/4 で処理可能にする。
|
||||||
|
- 昇格成功 → Pattern 2/4 にルーティング(新しい carrier 情報付き)。
|
||||||
|
- 昇格失敗 → UnsupportedPattern(Fail-Fast)。
|
||||||
|
- **Pattern 2/4 + Pattern 5 の境界線**:
|
||||||
|
- **Pattern 2/4 の守備範囲**: LoopParam + OuterLocal のみ。LoopBodyLocal 条件は受け入れない。
|
||||||
|
- **Pattern 5 の守備範囲**: LoopBodyLocal を carrier に昇格。成功なら Pattern 2/4 へ委譲。
|
||||||
|
- **境界**: LoopConditionScopeBox が LoopBodyLocal 検出 → LoopBodyCarrierPromoter で昇格試行。
|
||||||
|
- **Design Strategy(Design D: Evaluated Bool Carrier)**:
|
||||||
|
- `ch == " " || ...` のような条件を `is_whitespace` (bool carrier) に変換。
|
||||||
|
- 昇格例:
|
||||||
|
```hako
|
||||||
|
// Before (blocked)
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ... { ... } else { break }
|
||||||
|
}
|
||||||
|
|
||||||
|
// After (Pattern2 compatible)
|
||||||
|
local is_whitespace = true
|
||||||
|
loop(start < end && is_whitespace) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
is_whitespace = (ch == " " || ...)
|
||||||
|
if is_whitespace { ... } else { break }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Current Status(Phase 171-C-1)**:
|
||||||
|
- ✅ API 定義: `PromotionRequest` → `PromotionResult`
|
||||||
|
- ✅ Skeleton 実装: LoopBodyLocal 検出・定義探索
|
||||||
|
- ⏳ Promotion logic: Phase 171-C-2 で Trim パターンの実際の昇格ロジックを実装予定
|
||||||
|
- **詳細**: `docs/development/current/main/phase171-pattern5-loop-inventory.md`
|
||||||
|
|
||||||
### 2.3 キャリア / Exit / Boundary ライン
|
### 2.3 キャリア / Exit / Boundary ライン
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,630 @@
|
|||||||
|
# Phase 166-impl-2: JsonParser/Trim Loop Re-inventory
|
||||||
|
|
||||||
|
**Status**: ✅ Complete
|
||||||
|
**Last Updated**: 2025-12-07
|
||||||
|
**Phase**: 166-impl-2 (Post-LoopConditionScopeBox validation)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document analyzes all loops in JsonParserBox and TrimTest to determine what the current JoinIR implementation (Pattern 1-4 + LoopConditionScopeBox) can handle.
|
||||||
|
|
||||||
|
**Key Finding**: The current implementation successfully compiles all JsonParserBox loops but fails on TrimTest due to LoopBodyLocal variable usage in loop conditions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loop Inventory
|
||||||
|
|
||||||
|
### 1. JsonParserBox Loops (tools/hako_shared/json_parser.hako)
|
||||||
|
|
||||||
|
| Line | Function | Pattern | Condition Variables | ConditionScope | Status | Notes |
|
||||||
|
|------|----------|---------|---------------------|----------------|--------|-------|
|
||||||
|
| 121 | `_parse_number` | Pattern2 | `p` (LoopParam) | LoopParam only | ✅ PASS | Simple increment loop with break |
|
||||||
|
| 150 | `_parse_string` | Pattern2 | `p` (LoopParam) | LoopParam only | ✅ PASS | Parse loop with break/continue |
|
||||||
|
| 203 | `_parse_array` | Pattern2 | `p` (LoopParam) | LoopParam only | ✅ PASS | Array element parsing loop |
|
||||||
|
| 256 | `_parse_object` | Pattern2 | `p` (LoopParam) | LoopParam only | ✅ PASS | Object key-value parsing loop |
|
||||||
|
| 312 | `_skip_whitespace` | Pattern2 | `p` (LoopParam) | LoopParam only | ✅ PASS | Whitespace skipping loop |
|
||||||
|
| 330 | `_trim` (leading) | Pattern2 | `start` (LoopParam), `end` (OuterLocal) | LoopParam + OuterLocal | ⚠️ BLOCKED | Uses loop-body `ch` in break condition |
|
||||||
|
| 340 | `_trim` (trailing) | Pattern2 | `end` (LoopParam), `start` (OuterLocal) | LoopParam + OuterLocal | ⚠️ BLOCKED | Uses loop-body `ch` in break condition |
|
||||||
|
| 357 | `_match_literal` | Pattern1 | `i` (LoopParam), `len` (OuterLocal) | LoopParam + OuterLocal | ✅ PASS | Simple iteration with early return |
|
||||||
|
| 373 | `_unescape_string` | Pattern2 | `i` (LoopParam) | LoopParam only | ✅ PASS | Complex escape processing with continue |
|
||||||
|
| 453 | `_atoi` | Pattern2 | `i` (LoopParam), `n` (OuterLocal) | LoopParam + OuterLocal | ✅ PASS | Number parsing with break |
|
||||||
|
|
||||||
|
**Summary**:
|
||||||
|
- **Total Loops**: 10
|
||||||
|
- **❌ FAIL**: 1+ loops (JsonParserBox fails to compile with JoinIR)
|
||||||
|
- **⚠️ FALSE POSITIVE**: Previous analysis was incorrect - JsonParserBox does NOT compile successfully
|
||||||
|
|
||||||
|
### 2. TrimTest Loops (local_tests/test_trim_main_pattern.hako)
|
||||||
|
|
||||||
|
| Line | Function | Pattern | Condition Variables | ConditionScope | Status | Notes |
|
||||||
|
|------|----------|---------|---------------------|----------------|--------|-------|
|
||||||
|
| 20 | `trim` (leading) | Pattern2 | `start` (LoopParam), `end` (OuterLocal) | LoopParam + OuterLocal | ❌ FAIL | `ch` is LoopBodyLocal used in break |
|
||||||
|
| 30 | `trim` (trailing) | Pattern2 | `end` (LoopParam), `start` (OuterLocal) | LoopParam + OuterLocal | ❌ FAIL | `ch` is LoopBodyLocal used in break |
|
||||||
|
|
||||||
|
**Summary**:
|
||||||
|
- **Total Loops**: 2
|
||||||
|
- **✅ PASS**: 0 loops (0%)
|
||||||
|
- **❌ FAIL**: 2 loops (100%) - UnsupportedPattern error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Results
|
||||||
|
|
||||||
|
### JsonParserBox Tests
|
||||||
|
|
||||||
|
**Compilation Status**: ❌ FAIL (UnsupportedPattern error in `_parse_object`)
|
||||||
|
|
||||||
|
**Test Command**:
|
||||||
|
```bash
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune tools/hako_shared/json_parser.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error**:
|
||||||
|
```
|
||||||
|
[ERROR] ❌ MIR compilation error: [cf_loop/pattern4] Lowering failed:
|
||||||
|
[joinir/pattern4] Unsupported condition: uses loop-body-local variables: ["s"].
|
||||||
|
Pattern 4 supports only loop parameters and outer-scope variables.
|
||||||
|
Consider using Pattern 5+ for complex loop conditions.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- Compilation fails on `_parse_object` loop (line 256)
|
||||||
|
- Error claims `s` is a LoopBodyLocal, but `s` is a function parameter
|
||||||
|
- **POTENTIAL BUG**: Variable scope detection may incorrectly classify function parameters
|
||||||
|
- Previous test success was misleading - we only saw tail output which showed runtime error, not compilation error
|
||||||
|
|
||||||
|
### TrimTest
|
||||||
|
|
||||||
|
**Compilation Status**: ❌ FAIL (UnsupportedPattern error)
|
||||||
|
|
||||||
|
**Test Command**:
|
||||||
|
```bash
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error**:
|
||||||
|
```
|
||||||
|
[ERROR] ❌ MIR compilation error: [cf_loop/pattern2] Lowering failed:
|
||||||
|
[joinir/pattern2] Unsupported condition: uses loop-body-local variables: ["ch", "end"].
|
||||||
|
Pattern 2 supports only loop parameters and outer-scope variables.
|
||||||
|
Consider using Pattern 5+ for complex loop conditions.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problematic Loop** (line 20-27):
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1) // LoopBodyLocal
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break // ⚠️ break condition implicitly depends on 'ch'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- Variable `ch` is declared inside loop body
|
||||||
|
- The `break` is inside an `if` that checks `ch`
|
||||||
|
- LoopConditionScopeBox correctly detects `ch` as LoopBodyLocal
|
||||||
|
- Pattern 2 cannot handle this case
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Detailed Loop Analysis
|
||||||
|
|
||||||
|
### ✅ Pattern 1: Simple Iteration (`_match_literal` line 357)
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(i < len) {
|
||||||
|
if s.substring(pos + i, pos + i + 1) != literal.substring(i, i + 1) {
|
||||||
|
return 0 // Early return (not break)
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works**:
|
||||||
|
- Condition uses only LoopParam (`i`) and OuterLocal (`len`)
|
||||||
|
- No break/continue (early return is handled differently)
|
||||||
|
- Pattern 1 minimal structure
|
||||||
|
|
||||||
|
### ✅ Pattern 2: Break with LoopParam only (`_skip_whitespace` line 312)
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
local ch = s.substring(p, p+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
p = p + 1
|
||||||
|
} else {
|
||||||
|
break // ✅ No problem - 'ch' not used in loop condition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works**:
|
||||||
|
- Loop condition `p < s.length()` uses only LoopParam (`p`)
|
||||||
|
- Variable `ch` is LoopBodyLocal but NOT used in loop condition
|
||||||
|
- Break is inside loop body, not affecting condition scope
|
||||||
|
- **Key insight**: LoopConditionScopeBox analyzes the loop condition expression `p < s.length()`, not the break condition `ch == " "`
|
||||||
|
|
||||||
|
### ⚠️ Pattern 2: Break with LoopBodyLocal (`_trim` line 330)
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break // ❌ Problem - TrimTest version detects 'ch' in condition scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it's blocked in TrimTest but passes in JsonParserBox**:
|
||||||
|
- **JsonParserBox version**: Compiles successfully (see line 330-337)
|
||||||
|
- **TrimTest version**: Fails with UnsupportedPattern error
|
||||||
|
- **Difference**: The error message shows `["ch", "end"]` for TrimTest
|
||||||
|
- **Hypothesis**: There may be a subtle difference in how the condition scope is analyzed between the two files
|
||||||
|
|
||||||
|
**Investigation needed**: Why does the identical loop structure pass in JsonParserBox but fail in TrimTest?
|
||||||
|
|
||||||
|
### ✅ Pattern 2: Complex continue (`_unescape_string` line 373)
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
// ... complex nested if with multiple continue statements
|
||||||
|
if process_escape == 1 {
|
||||||
|
if next == "n" {
|
||||||
|
result = result + "\n"
|
||||||
|
i = i + 2
|
||||||
|
continue // ✅ Works fine
|
||||||
|
}
|
||||||
|
// ... more continue cases
|
||||||
|
}
|
||||||
|
result = result + ch
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works**:
|
||||||
|
- Loop condition `i < s.length()` uses only LoopParam
|
||||||
|
- All LoopBodyLocal variables (`ch`, `next`, etc.) are NOT in condition scope
|
||||||
|
- Continue statements are fine as long as condition is simple
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 1-4 Scope Definition
|
||||||
|
|
||||||
|
### What LoopConditionScopeBox Checks
|
||||||
|
|
||||||
|
**LoopConditionScopeBox** (from Phase 170-D) analyzes the **loop condition expression** only, not the entire loop body:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Loop condition analysis
|
||||||
|
loop(start < end) { // ← Analyzes THIS expression only
|
||||||
|
local ch = ... // ← Does NOT analyze loop body
|
||||||
|
if ch == " " { // ← Does NOT analyze if conditions
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported Variable Scopes**:
|
||||||
|
1. **LoopParam**: Variables that are loop parameters (`start`, `end`, `i`, `p`)
|
||||||
|
2. **OuterLocal**: Variables defined before the loop in outer scope
|
||||||
|
3. **LoopBodyLocal**: ❌ Variables defined inside loop body (NOT supported)
|
||||||
|
|
||||||
|
### Pattern 1-4 Official Support Matrix
|
||||||
|
|
||||||
|
| Pattern | Condition Variables | Break | Continue | Support Status |
|
||||||
|
|---------|-------------------|-------|----------|----------------|
|
||||||
|
| Pattern 1 | LoopParam + OuterLocal | ❌ No | ❌ No | ✅ Full support |
|
||||||
|
| Pattern 2 | LoopParam + OuterLocal | ✅ Yes | ❌ No | ✅ Full support |
|
||||||
|
| Pattern 3 | LoopParam + OuterLocal | ✅ Yes | ✅ Yes | ✅ Full support (with ExitPHI) |
|
||||||
|
| Pattern 4 | LoopParam + OuterLocal | ✅ Yes | ✅ Yes | ✅ Full support (with continue carrier) |
|
||||||
|
|
||||||
|
**Key Constraint**: All patterns require that the loop condition expression uses ONLY LoopParam and OuterLocal variables.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Bug Discovery
|
||||||
|
|
||||||
|
### Variable Scope Classification Error
|
||||||
|
|
||||||
|
**Issue**: LoopConditionScopeBox incorrectly classifies function parameters as LoopBodyLocal variables.
|
||||||
|
|
||||||
|
**Evidence**:
|
||||||
|
1. `_parse_object(s, pos)` - function parameter `s` reported as LoopBodyLocal
|
||||||
|
2. Error message: `uses loop-body-local variables: ["s"]`
|
||||||
|
3. Expected: Function parameters should be classified as OuterLocal or function-scope
|
||||||
|
|
||||||
|
**Impact**:
|
||||||
|
- **FALSE NEGATIVE**: Legitimate loops are rejected
|
||||||
|
- **JsonParserBox completely non-functional** with JoinIR due to this bug
|
||||||
|
- Previous 80% success claim was INCORRECT
|
||||||
|
|
||||||
|
**Root Cause Analysis Needed**:
|
||||||
|
```rust
|
||||||
|
// File: src/mir/join_ir/lowering/loop_scope_shape/condition.rs
|
||||||
|
// Suspected: detect_variable_scope() logic
|
||||||
|
|
||||||
|
// Function parameters should be:
|
||||||
|
// - Available in loop scope
|
||||||
|
// - NOT classified as LoopBodyLocal
|
||||||
|
// - Should be OuterLocal or special FunctionParam category
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Case**:
|
||||||
|
```hako
|
||||||
|
_parse_object(s, pos) { // s and pos are function parameters
|
||||||
|
local p = pos + 1
|
||||||
|
loop(p < s.length()) { // ERROR: claims 's' is LoopBodyLocal
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Behavior**:
|
||||||
|
- `s`: Function parameter → OuterLocal (available before loop)
|
||||||
|
- `p`: Local variable → OuterLocal (defined before loop)
|
||||||
|
- Loop condition `p < s.length()` should be ✅ VALID
|
||||||
|
|
||||||
|
**Actual Behavior**:
|
||||||
|
- `s`: Incorrectly classified as LoopBodyLocal
|
||||||
|
- Loop rejected with UnsupportedPattern error
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
**PRIORITY 1**: Fix LoopConditionScopeBox variable scope detection
|
||||||
|
- Function parameters must be recognized as outer-scope
|
||||||
|
- Update `detect_variable_scope()` to handle function parameters correctly
|
||||||
|
- Add test case for function parameters in loop conditions
|
||||||
|
|
||||||
|
**PRIORITY 2**: Re-run this analysis after fix
|
||||||
|
- All 10 JsonParserBox loops may actually be Pattern 1-4 compatible
|
||||||
|
- Current analysis is invalidated by this bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
### Pattern 1-4 Coverage for JsonParser/Trim (INVALIDATED BY BUG)
|
||||||
|
|
||||||
|
**⚠️ WARNING**: The following analysis is INCORRECT due to the variable scope classification bug.
|
||||||
|
|
||||||
|
**Claimed Supported Loops** (8/10 = 80%) - INVALID:
|
||||||
|
1. ❌ `_parse_number` - Fails due to function parameter misclassification
|
||||||
|
2. ❌ `_parse_string` - Likely fails (not tested)
|
||||||
|
3. ❌ `_parse_array` - Likely fails (not tested)
|
||||||
|
4. ❌ `_parse_object` - **CONFIRMED FAIL**: `s` parameter classified as LoopBodyLocal
|
||||||
|
5. ❓ `_skip_whitespace` - Unknown
|
||||||
|
6. ❓ `_match_literal` - Unknown
|
||||||
|
7. ❓ `_unescape_string` - Unknown
|
||||||
|
8. ❓ `_atoi` - Unknown
|
||||||
|
|
||||||
|
**Blocked Loops** (2/10 = 20%):
|
||||||
|
1. `_trim` (leading whitespace) - Uses LoopBodyLocal `ch` in break logic (legitimately blocked)
|
||||||
|
2. `_trim` (trailing whitespace) - Uses LoopBodyLocal `ch` in break logic (legitimately blocked)
|
||||||
|
|
||||||
|
**Actual Coverage**: UNKNOWN - Cannot determine until bug is fixed
|
||||||
|
|
||||||
|
### Technical Insight (REVISED)
|
||||||
|
|
||||||
|
**Original Hypothesis** (INCORRECT):
|
||||||
|
- "80% success rate demonstrates Pattern 1-4 effectiveness"
|
||||||
|
- "Only trim loops need Pattern 5+"
|
||||||
|
|
||||||
|
**Actual Reality** (DISCOVERED):
|
||||||
|
- **BUG**: Function parameters incorrectly classified as LoopBodyLocal
|
||||||
|
- **BLOCKER**: Cannot evaluate Pattern 1-4 coverage until bug is fixed
|
||||||
|
- **TRUE UNKNOWNS**:
|
||||||
|
- How many loops actually work with current implementation?
|
||||||
|
- Are there other variable scope bugs beyond function parameters?
|
||||||
|
|
||||||
|
**Confirmed Issues**:
|
||||||
|
1. **Bug**: Function parameters classified as LoopBodyLocal (Priority 1 fix needed)
|
||||||
|
2. **Legitimate Block**: `_trim` loops use loop-body `ch` variable in break conditions (needs Pattern 5+ or .hako rewrite)
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
|
||||||
|
#### IMMEDIATE: Fix Variable Scope Bug (Phase 166-bugfix)
|
||||||
|
|
||||||
|
**Priority**: 🔥 CRITICAL - Blocks all JoinIR loop development
|
||||||
|
|
||||||
|
**Action Items**:
|
||||||
|
1. Investigate `LoopConditionScopeBox::detect_variable_scope()` in `src/mir/join_ir/lowering/loop_scope_shape/condition.rs`
|
||||||
|
2. Ensure function parameters are classified as OuterLocal, not LoopBodyLocal
|
||||||
|
3. Add test case: function with parameters used in loop conditions
|
||||||
|
4. Re-run this analysis after fix
|
||||||
|
|
||||||
|
**Test Case**:
|
||||||
|
```hako
|
||||||
|
static box FunctionParamTest {
|
||||||
|
method test(s, pos) {
|
||||||
|
local p = pos
|
||||||
|
loop(p < s.length()) { // Should work: 's' is function param
|
||||||
|
p = p + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For JsonParserBox (AFTER BUG FIX)
|
||||||
|
|
||||||
|
**Current Status**: ❌ BLOCKED by variable scope bug
|
||||||
|
|
||||||
|
**Post-Fix Expectations**:
|
||||||
|
- Most loops should work (likely 8-9 out of 10)
|
||||||
|
- Only `_trim` loops legitimately blocked
|
||||||
|
|
||||||
|
**Short-term Fix** (.hako rewrite for `_trim`):
|
||||||
|
```hako
|
||||||
|
// ❌ Current (blocked)
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Option A: Hoist peek outside loop
|
||||||
|
local ch = ""
|
||||||
|
loop(start < end) {
|
||||||
|
ch = s.substring(start, start+1) // No local declaration
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Option B: Use helper method
|
||||||
|
loop(start < end && me._is_whitespace_at(s, start)) {
|
||||||
|
start = start + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Long-term Solution** (Pattern 5+):
|
||||||
|
- Implement BoolExprLowerer for OR/AND chains in loop conditions
|
||||||
|
- Support LoopBodyLocal in condition scope
|
||||||
|
- Target: Phase 167+
|
||||||
|
|
||||||
|
#### For Phase 167+ (Pattern 5+ Implementation)
|
||||||
|
|
||||||
|
**Delegation Scope**:
|
||||||
|
1. **LoopBodyLocal in conditions**: Support variables defined in loop body used in break/continue conditions
|
||||||
|
2. **Complex boolean expressions**: OR/AND chains, short-circuit evaluation
|
||||||
|
3. **Nested control flow**: break inside if-else chains with multiple variables
|
||||||
|
|
||||||
|
**Not Needed for JsonParser**:
|
||||||
|
- The current 80% coverage is sufficient for MVP
|
||||||
|
- The 2 failing loops can be fixed with simple .hako rewrites
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Investigation: JsonParserBox vs TrimTest Discrepancy
|
||||||
|
|
||||||
|
### Mystery: Identical Loop Structure, Different Results
|
||||||
|
|
||||||
|
**JsonParserBox `_trim` (line 330)**: ✅ Compiles successfully
|
||||||
|
**TrimTest `trim` (line 20)**: ❌ Fails with UnsupportedPattern
|
||||||
|
|
||||||
|
**Hypothesis 1**: File-level difference
|
||||||
|
- Different `using` statements?
|
||||||
|
- Different compiler flags?
|
||||||
|
|
||||||
|
**Hypothesis 2**: Context difference
|
||||||
|
- Static box vs regular method?
|
||||||
|
- Different variable scoping?
|
||||||
|
|
||||||
|
**Hypothesis 3**: Error detection timing
|
||||||
|
- JsonParserBox error not shown in tail output?
|
||||||
|
- Need to check full compilation log?
|
||||||
|
|
||||||
|
**Action Item**: Re-run JsonParserBox test with full error logging:
|
||||||
|
```bash
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 NYASH_JOINIR_DEBUG=1 \
|
||||||
|
./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | \
|
||||||
|
grep -E "Unsupported|LoopBodyLocal"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate Actions
|
||||||
|
|
||||||
|
1. **✅ DONE**: Document current Pattern 1-4 coverage (this file)
|
||||||
|
2. **✅ DONE**: Identify blocked loops and their patterns
|
||||||
|
3. **TODO**: Investigate JsonParserBox vs TrimTest discrepancy
|
||||||
|
4. **TODO**: Update CURRENT_TASK.md with findings
|
||||||
|
5. **TODO**: Update joinir-architecture-overview.md with LoopConditionScopeBox
|
||||||
|
|
||||||
|
### Phase 167+ Planning
|
||||||
|
|
||||||
|
**Pattern 5+ Requirements** (based on blocked loops):
|
||||||
|
1. LoopBodyLocal support in condition scope
|
||||||
|
2. BoolExprLowerer for complex OR/AND chains
|
||||||
|
3. Proper PHI generation for loop-body variables
|
||||||
|
|
||||||
|
**Alternative Path** (.hako rewrites):
|
||||||
|
- Rewrite `_trim` to hoist variable declarations
|
||||||
|
- Use helper methods for complex conditions
|
||||||
|
- Target: 100% Pattern 1-4 coverage with minimal changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Full Loop Catalog
|
||||||
|
|
||||||
|
### All 10 JsonParserBox Loops
|
||||||
|
|
||||||
|
#### Loop 1: _parse_number (line 121-133)
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
local ch = s.substring(p, p+1)
|
||||||
|
local digit_pos = digits.indexOf(ch)
|
||||||
|
if digit_pos < 0 { break }
|
||||||
|
num_str = num_str + ch
|
||||||
|
p = p + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break only)
|
||||||
|
- **Condition**: `p` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 2: _parse_string (line 150-178)
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
local ch = s.substring(p, p+1)
|
||||||
|
if ch == '"' { return ... }
|
||||||
|
if ch == "\\" {
|
||||||
|
// escape handling
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
str = str + ch
|
||||||
|
p = p + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break + continue)
|
||||||
|
- **Condition**: `p` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 3: _parse_array (line 203-231)
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
local elem_result = me._parse_value(s, p)
|
||||||
|
if elem_result == null { return null }
|
||||||
|
arr.push(elem_result.get("value"))
|
||||||
|
p = elem_result.get("pos")
|
||||||
|
// ... more processing
|
||||||
|
if ch == "]" { return ... }
|
||||||
|
if ch == "," { continue }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (continue + early return)
|
||||||
|
- **Condition**: `p` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 4: _parse_object (line 256-304)
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
// Parse key-value pairs
|
||||||
|
local key_result = me._parse_string(s, p)
|
||||||
|
if key_result == null { return null }
|
||||||
|
// ... more processing
|
||||||
|
if ch == "}" { return ... }
|
||||||
|
if ch == "," { continue }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (continue + early return)
|
||||||
|
- **Condition**: `p` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 5: _skip_whitespace (line 312-320)
|
||||||
|
```hako
|
||||||
|
loop(p < s.length()) {
|
||||||
|
local ch = s.substring(p, p+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
p = p + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break only)
|
||||||
|
- **Condition**: `p` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 6: _trim leading (line 330-337)
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break only)
|
||||||
|
- **Condition**: `start` (LoopParam) + `end` (OuterLocal)
|
||||||
|
- **Status**: ⚠️ BLOCKED in TrimTest, ✅ PASS in JsonParserBox (needs investigation)
|
||||||
|
|
||||||
|
#### Loop 7: _trim trailing (line 340-347)
|
||||||
|
```hako
|
||||||
|
loop(end > start) {
|
||||||
|
local ch = s.substring(end-1, end)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
end = end - 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break only)
|
||||||
|
- **Condition**: `end` (LoopParam) + `start` (OuterLocal)
|
||||||
|
- **Status**: ⚠️ BLOCKED in TrimTest, ✅ PASS in JsonParserBox (needs investigation)
|
||||||
|
|
||||||
|
#### Loop 8: _match_literal (line 357-362)
|
||||||
|
```hako
|
||||||
|
loop(i < len) {
|
||||||
|
if s.substring(pos + i, pos + i + 1) != literal.substring(i, i + 1) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 1 (no break/continue)
|
||||||
|
- **Condition**: `i` (LoopParam) + `len` (OuterLocal)
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 9: _unescape_string (line 373-431)
|
||||||
|
```hako
|
||||||
|
loop(i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
// Complex escape processing
|
||||||
|
if process_escape == 1 {
|
||||||
|
if next == "n" {
|
||||||
|
result = result + "\n"
|
||||||
|
i = i + 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// ... more continue cases
|
||||||
|
}
|
||||||
|
result = result + ch
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (continue only)
|
||||||
|
- **Condition**: `i` (LoopParam) only
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
#### Loop 10: _atoi (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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Pattern**: 2 (break only)
|
||||||
|
- **Condition**: `i` (LoopParam) + `n` (OuterLocal)
|
||||||
|
- **Status**: ✅ PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Document Version**: 1.0
|
||||||
|
**Author**: Claude Code (Phase 166-impl-2 analysis)
|
||||||
|
**References**:
|
||||||
|
- Phase 170-D: LoopConditionScopeBox implementation
|
||||||
|
- Phase 166: Loop pattern detection
|
||||||
|
- tools/hako_shared/json_parser.hako
|
||||||
|
- local_tests/test_trim_main_pattern.hako
|
||||||
@ -1,8 +1,8 @@
|
|||||||
# Phase 170: JsonParserBox JoinIR Preparation & Re-validation - Completion Report
|
# Phase 170: JsonParserBox JoinIR Preparation & Re-validation - Completion Report
|
||||||
|
|
||||||
**Date**: 2025-12-07
|
**Date**: 2025-12-07
|
||||||
**Duration**: 1 session (autonomous work)
|
**Duration**: 2 sessions (autonomous work + bug fix verification)
|
||||||
**Status**: ✅ **Complete** - Environment prepared, blockers identified, next phase planned
|
**Status**: ✅ **Complete** - Environment prepared, bug fix verified, next phase planned
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -374,5 +374,77 @@ match update.kind {
|
|||||||
- 代表的な ループスモーク 16 本のうち 15 本が PASS(1 本は既知の別問題)で、
|
- 代表的な ループスモーク 16 本のうち 15 本が PASS(1 本は既知の別問題)で、
|
||||||
既存パターンへの回帰は維持されている。
|
既存パターンへの回帰は維持されている。
|
||||||
|
|
||||||
この状態を起点に、今後 Phase 170‑C‑3 以降で `LoopUpdateSummary` の中身(AST/MIR ベースの解析)だけを差し替えることで、
|
この状態を起点に、今後 Phase 170‑C‑3 以降で `LoopUpdateSummary` の中身(AST/MIR ベースの解析)だけを差し替えることで、
|
||||||
段階的に carrier 名ヒューリスティックを薄めていく計画。
|
段階的に carrier 名ヒューリスティックを薄めていく計画。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 170-D: Loop Condition Scope Analysis - Bug Fix Verification ✅
|
||||||
|
|
||||||
|
**Date**: 2025-12-07 (Session 2)
|
||||||
|
**Status**: ✅ **Bug Fix Complete and Verified**
|
||||||
|
|
||||||
|
### Bug Fix: Function Parameter Misclassification
|
||||||
|
|
||||||
|
**Issue**: Function parameters (`s`, `pos` in JsonParser methods) were incorrectly classified as **LoopBodyLocal** when used in loop conditions.
|
||||||
|
|
||||||
|
**Root Cause**: `is_outer_scope_variable()` in `condition_var_analyzer.rs` defaulted unknown variables (not in `variable_definitions`) to LoopBodyLocal.
|
||||||
|
|
||||||
|
**Fix** (User Implemented):
|
||||||
|
```rust
|
||||||
|
// File: src/mir/loop_pattern_detection/condition_var_analyzer.rs (lines 175-184)
|
||||||
|
|
||||||
|
// At this point:
|
||||||
|
// - Variable is NOT in body_locals
|
||||||
|
// - No explicit definition info
|
||||||
|
// This typically means "function parameter" or "outer local"
|
||||||
|
true // ✅ Default to OuterLocal for function parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification Results
|
||||||
|
|
||||||
|
**Test 1: Function Parameter Loop** ✅
|
||||||
|
- **File**: `/tmp/test_jsonparser_simple.hako`
|
||||||
|
- **Result**: `Phase 170-D: Condition variables verified: {"pos", "s", "len"}`
|
||||||
|
- **Analysis**: Function parameters correctly classified as OuterLocal
|
||||||
|
- **New blocker**: Method calls (Pattern 5+ feature, not a bug)
|
||||||
|
|
||||||
|
**Test 2: LoopBodyLocal Correct Rejection** ✅
|
||||||
|
- **File**: `local_tests/test_trim_main_pattern.hako`
|
||||||
|
- **Result**: `Phase 170-D: Condition variables verified: {"ch", "end", "start"}`
|
||||||
|
- **Error**: "Variable 'ch' not bound in ConditionEnv" (correct rejection)
|
||||||
|
- **Analysis**: `ch` is defined inside loop, correctly rejected by Pattern 2
|
||||||
|
|
||||||
|
**Test 3: JsonParser Full File** ✅
|
||||||
|
- **File**: `tools/hako_shared/json_parser.hako`
|
||||||
|
- **Result**: Error about MethodCall, not variable classification
|
||||||
|
- **Analysis**: Variable scope classification now correct, remaining errors are legitimate feature gaps
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
**What the Fix Achieves**:
|
||||||
|
- ✅ Function parameters work correctly: `s`, `pos` in JsonParser
|
||||||
|
- ✅ Carrier variables work correctly: `start`, `end` in trim loops
|
||||||
|
- ✅ Outer locals work correctly: `len`, `maxLen` from outer scope
|
||||||
|
- ✅ Correct rejection: LoopBodyLocal `ch` properly rejected (not a bug)
|
||||||
|
|
||||||
|
**Remaining Blockers** (Pattern 5+ features, not bugs):
|
||||||
|
- ⚠️ Method calls in conditions: `loop(pos < s.length())`
|
||||||
|
- ⚠️ Method calls in loop body: `s.substring(pos, pos+1)`
|
||||||
|
- ⚠️ LoopBodyLocal in break conditions: `if ch == " " { break }`
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
**Created**:
|
||||||
|
1. ✅ `phase170-d-fix-verification.md` - Comprehensive verification report
|
||||||
|
2. ✅ `phase170-d-fix-summary.md` - Executive summary
|
||||||
|
3. ✅ Updated `CURRENT_TASK.md` - Bug fix section added
|
||||||
|
4. ✅ Updated `phase170-d-impl-design.md` - Bug fix notes
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
**Priority 1**: Pattern 5+ implementation for LoopBodyLocal in break conditions
|
||||||
|
**Priority 2**: .hako rewrite strategy for complex method calls
|
||||||
|
**Priority 3**: Coverage metrics for JsonParser loop support
|
||||||
|
|
||||||
|
**Build Status**: ✅ `cargo build --release` successful (0 errors, 50 warnings)
|
||||||
|
|||||||
226
docs/development/current/main/phase170-d-fix-summary.md
Normal file
226
docs/development/current/main/phase170-d-fix-summary.md
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
# Phase 170-D Bug Fix Verification - Summary Report
|
||||||
|
|
||||||
|
**Date**: 2025-12-07
|
||||||
|
**Status**: ✅ Bug Fix Verified and Documented
|
||||||
|
**Files Updated**: 3 documentation files, 0 code changes (user implemented fix)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Executive Summary
|
||||||
|
|
||||||
|
The **LoopConditionScopeBox function parameter misclassification bug** has been successfully verified. Function parameters are now correctly classified as **OuterLocal** instead of being incorrectly treated as **LoopBodyLocal**.
|
||||||
|
|
||||||
|
**Key Result**: JsonParser loops now pass variable scope validation. Remaining errors are legitimate Pattern 5+ feature gaps (method calls), not bugs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Verification Results
|
||||||
|
|
||||||
|
### ✅ Test 1: Function Parameter Classification
|
||||||
|
|
||||||
|
**Test File**: `/tmp/test_jsonparser_simple.hako`
|
||||||
|
**Loop**: Pattern 2 with function parameters `s` and `pos`
|
||||||
|
|
||||||
|
**Result**:
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||||
|
⚠️ New error: MethodCall .substring() not supported (Pattern 5+ feature)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Bug fix works**: `s` and `pos` correctly classified as OuterLocal
|
||||||
|
- ✅ **No more misclassification errors**: Variable scope validation passes
|
||||||
|
- ⚠️ **Different blocker**: Method calls in loop body (legitimate feature gap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Test 2: LoopBodyLocal Correct Rejection
|
||||||
|
|
||||||
|
**Test File**: `local_tests/test_trim_main_pattern.hako`
|
||||||
|
**Loop**: Pattern 2 with LoopBodyLocal `ch` in break condition
|
||||||
|
|
||||||
|
**Result**:
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||||
|
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Correct rejection**: `ch` is defined inside loop (`local ch = ...`)
|
||||||
|
- ✅ **Accurate error message**: "Variable 'ch' not bound" (not misclassified)
|
||||||
|
- ✅ **Expected behavior**: Pattern 2 doesn't support LoopBodyLocal in break conditions
|
||||||
|
|
||||||
|
**Conclusion**: This is the **correct rejection** - not a bug, needs Pattern 5+ support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Test 3: JsonParser Full File
|
||||||
|
|
||||||
|
**Test File**: `tools/hako_shared/json_parser.hako`
|
||||||
|
|
||||||
|
**Result**:
|
||||||
|
```
|
||||||
|
❌ [ERROR] Unsupported expression in value context: MethodCall { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Variable classification working**: No "incorrect variable scope" errors
|
||||||
|
- ⚠️ **New blocker**: Method calls in loop conditions (`s.length()`)
|
||||||
|
- ✅ **Bug fix validated**: JsonParser now hits different limitations (not variable scope bugs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Documentation Created
|
||||||
|
|
||||||
|
### 1. `/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase170-d-fix-verification.md` ✅
|
||||||
|
|
||||||
|
**Comprehensive verification report**:
|
||||||
|
- Bug fix details (root cause + fix)
|
||||||
|
- Test results before/after (3 test cases)
|
||||||
|
- Pattern 1-4 coverage analysis
|
||||||
|
- Verification commands
|
||||||
|
- Impact assessment
|
||||||
|
- Next steps (Pattern 5+ implementation)
|
||||||
|
|
||||||
|
### 2. `CURRENT_TASK.md` Updated ✅
|
||||||
|
|
||||||
|
**Added Section**: "Bug Fix: Function Parameter Misclassification"
|
||||||
|
- Issue description
|
||||||
|
- Root cause
|
||||||
|
- Fix location (lines 175-184)
|
||||||
|
- Impact summary
|
||||||
|
- Link to verification doc
|
||||||
|
|
||||||
|
### 3. `phase170-d-impl-design.md` Updated ✅
|
||||||
|
|
||||||
|
**Added Section**: "Bug Fix: Function Parameter Misclassification (2025-12-07)"
|
||||||
|
- Detailed root cause analysis
|
||||||
|
- Before/after code comparison
|
||||||
|
- Impact assessment
|
||||||
|
- Test results
|
||||||
|
- Lessons learned
|
||||||
|
- Design principles
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Bug Fix Technical Details
|
||||||
|
|
||||||
|
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs`
|
||||||
|
**Lines**: 175-184
|
||||||
|
|
||||||
|
**The Fix** (user implemented):
|
||||||
|
```rust
|
||||||
|
// At this point:
|
||||||
|
// - The variable is NOT in body_locals
|
||||||
|
// - There is no explicit definition info for it
|
||||||
|
//
|
||||||
|
// This typically means "function parameter" or "outer local"
|
||||||
|
// (e.g. JsonParserBox.s, .pos, etc.). Those should be treated
|
||||||
|
// as OuterLocal for condition analysis, otherwise we wrongly
|
||||||
|
// block valid loops as using loop-body-local variables.
|
||||||
|
true // ✅ Default to OuterLocal for function parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Insight**: Unknown variables (not in `variable_definitions`) are typically **function parameters** or **outer locals**, not loop-body-locals. The fix defaults them to the safer (OuterLocal) classification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Impact Assessment
|
||||||
|
|
||||||
|
### What the Fix Achieves ✅
|
||||||
|
|
||||||
|
1. ✅ **Function parameters work**: `s`, `pos` in JsonParser methods
|
||||||
|
2. ✅ **Carrier variables work**: `start`, `end` in trim loops
|
||||||
|
3. ✅ **Outer locals work**: `len`, `maxLen` from outer scope
|
||||||
|
4. ✅ **Correct rejection**: LoopBodyLocal `ch` properly rejected (not a bug)
|
||||||
|
|
||||||
|
### What Still Needs Work ⚠️
|
||||||
|
|
||||||
|
**Pattern 5+ Features** (not bugs):
|
||||||
|
- Method calls in conditions: `loop(pos < s.length())`
|
||||||
|
- Method calls in loop body: `s.substring(pos, pos+1)`
|
||||||
|
- LoopBodyLocal in break conditions: `if ch == " " { break }`
|
||||||
|
|
||||||
|
**Other Issues** (orthogonal):
|
||||||
|
- Exit Line / Boundary errors (separate architectural issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Next Steps
|
||||||
|
|
||||||
|
### Priority 1: Pattern 5+ Implementation
|
||||||
|
|
||||||
|
**Target loops**:
|
||||||
|
- `_trim` loops (LoopBodyLocal `ch` in break condition)
|
||||||
|
- `_parse_object`, `_parse_array` (method calls in loop body)
|
||||||
|
|
||||||
|
**Estimated impact**: 10-15 additional loops in JsonParser
|
||||||
|
|
||||||
|
### Priority 2: .hako Rewrite Strategy
|
||||||
|
|
||||||
|
**Simplification approach**:
|
||||||
|
- Hoist `.length()` calls to outer locals
|
||||||
|
- Pre-compute `.substring()` results outside loop
|
||||||
|
- Simplify break conditions to use simple comparisons
|
||||||
|
|
||||||
|
**Trade-off**: Some loops may be easier to rewrite than to implement Pattern 5+
|
||||||
|
|
||||||
|
### Priority 3: Coverage Metrics
|
||||||
|
|
||||||
|
**Systematic observation needed**:
|
||||||
|
```bash
|
||||||
|
# Run coverage analysis on JsonParser
|
||||||
|
./tools/analyze_joinir_coverage.sh tools/hako_shared/json_parser.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected categories**:
|
||||||
|
- Pattern 1: Simple loops (no break/continue)
|
||||||
|
- Pattern 2: Loops with break (variable scope OK, method calls blocked)
|
||||||
|
- Pattern 3: Loops with if-else PHI
|
||||||
|
- Pattern 4: Loops with continue
|
||||||
|
- Unsupported: Pattern 5+ needed (method calls, LoopBodyLocal conditions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Conclusion
|
||||||
|
|
||||||
|
**Bug Fix Status**: ✅ Complete and Verified
|
||||||
|
|
||||||
|
**Key Achievement**: Function parameters correctly classified as OuterLocal
|
||||||
|
|
||||||
|
**Verification Outcome**: All tests demonstrate correct behavior:
|
||||||
|
- Function parameters: ✅ Correctly classified
|
||||||
|
- LoopBodyLocal: ✅ Correctly rejected
|
||||||
|
- Error messages: ✅ Accurate (method calls, not variable scope)
|
||||||
|
|
||||||
|
**Overall Impact**: Significant progress - variable scope classification is now correct. Remaining errors are **legitimate feature gaps** (Pattern 5+ needed), not misclassification bugs.
|
||||||
|
|
||||||
|
**Build Status**: ✅ `cargo build --release` successful (0 errors, 50 warnings)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Verification Commands Used
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build verification
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# Test 1: Function parameter loop
|
||||||
|
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune /tmp/test_jsonparser_simple.hako 2>&1 | \
|
||||||
|
grep -E "Phase 170-D|Error"
|
||||||
|
|
||||||
|
# Test 2: TrimTest (LoopBodyLocal)
|
||||||
|
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | \
|
||||||
|
grep -E "Phase 170-D|Variable 'ch'"
|
||||||
|
|
||||||
|
# Test 3: JsonParser full
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | \
|
||||||
|
tail -40
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of Report**
|
||||||
255
docs/development/current/main/phase170-d-fix-verification.md
Normal file
255
docs/development/current/main/phase170-d-fix-verification.md
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
# Phase 170-D Bug Fix Verification
|
||||||
|
|
||||||
|
**Date**: 2025-12-07
|
||||||
|
**Status**: Fix Complete ✅
|
||||||
|
**Impact**: Function parameters now correctly classified as OuterLocal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The LoopConditionScopeBox function parameter misclassification bug has been fixed. Function parameters (`s`, `pos`, etc.) are now correctly treated as **OuterLocal** instead of being incorrectly defaulted to **LoopBodyLocal**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Fix Details
|
||||||
|
|
||||||
|
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs`
|
||||||
|
|
||||||
|
**Root Cause**:
|
||||||
|
- Unknown variables (not in `variable_definitions`) were defaulted to LoopBodyLocal
|
||||||
|
- Function parameters have no explicit definition in the loop body, so they appeared "unknown"
|
||||||
|
- Result: Valid loops using function parameters were incorrectly rejected
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
```rust
|
||||||
|
pub fn is_outer_scope_variable(var_name: &str, scope: Option<&LoopScopeShape>) -> bool {
|
||||||
|
match scope {
|
||||||
|
None => false,
|
||||||
|
Some(scope) => {
|
||||||
|
// ① body_locals に入っていたら絶対に outer ではない
|
||||||
|
if scope.body_locals.contains(var_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ② pinned(ループ引数など)は outer 扱い
|
||||||
|
if scope.pinned.contains(var_name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ③ variable_definitions の情報がある場合だけ、ブロック分布で判断
|
||||||
|
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||||
|
// (Carrier detection logic...)
|
||||||
|
// ...
|
||||||
|
return false; // body で定義されている → body-local
|
||||||
|
}
|
||||||
|
|
||||||
|
// ④ どこにも出てこない変数 = 関数パラメータ/外側ローカル → OuterLocal
|
||||||
|
true // ← KEY FIX: Default to OuterLocal, not LoopBodyLocal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Change**: Lines 175-184 now default unknown variables to **OuterLocal** (function parameters).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Results After Fix
|
||||||
|
|
||||||
|
### Test 1: Simple Function Parameter Loop
|
||||||
|
|
||||||
|
**File**: `/tmp/test_jsonparser_simple.hako`
|
||||||
|
|
||||||
|
**Loop Pattern**: Pattern 2 (loop with break), using function parameters `s` and `pos`
|
||||||
|
|
||||||
|
**Before Fix**:
|
||||||
|
```
|
||||||
|
❌ UnsupportedPattern: Variable 's' and 'pos' incorrectly classified as LoopBodyLocal
|
||||||
|
```
|
||||||
|
|
||||||
|
**After Fix**:
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||||
|
⚠️ Different error: Method call `.substring()` not supported in loop body (separate limitation)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Function parameters correctly classified**: `s` and `pos` are now OuterLocal
|
||||||
|
- ⚠️ **New blocker**: Method calls in loop body (Pattern 5+ feature)
|
||||||
|
- **Impact**: Bug fix works correctly - variable classification is fixed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 2: TrimTest (LoopBodyLocal in Break Condition)
|
||||||
|
|
||||||
|
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||||
|
|
||||||
|
**Loop Pattern**: Pattern 2, using LoopBodyLocal `ch` in break condition
|
||||||
|
|
||||||
|
**Result**:
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||||
|
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Correctly rejects LoopBodyLocal**: `ch` is defined inside loop (`local ch = ...`)
|
||||||
|
- ✅ **Correct error message**: "Variable 'ch' not bound" (not misclassified)
|
||||||
|
- ✅ **Expected behavior**: Pattern 2 doesn't support LoopBodyLocal in break conditions
|
||||||
|
|
||||||
|
**Validation**: This is the **correct rejection** - `ch` should need Pattern 5+ support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Test 3: JsonParser Full File
|
||||||
|
|
||||||
|
**File**: `tools/hako_shared/json_parser.hako`
|
||||||
|
|
||||||
|
**Result**:
|
||||||
|
```
|
||||||
|
❌ [ERROR] Unsupported expression in value context: MethodCall { object: Variable { name: "s" }, method: "length", ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- ✅ **Variable classification working**: No more "variable incorrectly classified" errors
|
||||||
|
- ⚠️ **New blocker**: Method calls in loop conditions (`s.length()`)
|
||||||
|
- **Impact**: Bug fix successful - JsonParser now hits **different** limitations (not variable scope bugs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 1-4 Coverage Analysis (After Fix)
|
||||||
|
|
||||||
|
### ✅ Fixed by Bug Fix
|
||||||
|
|
||||||
|
**Before Fix**: X loops incorrectly rejected due to function parameter misclassification
|
||||||
|
|
||||||
|
**After Fix**: These loops now correctly pass variable classification:
|
||||||
|
- **Simple loops using function parameters**: ✅ `s`, `pos` classified as OuterLocal
|
||||||
|
- **Loops with outer locals**: ✅ `len`, `maxLen` classified correctly
|
||||||
|
- **Carrier variables**: ✅ `start`, `end` (header+latch) classified as OuterLocal
|
||||||
|
|
||||||
|
### ⚠️ Remaining Limitations (Not Bug - Missing Features)
|
||||||
|
|
||||||
|
**Pattern 2 doesn't support**:
|
||||||
|
1. **Method calls in loop condition**: `s.length()`, `s.substring()` → Need Pattern 5+
|
||||||
|
2. **Method calls in loop body**: `.substring()` in break guards → Need Pattern 5+
|
||||||
|
3. **LoopBodyLocal in break conditions**: `local ch = ...; if ch == ...` → Need Pattern 5+
|
||||||
|
|
||||||
|
**These are legitimate feature gaps, not bugs.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test 1: Function parameter loop (should pass variable verification)
|
||||||
|
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune /tmp/test_jsonparser_simple.hako 2>&1 | \
|
||||||
|
grep "Phase 170-D"
|
||||||
|
|
||||||
|
# Expected: Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||||
|
|
||||||
|
# Test 2: LoopBodyLocal in break (should correctly reject)
|
||||||
|
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | \
|
||||||
|
grep "Phase 170-D\|Variable 'ch'"
|
||||||
|
|
||||||
|
# Expected:
|
||||||
|
# Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||||
|
# ERROR: Variable 'ch' not bound in ConditionEnv
|
||||||
|
|
||||||
|
# Test 3: JsonParser (should pass variable verification, fail on method calls)
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||||
|
./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | \
|
||||||
|
grep -E "Phase 170-D|MethodCall"
|
||||||
|
|
||||||
|
# Expected: Error about MethodCall, not about variable classification
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Impact Assessment
|
||||||
|
|
||||||
|
### ✅ What the Fix Achieves
|
||||||
|
|
||||||
|
1. **Function parameters work correctly**: `s`, `pos` in JsonParser methods
|
||||||
|
2. **Carrier variables work correctly**: `start`, `end` in trim loops
|
||||||
|
3. **Outer locals work correctly**: `len`, `maxLen` from outer scope
|
||||||
|
4. **Correct rejection**: LoopBodyLocal `ch` properly rejected (not a bug)
|
||||||
|
|
||||||
|
### ⚠️ What Still Needs Work
|
||||||
|
|
||||||
|
**Pattern 5+ Features** (not covered by this fix):
|
||||||
|
- Method calls in conditions: `loop(pos < s.length())`
|
||||||
|
- Method calls in loop body: `s.substring(pos, pos+1)`
|
||||||
|
- LoopBodyLocal in break conditions: `if ch == " " { break }`
|
||||||
|
|
||||||
|
**Exit Line & Boundary Issues** (orthogonal to this fix):
|
||||||
|
- Some loops fail with ExitLine/Boundary errors
|
||||||
|
- These are separate architectural issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Priority 1: Pattern 5+ Implementation
|
||||||
|
|
||||||
|
**Target loops**:
|
||||||
|
- `_trim` loops (LoopBodyLocal `ch` in break condition)
|
||||||
|
- `_parse_object`, `_parse_array` (method calls in loop body)
|
||||||
|
|
||||||
|
**Estimated impact**: 10-15 additional loops in JsonParser
|
||||||
|
|
||||||
|
### Priority 2: .hako Rewrite Strategy
|
||||||
|
|
||||||
|
**For loops with complex method calls**:
|
||||||
|
- Hoist `.length()` calls to outer locals
|
||||||
|
- Pre-compute `.substring()` results outside loop
|
||||||
|
- Simplify break conditions to use simple comparisons
|
||||||
|
|
||||||
|
**Example rewrite**:
|
||||||
|
```hako
|
||||||
|
// Before (Pattern 5+ needed)
|
||||||
|
loop(pos < s.length()) {
|
||||||
|
local ch = s.substring(pos, pos+1)
|
||||||
|
if ch == "}" { break }
|
||||||
|
pos = pos + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// After (Pattern 2 compatible)
|
||||||
|
local len = s.length()
|
||||||
|
loop(pos < len) {
|
||||||
|
// Need to avoid method calls in break guard
|
||||||
|
// This still requires Pattern 5+ for ch definition
|
||||||
|
local ch = s.substring(pos, pos+1)
|
||||||
|
if ch == "}" { break }
|
||||||
|
pos = pos + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority 3: Coverage Metrics
|
||||||
|
|
||||||
|
**Run systematic observation**:
|
||||||
|
```bash
|
||||||
|
# Count loops by pattern support
|
||||||
|
./tools/analyze_joinir_coverage.sh tools/hako_shared/json_parser.hako
|
||||||
|
|
||||||
|
# Expected output:
|
||||||
|
# Pattern 1: X loops
|
||||||
|
# Pattern 2: Y loops (with method call blockers)
|
||||||
|
# Pattern 3: Z loops
|
||||||
|
# Pattern 4: W loops
|
||||||
|
# Unsupported (need Pattern 5+): N loops
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
✅ **Bug Fix Complete**: Function parameters correctly classified as OuterLocal
|
||||||
|
✅ **Verification Successful**: Tests demonstrate correct variable classification
|
||||||
|
✅ **Expected Rejections**: LoopBodyLocal in break conditions correctly rejected
|
||||||
|
⚠️ **Next Blockers**: Method calls in loops (Pattern 5+ features, not bugs)
|
||||||
|
|
||||||
|
**Overall Impact**: Significant progress - variable scope classification is now correct. Remaining errors are legitimate feature gaps, not misclassification bugs.
|
||||||
@ -198,6 +198,34 @@ Pattern 2 supports only loop parameters and outer-scope variables.
|
|||||||
|
|
||||||
**Approach**:
|
**Approach**:
|
||||||
1. Detect loop-body-local variable patterns
|
1. Detect loop-body-local variable patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Fix Note(Phase 170-D-impl-2+)
|
||||||
|
|
||||||
|
Phase 166 再観測中に、JsonParserBox._parse_object(s, pos) の `s`(関数パラメータ)が
|
||||||
|
LoopBodyLocal と誤判定される致命的バグが見つかった。
|
||||||
|
|
||||||
|
- 原因: `is_outer_scope_variable()` が `body_locals` を参照せず、
|
||||||
|
`pinned` / `variable_definitions` に無い変数を「LoopBodyLocal 寄り」とみなしていた
|
||||||
|
- 影響: 本来 Pattern2/4 でサポートすべき `loop(p < s.length())` 形式のループが
|
||||||
|
「loop-body-local 変数使用」として UnsupportedPattern エラーになっていた
|
||||||
|
|
||||||
|
修正方針と実装(概略):
|
||||||
|
|
||||||
|
- 先に `LoopScopeShape.body_locals` を確認し、ここに含まれる変数だけを LoopBodyLocal とみなす
|
||||||
|
- `variable_definitions` にエントリがあり、header/latch 以外で定義される変数も LoopBodyLocal とみなす
|
||||||
|
- 上記いずれにも該当しない変数(関数パラメータや外側ローカル)は OuterLocal として扱う
|
||||||
|
|
||||||
|
これにより:
|
||||||
|
|
||||||
|
- 関数パラメータ `s`, `pos` 等は正しく OuterLocal と分類され、
|
||||||
|
JsonParser/Trim 系の「素直な while ループ」は Pattern2/4 の対象に戻る
|
||||||
|
- 本当にループ内で導入された変数(例: `local ch = ...`)は LoopBodyLocal のまま検出され、
|
||||||
|
今後の Pattern5+ の設計対象として切り出される
|
||||||
|
|
||||||
|
詳細な実装は `src/mir/loop_pattern_detection/condition_var_analyzer.rs` の
|
||||||
|
`is_outer_scope_variable()` および付随ユニットテストに記録されている。
|
||||||
2. Expand LoopConditionScope with additional heuristics
|
2. Expand LoopConditionScope with additional heuristics
|
||||||
3. Implement selective patterns (e.g., local x = ...; while(x < N))
|
3. Implement selective patterns (e.g., local x = ...; while(x < N))
|
||||||
4. Reuse LoopConditionScope infrastructure
|
4. Reuse LoopConditionScope infrastructure
|
||||||
@ -323,19 +351,110 @@ Finished `release` profile [optimized] target(s) in 1m 08s
|
|||||||
- `test_scope_header_and_latch_variable`: Carrier variable classification (COMPLETED)
|
- `test_scope_header_and_latch_variable`: Carrier variable classification (COMPLETED)
|
||||||
- `test_scope_priority_in_add_var`: Scope priority validation (BONUS)
|
- `test_scope_priority_in_add_var`: Scope priority validation (BONUS)
|
||||||
|
|
||||||
|
## Bug Fix: Function Parameter Misclassification (2025-12-07)
|
||||||
|
|
||||||
|
### Issue
|
||||||
|
|
||||||
|
Function parameters (e.g., `s`, `pos` in JsonParser methods) were incorrectly classified as **LoopBodyLocal** when used in loop conditions or break guards.
|
||||||
|
|
||||||
|
### Root Cause
|
||||||
|
|
||||||
|
In `condition_var_analyzer.rs`, the `is_outer_scope_variable()` function's default case (lines 175-184) was treating unknown variables (not in `variable_definitions`) as body-local variables.
|
||||||
|
|
||||||
|
**Problem Logic**:
|
||||||
|
```rust
|
||||||
|
// OLD (buggy): Unknown variables defaulted to LoopBodyLocal
|
||||||
|
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||||
|
// (carrier detection...)
|
||||||
|
return false; // body-local
|
||||||
|
}
|
||||||
|
// No default case → implicit false → LoopBodyLocal
|
||||||
|
false // ❌ BUG: function parameters have no definition, so defaulted to body-local
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why function parameters appear "unknown"**:
|
||||||
|
- Function parameters (`s`, `pos`) are not defined in the loop body
|
||||||
|
- They don't appear in `variable_definitions` (which only tracks loop-internal definitions)
|
||||||
|
- Without explicit handling, they were incorrectly treated as body-local
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs` (lines 175-184)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// NEW (fixed): Unknown variables default to OuterLocal (function parameters)
|
||||||
|
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||||
|
// (carrier detection logic...)
|
||||||
|
return false; // body-local
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point:
|
||||||
|
// - Variable is NOT in body_locals
|
||||||
|
// - No explicit definition info
|
||||||
|
// This typically means "function parameter" or "outer local"
|
||||||
|
true // ✅ FIX: Default to OuterLocal for function parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Change**: Default unknown variables to `OuterLocal` instead of implicitly defaulting to `LoopBodyLocal`.
|
||||||
|
|
||||||
|
### Impact
|
||||||
|
|
||||||
|
**Before Fix**:
|
||||||
|
- ❌ JsonParser loops incorrectly rejected: "Variable 's' uses loop-body-local variables"
|
||||||
|
- ❌ Function parameters treated as LoopBodyLocal
|
||||||
|
- ❌ Valid Pattern 2 loops blocked by misclassification
|
||||||
|
|
||||||
|
**After Fix**:
|
||||||
|
- ✅ Function parameters correctly classified as OuterLocal
|
||||||
|
- ✅ JsonParser loops pass variable scope validation
|
||||||
|
- ✅ LoopBodyLocal `ch` (defined with `local ch = ...`) correctly rejected
|
||||||
|
- ⚠️ New blockers: Method calls in loops (Pattern 5+ features, not bugs)
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
**Test Results**:
|
||||||
|
|
||||||
|
1. **Function Parameter Loop** (`/tmp/test_jsonparser_simple.hako`):
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||||
|
⚠️ Error: MethodCall .substring() not supported (Pattern 5+ feature)
|
||||||
|
```
|
||||||
|
**Analysis**: Variable classification fixed, error now about method calls (separate issue)
|
||||||
|
|
||||||
|
2. **LoopBodyLocal in Break** (`test_trim_main_pattern.hako`):
|
||||||
|
```
|
||||||
|
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||||
|
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||||
|
```
|
||||||
|
**Analysis**: Correctly rejects `ch` (defined as `local ch = ...` inside loop)
|
||||||
|
|
||||||
|
**Documentation**: See [phase170-d-fix-verification.md](phase170-d-fix-verification.md) for comprehensive test results.
|
||||||
|
|
||||||
|
### Lessons Learned
|
||||||
|
|
||||||
|
**Design Principle**: When classifying variables in scope analysis:
|
||||||
|
1. **Check explicit markers first** (`body_locals`, `pinned`)
|
||||||
|
2. **Analyze definition locations** (`variable_definitions`)
|
||||||
|
3. **Default to OuterLocal** for unknowns (function parameters, globals)
|
||||||
|
|
||||||
|
**Fail-Fast Philosophy**: The bug fix maintains fail-fast behavior while being **less strict** about unknown variables - treating them as safer (OuterLocal) rather than more restrictive (LoopBodyLocal).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
1. **Phase 170-D-impl-4 Completion**:
|
1. **Phase 170-D-impl-4 Completion** ✅:
|
||||||
- Update CURRENT_TASK.md with completion markers
|
- Update CURRENT_TASK.md with completion markers
|
||||||
- Create integration test .hako files for unsupported patterns
|
- Create integration test .hako files for unsupported patterns
|
||||||
- Run full regression test suite
|
- Run full regression test suite
|
||||||
|
|
||||||
2. **Documentation**:
|
2. **Documentation** ✅:
|
||||||
- Update loop pattern documentation index
|
- Update loop pattern documentation index
|
||||||
- Add quick reference for Phase 170-D validation
|
- Add quick reference for Phase 170-D validation
|
||||||
|
- Bug fix verification document
|
||||||
|
|
||||||
3. **Future Work** (Phase 170-D-E):
|
3. **Future Work** (Phase 170-D-E):
|
||||||
- Pattern 5+ for loop-body-local variable support
|
- Pattern 5+ for loop-body-local variable support
|
||||||
- Extended scope heuristics
|
- Extended scope heuristics
|
||||||
- Condition simplification analysis
|
- Condition simplification analysis
|
||||||
|
- Method call support in loop conditions
|
||||||
|
|||||||
@ -0,0 +1,257 @@
|
|||||||
|
# Phase 171-A: Blocked Loop Inventory
|
||||||
|
|
||||||
|
**Date**: 2025-12-07
|
||||||
|
**Status**: Initial inventory complete
|
||||||
|
**Purpose**: Identify loops blocked by LoopBodyLocal variables in break conditions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document catalogs loops that cannot be lowered by Pattern 2/4 because they use **LoopBodyLocal** variables in their break conditions. These are candidates for Pattern 5 carrier promotion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blocked Loops Found
|
||||||
|
|
||||||
|
### 1. TrimTest.trim/1 - Leading Whitespace Trim
|
||||||
|
|
||||||
|
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||||
|
**Lines**: 20-27
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blocking Variable**: `ch` (LoopBodyLocal)
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- Loop parameter: `start` (LoopParam)
|
||||||
|
- Outer variable: `end` (OuterLocal)
|
||||||
|
- Break condition uses: `ch` (LoopBodyLocal)
|
||||||
|
- `ch` is defined inside loop body as `s.substring(start, start+1)`
|
||||||
|
|
||||||
|
**Error Message**:
|
||||||
|
```
|
||||||
|
[trace:debug] pattern2: Pattern 2 lowerer failed: Variable 'ch' not bound in ConditionEnv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why Blocked**:
|
||||||
|
Pattern 2 expects break conditions to only use:
|
||||||
|
- LoopParam (`start`)
|
||||||
|
- OuterLocal (`end`, `s`)
|
||||||
|
|
||||||
|
But the break condition `ch == " " || ...` uses `ch`, which is defined inside the loop body.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. TrimTest.trim/1 - Trailing Whitespace Trim
|
||||||
|
|
||||||
|
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||||
|
**Lines**: 30-37
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(end > start) {
|
||||||
|
local ch = s.substring(end-1, end)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
end = end - 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blocking Variable**: `ch` (LoopBodyLocal)
|
||||||
|
|
||||||
|
**Analysis**:
|
||||||
|
- Loop parameter: `end` (LoopParam)
|
||||||
|
- Outer variable: `start` (OuterLocal)
|
||||||
|
- Break condition uses: `ch` (LoopBodyLocal)
|
||||||
|
- `ch` is defined inside loop body as `s.substring(end-1, end)`
|
||||||
|
|
||||||
|
**Same blocking reason as Loop 1.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. JsonParserBox - MethodCall in Condition
|
||||||
|
|
||||||
|
**File**: `tools/hako_shared/json_parser.hako`
|
||||||
|
|
||||||
|
```hako
|
||||||
|
loop(i < s.length()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Blocking Issue**: `s.length()` is a MethodCall in the condition expression.
|
||||||
|
|
||||||
|
**Error Message**:
|
||||||
|
```
|
||||||
|
[ERROR] ❌ MIR compilation error: [cf_loop/pattern4] Lowering failed:
|
||||||
|
Unsupported expression in value context: MethodCall {
|
||||||
|
object: Variable { name: "s", ... },
|
||||||
|
method: "length",
|
||||||
|
arguments: [],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why Blocked**:
|
||||||
|
Pattern 4's value context lowering doesn't support MethodCall expressions yet.
|
||||||
|
|
||||||
|
**Note**: This is not a LoopBodyLocal issue, but a MethodCall limitation. May be addressed in Phase 171-D (Optional).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern5-A Target Decision
|
||||||
|
|
||||||
|
### Selected Target: TrimTest Loop 1 (Leading Whitespace)
|
||||||
|
|
||||||
|
We select the **first loop** from TrimTest as Pattern5-A target for the following reasons:
|
||||||
|
|
||||||
|
1. **Clear structure**: Simple substring + equality checks
|
||||||
|
2. **Representative**: Same pattern as many real-world parsers
|
||||||
|
3. **Self-contained**: Doesn't depend on complex outer state
|
||||||
|
4. **Testable**: Easy to write unit tests
|
||||||
|
|
||||||
|
### Pattern5-A Specification
|
||||||
|
|
||||||
|
**Loop Structure**:
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Variables**:
|
||||||
|
- `start`: LoopParam (carrier, mutated)
|
||||||
|
- `end`: OuterLocal (condition-only)
|
||||||
|
- `s`: OuterLocal (used in body)
|
||||||
|
- `ch`: LoopBodyLocal (blocking variable)
|
||||||
|
|
||||||
|
**Break Condition**:
|
||||||
|
```
|
||||||
|
!(ch == " " || ch == "\t" || ch == "\n" || ch == "\r")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Promotion Strategy: Design D (Evaluated Bool Carrier)
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
We choose **Design D** (Evaluated Bool Carrier) over other options:
|
||||||
|
|
||||||
|
**Why not carry `ch` directly?**
|
||||||
|
- `ch` is a StringBox, not a primitive value
|
||||||
|
- Would require complex carrier type system
|
||||||
|
- Would break existing Pattern 2/4 assumptions
|
||||||
|
|
||||||
|
**Design D approach**:
|
||||||
|
- Introduce a new carrier: `is_whitespace` (bool)
|
||||||
|
- Evaluate `ch == " " || ...` in loop body
|
||||||
|
- Store result in `is_whitespace` carrier
|
||||||
|
- Use `is_whitespace` in break condition
|
||||||
|
|
||||||
|
### Transformed Structure
|
||||||
|
|
||||||
|
**Before (Pattern5-A)**:
|
||||||
|
```hako
|
||||||
|
loop(start < end) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (Pattern2 compatible)**:
|
||||||
|
```hako
|
||||||
|
// Initialization (before loop)
|
||||||
|
local is_whitespace = true // Initial assumption
|
||||||
|
|
||||||
|
loop(start < end && is_whitespace) {
|
||||||
|
local ch = s.substring(start, start+1)
|
||||||
|
is_whitespace = (ch == " " || ch == "\t" || ch == "\n" || ch == "\r")
|
||||||
|
|
||||||
|
if is_whitespace {
|
||||||
|
start = start + 1
|
||||||
|
} else {
|
||||||
|
break // Now redundant, but kept for clarity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key transformations**:
|
||||||
|
1. Add `is_whitespace` carrier initialization
|
||||||
|
2. Update loop condition to include `is_whitespace`
|
||||||
|
3. Compute `is_whitespace` in loop body
|
||||||
|
4. Original if-else becomes simpler (could be optimized away)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps (Phase 171-C)
|
||||||
|
|
||||||
|
### Phase 171-C-1: Skeleton Implementation ✅
|
||||||
|
- Create `LoopBodyCarrierPromoter` box
|
||||||
|
- Define `PromotionRequest` / `PromotionResult` types
|
||||||
|
- Implement skeleton `try_promote()` method
|
||||||
|
- Add `find_definition_in_body()` helper
|
||||||
|
|
||||||
|
### Phase 171-C-2: Trim Pattern Promotion Logic
|
||||||
|
- Detect substring + equality pattern
|
||||||
|
- Generate `is_whitespace` carrier
|
||||||
|
- Generate initialization statement
|
||||||
|
- Generate update statement
|
||||||
|
|
||||||
|
### Phase 171-C-3: Integration with Pattern 2/4
|
||||||
|
- Call `LoopBodyCarrierPromoter::try_promote()` in routing
|
||||||
|
- If promotion succeeds, route to Pattern 2
|
||||||
|
- If promotion fails, return UnsupportedPattern
|
||||||
|
|
||||||
|
### Phase 171-D: MethodCall Support (Optional)
|
||||||
|
- Handle `s.length()` in loop conditions
|
||||||
|
- May require carrier promotion for method results
|
||||||
|
- Lower priority than Trim pattern
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Blocked Loops**:
|
||||||
|
- 2 loops in TrimTest (LoopBodyLocal `ch`)
|
||||||
|
- 1+ loops in JsonParser (MethodCall in condition)
|
||||||
|
|
||||||
|
**Pattern5-A Target**:
|
||||||
|
- TrimTest leading whitespace trim loop
|
||||||
|
- Clear, representative, testable
|
||||||
|
|
||||||
|
**Promotion Strategy**:
|
||||||
|
- Design D: Evaluated Bool Carrier
|
||||||
|
- Transform `ch` checks → `is_whitespace` carrier
|
||||||
|
- Make compatible with Pattern 2
|
||||||
|
|
||||||
|
**Implementation Status**:
|
||||||
|
- Phase 171-A: ✅ Inventory complete
|
||||||
|
- Phase 171-B: ✅ Target selected
|
||||||
|
- Phase 171-C-1: ✅ Skeleton implementation complete
|
||||||
|
- Phase 171-C-2: ✅ Trim pattern detection implemented
|
||||||
|
- `find_definition_in_body()`: AST traversal for variable definitions
|
||||||
|
- `is_substring_method_call()`: Detects `substring()` method calls
|
||||||
|
- `extract_equality_literals()`: Extracts string literals from OR chains
|
||||||
|
- `TrimPatternInfo`: Captures pattern details for carrier promotion
|
||||||
|
- Phase 171-C-3: ⏳ Integration with Pattern 2/4 routing
|
||||||
@ -130,8 +130,17 @@ pub fn is_outer_scope_variable(
|
|||||||
scope: Option<&LoopScopeShape>,
|
scope: Option<&LoopScopeShape>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match scope {
|
match scope {
|
||||||
None => false, // No scope info → assume body-local
|
// No scope information: be conservative but *not* over‑strict.
|
||||||
|
// We treat unknown as body-local only when we have a LoopScopeShape
|
||||||
|
// that explicitly marks it so (via body_locals / definitions).
|
||||||
|
// Here we simply say "unknown" and let the caller decide.
|
||||||
|
None => false,
|
||||||
Some(scope) => {
|
Some(scope) => {
|
||||||
|
// If the variable is explicitly marked as body-local, it is NOT outer.
|
||||||
|
if scope.body_locals.contains(var_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check 1: Is it a pinned variable (loop parameter or passed-in)?
|
// Check 1: Is it a pinned variable (loop parameter or passed-in)?
|
||||||
if scope.pinned.contains(var_name) {
|
if scope.pinned.contains(var_name) {
|
||||||
return true;
|
return true;
|
||||||
@ -151,15 +160,27 @@ pub fn is_outer_scope_variable(
|
|||||||
// This supports loop patterns like:
|
// This supports loop patterns like:
|
||||||
// local i = 0 (header)
|
// local i = 0 (header)
|
||||||
// loop(i < 10) {
|
// loop(i < 10) {
|
||||||
// ...
|
// ...
|
||||||
// i = i + 1 (latch)
|
// i = i + 1 (latch)
|
||||||
// }
|
// }
|
||||||
if def_blocks.iter().all(|b| *b == scope.header || *b == scope.latch) {
|
if def_blocks.iter().all(|b| *b == scope.header || *b == scope.latch) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any other definition pattern (e.g. body-only or body+header)
|
||||||
|
// is treated as body-local / internal.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
// At this point:
|
||||||
|
// - The variable is NOT in body_locals
|
||||||
|
// - There is no explicit definition info for it
|
||||||
|
//
|
||||||
|
// This typically means "function parameter" or "outer local"
|
||||||
|
// (e.g. JsonParserBox.s, .pos, etc.). Those should be treated
|
||||||
|
// as OuterLocal for condition analysis, otherwise we wrongly
|
||||||
|
// block valid loops as using loop-body-local variables.
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,6 +359,34 @@ mod tests {
|
|||||||
assert!(!is_outer_scope_variable("ch", Some(&scope)));
|
assert!(!is_outer_scope_variable("ch", Some(&scope)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_outer_scope_variable_function_param_like() {
|
||||||
|
// Variables that are *not* marked as body_locals and have no explicit
|
||||||
|
// variable_definitions entry represent things like function parameters
|
||||||
|
// or outer locals. These must be treated as OuterLocal so that valid
|
||||||
|
// conditions such as `p < s.length()` (with `s` a parameter) are
|
||||||
|
// accepted by Pattern 2/4.
|
||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
|
let scope = 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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
is_outer_scope_variable("s", Some(&scope)),
|
||||||
|
"Function parameter–like variable should be classified as OuterLocal"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Phase 170-ultrathink: Additional Edge Case Tests (Issue #3)
|
// Phase 170-ultrathink: Additional Edge Case Tests (Issue #3)
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
600
src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs
Normal file
600
src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
//! Phase 171-C: LoopBodyCarrierPromoter Box
|
||||||
|
//!
|
||||||
|
//! LoopBodyLocal 変数を carrier に昇格させることで、
|
||||||
|
//! Pattern 2/4 の範囲で処理可能にするための箱。
|
||||||
|
//!
|
||||||
|
//! ## Design Philosophy
|
||||||
|
//!
|
||||||
|
//! - 入力: LoopScopeShape + LoopConditionScope + break 条件 AST
|
||||||
|
//! - 出力: 昇格成功なら CarrierInfo、失敗なら理由
|
||||||
|
//! - 役割: LoopBodyLocal を「評価済み bool carrier」に変換
|
||||||
|
//!
|
||||||
|
//! ## Implementation Scope
|
||||||
|
//!
|
||||||
|
//! ### Phase 171-C-1: スケルトン実装 ✅
|
||||||
|
//! - LoopBodyLocal の検出
|
||||||
|
//! - 定義の探索
|
||||||
|
//!
|
||||||
|
//! ### Phase 171-C-2: Trim パターン昇格 ✅
|
||||||
|
//! - `local ch = s.substring(...)` パターン検出
|
||||||
|
//! - `ch == " " || ch == "\t" ...` の等価比較検出
|
||||||
|
//! - `is_whitespace` bool carrier への変換情報生成
|
||||||
|
|
||||||
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||||
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||||
|
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScope;
|
||||||
|
|
||||||
|
/// 昇格リクエスト
|
||||||
|
pub struct PromotionRequest<'a> {
|
||||||
|
/// ループのスコープ情報
|
||||||
|
pub scope: &'a LoopScopeShape,
|
||||||
|
/// 条件変数のスコープ分類
|
||||||
|
pub cond_scope: &'a LoopConditionScope,
|
||||||
|
/// break 条件の AST(Pattern 2 の場合)
|
||||||
|
pub break_cond: Option<&'a ASTNode>,
|
||||||
|
/// ループ本体の AST
|
||||||
|
pub loop_body: &'a [ASTNode],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase 171-C-2: 検出された Trim パターン情報
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TrimPatternInfo {
|
||||||
|
/// LoopBodyLocal 変数名(例: "ch")
|
||||||
|
pub var_name: String,
|
||||||
|
/// 比較対象の文字列リテラル(例: [" ", "\t", "\n", "\r"])
|
||||||
|
pub comparison_literals: Vec<String>,
|
||||||
|
/// 生成する carrier 名(例: "is_whitespace")
|
||||||
|
pub carrier_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 昇格結果
|
||||||
|
pub enum PromotionResult {
|
||||||
|
/// 昇格成功: Trim パターン情報を返す
|
||||||
|
///
|
||||||
|
/// Phase 171-C-2: CarrierInfo の実際の更新は Phase 171-C-3 で実装
|
||||||
|
Promoted {
|
||||||
|
/// Phase 171-C-2: 検出された Trim パターン情報
|
||||||
|
trim_info: TrimPatternInfo,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// 昇格不可: 理由を説明
|
||||||
|
CannotPromote {
|
||||||
|
reason: String,
|
||||||
|
vars: Vec<String>, // 問題の LoopBodyLocal
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phase 171-C: LoopBodyCarrierPromoter Box
|
||||||
|
pub struct LoopBodyCarrierPromoter;
|
||||||
|
|
||||||
|
impl LoopBodyCarrierPromoter {
|
||||||
|
/// LoopBodyLocal を carrier に昇格できるか試行
|
||||||
|
///
|
||||||
|
/// # Phase 171-C-2: Trim パターン実装
|
||||||
|
///
|
||||||
|
/// 現在の実装では:
|
||||||
|
/// 1. LoopBodyLocal を抽出
|
||||||
|
/// 2. 各変数の定義を探索
|
||||||
|
/// 3. Trim パターン(substring + equality)を検出
|
||||||
|
/// 4. 昇格可能なら TrimPatternInfo を返す
|
||||||
|
pub fn try_promote(request: &PromotionRequest) -> PromotionResult {
|
||||||
|
use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope;
|
||||||
|
|
||||||
|
// 1. LoopBodyLocal を抽出
|
||||||
|
let body_locals: Vec<&String> = request.cond_scope.vars.iter()
|
||||||
|
.filter(|v| v.scope == CondVarScope::LoopBodyLocal)
|
||||||
|
.map(|v| &v.name)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if body_locals.is_empty() {
|
||||||
|
// LoopBodyLocal がなければ昇格不要
|
||||||
|
return PromotionResult::CannotPromote {
|
||||||
|
reason: "No LoopBodyLocal variables to promote".to_string(),
|
||||||
|
vars: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[promoter/pattern5] Phase 171-C: Found {} LoopBodyLocal variables: {:?}",
|
||||||
|
body_locals.len(),
|
||||||
|
body_locals
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. 各 LoopBodyLocal の定義を探す
|
||||||
|
for var_name in &body_locals {
|
||||||
|
let definition = Self::find_definition_in_body(request.loop_body, var_name);
|
||||||
|
|
||||||
|
if let Some(def_node) = definition {
|
||||||
|
eprintln!(
|
||||||
|
"[promoter/pattern5] Found definition for '{}' in loop body",
|
||||||
|
var_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Phase 171-C-2: Trim パターンを検出
|
||||||
|
if Self::is_substring_method_call(def_node) {
|
||||||
|
eprintln!(
|
||||||
|
"[promoter/pattern5] '{}' is defined by substring() call - Trim pattern candidate",
|
||||||
|
var_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. break 条件から等価比較リテラルを抽出
|
||||||
|
if let Some(break_cond) = request.break_cond {
|
||||||
|
let literals = Self::extract_equality_literals(break_cond, var_name);
|
||||||
|
|
||||||
|
if !literals.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"[promoter/pattern5] Trim pattern detected! var='{}', literals={:?}",
|
||||||
|
var_name, literals
|
||||||
|
);
|
||||||
|
|
||||||
|
// 昇格成功!
|
||||||
|
let trim_info = TrimPatternInfo {
|
||||||
|
var_name: var_name.to_string(),
|
||||||
|
comparison_literals: literals,
|
||||||
|
carrier_name: format!("is_{}_match", var_name),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase 171-C-2: TrimPatternInfo を返す
|
||||||
|
// CarrierInfo の実際の更新は Phase 171-C-3 で実装
|
||||||
|
return PromotionResult::Promoted { trim_info };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"[promoter/pattern5] Definition for '{}' not found in loop body",
|
||||||
|
var_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 昇格パターンに一致しない
|
||||||
|
PromotionResult::CannotPromote {
|
||||||
|
reason: "No promotable Trim pattern detected".to_string(),
|
||||||
|
vars: body_locals.iter().map(|s| s.to_string()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ループ本体から変数の定義(Assignment)を探す
|
||||||
|
///
|
||||||
|
/// # Phase 171-C-2: 実装済み
|
||||||
|
///
|
||||||
|
/// iterative worklist で AST を探索し、`local var = ...` または
|
||||||
|
/// `var = ...` の代入を見つける。
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `body` - ループ本体の AST ノード列
|
||||||
|
/// * `var_name` - 探す変数名
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// 定義(代入の RHS)が見つかれば Some(&ASTNode)、なければ None
|
||||||
|
fn find_definition_in_body<'a>(body: &'a [ASTNode], var_name: &str) -> Option<&'a ASTNode> {
|
||||||
|
let mut worklist: Vec<&'a ASTNode> = body.iter().collect();
|
||||||
|
|
||||||
|
while let Some(node) = worklist.pop() {
|
||||||
|
match node {
|
||||||
|
// Assignment: target = value
|
||||||
|
ASTNode::Assignment { target, value, .. } => {
|
||||||
|
// target が Variable で、名前が一致すれば定義発見
|
||||||
|
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||||
|
if name == var_name {
|
||||||
|
return Some(value.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If: then_body と else_body を探索
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
for stmt in then_body {
|
||||||
|
worklist.push(stmt);
|
||||||
|
}
|
||||||
|
if let Some(else_stmts) = else_body {
|
||||||
|
for stmt in else_stmts {
|
||||||
|
worklist.push(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop: body を探索(ネストループ)
|
||||||
|
ASTNode::Loop { body: loop_body, .. } => {
|
||||||
|
for stmt in loop_body {
|
||||||
|
worklist.push(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// その他のノードは無視
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RHS が substring() メソッド呼び出しかどうかを判定
|
||||||
|
///
|
||||||
|
/// # Phase 171-C-2
|
||||||
|
///
|
||||||
|
/// Trim パターンでは `local ch = s.substring(start, start+1)` のように
|
||||||
|
/// substring メソッドで1文字を取り出すパターンを使う。
|
||||||
|
fn is_substring_method_call(node: &ASTNode) -> bool {
|
||||||
|
matches!(
|
||||||
|
node,
|
||||||
|
ASTNode::MethodCall { method, .. } if method == "substring"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// break 条件から、指定変数との等価比較リテラルを抽出
|
||||||
|
///
|
||||||
|
/// # Phase 171-C-2
|
||||||
|
///
|
||||||
|
/// `ch == " " || ch == "\t" || ch == "\n"` のような条件から
|
||||||
|
/// `[" ", "\t", "\n"]` を抽出する。
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cond` - break 条件の AST
|
||||||
|
/// * `var_name` - 比較対象の変数名
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// 等価比較で使われている文字列リテラルのリスト
|
||||||
|
fn extract_equality_literals(cond: &ASTNode, var_name: &str) -> Vec<String> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut worklist = vec![cond];
|
||||||
|
|
||||||
|
while let Some(node) = worklist.pop() {
|
||||||
|
match node {
|
||||||
|
// BinaryOp: Or で分岐、Eq で比較
|
||||||
|
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||||
|
match operator {
|
||||||
|
// Or: 両側を探索
|
||||||
|
BinaryOperator::Or => {
|
||||||
|
worklist.push(left.as_ref());
|
||||||
|
worklist.push(right.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal: var == literal パターンを検出
|
||||||
|
BinaryOperator::Equal => {
|
||||||
|
// left が Variable で var_name に一致
|
||||||
|
if let ASTNode::Variable { name, .. } = left.as_ref() {
|
||||||
|
if name == var_name {
|
||||||
|
// right が String リテラル
|
||||||
|
if let ASTNode::Literal { value: LiteralValue::String(s), .. } = right.as_ref() {
|
||||||
|
result.push(s.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// right が Variable で var_name に一致(逆順)
|
||||||
|
if let ASTNode::Variable { name, .. } = right.as_ref() {
|
||||||
|
if name == var_name {
|
||||||
|
if let ASTNode::Literal { value: LiteralValue::String(s), .. } = left.as_ref() {
|
||||||
|
result.push(s.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryOp: Not の内側を探索
|
||||||
|
ASTNode::UnaryOp { operand, .. } => {
|
||||||
|
worklist.push(operand.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ast::Span;
|
||||||
|
use crate::mir::BasicBlockId;
|
||||||
|
use crate::mir::loop_pattern_detection::loop_condition_scope::{
|
||||||
|
CondVarScope, LoopConditionScope,
|
||||||
|
};
|
||||||
|
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_promoter_no_body_locals() {
|
||||||
|
let scope = minimal_scope();
|
||||||
|
let cond_scope = LoopConditionScope::new(); // Empty, no LoopBodyLocal
|
||||||
|
|
||||||
|
let request = PromotionRequest {
|
||||||
|
scope: &scope,
|
||||||
|
cond_scope: &cond_scope,
|
||||||
|
break_cond: None,
|
||||||
|
loop_body: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
PromotionResult::CannotPromote { reason, vars } => {
|
||||||
|
assert!(vars.is_empty());
|
||||||
|
assert!(reason.contains("No LoopBodyLocal"));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected CannotPromote when no LoopBodyLocal variables"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_promoter_body_local_no_definition() {
|
||||||
|
// LoopBodyLocal があるが、定義が見つからない場合
|
||||||
|
let scope = minimal_scope();
|
||||||
|
let cond_scope = cond_scope_with_body_local("ch");
|
||||||
|
|
||||||
|
let request = PromotionRequest {
|
||||||
|
scope: &scope,
|
||||||
|
cond_scope: &cond_scope,
|
||||||
|
break_cond: None,
|
||||||
|
loop_body: &[], // Empty body - no definition
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
PromotionResult::CannotPromote { reason, vars } => {
|
||||||
|
assert!(vars.contains(&"ch".to_string()));
|
||||||
|
assert!(reason.contains("No promotable Trim pattern"));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected CannotPromote when definition not found"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Phase 171-C-2: Trim Pattern Detection Tests
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_definition_in_body_simple() {
|
||||||
|
// Test: local ch = s.substring(...)
|
||||||
|
let body = vec![
|
||||||
|
assignment("ch", method_call("s", "substring")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch");
|
||||||
|
|
||||||
|
assert!(result.is_some(), "Definition should be found");
|
||||||
|
match result.unwrap() {
|
||||||
|
ASTNode::MethodCall { method, .. } => {
|
||||||
|
assert_eq!(method, "substring");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected MethodCall"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_definition_in_body_nested_if() {
|
||||||
|
// Test: Definition inside if-else block
|
||||||
|
let body = vec![
|
||||||
|
ASTNode::If {
|
||||||
|
condition: Box::new(var_node("flag")),
|
||||||
|
then_body: vec![
|
||||||
|
assignment("ch", method_call("s", "substring")),
|
||||||
|
],
|
||||||
|
else_body: None,
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::find_definition_in_body(&body, "ch");
|
||||||
|
|
||||||
|
assert!(result.is_some(), "Definition should be found inside if block");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_substring_method_call() {
|
||||||
|
let substring_call = method_call("s", "substring");
|
||||||
|
let other_call = method_call("s", "length");
|
||||||
|
|
||||||
|
assert!(LoopBodyCarrierPromoter::is_substring_method_call(&substring_call));
|
||||||
|
assert!(!LoopBodyCarrierPromoter::is_substring_method_call(&other_call));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_equality_literals_single() {
|
||||||
|
// Test: ch == " "
|
||||||
|
let cond = eq_cmp("ch", " ");
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::extract_equality_literals(&cond, "ch");
|
||||||
|
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert!(result.contains(&" ".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_equality_literals_or_chain() {
|
||||||
|
// Test: ch == " " || ch == "\t" || ch == "\n"
|
||||||
|
let cond = or_expr(
|
||||||
|
or_expr(
|
||||||
|
eq_cmp("ch", " "),
|
||||||
|
eq_cmp("ch", "\t"),
|
||||||
|
),
|
||||||
|
eq_cmp("ch", "\n"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::extract_equality_literals(&cond, "ch");
|
||||||
|
|
||||||
|
assert_eq!(result.len(), 3);
|
||||||
|
assert!(result.contains(&" ".to_string()));
|
||||||
|
assert!(result.contains(&"\t".to_string()));
|
||||||
|
assert!(result.contains(&"\n".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_equality_literals_wrong_var() {
|
||||||
|
// Test: other_var == " " (should not match for "ch")
|
||||||
|
let cond = eq_cmp("other_var", " ");
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::extract_equality_literals(&cond, "ch");
|
||||||
|
|
||||||
|
assert!(result.is_empty(), "Should not extract literals for wrong variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trim_pattern_full_detection() {
|
||||||
|
// Full Trim pattern test:
|
||||||
|
// - LoopBodyLocal: ch
|
||||||
|
// - Definition: ch = s.substring(...)
|
||||||
|
// - Break condition: ch == " " || ch == "\t"
|
||||||
|
|
||||||
|
let scope = 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 request = PromotionRequest {
|
||||||
|
scope: &scope,
|
||||||
|
cond_scope: &cond_scope,
|
||||||
|
break_cond: Some(&break_cond),
|
||||||
|
loop_body: &loop_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
PromotionResult::Promoted { trim_info } => {
|
||||||
|
assert_eq!(trim_info.var_name, "ch");
|
||||||
|
assert_eq!(trim_info.comparison_literals.len(), 2);
|
||||||
|
assert!(trim_info.comparison_literals.contains(&" ".to_string()));
|
||||||
|
assert!(trim_info.comparison_literals.contains(&"\t".to_string()));
|
||||||
|
assert_eq!(trim_info.carrier_name, "is_ch_match");
|
||||||
|
}
|
||||||
|
PromotionResult::CannotPromote { reason, .. } => {
|
||||||
|
panic!("Expected Promoted, got CannotPromote: {}", reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trim_pattern_with_4_whitespace_chars() {
|
||||||
|
// Full whitespace pattern: " " || "\t" || "\n" || "\r"
|
||||||
|
let scope = 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(
|
||||||
|
or_expr(
|
||||||
|
eq_cmp("ch", " "),
|
||||||
|
eq_cmp("ch", "\t"),
|
||||||
|
),
|
||||||
|
or_expr(
|
||||||
|
eq_cmp("ch", "\n"),
|
||||||
|
eq_cmp("ch", "\r"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = PromotionRequest {
|
||||||
|
scope: &scope,
|
||||||
|
cond_scope: &cond_scope,
|
||||||
|
break_cond: Some(&break_cond),
|
||||||
|
loop_body: &loop_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LoopBodyCarrierPromoter::try_promote(&request);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
PromotionResult::Promoted { trim_info } => {
|
||||||
|
assert_eq!(trim_info.comparison_literals.len(), 4);
|
||||||
|
}
|
||||||
|
PromotionResult::CannotPromote { reason, .. } => {
|
||||||
|
panic!("Expected Promoted, got CannotPromote: {}", reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -758,3 +758,6 @@ pub mod condition_var_analyzer;
|
|||||||
|
|
||||||
// Phase 170-ultrathink: Error Message Utilities
|
// Phase 170-ultrathink: Error Message Utilities
|
||||||
pub mod error_messages;
|
pub mod error_messages;
|
||||||
|
|
||||||
|
// Phase 171-C: LoopBodyLocal Carrier Promotion
|
||||||
|
pub mod loop_body_carrier_promoter;
|
||||||
|
|||||||
Reference in New Issue
Block a user