feat(mir): Phase 137-5 - Decision Policy SSOT化完了
## 目的 Canonicalizer の RoutingDecision.chosen を「lowerer 選択の最終結果」にする (構造クラス名ではなく ExitContract ベースの決定) ## 実装内容 ### 1. Canonicalizer の決定ロジック修正 - `src/mir/loop_canonicalizer/mod.rs` - `skip_whitespace` パターン認識で ExitContract (has_break=true) を考慮 - Pattern3IfPhi → Pattern2Break に修正(構造は似ているが break あり) - 単体テスト更新(Pattern2Break 期待に変更) ### 2. Parity 検証テスト修正 - `src/mir/builder/control_flow/joinir/routing.rs` - `test_parity_check_mismatch_detected` → `test_parity_check_skip_whitespace_match` - Canonicalizer と Router の一致を検証(ミスマッチ検出からマッチ検証へ) - Phase 137-5 の SSOT 原則を反映 ### 3. ドキュメント更新 - `docs/development/current/main/design/loop-canonicalizer.md` - Phase 137-5: Decision Policy SSOT セクション追加 - ExitContract 優先の原則を明記 - skip_whitespace の例を追加 - `docs/development/current/main/phases/phase-137/README.md` - Phase 4 完了マーク追加 - Phase 5 完了セクション追加(実装・検証・効果) ## 検証結果 - ✅ 単体テスト: `cargo test --release --lib loop_canonicalizer::tests` (11/11 passed) - ✅ Parity テスト: `cargo test --release --lib 'routing::tests::test_parity'` (2/2 passed) - ✅ Strict モード: `HAKO_JOINIR_STRICT=1` で skip_whitespace parity OK ## 効果 - Router と Canonicalizer の pattern 選択が一致 - ExitContract が pattern 決定の SSOT として明確化 - 構造的特徴(if-else 等)は notes に記録(将来拡張に備える) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# Loop Canonicalizer(設計 SSOT)
|
||||
|
||||
Status: Phase 2 done(dev-only 観測まで)
|
||||
Status: Phase 3 done(skip_whitespace の安定認識まで)
|
||||
Scope: ループ形の組み合わせ爆発を抑えるための “前処理” の設計(fixture/shape guard/fail-fast と整合)
|
||||
Related:
|
||||
- SSOT (契約/不変条件): `docs/development/current/main/joinir-architecture-overview.md`
|
||||
@ -166,6 +166,7 @@ pub enum CarrierRole {
|
||||
注意:
|
||||
- Phase 2 で `canonicalize_loop_expr(...) -> Result<(LoopSkeleton, RoutingDecision), String>` を導入し、JoinIR ループ入口で dev-only 観測できるようにした(既定挙動は不変)。
|
||||
- 観測ポイント(JoinIR ループ入口): `src/mir/builder/control_flow/joinir/routing.rs`(`joinir_dev_enabled()` 配下)
|
||||
- Phase 3 で `skip_whitespace` の最小形を `Pattern3IfPhi` として安定に識別できるようにした(dev-only 観測)。
|
||||
|
||||
## Capability の語彙(Fail-Fast reason タグ)
|
||||
|
||||
@ -197,6 +198,7 @@ Canonicalizer の判定結果は `RoutingDecision` に集約し、以下に流
|
||||
```rust
|
||||
pub struct RoutingDecision {
|
||||
/// 選択された Pattern(None = Fail-Fast)
|
||||
/// Phase 137-5: ExitContract に基づく最終 lowerer 選択を反映
|
||||
pub chosen: Option<LoopPatternKind>,
|
||||
|
||||
/// 不足している Capability のリスト
|
||||
@ -210,6 +212,20 @@ pub struct RoutingDecision {
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 137-5: Decision Policy SSOT
|
||||
|
||||
**原則**: `RoutingDecision.chosen` は「lowerer 選択の最終結果」を返す(構造クラス名ではなく)。
|
||||
|
||||
- **ExitContract が優先**: `has_break=true` なら `Pattern2Break`、`has_continue=true` なら `Pattern4Continue`
|
||||
- **構造的特徴は notes へ**: 「if-else 構造がある」等の情報は `notes` フィールドに記録
|
||||
- **一致保証**: Router と Canonicalizer の pattern 選択が一致することを parity check で検証
|
||||
|
||||
**例**: `skip_whitespace` パターン
|
||||
- 構造: if-else 形式(Pattern3IfPhi に似ている)
|
||||
- ExitContract: `has_break=true`
|
||||
- **chosen**: `Pattern2Break`(ExitContract が決定)
|
||||
- **notes**: "if-else structure with break in else branch"(構造特徴を記録)
|
||||
|
||||
### 出力先マッピング
|
||||
|
||||
| 出力先 | 条件 | 用途 |
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Phase 137: Loop Canonicalizer(前処理 SSOT)
|
||||
|
||||
## Status
|
||||
- 状態: 🔶 進行中(Phase 2 完了)
|
||||
- 状態: 🔶 進行中(Phase 3 完了)
|
||||
|
||||
## Goal
|
||||
- ループ形の組み合わせ爆発を抑えるため、`AST → LoopSkeleton → (capability/routing)` の前処理を SSOT 化する。
|
||||
@ -24,8 +24,32 @@
|
||||
|
||||
## Phase 3(次): Pattern 検出(Skeleton→Decision の精密化)
|
||||
|
||||
- 目標: `skip_whitespace` を Skeleton から安定に識別し、`RoutingDecision.chosen` と `missing_caps` を期待通りに固定する。
|
||||
- 注意: routing/lowering の変更は dev-only の観測結果が固まってから。
|
||||
### Phase 3(完了): `skip_whitespace` の安定認識
|
||||
|
||||
- 実装: `src/mir/loop_canonicalizer/mod.rs`(`try_extract_skip_whitespace_pattern`)
|
||||
- 効果: `tools/selfhost/test_pattern3_skip_whitespace.hako` を `Pattern3IfPhi` として認識し、`missing_caps=[]` を固定できるようになった(dev-only 観測)。
|
||||
|
||||
注意:
|
||||
- routing/lowering の変更は “パリティ検証(Phase 4)” を挟んでから行う(既定挙動は不変)。
|
||||
|
||||
## Phase 4(完了): Router パリティ検証(dev-only / Fail-Fast)
|
||||
|
||||
- 目標: 既存 JoinIR ルータ(現行の Pattern 選択)と Canonicalizer の `RoutingDecision` が一致することを検証し、ズレた場合は理由付きで Fail-Fast(dev-only)。
|
||||
- 目的: いきなり routing を差し替えず、安全に "観測→一致→段階投入" の導線を作る。
|
||||
- 実装: `src/mir/builder/control_flow/joinir/routing.rs`(`verify_router_parity`)
|
||||
|
||||
## Phase 5(完了): Decision Policy SSOT 化
|
||||
|
||||
- 目標: `RoutingDecision.chosen` を「lowerer 選択の最終結果」にする(構造クラス名ではなく)
|
||||
- 実装:
|
||||
- `src/mir/loop_canonicalizer/mod.rs`: ExitContract に基づく pattern 選択
|
||||
- `has_break=true` → `Pattern2Break`(構造的に Pattern3 に似ていても)
|
||||
- `has_continue=true` → `Pattern4Continue`
|
||||
- 検証: `tools/selfhost/test_pattern3_skip_whitespace.hako` で parity OK(`HAKO_JOINIR_STRICT=1`)
|
||||
- 効果:
|
||||
- Router と Canonicalizer の一致性確保
|
||||
- ExitContract が pattern 選択の決定要因として明確化
|
||||
- 構造的特徴(if-else 等)は `notes` に記録(将来の Pattern 細分化に備える)
|
||||
|
||||
## SSOT
|
||||
|
||||
|
||||
@ -351,7 +351,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parity_check_mismatch_detected() {
|
||||
fn test_parity_check_skip_whitespace_match() {
|
||||
use crate::mir::loop_canonicalizer::canonicalize_loop_expr;
|
||||
use crate::mir::builder::control_flow::joinir::patterns::ast_feature_extractor as ast_features;
|
||||
|
||||
@ -373,18 +373,20 @@ mod tests {
|
||||
let features = ast_features::extract_features(condition, body, has_continue, has_break);
|
||||
let actual_pattern = crate::mir::loop_pattern_detection::classify(&features);
|
||||
|
||||
// Verify mismatch
|
||||
// Canonicalizer: Pattern3IfPhi (recognizes if-else structure)
|
||||
// Router: Pattern2Break (sees has_break)
|
||||
// Phase 137-5: Verify MATCH (ExitContract policy fix)
|
||||
// Both canonicalizer and router should agree on Pattern2Break
|
||||
// because has_break=true (ExitContract determines pattern choice)
|
||||
assert_eq!(
|
||||
canonical_pattern,
|
||||
crate::mir::loop_pattern_detection::LoopPatternKind::Pattern3IfPhi
|
||||
crate::mir::loop_pattern_detection::LoopPatternKind::Pattern2Break,
|
||||
"Canonicalizer should choose Pattern2Break for has_break=true"
|
||||
);
|
||||
assert_eq!(
|
||||
actual_pattern,
|
||||
crate::mir::loop_pattern_detection::LoopPatternKind::Pattern2Break
|
||||
crate::mir::loop_pattern_detection::LoopPatternKind::Pattern2Break,
|
||||
"Router should classify as Pattern2Break for has_break=true"
|
||||
);
|
||||
assert_ne!(canonical_pattern, actual_pattern, "Phase 137-4: This test verifies mismatch detection");
|
||||
assert_eq!(canonical_pattern, actual_pattern, "Phase 137-5: Canonicalizer and router should agree (SSOT policy)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -503,8 +503,10 @@ pub fn canonicalize_loop_expr(
|
||||
break_has_value: false,
|
||||
};
|
||||
|
||||
// Success! Return Pattern3WithIfPhi
|
||||
let decision = RoutingDecision::success(LoopPatternKind::Pattern3IfPhi);
|
||||
// Phase 137-5: Decision policy SSOT - ExitContract determines pattern choice
|
||||
// Since has_break=true, this should route to Pattern2Break (not Pattern3IfPhi)
|
||||
// Pattern3IfPhi is for if-else PHI *without* break statements
|
||||
let decision = RoutingDecision::success(LoopPatternKind::Pattern2Break);
|
||||
return Ok((skeleton, decision));
|
||||
}
|
||||
|
||||
@ -694,7 +696,8 @@ mod tests {
|
||||
|
||||
// Verify success
|
||||
assert!(decision.is_success());
|
||||
assert_eq!(decision.chosen, Some(LoopPatternKind::Pattern3IfPhi));
|
||||
// Phase 137-5: Pattern choice reflects ExitContract (has_break=true → Pattern2Break)
|
||||
assert_eq!(decision.chosen, Some(LoopPatternKind::Pattern2Break));
|
||||
assert_eq!(decision.missing_caps.len(), 0);
|
||||
|
||||
// Verify skeleton structure
|
||||
@ -804,7 +807,8 @@ mod tests {
|
||||
|
||||
// Verify success
|
||||
assert!(decision.is_success());
|
||||
assert_eq!(decision.chosen, Some(LoopPatternKind::Pattern3IfPhi));
|
||||
// Phase 137-5: Pattern choice reflects ExitContract (has_break=true → Pattern2Break)
|
||||
assert_eq!(decision.chosen, Some(LoopPatternKind::Pattern2Break));
|
||||
|
||||
// Verify skeleton has Body step
|
||||
assert_eq!(skeleton.steps.len(), 3); // HeaderCond + Body + Update
|
||||
|
||||
Reference in New Issue
Block a user