From b5661c191526d1e368e5bf5609b7c231134aa4fc Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 10 Dec 2025 16:20:44 +0900 Subject: [PATCH] feat(joinir): Phase 223.5 - LoopBodyCondPromoter Pattern2 integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Integrate LoopBodyCondPromoter into Pattern2 (break condition analysis) - Add Pattern2 error message functions to error_messages.rs - Create A-4 minimal test (phase2235_p2_digit_pos_min.hako) - Unified promotion structure for Pattern2/Pattern4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/tests/phase2235_p2_digit_pos_min.hako | 51 +++++ .../joinir/patterns/pattern2_with_break.rs | 79 +++++++ .../loop_pattern_detection/error_messages.rs | 207 ++++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 apps/tests/phase2235_p2_digit_pos_min.hako diff --git a/apps/tests/phase2235_p2_digit_pos_min.hako b/apps/tests/phase2235_p2_digit_pos_min.hako new file mode 100644 index 00000000..9f705f58 --- /dev/null +++ b/apps/tests/phase2235_p2_digit_pos_min.hako @@ -0,0 +1,51 @@ +// Phase 223.5: A-4 Pattern Test - Digit Position with Cascading LoopBodyLocal +// +// Test case: Number parsing loop with cascading LoopBodyLocal dependency +// - `ch = s.substring(p, p+1)` → LoopBodyLocal +// - `digit_pos = digits.indexOf(ch)` → depends on ch (cascading LoopBodyLocal) +// - `if digit_pos < 0 { break }` → break condition uses digit_pos +// +// Expected flow: +// Input: s = "123abc", digits = "0123456789" +// Loop iteration 1: ch = "1", digit_pos = 1, continue +// Loop iteration 2: ch = "2", digit_pos = 2, continue +// Loop iteration 3: ch = "3", digit_pos = 3, continue +// Loop iteration 4: ch = "a", digit_pos = -1, break +// Final: p = 3 (after processing "123") +// +// Phase 223.5 Result: LoopBodyCondPromoter detects pattern and fails-fast +// with: "Cannot promote LoopBodyLocal variables ["digit_pos"]: No promotable Trim pattern detected" +// +// This is EXPECTED - A-4 cascading pattern is more complex than A-3 Trim. +// Full cascading promotion support will be implemented in Phase 224+. +// +// NOTE: This is a simplified version of json_parser.hako digit parsing. + +static box Main { + main() { + local s = "123abc" + local digits = "0123456789" + local p = 0 + local num_str = "" + + // Pattern 2 loop (has break, no continue) + loop(p < s.length()) { + local ch = s.substring(p, p+1) + local digit_pos = digits.indexOf(ch) + + // Exit condition: non-digit character found + if digit_pos < 0 { + break + } + + // Continue parsing: digit found + num_str = num_str + ch + p = p + 1 + } + + // Output results + print("p = " + p) + print("num_str = " + num_str) + return p + } +} diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 1cf02848..c0314ef4 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -4,6 +4,7 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; use super::super::trace; +use crate::mir::loop_pattern_detection::error_messages; /// Phase 185-2: Collect body-local variable declarations from loop body /// @@ -266,6 +267,84 @@ impl MirBuilder { break_condition_raw.clone() }; + // Phase 223.5: LoopBodyLocal Condition Promotion + // + // Check for LoopBodyLocal in loop/break conditions and attempt promotion. + // Safe Trim patterns (Category A-3/A-4) are promoted to carriers. + { + use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; + use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ + LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult, + }; + + // Analyze both header condition and break condition for LoopBodyLocal + let conditions_to_analyze: Vec<&ASTNode> = vec![condition, &break_condition_node]; + + let cond_scope = LoopConditionScopeBox::analyze( + &loop_var_name, + &conditions_to_analyze, + Some(&scope), + ); + + if cond_scope.has_loop_body_local() { + // Try promotion using LoopBodyCondPromoter + let promotion_req = ConditionPromotionRequest { + loop_param_name: &loop_var_name, + cond_scope: &cond_scope, + scope_shape: Some(&scope), + break_cond: Some(&break_condition_node), // Pattern 2 has break + continue_cond: None, // Pattern 2 has no continue + loop_body: _body, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { + ConditionPromotionResult::Promoted { + carrier_info: promoted_carrier, + promoted_var, + carrier_name, + } => { + eprintln!( + "[pattern2/cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'", + promoted_var, carrier_name + ); + + // Merge promoted carrier into existing CarrierInfo + carrier_info.merge_from(&promoted_carrier); + + eprintln!( + "[pattern2/cond_promoter] Merged carrier '{}' into CarrierInfo (total carriers: {})", + carrier_name, + carrier_info.carrier_count() + ); + + // Check if this is a safe Trim pattern + if let Some(helper) = carrier_info.trim_helper() { + if helper.is_safe_trim() { + eprintln!( + "[pattern2/cond_promoter] Safe Trim pattern detected" + ); + eprintln!( + "[pattern2/cond_promoter] Carrier: '{}', original var: '{}', whitespace chars: {:?}", + helper.carrier_name, helper.original_var, helper.whitespace_chars + ); + // Continue with Pattern2 lowering (fall through) + } else { + return Err(error_messages::format_error_pattern2_trim_not_safe( + &helper.carrier_name, + helper.whitespace_count() + )); + } + } + // Carrier promoted and merged, proceed with normal lowering + } + ConditionPromotionResult::CannotPromote { reason, vars } => { + // Fail-Fast on promotion failure + return Err(error_messages::format_error_pattern2_promotion_failed(&vars, &reason)); + } + } + } + } + // Phase 180-3: Delegate Trim/P5 processing to TrimLoopLowerer let effective_break_condition = if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop( self, diff --git a/src/mir/loop_pattern_detection/error_messages.rs b/src/mir/loop_pattern_detection/error_messages.rs index 8e44d83f..bf9ed75a 100644 --- a/src/mir/loop_pattern_detection/error_messages.rs +++ b/src/mir/loop_pattern_detection/error_messages.rs @@ -95,3 +95,210 @@ mod tests { assert!(err.contains("Pattern 4 supports")); } } + +// ======================================================================== +// Pattern 4 Specific Error Messages +// ======================================================================== + +/// Pattern4: Cannot promote LoopBodyLocal variables in condition +/// +/// Used when LoopBodyCondPromoter fails to promote a LoopBodyLocal variable +/// used in loop conditions to a bool carrier. +/// +/// # Arguments +/// +/// * `vars` - Names of LoopBodyLocal variables that failed promotion +/// * `reason` - Human-readable reason for the failure +/// +/// # Example +/// +/// ``` +/// let err = format_error_pattern4_promotion_failed(&["ch"], "not a Trim pattern"); +/// // Returns: "[cf_loop/pattern4] Cannot promote LoopBodyLocal variables ["ch"]: not a Trim pattern" +/// ``` +pub fn format_error_pattern4_promotion_failed(vars: &[String], reason: &str) -> String { + format!( + "[cf_loop/pattern4] Cannot promote LoopBodyLocal variables {:?}: {}", + vars, reason + ) +} + +/// Pattern4: Trim pattern detected but not safe +/// +/// Used when a Trim pattern is detected but does not meet safety criteria +/// (e.g., too many whitespace characters, unsafe structure). +/// +/// # Arguments +/// +/// * `carrier_name` - Name of the carrier variable +/// * `whitespace_count` - Number of whitespace characters detected +/// +/// # Example +/// +/// ``` +/// let err = format_error_pattern4_trim_not_safe("is_whitespace", 5); +/// // Returns: "[cf_loop/pattern4] Trim pattern detected but not safe: carrier='is_whitespace', whitespace_count=5" +/// ``` +pub fn format_error_pattern4_trim_not_safe(carrier_name: &str, whitespace_count: usize) -> String { + format!( + "[cf_loop/pattern4] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", + carrier_name, whitespace_count + ) +} + +/// Pattern4: Lowering failed +/// +/// Generic error wrapper for Pattern 4 lowering failures. +/// +/// # Arguments +/// +/// * `cause` - The underlying error message +/// +/// # Example +/// +/// ``` +/// let err = format_error_pattern4_lowering_failed("JoinIR conversion error"); +/// // Returns: "[cf_loop/pattern4] Lowering failed: JoinIR conversion error" +/// ``` +pub fn format_error_pattern4_lowering_failed(cause: &str) -> String { + format!("[cf_loop/pattern4] Lowering failed: {}", cause) +} + +/// Pattern4: Carrier not found in variable_map +/// +/// Used when an expected carrier variable is missing from variable_map, +/// indicating an exit binding is not properly generated. +/// +/// # Arguments +/// +/// * `carrier_name` - Name of the missing carrier variable +/// +/// # Example +/// +/// ``` +/// let err = format_error_pattern4_carrier_not_found("sum"); +/// // Returns: "[cf_loop/pattern4] Carrier 'sum' not found in variable_map - exit binding missing" +/// ``` +pub fn format_error_pattern4_carrier_not_found(carrier_name: &str) -> String { + format!( + "[cf_loop/pattern4] Carrier '{}' not found in variable_map - exit binding missing", + carrier_name + ) +} + +// ======================================================================== +// Pattern 2 Specific Error Messages +// ======================================================================== + +/// Pattern2: Cannot promote LoopBodyLocal variables in condition +/// +/// Used when LoopBodyCondPromoter fails to promote a LoopBodyLocal variable +/// used in loop/break conditions to a bool carrier. +/// +/// # Arguments +/// +/// * `vars` - Names of LoopBodyLocal variables that failed promotion +/// * `reason` - Human-readable reason for the failure +/// +/// # Example +/// +/// ``` +/// let err = format_error_pattern2_promotion_failed(&["ch"], "not a Trim pattern"); +/// // Returns: "[cf_loop/pattern2] Cannot promote LoopBodyLocal variables ["ch"]: not a Trim pattern" +/// ``` +pub fn format_error_pattern2_promotion_failed(vars: &[String], reason: &str) -> String { + format!( + "[cf_loop/pattern2] Cannot promote LoopBodyLocal variables {:?}: {}", + vars, reason + ) +} + +/// Pattern2: Trim pattern detected but not safe +/// +/// Used when a Trim pattern is detected but does not meet safety criteria. +/// +/// # Arguments +/// +/// * `carrier_name` - Name of the carrier variable +/// * `whitespace_count` - Number of whitespace characters detected +pub fn format_error_pattern2_trim_not_safe(carrier_name: &str, whitespace_count: usize) -> String { + format!( + "[cf_loop/pattern2] Trim pattern detected but not safe: carrier='{}', whitespace_count={}", + carrier_name, whitespace_count + ) +} + +#[cfg(test)] +mod pattern2_tests { + use super::*; + + #[test] + fn test_format_error_pattern2_promotion_failed() { + let vars = vec!["ch".to_string(), "digit_pos".to_string()]; + let err = format_error_pattern2_promotion_failed(&vars, "cascading LoopBodyLocal"); + + assert!(err.contains("[cf_loop/pattern2]")); + assert!(err.contains("Cannot promote")); + assert!(err.contains("ch")); + assert!(err.contains("digit_pos")); + assert!(err.contains("cascading LoopBodyLocal")); + } + + #[test] + fn test_format_error_pattern2_trim_not_safe() { + let err = format_error_pattern2_trim_not_safe("is_digit", 3); + + assert!(err.contains("[cf_loop/pattern2]")); + assert!(err.contains("Trim pattern")); + assert!(err.contains("not safe")); + assert!(err.contains("is_digit")); + assert!(err.contains("whitespace_count=3")); + } +} + +#[cfg(test)] +mod pattern4_tests { + use super::*; + + #[test] + fn test_format_error_pattern4_promotion_failed() { + let vars = vec!["ch".to_string(), "temp".to_string()]; + let err = format_error_pattern4_promotion_failed(&vars, "not a Trim pattern"); + + assert!(err.contains("[cf_loop/pattern4]")); + assert!(err.contains("Cannot promote")); + assert!(err.contains("ch")); + assert!(err.contains("temp")); + assert!(err.contains("not a Trim pattern")); + } + + #[test] + fn test_format_error_pattern4_trim_not_safe() { + let err = format_error_pattern4_trim_not_safe("is_whitespace", 5); + + assert!(err.contains("[cf_loop/pattern4]")); + assert!(err.contains("Trim pattern")); + assert!(err.contains("not safe")); + assert!(err.contains("is_whitespace")); + assert!(err.contains("whitespace_count=5")); + } + + #[test] + fn test_format_error_pattern4_lowering_failed() { + let err = format_error_pattern4_lowering_failed("JoinIR error"); + + assert!(err.contains("[cf_loop/pattern4]")); + assert!(err.contains("Lowering failed")); + assert!(err.contains("JoinIR error")); + } + + #[test] + fn test_format_error_pattern4_carrier_not_found() { + let err = format_error_pattern4_carrier_not_found("sum"); + + assert!(err.contains("[cf_loop/pattern4]")); + assert!(err.contains("Carrier 'sum'")); + assert!(err.contains("not found")); + assert!(err.contains("exit binding missing")); + } +}