From 3fb5c1a5442edd4088f3237e06bac3b1d3bc6e41 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 07:26:08 +0900 Subject: [PATCH] phase29ai(p3): typed Freeze + candidate-set planner boundary --- .../control_flow/plan/planner/build.rs | 22 +++++-- .../control_flow/plan/planner/candidates.rs | 52 +++++++++++++++ .../control_flow/plan/planner/freeze.rs | 66 +++++++++++++++++++ .../builder/control_flow/plan/planner/mod.rs | 6 +- 4 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/mir/builder/control_flow/plan/planner/candidates.rs create mode 100644 src/mir/builder/control_flow/plan/planner/freeze.rs diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 3204b093..cd743c55 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -7,6 +7,7 @@ use crate::ast::ASTNode; use crate::mir::builder::control_flow::plan::facts::try_build_loop_facts; use crate::mir::builder::control_flow::plan::normalize::{canonicalize_loop_facts, CanonicalLoopFacts}; +use super::candidates::CandidateSet; use super::{Freeze, Plan}; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -20,12 +21,25 @@ pub(in crate::mir::builder) fn build_plan( return Ok(None); }; let canonical = canonicalize_loop_facts(facts); - let plan = build_plan_from_facts(canonical)?; - Ok(Some(plan)) + build_plan_from_facts(canonical) } pub(in crate::mir::builder) fn build_plan_from_facts( _facts: CanonicalLoopFacts, -) -> Result { - Err("freeze[phase29ai/p0]: build_plan_from_facts not implemented".to_string()) +) -> Result, Freeze> { + // Phase 29ai P3: CandidateSet-based boundary (SSOT) + // + // P3 note: Facts are currently `Ok(None)` (P0 skeleton), so this function is + // unreachable in normal execution today. We still implement the SSOT + // boundary here so that future Facts work cannot drift. + + let candidates = CandidateSet::new(); + + // No candidates in P3 (unreachable today); boundary remains stable. + match candidates.finalize()? { + Some(plan) => Ok(Some(plan)), + None => Err(Freeze::unsupported( + "facts were provided but no planner rule produced a candidate (P3 placeholder)", + )), + } } diff --git a/src/mir/builder/control_flow/plan/planner/candidates.rs b/src/mir/builder/control_flow/plan/planner/candidates.rs new file mode 100644 index 00000000..3f45446d --- /dev/null +++ b/src/mir/builder/control_flow/plan/planner/candidates.rs @@ -0,0 +1,52 @@ +//! Phase 29ai P3: CandidateSet — SSOT implementation +//! +//! SSOT: "0 candidates = Ok(None), 1 = Ok(Some), 2+ = Freeze(ambiguous)" + +#![allow(dead_code)] + +use super::{Freeze, Plan, PlanKind}; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct PlanCandidate { + pub kind: PlanKind, + pub rule: &'static str, +} + +#[derive(Debug, Default)] +pub(in crate::mir::builder) struct CandidateSet { + candidates: Vec, +} + +impl CandidateSet { + pub(in crate::mir::builder) fn new() -> Self { + Self { + candidates: Vec::new(), + } + } + + pub(in crate::mir::builder) fn push(&mut self, candidate: PlanCandidate) { + self.candidates.push(candidate); + } + + pub(in crate::mir::builder) fn finalize(self) -> Result, Freeze> { + match self.candidates.len() { + 0 => Ok(None), + 1 => { + let c = self.candidates.into_iter().next().expect("len == 1"); + Ok(Some(Plan { kind: c.kind })) + } + n => { + let rules = self + .candidates + .iter() + .map(|c| c.rule) + .collect::>() + .join(", "); + Err(Freeze::ambiguous(format!( + "multiple plan candidates (count={}): {}", + n, rules + ))) + } + } + } +} diff --git a/src/mir/builder/control_flow/plan/planner/freeze.rs b/src/mir/builder/control_flow/plan/planner/freeze.rs new file mode 100644 index 00000000..6cf22dbc --- /dev/null +++ b/src/mir/builder/control_flow/plan/planner/freeze.rs @@ -0,0 +1,66 @@ +//! Phase 29ai P3: Typed Freeze (Fail-Fast) — SSOT implementation +//! +//! Tag taxonomy SSOT: +//! - docs/development/current/main/design/planfrag-freeze-taxonomy.md + +#![allow(dead_code)] + +use std::fmt; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Freeze { + pub tag: &'static str, + pub message: String, + pub hint: Option, +} + +impl Freeze { + pub(in crate::mir::builder) fn contract(message: impl Into) -> Self { + Self { + tag: "contract", + message: message.into(), + hint: None, + } + } + + pub(in crate::mir::builder) fn ambiguous(message: impl Into) -> Self { + Self { + tag: "ambiguous", + message: message.into(), + hint: None, + } + } + + pub(in crate::mir::builder) fn unsupported(message: impl Into) -> Self { + Self { + tag: "unsupported", + message: message.into(), + hint: None, + } + } + + pub(in crate::mir::builder) fn bug(message: impl Into) -> Self { + Self { + tag: "bug", + message: message.into(), + hint: None, + } + } + + pub(in crate::mir::builder) fn with_hint(mut self, hint: impl Into) -> Self { + self.hint = Some(hint.into()); + self + } +} + +impl fmt::Display for Freeze { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[plan/freeze:{}] {}", self.tag, self.message)?; + if let Some(hint) = &self.hint { + write!(f, "\n hint: {}", hint)?; + } + Ok(()) + } +} + +impl std::error::Error for Freeze {} diff --git a/src/mir/builder/control_flow/plan/planner/mod.rs b/src/mir/builder/control_flow/plan/planner/mod.rs index 72fc5372..df7f677d 100644 --- a/src/mir/builder/control_flow/plan/planner/mod.rs +++ b/src/mir/builder/control_flow/plan/planner/mod.rs @@ -6,8 +6,10 @@ #![allow(dead_code, unused_imports)] pub(in crate::mir::builder) mod build; +pub(in crate::mir::builder) mod candidates; +pub(in crate::mir::builder) mod freeze; -pub(in crate::mir::builder) type Freeze = String; +pub(in crate::mir::builder) use freeze::Freeze; #[derive(Debug, Clone)] pub(in crate::mir::builder) struct Plan { @@ -19,4 +21,4 @@ pub(in crate::mir::builder) enum PlanKind { Placeholder, } -pub(in crate::mir::builder) use build::{build_plan, build_plan_from_facts}; +pub(in crate::mir::builder) use build::build_plan;