//! Loop Processing Context - SSOT for AST + Skeleton + Pattern //! //! Phase 140-P5: Unified context for loop processing that integrates: //! - AST information (condition, body, span) //! - Canonicalizer output (LoopSkeleton, RoutingDecision) //! - Router information (LoopPatternKind, LoopFeatures) //! //! This eliminates duplicate AST reconstruction and centralizes loop processing state. use crate::ast::{ASTNode, Span}; use crate::mir::loop_canonicalizer::{LoopSkeleton, RoutingDecision}; use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind}; /// Loop processing context - SSOT for AST + Skeleton + Pattern /// /// This context integrates all information needed for loop processing: /// - AST: The source structure (condition, body, span) /// - Canonicalizer: Normalized skeleton and routing decision (optional) /// - Router: Pattern classification and features /// /// # Lifecycle /// /// 1. Create with `new()` - AST + Router info only /// 2. Call `set_canonicalizer_result()` - Add Canonicalizer output /// 3. Call `verify_parity()` - Check consistency (dev-only) #[derive(Debug)] pub struct LoopProcessingContext<'a> { // ======================================================================== // AST Information // ======================================================================== /// Loop condition AST node pub condition: &'a ASTNode, /// Loop body statements pub body: &'a [ASTNode], /// Source location for debugging pub span: Span, // ======================================================================== // Canonicalizer Output (Optional - set after canonicalization) // ======================================================================== /// Normalized loop skeleton (None = canonicalizer not run yet) pub skeleton: Option, /// Routing decision from canonicalizer (None = canonicalizer not run yet) pub decision: Option, // ======================================================================== // Router Information (Always Present) // ======================================================================== /// Pattern kind determined by router pub pattern_kind: LoopPatternKind, /// Loop features extracted from AST pub features: LoopFeatures, } impl<'a> LoopProcessingContext<'a> { /// Create new context (canonicalizer not run yet) /// /// # Parameters /// /// - `condition`: Loop condition AST node /// - `body`: Loop body statements /// - `span`: Source location /// - `pattern_kind`: Pattern classification from router /// - `features`: Loop features extracted from AST pub fn new( condition: &'a ASTNode, body: &'a [ASTNode], span: Span, pattern_kind: LoopPatternKind, features: LoopFeatures, ) -> Self { Self { condition, body, span, skeleton: None, decision: None, pattern_kind, features, } } /// Set canonicalizer result (skeleton + decision) /// /// This should be called after running the canonicalizer. /// After this call, `verify_parity()` can be used to check consistency. pub fn set_canonicalizer_result(&mut self, skeleton: LoopSkeleton, decision: RoutingDecision) { self.skeleton = Some(skeleton); self.decision = Some(decision); } /// Reconstruct loop AST for canonicalizer input /// /// This is used for parity verification when we need to run the canonicalizer /// on the same AST that the router processed. pub fn to_loop_ast(&self) -> ASTNode { ASTNode::Loop { condition: Box::new(self.condition.clone()), body: self.body.to_vec(), span: self.span.clone(), } } /// Verify parity between canonicalizer and router (dev-only) /// /// Checks that the canonicalizer's pattern choice matches the router's /// pattern_kind. On mismatch: /// - Strict mode (HAKO_JOINIR_STRICT=1): Returns error /// - Debug mode: Logs warning /// /// Returns Ok(()) if: /// - Canonicalizer not run yet (decision is None) /// - Patterns match /// - Non-strict mode (mismatch logged only) pub fn verify_parity(&self) -> Result<(), String> { use crate::config::env::joinir_dev; // Canonicalizer not run yet - skip verification let decision = match &self.decision { Some(d) => d, None => return Ok(()), }; // Canonicalizer failed (Fail-Fast) - no pattern to compare let canonical_pattern = match decision.chosen { Some(p) => p, None => return Ok(()), // Router might still handle it }; // Compare patterns let actual_pattern = self.pattern_kind; if canonical_pattern != actual_pattern { let msg = format!( "[loop_canonicalizer/PARITY] MISMATCH: canonical={:?}, actual={:?}", canonical_pattern, actual_pattern ); if joinir_dev::strict_enabled() { // Strict mode: fail fast return Err(msg); } else { // Debug mode: log only crate::mir::builder::control_flow::joinir::trace::trace() .dev("loop_canonicalizer/parity", &msg); } } else { // Patterns match - success! crate::mir::builder::control_flow::joinir::trace::trace().dev( "loop_canonicalizer/parity", &format!( "[loop_canonicalizer/PARITY] OK: canonical and actual agree on {:?}", canonical_pattern ), ); } Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::ast::LiteralValue; /// Helper: Create a simple loop context for testing fn make_simple_context<'a>( condition: &'a ASTNode, body: &'a [ASTNode], ) -> LoopProcessingContext<'a> { LoopProcessingContext::new( condition, body, Span::unknown(), LoopPatternKind::Pattern1SimpleWhile, LoopFeatures { has_if: false, has_break: false, has_continue: false, has_if_else_phi: false, carrier_count: 0, break_count: 0, continue_count: 0, is_infinite_loop: false, update_summary: None, }, ) } #[test] fn test_context_creation() { let condition = ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }; let body = vec![]; let ctx = make_simple_context(&condition, &body); // Check AST fields assert!(matches!( ctx.condition, ASTNode::Literal { value: LiteralValue::Integer(1), .. } )); assert_eq!(ctx.body.len(), 0); assert_eq!(ctx.span, Span::unknown()); // Check canonicalizer fields (not set yet) assert!(ctx.skeleton.is_none()); assert!(ctx.decision.is_none()); // Check router fields assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern1SimpleWhile); } #[test] fn test_to_loop_ast_reconstruction() { let condition = ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }; let body = vec![]; let ctx = make_simple_context(&condition, &body); let loop_ast = ctx.to_loop_ast(); // Check reconstructed AST match loop_ast { ASTNode::Loop { condition, body, span, } => { assert!(matches!( *condition, ASTNode::Literal { value: LiteralValue::Integer(1), .. } )); assert_eq!(body.len(), 0); assert_eq!(span, Span::unknown()); } _ => panic!("Expected Loop node"), } } #[test] fn test_verify_parity_without_canonicalizer() { let condition = ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }; let body = vec![]; let ctx = make_simple_context(&condition, &body); // Should succeed (canonicalizer not run yet) assert!(ctx.verify_parity().is_ok()); } #[test] fn test_verify_parity_with_matching_patterns() { use crate::mir::loop_canonicalizer::{ExitContract, RoutingDecision}; let condition = ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }; let body = vec![]; let mut ctx = make_simple_context(&condition, &body); // Set canonicalizer result with matching pattern let decision = RoutingDecision::success(LoopPatternKind::Pattern1SimpleWhile); ctx.set_canonicalizer_result( LoopSkeleton { steps: vec![], carriers: vec![], exits: ExitContract::none(), captured: None, span: Span::unknown(), }, decision, ); // Should succeed (patterns match) assert!(ctx.verify_parity().is_ok()); } #[test] fn test_verify_parity_with_fail_fast() { use crate::mir::loop_canonicalizer::{CapabilityTag, ExitContract, RoutingDecision}; let condition = ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown(), }; let body = vec![]; let mut ctx = make_simple_context(&condition, &body); // Set canonicalizer result with fail-fast decision let decision = RoutingDecision::fail_fast( vec![CapabilityTag::ConstStep], "Test fail-fast".to_string(), ); ctx.set_canonicalizer_result( LoopSkeleton { steps: vec![], carriers: vec![], exits: ExitContract::none(), captured: None, span: Span::unknown(), }, decision, ); // Should succeed (canonicalizer failed, no pattern to compare) assert!(ctx.verify_parity().is_ok()); } }