//! Phase 200-2: JoinInlineBoundaryBuilder - Builder pattern for JoinInlineBoundary construction //! //! This module provides a fluent Builder API to construct JoinInlineBoundary objects, //! reducing field manipulation scattering in pattern lowerers. //! //! # Design Philosophy //! //! - **Centralized Construction**: All boundary field manipulation in one place //! - **Type Safety**: Builder ensures fields are set correctly //! - **Readability**: Fluent API makes construction intent clear //! - **Maintainability**: Changes to boundary structure isolated to this builder //! //! # Example Usage //! //! ```ignore //! use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; //! //! let boundary = JoinInlineBoundaryBuilder::new() //! .with_inputs(vec![ValueId(0)], vec![loop_var_id]) //! .with_loop_var_name(Some(loop_param_name.to_string())) //! .with_condition_bindings(condition_bindings) //! .with_exit_bindings(exit_bindings) //! .with_expr_result(fragment_meta.expr_result) //! .build(); //! ``` use crate::mir::ValueId; use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; use super::condition_to_joinir::ConditionBinding; /// Role of a parameter in JoinIR lowering (Phase 200-A) /// /// This enum explicitly classifies parameters to ensure correct routing /// during JoinIR → MIR lowering and boundary construction. /// /// # Invariants /// /// - **LoopParam**: Participates in header PHI, updated in loop body /// - Example: `i` in `loop(i < len)` - iteration variable /// - Routing: join_inputs + host_inputs + header PHI + exit_bindings /// /// - **Condition**: Used in condition only, NOT in header PHI, NOT in ExitLine /// - Example: `digits` in `digits.indexOf(ch)` - function-scoped constant /// - Routing: condition_bindings ONLY (no PHI, no exit_bindings) /// - Rationale: Condition-only vars are immutable and not updated in loop /// /// - **Carrier**: Updated in loop body, participates in header PHI and ExitLine /// - Example: `sum`, `count` in accumulation loops /// - Routing: join_inputs + host_inputs + header PHI + exit_bindings /// /// - **ExprResult**: Return value of the loop expression /// - Example: Loop result in `return loop(...)` /// - Routing: Handled by exit_phi_builder (set_expr_result) /// /// # Phase 200-A Status /// /// Enum is defined but not yet used for routing. Routing implementation /// will be added in Phase 200-B when CapturedEnv integration is complete. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParamRole { /// Loop iteration variable (e.g., `i` in `loop(i < len)`) LoopParam, /// Condition-only parameter (e.g., `digits` in `digits.indexOf(ch)`) /// NOT included in header PHI or ExitLine Condition, /// State carried across iterations (e.g., `sum`, `count`) Carrier, /// Expression result returned by the loop ExprResult, } /// Builder for constructing JoinInlineBoundary objects /// /// Provides a fluent API to set boundary fields without direct field manipulation. pub struct JoinInlineBoundaryBuilder { boundary: JoinInlineBoundary, } impl JoinInlineBoundaryBuilder { /// Create a new builder with default empty boundary pub fn new() -> Self { Self { boundary: JoinInlineBoundary { join_inputs: vec![], host_inputs: vec![], join_outputs: vec![], #[allow(deprecated)] host_outputs: vec![], exit_bindings: vec![], #[allow(deprecated)] condition_inputs: vec![], condition_bindings: vec![], expr_result: None, loop_var_name: None, } } } /// Set input mappings (JoinIR-local ↔ Host ValueIds) /// /// # Arguments /// /// * `join_inputs` - JoinIR-local ValueIds (e.g., [ValueId(0)]) /// * `host_inputs` - Host ValueIds (e.g., [loop_var_id]) /// /// # Panics /// /// Panics if `join_inputs` and `host_inputs` have different lengths. pub fn with_inputs(mut self, join_inputs: Vec, host_inputs: Vec) -> Self { assert_eq!( join_inputs.len(), host_inputs.len(), "join_inputs and host_inputs must have same length" ); self.boundary.join_inputs = join_inputs; self.boundary.host_inputs = host_inputs; self } /// Set output mappings (JoinIR-local ↔ Host ValueIds) (DEPRECATED) /// /// **DEPRECATED**: Use `with_exit_bindings` instead for explicit carrier naming. #[deprecated(since = "Phase 200-2", note = "Use with_exit_bindings instead")] pub fn with_outputs(mut self, join_outputs: Vec, host_outputs: Vec) -> Self { self.boundary.join_outputs = join_outputs; #[allow(deprecated)] { self.boundary.host_outputs = host_outputs; } self } /// Set condition bindings (Phase 171-fix) /// /// Each binding explicitly maps: /// - Variable name /// - HOST ValueId → JoinIR ValueId pub fn with_condition_bindings(mut self, bindings: Vec) -> Self { self.boundary.condition_bindings = bindings; self } /// Set exit bindings (Phase 190+) /// /// Each binding explicitly names the carrier variable and its source/destination. pub fn with_exit_bindings(mut self, bindings: Vec) -> Self { self.boundary.exit_bindings = bindings; self } /// Set loop variable name (Phase 33-16) /// /// Used for LoopHeaderPhiBuilder to track which PHI corresponds to the loop variable. pub fn with_loop_var_name(mut self, name: Option) -> Self { self.boundary.loop_var_name = name; self } /// Set expression result (Phase 33-14) /// /// If the loop is used as an expression, this is the JoinIR-local ValueId /// of k_exit's return value. pub fn with_expr_result(mut self, expr: Option) -> Self { self.boundary.expr_result = expr; self } /// Build the final JoinInlineBoundary pub fn build(self) -> JoinInlineBoundary { self.boundary } /// Add a parameter with explicit role (Phase 200-A) /// /// This method allows adding parameters with explicit role classification, /// ensuring correct routing during JoinIR → MIR lowering. /// /// # Phase 200-A Status /// /// Currently stores parameters based on role but does not use role for advanced routing. /// Full role-based routing will be implemented in Phase 200-B. /// /// # Arguments /// /// * `name` - Variable name (e.g., "i", "digits", "sum") /// * `host_id` - Host function's ValueId for this variable /// * `role` - Parameter role (LoopParam / Condition / Carrier / ExprResult) /// /// # Routing Rules (Phase 200-B+) /// /// - **LoopParam**: add_input (join_inputs + host_inputs) /// - **Condition**: add to condition_bindings (no PHI, no exit_bindings) /// - **Carrier**: add_input + exit_bindings /// - **ExprResult**: set_expr_result (handled separately) /// /// # Example (Future Phase 200-B) /// /// ```ignore /// builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam); /// builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition); /// builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier); /// ``` pub fn add_param_with_role(&mut self, _name: &str, host_id: ValueId, role: ParamRole) { // Phase 200-A: Basic routing only // TODO(Phase 200-B): Implement full role-based routing // // Routing implementation: // - LoopParam: join_inputs + host_inputs // - Condition: condition_bindings (with JoinIR-local ValueId allocation) // - Carrier: join_inputs + host_inputs + exit_bindings // - ExprResult: Handled by set_expr_result match role { ParamRole::LoopParam | ParamRole::Carrier => { // Existing behavior: add to join_inputs // Note: In Phase 200-A, we don't have a simple add_input method // that takes a name. This is a skeleton implementation. // In Phase 200-B, we'll need to allocate JoinIR-local ValueIds. let join_id = ValueId(self.boundary.join_inputs.len() as u32); self.boundary.join_inputs.push(join_id); self.boundary.host_inputs.push(host_id); } ParamRole::Condition => { // Phase 200-A: Log only // TODO(Phase 200-B): Add to condition_bindings without PHI // 1. Allocate JoinIR-local ValueId // 2. Create ConditionBinding { name, host_id, join_id } // 3. Add to self.boundary.condition_bindings } ParamRole::ExprResult => { // Handled separately by set_expr_result // No action needed here } } } } impl Default for JoinInlineBoundaryBuilder { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_builder_basic() { let boundary = JoinInlineBoundaryBuilder::new() .with_inputs(vec![ValueId(0)], vec![ValueId(4)]) .build(); assert_eq!(boundary.join_inputs, vec![ValueId(0)]); assert_eq!(boundary.host_inputs, vec![ValueId(4)]); assert_eq!(boundary.join_outputs.len(), 0); assert_eq!(boundary.exit_bindings.len(), 0); assert_eq!(boundary.condition_bindings.len(), 0); assert_eq!(boundary.expr_result, None); assert_eq!(boundary.loop_var_name, None); } #[test] fn test_builder_full() { let condition_binding = ConditionBinding { name: "start".to_string(), host_value: ValueId(33), join_value: ValueId(1), }; let exit_binding = LoopExitBinding { carrier_name: "sum".to_string(), join_exit_value: ValueId(18), host_slot: ValueId(5), }; let boundary = JoinInlineBoundaryBuilder::new() .with_inputs(vec![ValueId(0)], vec![ValueId(4)]) .with_loop_var_name(Some("i".to_string())) .with_condition_bindings(vec![condition_binding]) .with_exit_bindings(vec![exit_binding]) .with_expr_result(Some(ValueId(20))) .build(); assert_eq!(boundary.join_inputs, vec![ValueId(0)]); assert_eq!(boundary.host_inputs, vec![ValueId(4)]); assert_eq!(boundary.loop_var_name, Some("i".to_string())); assert_eq!(boundary.condition_bindings.len(), 1); assert_eq!(boundary.exit_bindings.len(), 1); assert_eq!(boundary.expr_result, Some(ValueId(20))); } #[test] #[should_panic(expected = "join_inputs and host_inputs must have same length")] fn test_builder_mismatched_inputs() { JoinInlineBoundaryBuilder::new() .with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(4)]) .build(); } #[test] fn test_builder_default() { let builder = JoinInlineBoundaryBuilder::default(); let boundary = builder.build(); assert_eq!(boundary.join_inputs.len(), 0); assert_eq!(boundary.host_inputs.len(), 0); } #[test] fn test_builder_pattern3_style() { // Pattern3 style: Two carriers (i + sum), exit_bindings, loop_var_name let boundary = JoinInlineBoundaryBuilder::new() .with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(100), ValueId(101)]) .with_exit_bindings(vec![ LoopExitBinding { carrier_name: "sum".to_string(), join_exit_value: ValueId(18), host_slot: ValueId(101), } ]) .with_loop_var_name(Some("i".to_string())) .build(); assert_eq!(boundary.join_inputs.len(), 2); assert_eq!(boundary.host_inputs.len(), 2); assert_eq!(boundary.exit_bindings.len(), 1); assert_eq!(boundary.exit_bindings[0].carrier_name, "sum"); assert_eq!(boundary.loop_var_name, Some("i".to_string())); assert_eq!(boundary.expr_result, None); } #[test] fn test_builder_pattern4_style() { // Pattern4 style: Dynamic carrier count, continue support let boundary = JoinInlineBoundaryBuilder::new() .with_inputs( vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers vec![ValueId(100), ValueId(101), ValueId(102)] ) .with_exit_bindings(vec![ LoopExitBinding { carrier_name: "i".to_string(), join_exit_value: ValueId(11), host_slot: ValueId(100), }, LoopExitBinding { carrier_name: "sum".to_string(), join_exit_value: ValueId(20), host_slot: ValueId(101), } ]) .with_loop_var_name(Some("i".to_string())) .build(); assert_eq!(boundary.exit_bindings.len(), 2); assert!(boundary.loop_var_name.is_some()); assert_eq!(boundary.join_inputs.len(), 3); assert_eq!(boundary.host_inputs.len(), 3); } // Phase 200-A: ParamRole tests #[test] fn test_param_role_loop_param() { let mut builder = JoinInlineBoundaryBuilder::new(); builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam); let boundary = builder.build(); assert_eq!(boundary.join_inputs.len(), 1); assert_eq!(boundary.host_inputs.len(), 1); assert_eq!(boundary.host_inputs[0], ValueId(100)); } #[test] fn test_param_role_condition() { let mut builder = JoinInlineBoundaryBuilder::new(); // Phase 200-A: Condition role is logged but not yet routed builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition); let boundary = builder.build(); // Phase 200-A: No action for Condition role yet // Phase 200-B: This will add to condition_bindings assert_eq!(boundary.join_inputs.len(), 0); assert_eq!(boundary.condition_bindings.len(), 0); } #[test] fn test_param_role_carrier() { let mut builder = JoinInlineBoundaryBuilder::new(); builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier); let boundary = builder.build(); assert_eq!(boundary.join_inputs.len(), 1); assert_eq!(boundary.host_inputs.len(), 1); assert_eq!(boundary.host_inputs[0], ValueId(101)); } }