phase29ab(p3): fix PromoteDecision contract and add negative smokes
This commit is contained in:
29
apps/tests/phase29ab_pattern2_seg_freeze_min.hako
Normal file
29
apps/tests/phase29ab_pattern2_seg_freeze_min.hako
Normal file
@ -0,0 +1,29 @@
|
||||
// Phase 29ab P3: Pattern2 LoopBodyLocal seg Freeze (read-only violation)
|
||||
//
|
||||
// Goal:
|
||||
// - break condition uses LoopBodyLocal seg
|
||||
// - seg is reassigned in the loop body (read-only contract violation)
|
||||
// - Pattern2 promotion must Freeze (fail-fast)
|
||||
//
|
||||
// Expected: JoinIR freeze error (non-zero exit)
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local s = "ab "
|
||||
local i = 0
|
||||
|
||||
loop(i < s.length()) {
|
||||
local seg = s.substring(i, i + 1)
|
||||
|
||||
if seg == " " || seg == "\t" {
|
||||
break
|
||||
}
|
||||
|
||||
seg = "x"
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(i)
|
||||
return i
|
||||
}
|
||||
}
|
||||
27
apps/tests/phase29ab_pattern2_seg_notapplicable_min.hako
Normal file
27
apps/tests/phase29ab_pattern2_seg_notapplicable_min.hako
Normal file
@ -0,0 +1,27 @@
|
||||
// Phase 29ab P3: Pattern2 LoopBodyLocal seg NotApplicable (shape mismatch)
|
||||
//
|
||||
// Goal:
|
||||
// - break condition uses no LoopBodyLocal vars
|
||||
// - Pattern2 promotion should be NotApplicable and continue safely
|
||||
//
|
||||
// Expected: prints "2" and returns 2
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local s = "ab "
|
||||
local i = 0
|
||||
|
||||
loop(i < s.length()) {
|
||||
local seg = s.substring(i, i + 1)
|
||||
|
||||
if i >= 2 {
|
||||
break
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(i)
|
||||
return i
|
||||
}
|
||||
}
|
||||
@ -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`
|
||||
@ -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),
|
||||
}
|
||||
|
||||
@ -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 }))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# Phase 29ab P3: Pattern2 LoopBodyLocal seg Freeze (VM backend)
|
||||
# Tests: read-only violation must fail-fast with joinir freeze tag
|
||||
|
||||
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||
export SMOKES_USE_PYVM=0
|
||||
require_env || exit 2
|
||||
|
||||
INPUT="$NYASH_ROOT/apps/tests/phase29ab_pattern2_seg_freeze_min.hako"
|
||||
RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10}
|
||||
|
||||
set +e
|
||||
OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env NYASH_DISABLE_PLUGINS=1 HAKO_JOINIR_STRICT=1 "$NYASH_BIN" "$INPUT" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$EXIT_CODE" -eq 124 ]; then
|
||||
test_fail "phase29ab_pattern2_seg_freeze_min_vm: hakorune timed out (>${RUN_TIMEOUT_SECS}s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||
echo "[FAIL] Expected JoinIR freeze error, got exit 0"
|
||||
echo "[INFO] Output:"
|
||||
echo "$OUTPUT" | tail -n 40 || true
|
||||
test_fail "phase29ab_pattern2_seg_freeze_min_vm: Unexpected success"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if echo "$OUTPUT" | grep -q "\[joinir/freeze\]"; then
|
||||
test_pass "phase29ab_pattern2_seg_freeze_min_vm: joinir freeze detected"
|
||||
exit 0
|
||||
else
|
||||
echo "[FAIL] Expected joinir freeze tag in output"
|
||||
echo "[INFO] Exit code: $EXIT_CODE"
|
||||
echo "[INFO] Output:"
|
||||
echo "$OUTPUT" | tail -n 60 || true
|
||||
test_fail "phase29ab_pattern2_seg_freeze_min_vm: Missing joinir freeze tag"
|
||||
exit 1
|
||||
fi
|
||||
@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# Phase 29ab P3: Pattern2 promotion NotApplicable (VM backend)
|
||||
# Tests: no LoopBodyLocal in condition -> continue, output should be 2
|
||||
|
||||
source "$(dirname "$0")/../../../lib/test_runner.sh"
|
||||
export SMOKES_USE_PYVM=0
|
||||
require_env || exit 2
|
||||
|
||||
INPUT="$NYASH_ROOT/apps/tests/phase29ab_pattern2_seg_notapplicable_min.hako"
|
||||
RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10}
|
||||
|
||||
set +e
|
||||
OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env NYASH_DISABLE_PLUGINS=1 HAKO_JOINIR_STRICT=1 "$NYASH_BIN" "$INPUT" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$EXIT_CODE" -eq 124 ]; then
|
||||
test_fail "phase29ab_pattern2_seg_notapplicable_min_vm: hakorune timed out (>${RUN_TIMEOUT_SECS}s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_CLEAN=$(echo "$OUTPUT" | filter_noise)
|
||||
|
||||
if echo "$OUTPUT_CLEAN" | grep -q "^2$" || echo "$OUTPUT" | grep -q "^RC: 2$"; then
|
||||
test_pass "phase29ab_pattern2_seg_notapplicable_min_vm: promotion not applicable (output: 2)"
|
||||
exit 0
|
||||
else
|
||||
echo "[FAIL] Unexpected output (expected: 2)"
|
||||
echo "[INFO] Exit code: $EXIT_CODE"
|
||||
echo "[INFO] Output (clean):"
|
||||
echo "$OUTPUT_CLEAN" | tail -n 20 || true
|
||||
test_fail "phase29ab_pattern2_seg_notapplicable_min_vm: Unexpected output"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user