diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index c9a36043..f6fac399 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,7 +3,7 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next: TBD (P52 done; see `docs/development/current/main/phases/phase-29ao/README.md`) +- Next: TBD (P53 done; see `docs/development/current/main/phases/phase-29ao/README.md`) ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index ad1eb694..3cd1d3f9 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P52 done) +- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P53 done) ## Near-Term Candidates diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index 60bba121..a1f27631 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next step: TBD (P52 done) +- Next step: TBD (P53 done) ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ao/README.md b/docs/development/current/main/phases/phase-29ao/README.md index 85b66e61..603a49c2 100644 --- a/docs/development/current/main/phases/phase-29ao/README.md +++ b/docs/development/current/main/phases/phase-29ao/README.md @@ -286,6 +286,11 @@ Gate(SSOT): - 指示書: `docs/development/current/main/phases/phase-29ao/P52-SPLITSCAN-V0-REJECT-AND-ADOPT-GATE-INSTRUCTIONS.md` - ねらい: SplitScan の v0/v1 分離を両側の reject で固定し、adopt 分岐を value_join_needed だけに単純化(仕様不変) +## P53: CoreLoopComposer v0/v1 — ScanWithInit v0/v1 boundary ✅ + +- 指示書: `docs/development/current/main/phases/phase-29ao/P53-SCANWITHINIT-V0V1-BOUNDARY-INSTRUCTIONS.md` +- ねらい: ScanWithInit を v0/v1 分離で固定し、value_join_needed の境界をSSOT化(仕様不変) + ## Next(planned) - Next: TBD diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs index af175f16..597d366f 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs @@ -491,6 +491,77 @@ mod tests { assert!(composed.is_none()); } + #[test] + fn coreloop_v0_rejects_scan_with_init_when_value_join_needed() { + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(ASTNode::MethodCall { + object: Box::new(v("s")), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let mut kinds_present = BTreeSet::new(); + kinds_present.insert(ExitKindFacts::Return); + let features = LoopFeatureFacts { + exit_usage: ExitUsageFacts { + has_break: false, + has_continue: false, + has_return: true, + }, + exit_map: Some(ExitMapFacts { kinds_present }), + value_join: Some(ValueJoinFacts { needed: true }), + cleanup: None, + }; + let facts = LoopFacts { + condition_shape: ConditionShape::VarLessLength { + idx_var: "i".to_string(), + haystack_var: "s".to_string(), + method: LengthMethod::Length, + }, + step_shape: StepShape::AssignAddConst { + var: "i".to_string(), + k: 1, + }, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + 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 mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v0_scan_with_init_join".to_string()); + let ctx = LoopPatternContext::new( + &condition, + &[], + "coreloop_v0_scan_with_init_join", + false, + false, + ); + let composed = + try_compose_core_loop_v0_scan_with_init(&mut builder, &canonical, &ctx) + .expect("Ok"); + assert!(composed.is_none()); + } + #[test] fn coreloop_v0_composes_split_scan_subset() { let condition = ASTNode::Literal { diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs index 913006a8..7044bccd 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs @@ -43,6 +43,20 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v1_split_scan( Ok(Some(core)) } +pub(in crate::mir::builder) fn try_compose_core_loop_v1_scan_with_init( + _builder: &mut MirBuilder, + facts: &CanonicalLoopFacts, + _ctx: &LoopPatternContext, +) -> Result, String> { + if !facts.value_join_needed { + return Ok(None); + } + if facts.facts.scan_with_init.is_none() { + return Ok(None); + } + Ok(None) +} + pub(in crate::mir::builder) fn try_compose_core_loop_v1_pattern2_break( builder: &mut MirBuilder, facts: &CanonicalLoopFacts, @@ -132,14 +146,16 @@ mod tests { use super::{ try_compose_core_loop_v1_pattern2_break, try_compose_core_loop_v1_pattern3_ifphi, try_compose_core_loop_v1_pattern5_infinite_early_exit, - try_compose_core_loop_v1_split_scan, + try_compose_core_loop_v1_scan_with_init, try_compose_core_loop_v1_split_scan, }; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::builder::control_flow::plan::facts::feature_facts::{ CleanupFacts, CleanupKindFacts, ExitKindFacts, ExitMapFacts, LoopFeatureFacts, ValueJoinFacts, }; - use crate::mir::builder::control_flow::plan::facts::loop_facts::LoopFacts; + use crate::mir::builder::control_flow::plan::facts::loop_facts::{ + LoopFacts, ScanWithInitFacts, + }; use crate::mir::builder::control_flow::plan::facts::pattern3_ifphi_facts::Pattern3IfPhiFacts; use crate::mir::builder::control_flow::plan::facts::pattern5_infinite_early_exit_facts::Pattern5InfiniteEarlyExitFacts; use crate::mir::builder::control_flow::plan::facts::pattern2_break_facts::Pattern2BreakFacts; @@ -334,6 +350,55 @@ mod tests { assert!(composed.is_none()); } + #[test] + fn coreloop_v1_rejects_scan_with_init_value_join() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let features = LoopFeatureFacts { + value_join: Some(ValueJoinFacts { needed: true }), + ..LoopFeatureFacts::default() + }; + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + 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 mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v1_scan_with_init_join".to_string()); + let ctx = LoopPatternContext::new( + &condition, + &[], + "coreloop_v1_scan_with_init_join", + false, + false, + ); + let composed = + try_compose_core_loop_v1_scan_with_init(&mut builder, &canonical, &ctx) + .expect("Ok"); + assert!(composed.is_none()); + } + #[test] fn coreloop_v1_composes_pattern2_with_value_join() { let loop_condition = ASTNode::BinaryOp { diff --git a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs index 84035eda..cec37059 100644 --- a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs +++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs @@ -5,7 +5,8 @@ use super::coreloop_v0::{ }; use super::coreloop_v1::{ try_compose_core_loop_v1_pattern2_break, try_compose_core_loop_v1_pattern3_ifphi, - try_compose_core_loop_v1_pattern5_infinite_early_exit, try_compose_core_loop_v1_split_scan, + try_compose_core_loop_v1_pattern5_infinite_early_exit, + try_compose_core_loop_v1_scan_with_init, try_compose_core_loop_v1_split_scan, }; use super::PlanNormalizer; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; @@ -77,7 +78,12 @@ pub(in crate::mir::builder) fn try_release_adopt_core_plan_for_pattern6_scan_wit return Ok(None); } - match try_compose_core_loop_v0_scan_with_init(builder, facts, ctx) { + let composed = if facts.value_join_needed { + try_compose_core_loop_v1_scan_with_init(builder, facts, ctx) + } else { + try_compose_core_loop_v0_scan_with_init(builder, facts, ctx) + }; + match composed { Ok(Some(core)) => Ok(Some(core)), Ok(None) | Err(_) => Ok(None), } @@ -377,8 +383,12 @@ pub(in crate::mir::builder) fn try_shadow_adopt_core_plan( if facts.facts.scan_with_init.is_none() { return Err("pattern6 strict/dev adopt failed: facts mismatch".to_string()); } - let core_plan = try_compose_core_loop_v0_scan_with_init(builder, facts, ctx)? - .ok_or_else(|| "pattern6 strict/dev adopt failed: compose rejected".to_string())?; + let core_plan = if facts.value_join_needed { + try_compose_core_loop_v1_scan_with_init(builder, facts, ctx)? + } else { + try_compose_core_loop_v0_scan_with_init(builder, facts, ctx)? + } + .ok_or_else(|| "pattern6 strict/dev adopt failed: compose rejected".to_string())?; Ok(Some(ShadowAdoptOutcome { core_plan, tag: "[coreplan/shadow_adopt:pattern6_scan_with_init]",