fix(normalization): Tighten loop(true) gate to prevent loop(i<n) mismatch

Problem:
- Phase 97 LLVM EXE tests (next_non_ws, json_loader_escape) were failing
- NormalizationPlan accepted loop(i < n) but couldn't lower it
- Execute stage returned hard error instead of fallback

Root Cause:
- Plan stage only checked `ASTNode::Loop`, not condition
- loop(i < n) got Plan → Execute tried to normalize → error
- Should: loop(i < n) returns Ok(None) → fallback to Pattern2

Fix:
- Tighten gate in plan_box.rs (lines 50-71)
- Only accept loop(true) - literal Bool true
- loop(i < n) now returns Ok(None) → clean fallback

Added:
- Test: test_plan_block_suffix_no_match_loop_not_true()
- Verifies loop(i < n) returns None (not Plan)

Verification:
- Phase 97 next_non_ws LLVM EXE: PASS
- Phase 97 json_loader_escape LLVM EXE: PASS
- Phase 131/135/136/137 regression: all PASS

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-19 00:15:51 +09:00
parent ff09adebe0
commit 1264b3593d

View File

@ -12,7 +12,7 @@
//! - Returns Ok(None): Not a normalized pattern, use legacy fallback //! - Returns Ok(None): Not a normalized pattern, use legacy fallback
//! - Returns Err(_): Internal error (malformed AST) //! - Returns Err(_): Internal error (malformed AST)
use crate::ast::ASTNode; use crate::ast::{ASTNode, LiteralValue};
use crate::mir::builder::MirBuilder; use crate::mir::builder::MirBuilder;
use super::plan::{NormalizationPlan, PlanKind}; use super::plan::{NormalizationPlan, PlanKind};
@ -47,9 +47,26 @@ impl NormalizationPlanBox {
return Ok(None); return Ok(None);
} }
// First statement must be a loop // First statement must be a loop with condition `true`
let is_loop = matches!(&remaining[0], ASTNode::Loop { .. }); // Phase 131-136 ONLY support loop(true), not loop(i < n) etc.
if !is_loop { let is_loop_true = match &remaining[0] {
ASTNode::Loop { condition, .. } => {
// Only accept loop(true) - literal Bool true
matches!(
condition.as_ref(),
ASTNode::Literal { value: LiteralValue::Bool(true), .. }
)
}
_ => false,
};
if !is_loop_true {
if debug {
trace.routing(
"normalization/plan",
func_name,
"First statement is not loop(true), returning None (not a normalized pattern)",
);
}
return Ok(None); return Ok(None);
} }
@ -363,4 +380,50 @@ mod tests {
assert_eq!(plan.consumed, 2); // loop + return assert_eq!(plan.consumed, 2); // loop + return
assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 0 }); assert_eq!(plan.kind, PlanKind::LoopWithPost { post_assign_count: 0 });
} }
#[test]
fn test_plan_block_suffix_no_match_loop_not_true() {
use crate::mir::builder::MirBuilder;
// Phase 97 regression test: loop(i < n) should NOT match
// Normalized shadow only supports loop(true)
let loop_conditional = ASTNode::Loop {
condition: Box::new(ASTNode::BinaryOp {
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: make_span(),
}),
operator: BinaryOperator::Less,
right: Box::new(ASTNode::Variable {
name: "n".to_string(),
span: make_span(),
}),
span: make_span(),
}),
body: vec![
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: make_span(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: make_span(),
}),
span: make_span(),
},
ASTNode::Break { span: make_span() },
],
span: make_span(),
};
let remaining = vec![loop_conditional, make_return("x")];
let builder = MirBuilder::new();
let plan = NormalizationPlanBox::plan_block_suffix(&builder, &remaining, "test", false)
.expect("Should not error");
// loop(i < n) is NOT supported by Normalized - should return None
assert!(plan.is_none(), "loop(i < n) should NOT match Normalized pattern");
}
} }