fix(pattern2): abort entire Pattern2 on unpromoted LoopBodyLocal instead of partial execution

Phase 263 P0: Pattern2 で処理できない LoopBodyLocal を検出したら Pattern2 全体を早期終了

Changes:
- promote_step_box.rs: 戻り値を Result<Option<_>, String> に変更
  - Reject を二分化: 対象外(not_readonly等)→ Ok(None)、対象だが未対応 → Err
- pattern2_lowering_orchestrator.rs: Ok(None) 検出で早期 return
- apps/tests/phase263_p0_pattern2_seg_min.hako: test simplification

後続経路(legacy binding等)へ fallback させる(detection→extract→lower SSOT 維持)
Fail-Fast 原則: 対象外は Ok(None) で後続経路へ、対象だが未対応は Err で即座に失敗

Fixes: core_direct_array_oob_set_rc_vm smoke test FAIL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 10:23:38 +09:00
parent a15821cb17
commit 93022e7e10
3 changed files with 49 additions and 33 deletions

View File

@ -7,29 +7,19 @@
static box Main {
main() {
local result = parse_segments()
local i = 0
local seg = ""
loop(i < 5) {
seg = "segment" // ← loop body で代入ReadOnlySlot 契約違反)
if seg == "end" {
break
}
i = i + 1
}
return 0
}
}
// Pattern2 が検出するループ構造
method parse_segments() {
local i = 0
local seg = ""
loop(i < 5) {
seg = get_segment(i) // ← loop body で代入ReadOnlySlot 契約違反)
if seg == "end" {
break
}
i = i + 1
}
return i
}
method get_segment(idx) {
return "seg"
}

View File

@ -63,7 +63,15 @@ impl Pattern2LoweringOrchestrator {
let inputs = ApplyPolicyStepBox::apply(condition, body, facts)?;
let promoted = PromoteStepBox::run(builder, condition, body, inputs, debug, verbose)?;
let mut inputs = promoted.inputs;
let mut inputs = match promoted {
Some(result) => result.inputs,
None => {
// Phase 263 P0: Pattern2 cannot handle this loop (e.g., reassigned LoopBodyLocal)
// Return Ok(None) to allow router to try next path (legacy binding)
super::super::trace::trace().debug("pattern2", "Pattern2 aborted (promotion failed), allowing fallback");
return Ok(None);
}
};
// Phase 256.5: Wire current_static_box_name from builder context or function name
inputs.current_static_box_name = current_box_name_for_lowering(builder);

View File

@ -26,9 +26,11 @@ impl PromoteStepBox {
mut inputs: Pattern2Inputs,
debug: bool,
verbose: bool,
) -> Result<PromoteStepResult, String> {
Self::promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)?;
Ok(PromoteStepResult { inputs })
) -> Result<Option<PromoteStepResult>, String> {
match Self::promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)? {
Some(()) => Ok(Some(PromoteStepResult { inputs })),
None => Ok(None), // Pattern2 cannot handle this loop
}
}
pub(in crate::mir::builder) fn promote_and_prepare_carriers(
@ -38,7 +40,7 @@ impl PromoteStepBox {
inputs: &mut Pattern2Inputs,
debug: bool,
verbose: bool,
) -> Result<(), String> {
) -> Result<Option<()>, String> {
use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer;
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
@ -119,10 +121,26 @@ impl PromoteStepBox {
inputs.read_only_body_local_slot = Some(slot);
}
PolicyDecision::Reject(reason) => {
return Err(error_messages::format_error_pattern2_promotion_failed(
&cond_body_local_vars,
&reason,
));
// Phase 263 P0 + Step 2.5: Reject を二分化
if reason.contains("not_readonly")
|| reason.contains("No promotable pattern detected")
{
// 対象外: Pattern2 で処理できない形 → Ok(None) で後続経路へ
#[cfg(debug_assertions)]
{
eprintln!(
"[pattern2/promote_step] Pattern2 対象外LoopBodyLocal {:?}: {}. 後続経路へfallback.",
cond_body_local_vars, reason
);
}
return Ok(None); // Pattern2 全体を中止
} else {
// 対象だが未対応freeze級: 実装バグ or 将来実装予定 → Err で Fail-Fast
return Err(format!(
"[pattern2/promote_step] Pattern2 未対応エラーLoopBodyLocal {:?}: {}",
cond_body_local_vars, reason
));
}
}
PolicyDecision::None => {}
}
@ -174,7 +192,7 @@ impl PromoteStepBox {
}
}
Ok(())
Ok(Some(()))
}
}