diff --git a/src/mir/join_ir/lowering/exit_meta_builder.rs b/src/mir/join_ir/lowering/exit_meta_builder.rs new file mode 100644 index 00000000..ed34e91b --- /dev/null +++ b/src/mir/join_ir/lowering/exit_meta_builder.rs @@ -0,0 +1,175 @@ +//! Phase 118: IfSumExitMetaBuilderBox - Exit metadata builder for if-sum pattern +//! +//! This module provides a box-based abstraction for building ExitMeta structures +//! in the if-sum pattern lowering process. +//! +//! # Design Philosophy +//! +//! - **Single Responsibility**: Only builds ExitMeta from carrier bindings +//! - **Fail-Fast**: Validates carrier names immediately +//! - **Box Theory**: Encapsulates ExitMeta construction logic +//! +//! # Usage +//! +//! ```ignore +//! let builder = IfSumExitMetaBuilderBox::new(); +//! let exit_meta = builder.build_single(carrier_name, exit_value_id)?; +//! ``` + +use crate::mir::join_ir::lowering::carrier_info::ExitMeta; +use crate::mir::ValueId; + +/// Phase 118: Box for building ExitMeta in if-sum pattern +/// +/// This box separates the concern of "which carriers should be in exit_bindings" +/// from the JoinIR generation logic. +/// +/// # Fail-Fast Contract +/// +/// - Carrier name must be non-empty +/// - ValueId must be valid (no validation possible at this level) +/// +/// # Example +/// +/// ```ignore +/// let builder = IfSumExitMetaBuilderBox::new(); +/// let exit_meta = builder.build_single("sum", sum_final)?; +/// // Creates: ExitMeta { exit_values: [("sum", sum_final)] } +/// ``` +pub struct IfSumExitMetaBuilderBox; + +impl IfSumExitMetaBuilderBox { + /// Create a new IfSumExitMetaBuilderBox + pub fn new() -> Self { + Self + } + + /// Build ExitMeta for a single carrier + /// + /// # Arguments + /// + /// * `carrier_name` - Name of the carrier variable (e.g., "sum") + /// * `exit_value` - JoinIR-local ValueId for the exit value (e.g., sum_final from k_exit) + /// + /// # Returns + /// + /// * `Ok(ExitMeta)` - Successfully built ExitMeta + /// * `Err(String)` - Carrier name validation failed (Fail-Fast) + /// + /// # Fail-Fast Guarantee + /// + /// This method immediately returns an error if the carrier name is empty, + /// preventing downstream issues from undefined carrier bindings. + pub fn build_single(&self, carrier_name: String, exit_value: ValueId) -> Result { + // Fail-Fast: Validate carrier name + if carrier_name.is_empty() { + return Err("[IfSumExitMetaBuilderBox] Carrier name cannot be empty".to_string()); + } + + // Build ExitMeta with single carrier + let mut exit_values = vec![]; + exit_values.push((carrier_name.clone(), exit_value)); + + Ok(ExitMeta::multiple(exit_values)) + } + + /// Build ExitMeta for multiple carriers + /// + /// # Arguments + /// + /// * `carrier_bindings` - Vector of (carrier_name, exit_value) tuples + /// + /// # Returns + /// + /// * `Ok(ExitMeta)` - Successfully built ExitMeta + /// * `Err(String)` - Validation failed (Fail-Fast) + /// + /// # Fail-Fast Guarantee + /// + /// This method immediately returns an error if: + /// - Any carrier name is empty + /// - No carrier bindings provided + pub fn build_multiple(&self, carrier_bindings: Vec<(String, ValueId)>) -> Result { + // Fail-Fast: Validate at least one carrier + if carrier_bindings.is_empty() { + return Err("[IfSumExitMetaBuilderBox] At least one carrier binding required".to_string()); + } + + // Fail-Fast: Validate all carrier names + for (name, _) in &carrier_bindings { + if name.is_empty() { + return Err("[IfSumExitMetaBuilderBox] Carrier name cannot be empty".to_string()); + } + } + + Ok(ExitMeta::multiple(carrier_bindings)) + } +} + +impl Default for IfSumExitMetaBuilderBox { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_single_success() { + let builder = IfSumExitMetaBuilderBox::new(); + let result = builder.build_single("sum".to_string(), ValueId(42)); + + assert!(result.is_ok()); + let exit_meta = result.unwrap(); + assert_eq!(exit_meta.exit_values.len(), 1); + assert_eq!(exit_meta.exit_values[0].0, "sum"); + assert_eq!(exit_meta.exit_values[0].1, ValueId(42)); + } + + #[test] + fn test_build_single_empty_name_fails() { + let builder = IfSumExitMetaBuilderBox::new(); + let result = builder.build_single("".to_string(), ValueId(42)); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("cannot be empty")); + } + + #[test] + fn test_build_multiple_success() { + let builder = IfSumExitMetaBuilderBox::new(); + let carriers = vec![ + ("sum".to_string(), ValueId(10)), + ("count".to_string(), ValueId(20)), + ]; + let result = builder.build_multiple(carriers); + + assert!(result.is_ok()); + let exit_meta = result.unwrap(); + assert_eq!(exit_meta.exit_values.len(), 2); + } + + #[test] + fn test_build_multiple_empty_list_fails() { + let builder = IfSumExitMetaBuilderBox::new(); + let result = builder.build_multiple(vec![]); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("At least one carrier")); + } + + #[test] + fn test_build_multiple_empty_name_fails() { + let builder = IfSumExitMetaBuilderBox::new(); + let carriers = vec![ + ("sum".to_string(), ValueId(10)), + ("".to_string(), ValueId(20)), + ]; + let result = builder.build_multiple(carriers); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("cannot be empty")); + } +} diff --git a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs index be16cad2..488f7154 100644 --- a/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs +++ b/src/mir/join_ir/lowering/loop_with_if_phi_if_sum.rs @@ -29,9 +29,10 @@ //! | Flexibility | Test-only | Any if-sum pattern | use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::carrier_info::{ExitMeta, JoinFragmentMeta}; +use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowerer::lower_value_expression; +use crate::mir::join_ir::lowering::exit_meta_builder::IfSumExitMetaBuilderBox; #[cfg(debug_assertions)] use crate::mir::join_ir::lowering::condition_pattern::{ analyze_condition_capability, ConditionCapability, @@ -321,12 +322,10 @@ pub fn lower_if_sum_pattern( join_module.add_function(k_exit_func); join_module.entry = Some(main_id); - // Build ExitMeta + // Phase 118: Build ExitMeta using IfSumExitMetaBuilderBox // SSOT: use the accumulator name extracted from AST (no hardcoded carrier names). - let mut exit_values = vec![]; - exit_values.push((update_var.clone(), sum_final)); - - let exit_meta = ExitMeta::multiple(exit_values); + let exit_meta_builder = IfSumExitMetaBuilderBox::new(); + let exit_meta = exit_meta_builder.build_single(update_var.clone(), sum_final)?; // Phase 215-2: Use with_expr_result to propagate sum as loop result // sum_final is the k_exit return value - this is what `return sum` should use diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 9865292a..114d07f8 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -36,6 +36,7 @@ pub mod debug_output_box; // Phase 85: Centralized debug output management pub mod digitpos_condition_normalizer; // Phase 224-E: DigitPos condition normalizer (digit_pos < 0 → !is_digit_pos) pub mod error_tags; // Phase 86: Centralized error message formatting pub(crate) mod exit_args_resolver; // Internal exit argument resolution +pub mod exit_meta_builder; // Phase 118: ExitMeta builder box for if-sum pattern pub mod expr_lowerer; // Phase 231: Unified expression lowering with scope management pub mod funcscanner_append_defs; pub mod funcscanner_trim;