phase29ab(p3): fix PromoteDecision contract and add negative smokes

This commit is contained in:
2025-12-28 10:57:55 +09:00
parent 280a5a8187
commit 84e1cd7c7b
8 changed files with 184 additions and 30 deletions

View File

@ -0,0 +1,31 @@
# Pattern2 Promotion API Contract (SSOT)
This directory is the single entry point for Pattern2 LoopBodyLocal promotion.
All callers must go through `try_promote()` and must honor the decision contract
below.
## PromoteDecision Contract
- `Promoted`
- All contract checks satisfied.
- Pattern2 continues in the JoinIR path.
- `NotApplicable`
- Promotion not applicable (no LoopBodyLocal in conditions).
- The caller continues Pattern2 with unchanged inputs.
- Example causes:
- No LoopBodyLocal variables in the break condition.
- `Freeze`
- Contract violation or unimplemented behavior.
- Fail-fast with a clear error tag, no fallback.
- Example causes:
- Read-only contract broken (assignment detected).
- Missing required metadata (loop scope/break guard).
## Reject Mapping Rules (PolicyDecision::Reject -> PromoteDecision)
The mapping lives in `promote_runner.rs` and must remain stable:
- Any `PolicyDecision::Reject` becomes `Freeze`
- Promotion not applicable (no LoopBodyLocal vars) uses `NotApplicable`

View File

@ -16,11 +16,11 @@ pub(crate) enum PromoteDecision {
/// Promotion succeeded - Pattern2 can proceed
Promoted(PromoteStepResult),
/// Pattern2 not applicable (e.g., reassigned LoopBodyLocal, no promotable pattern)
/// → Router should try next path (legacy binding, etc.)
NotApplicable,
/// Promotion not applicable (e.g., no LoopBodyLocal in conditions)
/// → Continue Pattern2 with unchanged inputs
NotApplicable(PromoteStepResult),
/// Pattern2 should handle this but implementation is missing
/// Contract violation or unimplemented behavior
/// → Fail-Fast with error message
Freeze(String),
}

View File

@ -42,7 +42,9 @@ pub(in crate::mir::builder) fn try_promote(
.map(|v| v.name.clone())
.collect();
if cond_scope.has_loop_body_local() {
let has_body_locals_in_conditions = cond_scope.has_loop_body_local();
if has_body_locals_in_conditions {
// Policy-controlled: some families must not run promotion/slot heuristics here.
// Example: balanced depth-scan uses derived vars and doesn't have a break-guard node.
if matches!(
@ -106,25 +108,11 @@ pub(in crate::mir::builder) fn try_promote(
}
PolicyDecision::Reject(reason) => {
// Phase 263 P0.1: Reject を PromoteDecision で二分化(型安全)
if reason.contains("not_readonly")
|| reason.contains("No promotable pattern detected")
{
// 対象外: Pattern2 で処理できない形 → NotApplicable で後続経路へ
#[cfg(debug_assertions)]
{
eprintln!(
"[pattern2/api/promote] Pattern2 対象外LoopBodyLocal {:?}: {}. 後続経路へfallback.",
cond_body_local_vars, reason
);
}
return Ok(PromoteDecision::NotApplicable);
} else {
// 対象だが未対応freeze級: 実装バグ or 将来実装予定 → Freeze で Fail-Fast
return Ok(PromoteDecision::Freeze(format!(
"[pattern2/api/promote] Pattern2 未対応エラーLoopBodyLocal {:?}: {}",
cond_body_local_vars, reason
)));
}
// 対象だが未対応freeze級: 実装バグ or 将来実装予定 → Freeze で Fail-Fast
return Ok(PromoteDecision::Freeze(format!(
"[pattern2/api/promote] Pattern2 未対応エラーLoopBodyLocal {:?}: {}",
cond_body_local_vars, reason
)));
}
PolicyDecision::None => {}
}
@ -176,5 +164,9 @@ pub(in crate::mir::builder) fn try_promote(
}
}
Ok(PromoteDecision::Promoted(PromoteStepResult { inputs }))
if has_body_locals_in_conditions {
Ok(PromoteDecision::Promoted(PromoteStepResult { inputs }))
} else {
Ok(PromoteDecision::NotApplicable(PromoteStepResult { inputs }))
}
}

View File

@ -67,11 +67,12 @@ impl Pattern2LoweringOrchestrator {
PromoteDecision::Promoted(result) => {
result.inputs
}
PromoteDecision::NotApplicable => {
// 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 (not applicable), allowing fallback");
return Ok(None);
PromoteDecision::NotApplicable(result) => {
super::super::trace::trace().debug(
"pattern2",
"Pattern2 promotion not applicable, continuing without promotion",
);
result.inputs
}
PromoteDecision::Freeze(reason) => {
// Pattern2 should handle this but implementation is missing → Fail-Fast