phase29ao(p53): scan_with_init v0/v1 boundary

This commit is contained in:
2025-12-30 21:35:27 +09:00
parent b2587d5de3
commit 00130c0852
7 changed files with 160 additions and 9 deletions

View File

@ -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)

View File

@ -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

View File

@ -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再発防止の土台

View File

@ -286,6 +286,11 @@ GateSSOT:
- 指示書: `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化仕様不変
## Nextplanned
- Next: TBD

View File

@ -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 {

View File

@ -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<Option<CorePlan>, 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 {

View File

@ -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]",