diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 4ea39ea4..27bf3ec1 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -45,6 +45,11 @@ pub(in crate::mir::builder) fn build_plan_from_facts_ctx( // unreachable in normal execution today. We still implement the SSOT // boundary here so that future Facts work cannot drift. + match facts.facts.skeleton.kind { + SkeletonKind::Loop => {} + _ => return Ok(None), + } + let allow_pattern1 = match ctx.pattern_kind { Some(LoopPatternKind::Pattern1SimpleWhile) | None => true, Some(_) => false, @@ -451,6 +456,37 @@ mod tests { } } + #[test] + fn planner_gates_non_loop_skeletons() { + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::If2, + }, + features: LoopFeatureFacts::default(), + scan_with_init: Some(ScanWithInitFacts { + loop_var: "i".to_string(), + haystack: "s".to_string(), + needle: "ch".to_string(), + step_lit: 1, + }), + split_scan: None, + pattern1_simplewhile: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: None, + pattern8_bool_predicate_scan: None, + pattern9_accum_const_loop: None, + pattern2_break: None, + pattern2_loopbodylocal: None, + }; + let canonical = canonicalize_loop_facts(facts); + let plan = build_plan_from_facts_ctx(&PlannerContext::default_for_legacy(), canonical) + .expect("Ok"); + assert!(plan.is_none()); + } + #[test] fn planner_builds_pattern1_simplewhile_plan_from_facts() { let loop_condition = ASTNode::BinaryOp {