diff --git a/src/mir/control_tree/mod.rs b/src/mir/control_tree/mod.rs index 85be1f87..fc0b7ae4 100644 --- a/src/mir/control_tree/mod.rs +++ b/src/mir/control_tree/mod.rs @@ -3,11 +3,20 @@ //! Policy: //! - This module must NOT reference MIR/JoinIR values (`ValueId`, `BlockId`, `PHI`, ...). //! - It only describes AST control structure and derived features/capabilities. +//! +//! Phase 120: Facts→Decision→Emit architecture +//! - step_tree_facts: Raw data collection (exits/writes/caps/cond_sig) +//! - step_tree_contract_box: Facts → Contract transformation +//! - step_tree: Structure + integration mod step_tree; +mod step_tree_contract_box; +mod step_tree_facts; pub use step_tree::{ AstSummary, ExitKind, StepCapability, StepNode, StepStmtKind, StepTree, StepTreeBuilderBox, - StepTreeContract, StepTreeFeatures, StepTreeSignature, + StepTreeFeatures, StepTreeSignature, }; +pub use step_tree_contract_box::{StepTreeContract, StepTreeContractBox}; +pub use step_tree_facts::StepTreeFacts; diff --git a/src/mir/control_tree/step_tree.rs b/src/mir/control_tree/step_tree.rs index 736e219a..b2797e95 100644 --- a/src/mir/control_tree/step_tree.rs +++ b/src/mir/control_tree/step_tree.rs @@ -1,4 +1,8 @@ use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator}; +use crate::mir::control_tree::step_tree_contract_box::{ + StepTreeContract, StepTreeContractBox, +}; +use crate::mir::control_tree::step_tree_facts::StepTreeFacts; use std::collections::BTreeSet; @@ -115,56 +119,7 @@ pub enum StepCapability { Arrow, } -#[derive(Debug, Clone, PartialEq, Default)] -pub struct StepTreeContract { - pub exits: BTreeSet, - pub writes: BTreeSet, - pub required_caps: BTreeSet, - pub cond_sig: Vec, -} - -impl StepTreeContract { - pub fn signature_basis_string(&self, node_kinds: &str) -> String { - let exits = self - .exits - .iter() - .map(|e| match e { - ExitKind::Return => "return", - ExitKind::Break => "break", - ExitKind::Continue => "continue", - }) - .collect::>() - .join(","); - let writes = self.writes.iter().cloned().collect::>().join(","); - let caps = self - .required_caps - .iter() - .map(|c| match c { - StepCapability::If => "If", - StepCapability::Loop => "Loop", - StepCapability::NestedIf => "NestedIf", - StepCapability::NestedLoop => "NestedLoop", - StepCapability::Return => "Return", - StepCapability::Break => "Break", - StepCapability::Continue => "Continue", - StepCapability::TryCatch => "TryCatch", - StepCapability::Throw => "Throw", - StepCapability::Lambda => "Lambda", - StepCapability::While => "While", - StepCapability::ForRange => "ForRange", - StepCapability::Match => "Match", - StepCapability::Arrow => "Arrow", - }) - .collect::>() - .join(","); - let cond_sig = self.cond_sig.join("|"); - - format!( - "kinds={};exits={};writes={};caps={};conds={}", - node_kinds, exits, writes, caps, cond_sig - ) - } -} +// StepTreeContract moved to step_tree_contract_box.rs (Phase 120) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct StepTreeSignature(pub u64); @@ -457,7 +412,9 @@ impl StepTreeBuilderBox { } fn build_step_tree(root: StepNode, features: StepTreeFeatures) -> StepTree { - let contract = StepTreeContractBox::compute(&root, &features); + // Phase 120: Facts → Contract → Signature (separated concerns) + let facts = extract_facts_from_tree(&root, &features); + let contract = StepTreeContractBox::from_facts(&facts); let mut kinds = Vec::new(); collect_node_kinds(&root, &mut kinds); let kinds = kinds.join(","); @@ -472,6 +429,112 @@ fn build_step_tree(root: StepNode, features: StepTreeFeatures) -> StepTree { } } +/// Extract raw facts from StepNode tree (Phase 120) +fn extract_facts_from_tree(root: &StepNode, features: &StepTreeFeatures) -> StepTreeFacts { + let mut facts = StepTreeFacts::new(); + + // Required caps from features (structural only) + if features.has_if { + facts.add_capability(StepCapability::If); + } + if features.max_if_depth > 1 { + facts.add_capability(StepCapability::NestedIf); + } + if features.has_loop { + facts.add_capability(StepCapability::Loop); + } + if features.max_loop_depth > 1 { + facts.add_capability(StepCapability::NestedLoop); + } + if features.has_return { + facts.add_capability(StepCapability::Return); + } + if features.has_break { + facts.add_capability(StepCapability::Break); + } + if features.has_continue { + facts.add_capability(StepCapability::Continue); + } + + walk_for_facts(root, &mut facts); + facts +} + +/// Walk StepNode tree to collect facts (Phase 120) +fn walk_for_facts(node: &StepNode, facts: &mut StepTreeFacts) { + match node { + StepNode::Block(nodes) => { + for n in nodes { + walk_for_facts(n, facts); + } + } + StepNode::If { + cond, + then_branch, + else_branch, + .. + } => { + facts.add_cond_sig(cond.to_compact_string()); + walk_for_facts(then_branch, facts); + if let Some(else_branch) = else_branch { + walk_for_facts(else_branch, facts); + } + } + StepNode::Loop { cond, body, .. } => { + facts.add_cond_sig(cond.to_compact_string()); + walk_for_facts(body, facts); + } + StepNode::Stmt { kind, .. } => { + match kind { + StepStmtKind::LocalDecl { vars } => { + for v in vars { + facts.add_write(v.clone()); + } + } + StepStmtKind::Assign { target } => { + if let Some(name) = target.as_ref() { + facts.add_write(name.clone()); + } + } + StepStmtKind::Print => {} + StepStmtKind::Return => { + facts.add_exit(ExitKind::Return); + } + StepStmtKind::Break => { + facts.add_exit(ExitKind::Break); + } + StepStmtKind::Continue => { + facts.add_exit(ExitKind::Continue); + } + StepStmtKind::Other(name) => match *name { + "TryCatch" => { + facts.add_capability(StepCapability::TryCatch); + } + "Throw" => { + facts.add_capability(StepCapability::Throw); + } + "Lambda" => { + facts.add_capability(StepCapability::Lambda); + } + "While" => { + facts.add_capability(StepCapability::While); + } + "ForRange" => { + facts.add_capability(StepCapability::ForRange); + } + "MatchExpr" => { + facts.add_capability(StepCapability::Match); + } + "Arrow" => { + facts.add_capability(StepCapability::Arrow); + } + _ => {} + }, + } + } + } +} + fn merge_features(mut a: StepTreeFeatures, b: StepTreeFeatures) -> StepTreeFeatures { a.has_if |= b.has_if; a.has_loop |= b.has_loop; @@ -483,113 +546,8 @@ fn merge_features(mut a: StepTreeFeatures, b: StepTreeFeatures) -> StepTreeFeatu a } -struct StepTreeContractBox; - -impl StepTreeContractBox { - fn compute(root: &StepNode, features: &StepTreeFeatures) -> StepTreeContract { - let mut contract = StepTreeContract::default(); - - // Required caps from features (structural only). - if features.has_if { - contract.required_caps.insert(StepCapability::If); - } - if features.max_if_depth > 1 { - contract.required_caps.insert(StepCapability::NestedIf); - } - if features.has_loop { - contract.required_caps.insert(StepCapability::Loop); - } - if features.max_loop_depth > 1 { - contract.required_caps.insert(StepCapability::NestedLoop); - } - if features.has_return { - contract.required_caps.insert(StepCapability::Return); - } - if features.has_break { - contract.required_caps.insert(StepCapability::Break); - } - if features.has_continue { - contract.required_caps.insert(StepCapability::Continue); - } - - Self::walk(root, &mut contract); - contract - } - - fn walk(node: &StepNode, contract: &mut StepTreeContract) { - match node { - StepNode::Block(nodes) => { - for n in nodes { - Self::walk(n, contract); - } - } - StepNode::If { - cond, - then_branch, - else_branch, - .. - } => { - contract.cond_sig.push(cond.to_compact_string()); - Self::walk(then_branch, contract); - if let Some(else_branch) = else_branch { - Self::walk(else_branch, contract); - } - } - StepNode::Loop { cond, body, .. } => { - contract.cond_sig.push(cond.to_compact_string()); - Self::walk(body, contract); - } - StepNode::Stmt { kind, .. } => { - match kind { - StepStmtKind::LocalDecl { vars } => { - for v in vars { - contract.writes.insert(v.clone()); - } - } - StepStmtKind::Assign { target } => { - if let Some(name) = target.as_ref() { - contract.writes.insert(name.clone()); - } - } - StepStmtKind::Print => {} - StepStmtKind::Return => { - contract.exits.insert(ExitKind::Return); - } - StepStmtKind::Break => { - contract.exits.insert(ExitKind::Break); - } - StepStmtKind::Continue => { - contract.exits.insert(ExitKind::Continue); - } - StepStmtKind::Other(name) => match *name { - "TryCatch" => { - contract.required_caps.insert(StepCapability::TryCatch); - } - "Throw" => { - contract.required_caps.insert(StepCapability::Throw); - } - "Lambda" => { - contract.required_caps.insert(StepCapability::Lambda); - } - "While" => { - contract.required_caps.insert(StepCapability::While); - } - "ForRange" => { - contract.required_caps.insert(StepCapability::ForRange); - } - "MatchExpr" => { - contract.required_caps.insert(StepCapability::Match); - } - "Arrow" => { - contract.required_caps.insert(StepCapability::Arrow); - } - _ => {} - }, - } - } - } - } -} +// StepTreeContractBox moved to step_tree_contract_box.rs (Phase 120) +// extract_facts_from_tree + walk_for_facts replace the old compute/walk pattern fn collect_node_kinds(node: &StepNode, out: &mut Vec) { match node { diff --git a/src/mir/control_tree/step_tree_contract_box.rs b/src/mir/control_tree/step_tree_contract_box.rs new file mode 100644 index 00000000..ca22c51d --- /dev/null +++ b/src/mir/control_tree/step_tree_contract_box.rs @@ -0,0 +1,133 @@ +//! StepTreeContractBox - facts → contract transformation (Phase 120) +//! +//! Responsibility: +//! - Transform StepTreeFacts into StepTreeContract +//! - Format facts into contract structure +//! - NO decision-making - just data transformation +//! - Maintain determinism (BTreeSet/BTreeMap order) +//! +//! Design: +//! - Pure transformation: facts → contract (idempotent) +//! - No AST traversal, no interpretation +//! - Contract is the formatted representation of facts + +use crate::mir::control_tree::step_tree_facts::StepTreeFacts; +use crate::mir::control_tree::{ExitKind, StepCapability}; +use std::collections::BTreeSet; + +/// Structured contract derived from facts (formatted, stable representation) +#[derive(Debug, Clone, PartialEq, Default)] +pub struct StepTreeContract { + pub exits: BTreeSet, + pub writes: BTreeSet, + pub required_caps: BTreeSet, + pub cond_sig: Vec, +} + +impl StepTreeContract { + /// Generate signature basis string (stable representation for hashing) + /// + /// This is used for StepTreeSignature computation. + /// Format: "kinds=...;exits=...;writes=...;caps=...;conds=..." + /// + /// Invariants: + /// - Span is NOT included (determinism) + /// - BTreeSet iteration order is stable + /// - cond_sig order is preserved from traversal + pub fn signature_basis_string(&self, node_kinds: &str) -> String { + let exits = self + .exits + .iter() + .map(|e| match e { + ExitKind::Return => "return", + ExitKind::Break => "break", + ExitKind::Continue => "continue", + }) + .collect::>() + .join(","); + let writes = self.writes.iter().cloned().collect::>().join(","); + let caps = self + .required_caps + .iter() + .map(|c| match c { + StepCapability::If => "If", + StepCapability::Loop => "Loop", + StepCapability::NestedIf => "NestedIf", + StepCapability::NestedLoop => "NestedLoop", + StepCapability::Return => "Return", + StepCapability::Break => "Break", + StepCapability::Continue => "Continue", + StepCapability::TryCatch => "TryCatch", + StepCapability::Throw => "Throw", + StepCapability::Lambda => "Lambda", + StepCapability::While => "While", + StepCapability::ForRange => "ForRange", + StepCapability::Match => "Match", + StepCapability::Arrow => "Arrow", + }) + .collect::>() + .join(","); + let cond_sig = self.cond_sig.join("|"); + + format!( + "kinds={};exits={};writes={};caps={};conds={}", + node_kinds, exits, writes, caps, cond_sig + ) + } +} + +/// StepTreeContractBox - pure transformation from facts to contract +pub struct StepTreeContractBox; + +impl StepTreeContractBox { + /// Transform facts into contract (idempotent, deterministic) + /// + /// This is a pure transformation: + /// - No decision-making + /// - No AST traversal + /// - Same facts → same contract (idempotent) + pub fn from_facts(facts: &StepTreeFacts) -> StepTreeContract { + StepTreeContract { + exits: facts.exits.clone(), + writes: facts.writes.clone(), + required_caps: facts.required_caps.clone(), + cond_sig: facts.cond_sig.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_facts_is_idempotent() { + let mut facts = StepTreeFacts::new(); + facts.add_exit(ExitKind::Return); + facts.add_write("x".to_string()); + facts.add_capability(StepCapability::If); + facts.add_cond_sig("var:cond".to_string()); + + let contract1 = StepTreeContractBox::from_facts(&facts); + let contract2 = StepTreeContractBox::from_facts(&facts); + + assert_eq!(contract1, contract2); + } + + #[test] + fn signature_basis_string_is_deterministic() { + let mut facts = StepTreeFacts::new(); + // Add in different order + facts.add_write("z".to_string()); + facts.add_write("a".to_string()); + facts.add_write("m".to_string()); + + let contract = StepTreeContractBox::from_facts(&facts); + let basis1 = contract.signature_basis_string("Block"); + let basis2 = contract.signature_basis_string("Block"); + + assert_eq!(basis1, basis2); + // BTreeSet should give stable order: a, m, z + assert!(basis1.contains("writes=a,m,z")); + } +} diff --git a/src/mir/control_tree/step_tree_facts.rs b/src/mir/control_tree/step_tree_facts.rs new file mode 100644 index 00000000..cad0d8eb --- /dev/null +++ b/src/mir/control_tree/step_tree_facts.rs @@ -0,0 +1,63 @@ +//! StepTreeFacts - raw structural facts extraction (Phase 120) +//! +//! Responsibility: +//! - Collect raw structural facts from StepNode tree (exits/writes/required_caps/cond_sig) +//! - NO formatting, NO decision-making, NO signature generation +//! - Pure "facts only" - data collection without interpretation +//! +//! Design: +//! - Facts are collected during tree traversal +//! - BTreeSet for deterministic iteration (order stability) +//! - No dependency on contract or signature logic + +use crate::mir::control_tree::{ExitKind, StepCapability}; +use std::collections::BTreeSet; + +/// Raw structural facts extracted from StepTree (facts only, no interpretation) +#[derive(Debug, Clone, PartialEq, Default)] +pub struct StepTreeFacts { + /// Exit kinds found in the tree (return/break/continue) + pub exits: BTreeSet, + /// Variable writes (Local declarations + Assignment targets) + pub writes: BTreeSet, + /// Required capabilities (structural features like NestedLoop, TryCatch, etc.) + pub required_caps: BTreeSet, + /// Condition signatures (compact string representations of if/loop conditions) + /// - Collected in traversal order for signature stability + pub cond_sig: Vec, +} + +impl StepTreeFacts { + /// Create empty facts + pub fn new() -> Self { + Self::default() + } + + /// Add an exit kind + pub fn add_exit(&mut self, kind: ExitKind) { + self.exits.insert(kind); + } + + /// Add a variable write + pub fn add_write(&mut self, var: String) { + self.writes.insert(var); + } + + /// Add a required capability + pub fn add_capability(&mut self, cap: StepCapability) { + self.required_caps.insert(cap); + } + + /// Add a condition signature + pub fn add_cond_sig(&mut self, sig: String) { + self.cond_sig.push(sig); + } + + /// Merge another facts into this one + pub fn merge(&mut self, other: StepTreeFacts) { + self.exits.extend(other.exits); + self.writes.extend(other.writes); + self.required_caps.extend(other.required_caps); + self.cond_sig.extend(other.cond_sig); + } +}