feat(joinir): Phase 223.5 - LoopBodyCondPromoter Pattern2 integration
- 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 <noreply@anthropic.com>
This commit is contained in:
51
apps/tests/phase2235_p2_digit_pos_min.hako
Normal file
51
apps/tests/phase2235_p2_digit_pos_min.hako
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ use crate::ast::ASTNode;
|
|||||||
use crate::mir::builder::MirBuilder;
|
use crate::mir::builder::MirBuilder;
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
use super::super::trace;
|
use super::super::trace;
|
||||||
|
use crate::mir::loop_pattern_detection::error_messages;
|
||||||
|
|
||||||
/// Phase 185-2: Collect body-local variable declarations from loop body
|
/// Phase 185-2: Collect body-local variable declarations from loop body
|
||||||
///
|
///
|
||||||
@ -266,6 +267,84 @@ impl MirBuilder {
|
|||||||
break_condition_raw.clone()
|
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
|
// 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(
|
let effective_break_condition = if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -95,3 +95,210 @@ mod tests {
|
|||||||
assert!(err.contains("Pattern 4 supports"));
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user