feat(joinir): Phase 170-D-impl-1 LoopConditionScopeBox skeleton creation

Implement the LoopConditionScope analysis infrastructure for Pattern2/4 validation:

New module:
- src/mir/loop_pattern_detection/loop_condition_scope.rs (220 lines)

Types:
- CondVarScope enum: LoopParam, OuterLocal, LoopBodyLocal
- CondVarInfo: Variable name + scope classification
- LoopConditionScope: Collection of analyzed variables with helper methods

Box implementation (LoopConditionScopeBox):
- analyze(): Main entry point - extracts and classifies condition variables
- extract_vars(): Recursive AST traversal to find all variable references
- is_outer_local(): Heuristic for outer scope detection (phase 170-D simplified)

Helper methods:
- has_loop_body_local(): Check for unsupported loop-body variables
- all_in(): Validate scope compatibility
- var_names(): Get variable set
- add_var(): Add with deduplication

Tests: 5 unit tests for core functionality

Architecture: Refactored loop_pattern_detection.rs → loop_pattern_detection/mod.rs
for modular organization.

Build:  Passed with no errors

🤖 Generated with Claude Code
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 21:29:19 +09:00
parent 7b83d214ae
commit 1356b61ff7
4 changed files with 358 additions and 0 deletions

View File

@ -0,0 +1,110 @@
# Phase 170D: LoopConditionScopeBox 設計メモ
日付: 20251207
状態: 設計完了(実装は今後の Phase で)
## 背景
Pattern2/Pattern4Loop with Break / Loop with Continueは、もともと
- ループパラメータ(例: `i`
- ループ外のローカル・引数(例: `start`, `end`, `len`
のみを条件式に使う前提で設計されていた。
しかし JsonParserBox / trim などでは、
```hako
local ch = …
if (ch != " ") { break }
```
のように「ループ本体ローカル」を条件に使うパターンが現れ、この範囲を超えると
- バグが出たりValueId 伝播ミス)
- たまたま動いたり
という **曖昧な状態** になっていた。
これを箱理論的に整理するために、条件で使われる変数の「スコープ」を明示的に分類する
LoopConditionScopeBox を導入する。
## LoopConditionScopeBox の責務
責務は 1 つだけ:
> 「条件式に登場する変数が、どのスコープで定義されたか」を教える
### 型イメージ
```rust
pub enum CondVarScope {
LoopParam, // ループパラメータ (i)
OuterLocal, // ループ外のローカル/引数 (start, end, len)
LoopBodyLocal, // ループ本体で定義された変数 (ch)
}
pub struct CondVarInfo {
pub name: String,
pub scope: CondVarScope,
}
pub struct LoopConditionScope {
pub vars: Vec<CondVarInfo>,
}
```
主なメソッド例:
```rust
impl LoopConditionScope {
pub fn has_loop_body_local(&self) -> bool { }
pub fn all_in(&self, allowed: &[CondVarScope]) -> bool { }
pub fn vars_in(&self, scope: CondVarScope) -> impl Iterator<Item = &CondVarInfo> { }
}
```
### 入力 / 出力
入力:
- ループヘッダ条件 AST
- break/continue 条件 ASTPattern2/4
- LoopScopeShapeどの変数がどのスコープで定義されたか
出力:
- `LoopConditionScope`CondVarInfo の集合)
## Pattern2/Pattern4 との関係
Pattern2/4 は LoopConditionScopeBox の結果だけを見て「対応可否」を決める:
```rust
let cond_scope = LoopConditionScopeBox::analyze(&loop_ast, &loop_scope);
// 対応範囲LoopParam + OuterLocal のみ
if !cond_scope.all_in(&[CondVarScope::LoopParam, CondVarScope::OuterLocal]) {
return Err(JoinIrError::UnsupportedPattern { });
}
```
これにより、
- いままで暗黙だった「対応範囲」が **設計として明示**される
- LoopBodyLocal を条件に含む trim/JsonParser 系ループは
- 現状は `[joinir/freeze] UnsupportedPattern` にする
- 将来 Pattern5+ で扱いたくなったときに、LoopConditionScopeBox の結果を使って設計できる
## 将来の拡張
LoopBodyLocal を含む条件式を扱いたくなった場合は:
- LoopConditionScopeBox の結果から `vars_in(LoopBodyLocal)` を取り出し、
- その変数を carrier に昇格させる
- もしくは LoopHeader に「状態保持用」の追加パラメータを生やす
- それを新しい Pattern5 として設計すれば、既存 Pattern2/4 の仕様を崩さずに拡張できる。
このドキュメントは設計メモのみであり、実装は別フェーズPhase 170Dimpl など)で行う。