From 5ba68da9a0f2a4733a272ebceaf172c7e25b4119 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 08:04:00 +0900 Subject: [PATCH] phase29ai(p5): route JoinIR plan extraction via single_planner --- .../joinir/patterns/pattern2_with_break.rs | 4 +- .../control_flow/joinir/patterns/router.rs | 168 ++---------------- .../builder/control_flow/joinir/routing.rs | 5 +- src/mir/builder/control_flow/plan/mod.rs | 2 + .../plan/single_planner/legacy_rules/mod.rs | 14 ++ .../single_planner/legacy_rules/pattern1.rs | 9 + .../single_planner/legacy_rules/pattern2.rs | 9 + .../single_planner/legacy_rules/pattern3.rs | 9 + .../single_planner/legacy_rules/pattern4.rs | 9 + .../single_planner/legacy_rules/pattern5.rs | 9 + .../single_planner/legacy_rules/pattern6.rs | 9 + .../single_planner/legacy_rules/pattern7.rs | 9 + .../single_planner/legacy_rules/pattern8.rs | 9 + .../single_planner/legacy_rules/pattern9.rs | 9 + .../control_flow/plan/single_planner/mod.rs | 18 ++ .../control_flow/plan/single_planner/rules.rs | 111 ++++++++++++ 16 files changed, 243 insertions(+), 160 deletions(-) create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/mod.rs create mode 100644 src/mir/builder/control_flow/plan/single_planner/rules.rs diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 85aaa9ac..50cd7457 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -407,7 +407,7 @@ mod tests { }, ]; - let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true); + let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true, false); let builder = MirBuilder::new(); assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern2Break); @@ -452,7 +452,7 @@ mod tests { }, ]; - let ctx = LoopPatternContext::new(&condition, &body, "_atoi", true); + let ctx = LoopPatternContext::new(&condition, &body, "_atoi", true, false); let builder = MirBuilder::new(); // Verify pattern classification diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index 966df22b..a4f71c02 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -28,6 +28,7 @@ use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind}; use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer; use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer; use crate::mir::builder::control_flow::plan::verifier::PlanVerifier; +use crate::mir::builder::control_flow::plan::single_planner; /// AST Feature Extractor (declared in mod.rs as pub module, import from parent) use super::ast_feature_extractor as ast_features; @@ -49,6 +50,9 @@ pub(crate) struct LoopPatternContext<'a> { /// Debug logging enabled pub debug: bool, + /// In static box context? (affects Pattern8 routing) + pub in_static_box: bool, + /// Has continue statement(s) in body? (Phase 194+) #[allow(dead_code)] pub has_continue: bool, @@ -93,6 +97,7 @@ impl<'a> LoopPatternContext<'a> { body: &'a [ASTNode], func_name: &'a str, debug: bool, + in_static_box: bool, ) -> Self { // Use AST Feature Extractor for break/continue detection let has_continue = ast_features::detect_continue_in_body(body); @@ -110,6 +115,7 @@ impl<'a> LoopPatternContext<'a> { body, func_name, debug, + in_static_box, has_continue, has_break, features, @@ -126,9 +132,10 @@ impl<'a> LoopPatternContext<'a> { body: &'a [ASTNode], func_name: &'a str, debug: bool, + in_static_box: bool, fn_body: &'a [ASTNode], ) -> Self { - let mut ctx = Self::new(condition, body, func_name, debug); + let mut ctx = Self::new(condition, body, func_name, debug, in_static_box); ctx.fn_body = Some(fn_body); ctx } @@ -159,157 +166,7 @@ fn lower_via_plan( PlanLowerer::lower(builder, core_plan, ctx) } -/// Phase 287: Plan extractor function type for routing table -/// -/// Extractors have two signatures: -/// - Simple: (condition, body) - Pattern1/4/9 -/// - WithFnBody: (condition, body, fn_body) - Pattern6/7 -type SimplePlanExtractor = fn(&ASTNode, &[ASTNode]) -> Result, String>; - -/// Phase 287: Plan extractor table entry -struct PlanExtractorEntry { - /// Pattern name for debug logging - name: &'static str, - - /// Extractor function - extractor: PlanExtractorVariant, -} - -/// Phase 287: Extractor function variant (handles different signatures) -enum PlanExtractorVariant { - /// Simple extractor: (condition, body) - Simple(SimplePlanExtractor), - - /// Extractor with fn_body: (condition, body, fn_body) - Pattern6 - WithFnBody(fn(&ASTNode, &[ASTNode], Option<&[ASTNode]>) -> Result, String>), - - /// Extractor with post_loop_code: (condition, body, post_loop_code) - Pattern7 - /// - /// NOTE (Phase 286): Currently always called with &[] for post_loop_code. - /// This variant is kept for future extension (post-loop segment analysis). - /// The _post_loop_code parameter in Pattern7 extractor is intentionally unused. - WithPostLoop(fn(&ASTNode, &[ASTNode], &[ASTNode]) -> Result, String>), -} - -/// Phase 287: Plan extractor routing table (SSOT) -/// -/// Order is important: more specific patterns first -/// - Pattern6/7: Need fn_body for capture analysis -/// - Pattern8: Bool predicate scan (early-exit return, more specific) -/// - Pattern3: If-phi merge (carrier with conditional update, more specific than Pattern4) -/// - Pattern4: Continue loop (more specific than Pattern1) -/// - Pattern9: Accum const loop (2 carriers, more specific than Pattern1) -/// - Pattern1: Simple while (fallback) -static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[ - PlanExtractorEntry { - name: "Pattern6_ScanWithInit (Phase 273)", - extractor: PlanExtractorVariant::WithFnBody(super::pattern6_scan_with_init::extract_scan_with_init_plan), - }, - PlanExtractorEntry { - name: "Pattern7_SplitScan (Phase 273)", - extractor: PlanExtractorVariant::WithPostLoop(super::pattern7_split_scan::extract_split_scan_plan), - }, - PlanExtractorEntry { - name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern5::extract_pattern5_plan), - }, - PlanExtractorEntry { - name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan), - }, - PlanExtractorEntry { - name: "Pattern3_IfPhi (Phase 286 P2.6)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern3::extract_pattern3_plan), - }, - PlanExtractorEntry { - name: "Pattern4_Continue (Phase 286 P2)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern4::extract_pattern4_plan), - }, - PlanExtractorEntry { - name: "Pattern9_AccumConstLoop (Phase 286 P2.3)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern9::extract_pattern9_plan), - }, - PlanExtractorEntry { - name: "Pattern2_Break (Phase 286 P3.1)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern2::extract_pattern2_plan), - }, - PlanExtractorEntry { - name: "Pattern1_SimpleWhile (Phase 286 P2.1)", - extractor: PlanExtractorVariant::Simple(super::extractors::pattern1::extract_pattern1_plan), - }, -]; - -/// Phase 287: Try all plan extractors in priority order -/// -/// Returns Ok(Some(value_id)) if any extractor succeeds -/// Returns Ok(None) if no extractor matches -/// Returns Err if extraction or lowering fails -fn try_plan_extractors( - builder: &mut MirBuilder, - ctx: &LoopPatternContext, -) -> Result, String> { - use super::super::trace; - - for entry in PLAN_EXTRACTORS { - // Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast) - // Pattern1 should only match Pattern1SimpleWhile pattern_kind - // This prevents Pattern1 from incorrectly matching Pattern3 fixtures - if entry.name.contains("Pattern1") { - use crate::mir::loop_pattern_detection::LoopPatternKind; - if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile { - continue; - } - } - - // Try extraction based on variant - let plan_opt = match &entry.extractor { - PlanExtractorVariant::Simple(extractor) => { - extractor(ctx.condition, ctx.body)? - } - PlanExtractorVariant::WithFnBody(extractor) => { - // Pattern6: needs fn_body for capture analysis - extractor(ctx.condition, ctx.body, ctx.fn_body)? - } - PlanExtractorVariant::WithPostLoop(extractor) => { - // Pattern7: uses empty slice for post_loop_code (3rd param) - extractor(ctx.condition, ctx.body, &[])? - } - }; - - // If extraction succeeded, check if we can lower it - if let Some(domain_plan) = plan_opt { - // Phase 286 P3: Pattern8 static box filtering - // Plan extractors are pure (no builder access), so we filter here - // Static box loops with `me.method()` should be handled by ReceiverNormalizeBox, not Pattern8 - if entry.name.contains("Pattern8") && builder.comp_ctx.current_static_box.is_some() { - if ctx.debug { - trace::trace().debug( - "route/plan", - &format!("{} extracted but rejected: static box context (fallback to legacy)", entry.name), - ); - } - // Skip this pattern, try next extractor - continue; - } - - let log_msg = format!("route=plan strategy=extract pattern={}", entry.name); - trace::trace().pattern("route", &log_msg, true); - - // Phase 286 P2.6.1: Fail-Fast 統一 - extract 成功 → normalize/lower 失敗は即 Err - // Pattern3 stub fallback 撤去(normalizer 実装済み) - return lower_via_plan(builder, domain_plan, ctx); - } else { - // Extraction returned None - try next extractor - if ctx.debug { - let debug_msg = format!("{} extraction returned None, trying next pattern", entry.name); - trace::trace().debug("route", &debug_msg); - } - } - } - - // No extractor matched - Ok(None) -} +// Phase 29ai P5: Plan extractor routing moved to `plan::single_planner`. /// Phase 272 P0.2 Refactoring: can_lower() strategy classification /// @@ -473,10 +330,9 @@ pub(crate) fn route_loop_pattern( ) -> Result, String> { use super::super::trace; - // Phase 287: Try all Plan extractors in priority order (Pattern6/7/4/9/1) - // This replaces ~100 lines of repetitive extraction blocks - if let Some(value_id) = try_plan_extractors(builder, ctx)? { - return Ok(Some(value_id)); + // Phase 29ai P5: Single entrypoint for plan extraction (router has no rule table). + if let Some(domain_plan) = single_planner::try_build_domain_plan(ctx)? { + return lower_via_plan(builder, domain_plan, ctx); } // Phase 183: Route based on pre-classified pattern kind diff --git a/src/mir/builder/control_flow/joinir/routing.rs b/src/mir/builder/control_flow/joinir/routing.rs index d14dda37..3b4ac465 100644 --- a/src/mir/builder/control_flow/joinir/routing.rs +++ b/src/mir/builder/control_flow/joinir/routing.rs @@ -452,15 +452,16 @@ impl MirBuilder { } ), ); + let in_static_box = self.comp_ctx.current_static_box.is_some(); let ctx = if let Some(ref fn_body) = fn_body_clone { trace::trace().routing( "router", func_name, &format!("Creating ctx with fn_body ({} nodes)", fn_body.len()), ); - LoopPatternContext::with_fn_body(condition, body, &func_name, debug, fn_body) + LoopPatternContext::with_fn_body(condition, body, &func_name, debug, in_static_box, fn_body) } else { - LoopPatternContext::new(condition, body, &func_name, debug) + LoopPatternContext::new(condition, body, &func_name, debug, in_static_box) }; // Phase 137-4: Router parity verification (after ctx is created) diff --git a/src/mir/builder/control_flow/plan/mod.rs b/src/mir/builder/control_flow/plan/mod.rs index 01a99631..3c6763fd 100644 --- a/src/mir/builder/control_flow/plan/mod.rs +++ b/src/mir/builder/control_flow/plan/mod.rs @@ -35,6 +35,8 @@ pub(in crate::mir::builder) mod facts; pub(in crate::mir::builder) mod normalize; pub(in crate::mir::builder) mod planner; pub(in crate::mir::builder) mod emit; +// Phase 29ai P5: JoinIR router → single plan extraction entrypoint +pub(in crate::mir::builder) mod single_planner; // ============================================================================ // DomainPlan (Pattern固有) diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs new file mode 100644 index 00000000..d71fb6ac --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/mod.rs @@ -0,0 +1,14 @@ +//! Phase 29ai P5: Legacy rule wrappers (thin bridge) +//! +//! These wrappers must preserve the legacy extractor behavior and error strings. + +pub(in crate::mir::builder) mod pattern1; +pub(in crate::mir::builder) mod pattern2; +pub(in crate::mir::builder) mod pattern3; +pub(in crate::mir::builder) mod pattern4; +pub(in crate::mir::builder) mod pattern5; +pub(in crate::mir::builder) mod pattern6; +pub(in crate::mir::builder) mod pattern7; +pub(in crate::mir::builder) mod pattern8; +pub(in crate::mir::builder) mod pattern9; + diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs new file mode 100644 index 00000000..4dfd0002 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern1.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern1::extract_pattern1_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern1_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs new file mode 100644 index 00000000..e8ecf382 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern2.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern2::extract_pattern2_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern2_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs new file mode 100644 index 00000000..f46684b9 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern3.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern3::extract_pattern3_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern3_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs new file mode 100644 index 00000000..21e58e5e --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern4.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern4::extract_pattern4_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern4_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs new file mode 100644 index 00000000..6c428281 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern5.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern5::extract_pattern5_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern5_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs new file mode 100644 index 00000000..c71c7e2a --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern6.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::pattern6_scan_with_init::extract_scan_with_init_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_scan_with_init_plan(ctx.condition, ctx.body, ctx.fn_body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs new file mode 100644 index 00000000..1b8801a4 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern7.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::pattern7_split_scan::extract_split_scan_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_split_scan_plan(ctx.condition, ctx.body, &[]) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs new file mode 100644 index 00000000..7ab14e06 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern8.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern8::extract_pattern8_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern8_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs new file mode 100644 index 00000000..68043a57 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/legacy_rules/pattern9.rs @@ -0,0 +1,9 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::DomainPlan; +use crate::mir::builder::control_flow::joinir::patterns::extractors::pattern9::extract_pattern9_plan; + +pub(in crate::mir::builder) fn extract( + ctx: &LoopPatternContext, +) -> Result, String> { + extract_pattern9_plan(ctx.condition, ctx.body) +} diff --git a/src/mir/builder/control_flow/plan/single_planner/mod.rs b/src/mir/builder/control_flow/plan/single_planner/mod.rs new file mode 100644 index 00000000..cc7ebc52 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/mod.rs @@ -0,0 +1,18 @@ +//! Phase 29ai P5: Single-planner bridge (router → 1 entrypoint) +//! +//! SSOT entrypoint for DomainPlan extraction. Router should call only this. +//! Contract: keep `Result<_, String>` to preserve existing behavior/messages. + +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; + +use super::DomainPlan; + +pub(in crate::mir::builder) mod legacy_rules; +mod rules; + +pub(in crate::mir::builder) fn try_build_domain_plan( + ctx: &LoopPatternContext, +) -> Result, String> { + rules::try_build_domain_plan(ctx) +} + diff --git a/src/mir/builder/control_flow/plan/single_planner/rules.rs b/src/mir/builder/control_flow/plan/single_planner/rules.rs new file mode 100644 index 00000000..56e65d68 --- /dev/null +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -0,0 +1,111 @@ +//! Phase 29ai P5: Rule list (order SSOT) + guards +//! +//! IMPORTANT: Keep rule order identical to the legacy `PLAN_EXTRACTORS` table +//! (observability/behavior must not change). + +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::loop_pattern_detection::LoopPatternKind; + +use super::legacy_rules; +use crate::mir::builder::control_flow::plan::DomainPlan; + +pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result, String> { + use crate::mir::builder::control_flow::joinir::trace; + + // NOTE: Names must match the previous PLAN_EXTRACTORS entries exactly. + // Order must match exactly. + let rules: &[RuleEntry] = &[ + RuleEntry { + name: "Pattern6_ScanWithInit (Phase 273)", + kind: RuleKind::Pattern6, + }, + RuleEntry { + name: "Pattern7_SplitScan (Phase 273)", + kind: RuleKind::Pattern7, + }, + RuleEntry { + name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)", + kind: RuleKind::Simple(legacy_rules::pattern5::extract), + }, + RuleEntry { + name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)", + kind: RuleKind::Simple(legacy_rules::pattern8::extract), + }, + RuleEntry { + name: "Pattern3_IfPhi (Phase 286 P2.6)", + kind: RuleKind::Simple(legacy_rules::pattern3::extract), + }, + RuleEntry { + name: "Pattern4_Continue (Phase 286 P2)", + kind: RuleKind::Simple(legacy_rules::pattern4::extract), + }, + RuleEntry { + name: "Pattern9_AccumConstLoop (Phase 286 P2.3)", + kind: RuleKind::Simple(legacy_rules::pattern9::extract), + }, + RuleEntry { + name: "Pattern2_Break (Phase 286 P3.1)", + kind: RuleKind::Simple(legacy_rules::pattern2::extract), + }, + RuleEntry { + name: "Pattern1_SimpleWhile (Phase 286 P2.1)", + kind: RuleKind::Simple(legacy_rules::pattern1::extract), + }, + ]; + + for entry in rules { + // Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast) + // Pattern1 should only match Pattern1SimpleWhile pattern_kind + // This prevents Pattern1 from incorrectly matching Pattern3 fixtures + if entry.name.contains("Pattern1") && ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile + { + continue; + } + + let plan_opt = match entry.kind { + RuleKind::Simple(extract) => extract(ctx), + RuleKind::Pattern6 => legacy_rules::pattern6::extract(ctx), + RuleKind::Pattern7 => legacy_rules::pattern7::extract(ctx), + }?; + + if let Some(domain_plan) = plan_opt { + // Phase 286 P3: Pattern8 static box filtering + // Plan extractors are pure (no builder access), so we filter here. + if entry.name.contains("Pattern8") && ctx.in_static_box { + if ctx.debug { + trace::trace().debug( + "route/plan", + &format!( + "{} extracted but rejected: static box context (fallback to legacy)", + entry.name + ), + ); + } + continue; + } + + let log_msg = format!("route=plan strategy=extract pattern={}", entry.name); + trace::trace().pattern("route", &log_msg, true); + return Ok(Some(domain_plan)); + } else if ctx.debug { + let debug_msg = + format!("{} extraction returned None, trying next pattern", entry.name); + trace::trace().debug("route", &debug_msg); + } + } + + Ok(None) +} + +#[derive(Debug, Clone, Copy)] +struct RuleEntry { + name: &'static str, + kind: RuleKind, +} + +#[derive(Debug, Clone, Copy)] +enum RuleKind { + Simple(fn(&LoopPatternContext) -> Result, String>), + Pattern6, + Pattern7, +}