From 58f66e3fa2c0019a497cf5f829ff9be7ef9c412f Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Tue, 16 Dec 2025 06:17:03 +0900 Subject: [PATCH] =?UTF-8?q?feat(mir):=20Phase=20137-5=20-=20Decision=20Pol?= =?UTF-8?q?icy=20SSOT=E5=8C=96=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 目的 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 --- .../current/main/design/loop-canonicalizer.md | 18 ++++++++++- .../current/main/phases/phase-137/README.md | 30 +++++++++++++++++-- .../builder/control_flow/joinir/routing.rs | 16 +++++----- src/mir/loop_canonicalizer/mod.rs | 12 +++++--- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/docs/development/current/main/design/loop-canonicalizer.md b/docs/development/current/main/design/loop-canonicalizer.md index 401ba321..839e2c42 100644 --- a/docs/development/current/main/design/loop-canonicalizer.md +++ b/docs/development/current/main/design/loop-canonicalizer.md @@ -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, /// 不足している 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"(構造特徴を記録) + ### 出力先マッピング | 出力先 | 条件 | 用途 | diff --git a/docs/development/current/main/phases/phase-137/README.md b/docs/development/current/main/phases/phase-137/README.md index fe22ef08..02074ff0 100644 --- a/docs/development/current/main/phases/phase-137/README.md +++ b/docs/development/current/main/phases/phase-137/README.md @@ -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 diff --git a/src/mir/builder/control_flow/joinir/routing.rs b/src/mir/builder/control_flow/joinir/routing.rs index 05cec24a..0f3419c1 100644 --- a/src/mir/builder/control_flow/joinir/routing.rs +++ b/src/mir/builder/control_flow/joinir/routing.rs @@ -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] diff --git a/src/mir/loop_canonicalizer/mod.rs b/src/mir/loop_canonicalizer/mod.rs index a4ca500a..45d28d9c 100644 --- a/src/mir/loop_canonicalizer/mod.rs +++ b/src/mir/loop_canonicalizer/mod.rs @@ -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