phase29ap(p8): remove pattern4 legacy routing

This commit is contained in:
2025-12-31 06:49:41 +09:00
parent abff2ca682
commit 02a312a522
14 changed files with 61 additions and 936 deletions

View File

@ -0,0 +1,17 @@
// Phase 29ap P8: Pattern4 continue minimal fixture (plan routing)
// Expected output: 6
static box Main {
main() {
local i, sum
i = 0
sum = 0
loop(i < 4) {
if (i == 0) { i = i + 1; continue }
sum = sum + i
i = i + 1
}
print(sum)
return 0
}
}

View File

@ -3,7 +3,7 @@
## Current Focus
- Phase: `docs/development/current/main/phases/phase-29ap/README.md`
- Next: Phase 29ap P8 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`)
- Next: Phase 29ap P9 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`)
## Gate (SSOT)

View File

@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev
## Active
- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P8 planned)
- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P9 planned)
- JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md`
- CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md`

View File

@ -34,7 +34,7 @@ Related:
## 1.1 Current (active)
- Active phase: `docs/development/current/main/phases/phase-29ap/README.md`
- Next step: Phase 29ap P8 (planned)
- Next step: Phase 29ap P9 (planned)
## 2. すでに固めた SSOT再発防止の土台

View File

@ -10,6 +10,7 @@ Goal: JoinIR の最小回帰セットを SSOT として固定する。
- Pattern2 (release adopt, VM): `phase29ao_pattern2_release_adopt_vm`
- Pattern3 (IfPhi, VM): `phase118_pattern3_if_sum_vm`
- Pattern3 (release adopt, VM): `phase29ao_pattern3_release_adopt_vm`
- Pattern4 (continue min, VM): `phase29ap_pattern4_continue_min_vm`
- Pattern1 (strict shadow, VM): `phase29ao_pattern1_strict_shadow_vm`
- Pattern1 (subset reject, VM): `phase29ao_pattern1_subset_reject_extra_stmt_vm`
- Pattern1 (stdlib to_lower, VM): `phase29ap_stringutils_tolower_vm`

View File

@ -57,7 +57,7 @@ Gate (SSOT):
- Scope:
- Remove Pattern8 from `LOOP_PATTERNS` so plan/composer stays SSOT for normal loops.
- Keep legacy table as last resort for Pattern6_NestedLoopMinimal / Pattern4 / Pattern9 only.
- Keep legacy table as a last resort for the remaining legacy entries.
- Guardrails:
- No change to logs or error strings.
- Legacy routing remains a last-resort fallback.
@ -81,8 +81,6 @@ Gate (SSOT):
- No new logs or error strings.
- Subset only: if shape deviates, return `Ok(None)`.
## Next (planned)
## P7: Pattern9 legacy table removal ✅
- Scope:
@ -91,6 +89,15 @@ Gate (SSOT):
- Guardrails:
- No change to logs or error strings.
## P8: Remove Pattern4 legacy routing ✅
- Scope:
- Remove Pattern4_WithContinue from `LOOP_PATTERNS`.
- Add a minimal continue fixture + regression smoke under JoinIR gate.
- Guardrails:
- No change to logs or error strings.
- Plan/composer remains the only routing path for Pattern4.
## Next (planned)
- P8: Follow-up legacy table shrink or leave as-is with justification
- P9: Pattern6_NestedLoopMinimal migration/design (keep phase1883 gate green)

View File

@ -23,5 +23,5 @@
pub(crate) mod pattern1;
pub(crate) mod pattern2; // Phase 282 P4: Pattern2 extraction
pub(crate) mod pattern3; // Phase 282 P5: Pattern3 extraction
pub(crate) mod pattern4; // Phase 282 P6: Pattern4 extraction
// Pattern4 extraction migrated to plan routing (Phase 29ap P8).
// Pattern5/8/9 extractors moved to plan layer; joinir uses plan::single_planner routing.

View File

@ -1,3 +0,0 @@
//! Phase 29aj P1: Plan-layer extractor wrapper (SSOT)
pub(crate) use crate::mir::builder::control_flow::plan::extractors::pattern4::*;

View File

@ -3,7 +3,7 @@
//! Phase 2: Extracted from control_flow.rs
//! - Pattern 1: Simple While Loop (pattern1_minimal.rs)
//! - Pattern 3: Loop with If-Else PHI (pattern3_with_if_phi.rs)
//! - Pattern 4: Loop with Continue (pattern4_with_continue.rs) [Phase 194+]
//! - Pattern 4: Loop with Continue (migrated to plan routing in Phase 29ap P8)
//!
//! Phase 194: Table-driven router for pattern dispatch
//! - Router module provides table-driven pattern matching
@ -31,14 +31,13 @@
//! - condition_env_builder.rs: Unified ConditionEnv construction (Issue 5)
//!
//! Phase 33-23: Pattern-Specific Analyzers (Stage 2)
//! - pattern4_carrier_analyzer.rs: Pattern 4 carrier analysis and normalization (Issue 2)
//!
//! Stage 3 + Issue 1: Trim Pattern Extraction
//! - trim_pattern_validator.rs: Trim pattern validation and whitespace check generation
//! - trim_pattern_lowerer.rs: Trim-specific JoinIR lowering
//!
//! Phase 179-B: Generic Pattern Framework
//! - pattern_pipeline.rs: Unified preprocessing pipeline for Patterns 1-4
//! - pattern_pipeline.rs: Unified preprocessing pipeline for Patterns 1-3
//!
//! Phase 91 P5b: Escape Pattern Recognizer
//! - escape_pattern_recognizer.rs: P5b (escape sequence handling) pattern detection
@ -78,8 +77,6 @@ pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C
pub(in crate::mir::builder) mod loop_scope_shape_builder;
pub(in crate::mir::builder) mod pattern1_minimal;
pub(in crate::mir::builder) mod pattern3_with_if_phi;
pub(in crate::mir::builder) mod pattern4_carrier_analyzer;
pub(in crate::mir::builder) mod pattern4_with_continue;
pub(in crate::mir::builder) mod pattern6_nested_minimal; // Phase 188.3: 1-level nested loop (Pattern1 outer + Pattern1 inner)
pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid)
pub(in crate::mir::builder) mod pattern_pipeline;

View File

@ -1,366 +0,0 @@
//! Phase 33-23: Pattern 4 Carrier Analysis
//!
//! Extracts carrier analysis logic from pattern4_with_continue.rs.
//! Responsible for:
//! - Identifying which carriers are updated by continue branches
//! - Normalizing else-continue clauses
//! - Filtering out invalid carriers
//!
//! # Design Philosophy
//!
//! - **Single responsibility**: Focus only on carrier analysis for Pattern 4
//! - **Reusability**: Can be used by future continue-pattern variants
//! - **Testability**: Pure functions that are easy to unit test
//! - **Independence**: Does not depend on MirBuilder context
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar};
use crate::mir::join_ir::lowering::continue_branch_normalizer::ContinueBranchNormalizer;
use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr};
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
pub(crate) struct Pattern4CarrierAnalyzer;
impl Pattern4CarrierAnalyzer {
/// Analyze and filter carriers for continue pattern
///
/// Identifies which carriers are actually updated by continue branches
/// and filters out carriers that don't participate in the loop body.
///
/// # Arguments
///
/// * `loop_body` - The loop body AST nodes to analyze
/// * `all_carriers` - All potential carrier variables from initial analysis
///
/// # Returns
///
/// CarrierInfo containing only the carriers that are actually updated
///
/// # Example
///
/// ```nyash
/// local i = 0
/// local sum = 0
/// local M = 10 // Constant, should be filtered out
/// loop(i < M) {
/// i = i + 1
/// if i % 2 == 0 { continue }
/// sum = sum + i
/// }
/// // Result: carriers = [i, sum], NOT [i, sum, M]
/// ```
pub fn analyze_carriers(
loop_body: &[ASTNode],
all_carriers: &CarrierInfo,
) -> Result<CarrierInfo, String> {
// Identify which carriers are updated in loop body
let carrier_updates =
LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, &all_carriers.carriers);
// Filter carriers: only keep those that have update expressions
let updated_carriers: Vec<CarrierVar> = all_carriers
.carriers
.iter()
.filter(|carrier| carrier_updates.contains_key(&carrier.name))
.cloned()
.collect();
Ok(CarrierInfo {
loop_var_name: all_carriers.loop_var_name.clone(),
loop_var_id: all_carriers.loop_var_id,
carriers: updated_carriers,
trim_helper: all_carriers.trim_helper.clone(),
promoted_loopbodylocals: all_carriers.promoted_loopbodylocals.clone(), // Phase 224
#[cfg(feature = "normalized_dev")]
promoted_bindings: all_carriers.promoted_bindings.clone(), // Phase 76
})
}
/// Analyze carrier update expressions
///
/// Delegates to LoopUpdateAnalyzer but returns the result in a more
/// convenient form for Pattern 4 processing.
///
/// # Returns
///
/// Map from carrier name to UpdateExpr
pub fn analyze_carrier_updates(
loop_body: &[ASTNode],
carriers: &[CarrierVar],
) -> BTreeMap<String, UpdateExpr> {
// Phase 222.5-D: HashMap → BTreeMap for determinism
LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, carriers)
}
/// Normalize continue branches to standard form
///
/// Transforms else-continue patterns to a canonical form
/// for easier JoinIR lowering.
///
/// # Pattern
///
/// Transforms: `if (cond) { body } else { continue }`
/// Into: `if (!cond) { continue } else { body }`
///
/// # Arguments
///
/// * `body` - Loop body statements to normalize
///
/// # Returns
///
/// Normalized loop body with all continue statements in then branches
pub fn normalize_continue_branches(body: &[ASTNode]) -> Vec<ASTNode> {
ContinueBranchNormalizer::normalize_loop_body(body)
}
/// Verify continue pattern structure
///
/// Ensures the loop has proper continue structure for Pattern 4.
///
/// # Arguments
///
/// * `body` - Loop body statements to validate
///
/// # Returns
///
/// Ok(()) if continue structure is valid, Err(message) otherwise
#[allow(dead_code)]
pub fn validate_continue_structure(body: &[ASTNode]) -> Result<(), String> {
// Check for at least one continue statement
for stmt in body {
if Self::has_continue(stmt) {
return Ok(());
}
}
Err("No continue statement found in loop body".to_string())
}
/// Helper: Check if node or its children contain continue
///
/// Recursively searches the AST for continue statements.
///
/// # Arguments
///
/// * `node` - AST node to check
///
/// # Returns
///
/// true if the node or any of its children is a Continue statement
#[allow(dead_code)]
fn has_continue(node: &ASTNode) -> bool {
match node {
ASTNode::Continue { .. } => true,
ASTNode::If {
then_body,
else_body,
..
} => {
then_body.iter().any(|n| Self::has_continue(n))
|| else_body
.as_ref()
.map_or(false, |body| body.iter().any(|n| Self::has_continue(n)))
}
ASTNode::Loop { body, .. } => body.iter().any(|n| Self::has_continue(n)),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinaryOperator, LiteralValue, Span};
use crate::mir::ValueId;
#[test]
fn test_validate_continue_structure_present() {
// Test with continue statement
let body = vec![ASTNode::Continue {
span: Span::unknown(),
}];
assert!(Pattern4CarrierAnalyzer::validate_continue_structure(&body).is_ok());
}
#[test]
fn test_validate_continue_structure_absent() {
// Test without continue statement
let body = vec![ASTNode::Break {
span: Span::unknown(),
}];
assert!(Pattern4CarrierAnalyzer::validate_continue_structure(&body).is_err());
}
#[test]
fn test_has_continue_detection() {
let continue_node = ASTNode::Continue {
span: Span::unknown(),
};
assert!(Pattern4CarrierAnalyzer::has_continue(&continue_node));
let break_node = ASTNode::Break {
span: Span::unknown(),
};
assert!(!Pattern4CarrierAnalyzer::has_continue(&break_node));
}
#[test]
fn test_has_continue_nested_in_if() {
// if (x) { continue } else { ... }
let if_node = ASTNode::If {
condition: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: Span::unknown(),
}),
then_body: vec![ASTNode::Continue {
span: Span::unknown(),
}],
else_body: None,
span: Span::unknown(),
};
assert!(Pattern4CarrierAnalyzer::has_continue(&if_node));
}
#[test]
fn test_analyze_carriers_filtering() {
// Test that analyze_carriers filters out non-updated carriers
let span = Span::unknown();
// Create loop body: i = i + 1, sum = sum + i
let loop_body = vec![
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: span.clone(),
}),
value: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: span.clone(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: span.clone(),
}),
span: span.clone(),
}),
span: span.clone(),
},
ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "sum".to_string(),
span: span.clone(),
}),
value: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: "sum".to_string(),
span: span.clone(),
}),
right: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: span.clone(),
}),
span: span.clone(),
}),
span: span.clone(),
},
];
// Create CarrierInfo with i, sum, and M (constant)
let all_carriers = CarrierInfo {
loop_var_name: "i".to_string(),
loop_var_id: ValueId(0),
carriers: vec![
CarrierVar {
name: "i".to_string(),
host_id: ValueId(1),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
#[cfg(feature = "normalized_dev")]
binding_id: None,
},
CarrierVar {
name: "sum".to_string(),
host_id: ValueId(2),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
#[cfg(feature = "normalized_dev")]
binding_id: None,
},
CarrierVar {
name: "M".to_string(),
host_id: ValueId(3),
join_id: None,
role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState,
init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228
#[cfg(feature = "normalized_dev")]
binding_id: None,
},
],
trim_helper: None,
promoted_loopbodylocals: Vec::new(), // Phase 224
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(), // Phase 76
};
// Analyze carriers
let result = Pattern4CarrierAnalyzer::analyze_carriers(&loop_body, &all_carriers);
assert!(result.is_ok());
let filtered = result.unwrap();
// Should only have i and sum, not M
assert_eq!(filtered.carriers.len(), 2);
assert!(filtered.carriers.iter().any(|c| c.name == "i"));
assert!(filtered.carriers.iter().any(|c| c.name == "sum"));
assert!(!filtered.carriers.iter().any(|c| c.name == "M"));
}
#[test]
fn test_normalize_continue_branches() {
// Test normalization delegation
let span = Span::unknown();
// Create if-else-continue pattern
let body = vec![ASTNode::If {
condition: Box::new(ASTNode::Variable {
name: "x".to_string(),
span: span.clone(),
}),
then_body: vec![ASTNode::Assignment {
target: Box::new(ASTNode::Variable {
name: "y".to_string(),
span: span.clone(),
}),
value: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: span.clone(),
}),
span: span.clone(),
}],
else_body: Some(vec![ASTNode::Continue { span: span.clone() }]),
span: span.clone(),
}];
let normalized = Pattern4CarrierAnalyzer::normalize_continue_branches(&body);
// Should be transformed to if (!x) { continue } else { y = 1 }
assert_eq!(normalized.len(), 1);
if let ASTNode::If {
condition,
then_body,
..
} = &normalized[0]
{
// Condition should be negated
assert!(matches!(**condition, ASTNode::UnaryOp { .. }));
// Then body should be continue
assert_eq!(then_body.len(), 1);
assert!(matches!(then_body[0], ASTNode::Continue { .. }));
} else {
panic!("Expected If node");
}
}
}

View File

@ -1,534 +0,0 @@
//! Phase 33-21: Pattern 4 (Loop with Continue) Implementation
//!
//! **Current Status**: Fully implemented using Select-based continue semantics.
//!
//! ## Implementation Approach
//!
//! Continue is handled via Select instruction for carrier updates:
//! - When continue condition is true: carrier keeps its value (no update)
//! - When continue condition is false: carrier gets updated value
//!
//! This avoids the complexity of explicit latch blocks by using PHI-based
//! value selection at each iteration.
//!
//! ## Example
//!
//! ```nyash
//! local i = 0
//! local sum = 0
//! loop(i < 10) {
//! i = i + 1
//! if i % 2 == 0 { continue } // Skip even numbers
//! sum = sum + i
//! }
//! // sum = 25 (1+3+5+7+9)
//! ```
//!
//! ## Key Implementation Details
//!
//! - **Select instruction**: Used to conditionally skip carrier updates
//! - **Header PHI**: Tracks carrier values across iterations
//! - **ExitMeta**: Maps final carrier values to host variable slots
//! - **Phase 33-21 fix**: Correct remapping of function parameters to header PHI dsts
use super::super::trace;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::loop_pattern_detection::error_messages;
use crate::mir::ValueId;
use std::collections::BTreeMap;
// Phase 284 P1: has_return_in_body/has_return_node removed
// Return detection now handled by return_collector SSOT in conversion_pipeline.rs
/// Phase 282 P6: Detection function for Pattern 4 (ExtractionBased)
///
/// Migrated from StructureBased to ExtractionBased detection model.
/// Uses pure extractor (extract_loop_with_continue_parts) for SSOT.
///
/// Pattern 4 matches loops with continue statements.
///
/// # ExtractionBased Detection (Phase 282 P6)
///
/// Two-stage guard:
/// 1. **Safety valve**: ctx.pattern_kind == Pattern4Continue (O(1) perf guard)
/// 2. **Extractor**: extract_loop_with_continue_parts (SSOT)
///
/// # Detection Rules (from extractor)
///
/// 1. **Condition**: Comparison operator with left=variable (Pattern1 reuse)
/// 2. **Must have continue**: At least 1 continue statement (recursive detection)
/// 3. **No break**: break statements → Pattern2 territory
/// 4. **No return**: return statements → Err (close-but-unsupported, Phase 142 P2 pending)
/// 5. **Carrier validation**: Delegated to CommonPatternInitializer (existing logic)
///
/// If all conditions are met, Pattern 4 is detected.
pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
use super::common_init::CommonPatternInitializer;
use crate::mir::loop_pattern_detection::LoopPatternKind;
// Phase 282 P6 Step 1: Pattern kind safety valve (O(1) guard)
if ctx.pattern_kind != LoopPatternKind::Pattern4Continue {
return false;
}
// Phase 282 P6 Step 2: ExtractionBased detection (SSOT)
use super::extractors::pattern4::extract_loop_with_continue_parts;
match extract_loop_with_continue_parts(ctx.condition, ctx.body) {
Ok(Some(parts)) => {
trace::trace().debug(
"pattern4/can_lower",
&format!(
"✅ Pattern4 detected: loop_var='{}', continue_count={}",
parts.loop_var, parts.continue_count
),
);
// Phase 282 P6 Step 3: Carrier validation (existing logic preserved)
CommonPatternInitializer::check_carrier_updates_allowed(
ctx.body,
&parts.loop_var,
&builder.variable_ctx.variable_map,
)
}
Ok(None) => {
trace::trace().debug(
"pattern4/can_lower",
"Not Pattern4 (extraction returned None - structural mismatch)",
);
false
}
Err(e) => {
// USER CORRECTION: Log "unsupported" for Err cases (e.g., return found)
trace::trace().debug(
"pattern4/can_lower",
&format!("Pattern4 unsupported: {}", e),
);
false
}
}
}
/// Phase 282 P6: Lowering function for Pattern 4 (ExtractionBased)
///
/// Re-extracts pattern parts to enforce SSOT (extraction is single source of truth).
/// Wrapper around cf_loop_pattern4_with_continue to match router signature.
///
/// # Implementation (Phase 195-197 + Phase 282 P6)
///
/// 1. Re-extract pattern parts (SSOT enforcement - Phase 282 P6)
/// 2. Extract loop variables from condition
/// 3. Generate JoinIR with continue support (lower_loop_with_continue_minimal)
/// 4. Convert JoinModule → MirModule
/// 5. Create JoinInlineBoundary for input/output mapping
/// 6. Merge MIR blocks into current_function
/// 7. Return loop result (first carrier value)
pub(crate) fn lower(
builder: &mut MirBuilder,
ctx: &super::router::LoopPatternContext,
) -> Result<Option<ValueId>, String> {
// Phase 282 P6 Step 4: Re-extract to enforce SSOT (extraction must succeed in lower())
use super::extractors::pattern4::extract_loop_with_continue_parts;
let parts = match extract_loop_with_continue_parts(ctx.condition, ctx.body) {
Ok(Some(p)) => p,
Ok(None) => {
return Err(
"[pattern4/lower] Extraction returned None (should not happen - can_lower() passed)"
.to_string(),
);
}
Err(e) => {
// USER CORRECTION: Return Err directly for fail-fast (e.g., return found)
return Err(format!("[pattern4/lower] Extraction failed: {}", e));
}
};
trace::trace().debug(
"pattern4/lower",
&format!(
"Pattern4 lowering: loop_var='{}', continue_count={}",
parts.loop_var, parts.continue_count
),
);
// Phase 284 P1: Return check removed - now handled by return_collector SSOT
// in conversion_pipeline.rs (JoinIR line common entry point)
// Phase 33-19: Connect to actual implementation (zero behavior change)
builder.cf_loop_pattern4_with_continue(ctx.condition, ctx.body, ctx.func_name, ctx.debug)
}
impl MirBuilder {
/// Phase 179-B: Pattern 4 (Loop with Continue) lowerer
///
/// **Refactored**: Now uses PatternPipelineContext for unified preprocessing
///
/// # Pipeline (Phase 179-B)
/// 1. Build preprocessing context → PatternPipelineContext
/// 2. Pattern 4-specific processing (continue normalization, carrier analysis)
/// 3. Call JoinIR lowerer → JoinModule
/// 4. Create boundary from context → JoinInlineBoundary
/// 5. Merge MIR blocks → JoinIRConversionPipeline
///
/// # Example
///
/// ```nyash
/// local i = 0
/// local sum = 0
/// loop(i < 10) {
/// i = i + 1
/// if (i % 2 == 0) {
/// continue // Skip even numbers
/// }
/// sum = sum + i
/// }
/// // sum = 25 (1+3+5+7+9)
/// ```
///
/// Note: Pattern 4 has complex carrier analysis logic that remains inline
/// for now, using Pattern4CarrierAnalyzer for Select-based continue semantics.
pub(in crate::mir::builder) fn cf_loop_pattern4_with_continue(
&mut self,
condition: &ASTNode,
_body: &[ASTNode],
_func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
// Phase 195: Use unified trace
trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer");
let prepared = prepare_pattern4_context(self, condition, _body)?;
lower_pattern4_joinir(self, condition, &prepared, debug)
}
}
/// Preprocessed data for Pattern 4 lowering.
struct Pattern4Prepared<'a> {
loop_var_name: String,
loop_scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
carrier_info: crate::mir::join_ir::lowering::carrier_info::CarrierInfo,
carrier_updates: BTreeMap<String, UpdateExpr>,
/// Phase 284 P1: Reference to loop body for return detection
body: &'a [ASTNode],
}
/// Normalize, build context, analyze carriers, and promote loop-body locals.
fn prepare_pattern4_context<'a>(
builder: &mut MirBuilder,
condition: &ASTNode,
body: &'a [ASTNode],
) -> Result<Pattern4Prepared<'a>, String> {
use super::pattern4_carrier_analyzer::Pattern4CarrierAnalyzer;
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{
ConditionPromotionRequest, ConditionPromotionResult, LoopBodyCondPromoter,
};
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
// Normalize continue branches for analysis/lowering
let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(body);
// Build preprocessing context
let ctx = build_pattern_context(
builder,
condition,
&normalized_body,
PatternVariant::Pattern4,
)?;
let loop_var_name = ctx.loop_var_name.clone();
let loop_scope = ctx.loop_scope.clone();
let carrier_info_prelim = ctx.carrier_info.clone();
// Analyze carrier updates and filter carriers
let carrier_updates = Pattern4CarrierAnalyzer::analyze_carrier_updates(
&normalized_body,
&carrier_info_prelim.carriers,
);
let mut carrier_info =
Pattern4CarrierAnalyzer::analyze_carriers(&normalized_body, &carrier_info_prelim)?;
trace::trace().debug(
"pattern4",
&format!(
"CarrierInfo: loop_var={}, carriers={:?}",
carrier_info.loop_var_name,
carrier_info
.carriers
.iter()
.map(|c| &c.name)
.collect::<Vec<_>>()
),
);
trace::trace().debug(
"pattern4",
&format!(
"Analyzed {} carrier update expressions",
carrier_updates.len()
),
);
for (carrier_name, update_expr) in &carrier_updates {
trace::trace().debug(
"pattern4",
&format!(" {}{:?}", carrier_name, update_expr),
);
}
// LoopBodyLocal promotion for conditions (skip_whitespace etc.)
let continue_cond = LoopBodyCondPromoter::extract_continue_condition(&normalized_body);
let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond {
vec![condition, cont_cond]
} else {
vec![condition]
};
let cond_scope =
LoopConditionScopeBox::analyze(&loop_var_name, &conditions_to_analyze, Some(&loop_scope));
if cond_scope.has_loop_body_local() {
let promotion_req = ConditionPromotionRequest {
loop_param_name: &loop_var_name,
cond_scope: &cond_scope,
scope_shape: Some(&loop_scope),
break_cond: None, // Pattern 4 has no break
continue_cond,
loop_body: &normalized_body,
#[cfg(feature = "normalized_dev")]
// Phase 136 Step 4/7: Use binding_ctx for binding_map reference
binding_map: Some(builder.binding_ctx.binding_map()),
};
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {
ConditionPromotionResult::Promoted {
carrier_info: promoted_carrier,
promoted_var,
carrier_name,
} => {
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"LoopBodyLocal '{}' promoted to carrier '{}'",
promoted_var, carrier_name
),
);
#[cfg(feature = "normalized_dev")]
{
use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner;
let mut promoted_carrier = promoted_carrier;
CarrierBindingAssigner::assign_promoted_binding(
builder,
&mut promoted_carrier,
&promoted_var,
&carrier_name,
)
.map_err(|e| format!("[phase78/binding_assign] {:?}", e))?;
carrier_info.merge_from(&promoted_carrier);
}
#[cfg(not(feature = "normalized_dev"))]
{
carrier_info.merge_from(&promoted_carrier);
}
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"Merged carrier '{}' into CarrierInfo (total carriers: {})",
carrier_name,
carrier_info.carrier_count()
),
);
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
trace::trace().debug(
"pattern4/cond_promoter",
"Safe Trim/skip_whitespace pattern detected",
);
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"Carrier: '{}', original var: '{}', whitespace chars: {:?}",
helper.carrier_name, helper.original_var, helper.whitespace_chars
),
);
} else {
return Err(error_messages::format_error_pattern4_trim_not_safe(
&helper.carrier_name,
helper.whitespace_count(),
));
}
}
}
ConditionPromotionResult::CannotPromote { reason, vars } => {
return Err(error_messages::format_error_pattern4_promotion_failed(
&vars, &reason,
));
}
}
}
Ok(Pattern4Prepared {
loop_var_name,
loop_scope,
carrier_info,
carrier_updates,
body, // Phase 284 P1: Include body for return detection
})
}
/// JoinIR lowering stage for Pattern 4 using prepared context.
fn lower_pattern4_joinir(
builder: &mut MirBuilder,
condition: &ASTNode,
prepared: &Pattern4Prepared<'_>,
debug: bool,
) -> Result<Option<ValueId>, String> {
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
use super::conversion_pipeline::JoinIRConversionPipeline;
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
use crate::mir::join_ir::lowering::return_collector::{collect_return_from_body, ReturnInfo};
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
trace::trace().varmap("pattern4_start", &builder.variable_ctx.variable_map);
// Phase 284 P1: Detect return statements in body
let return_info: Option<ReturnInfo> = match collect_return_from_body(prepared.body) {
Ok(info) => info,
Err(e) => {
return Err(format!("[pattern4] Return detection failed: {}", e));
}
};
if let Some(ref ret_info) = return_info {
trace::trace().debug(
"pattern4",
&format!(
"Phase 284 P1: Return detected - value={}, has_condition={}, in_else={}",
ret_info.value,
ret_info.condition.is_some(),
ret_info.in_else
),
);
}
let mut join_value_space = JoinValueSpace::new();
#[cfg(feature = "normalized_dev")]
// Phase 136 Step 4/7: Use binding_ctx for binding_map clone
let binding_map_clone = builder.binding_ctx.binding_map().clone();
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(
prepared.loop_scope.clone(),
condition,
builder,
&prepared.carrier_info,
&prepared.carrier_updates,
&mut join_value_space,
return_info.as_ref(), // Phase 284 P1
#[cfg(feature = "normalized_dev")]
Some(&binding_map_clone),
) {
Ok(result) => result,
Err(e) => {
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Err(error_messages::format_error_pattern4_lowering_failed(&e));
}
};
trace::trace().debug(
"pattern4",
&format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len()),
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern4",
&format!(" {} → ValueId({})", carrier_name, join_value.0),
);
}
let exit_bindings = ExitMetaCollector::collect(
builder,
&exit_meta,
Some(&prepared.carrier_info), // Phase 228-8: Include ConditionOnly carriers
debug,
);
for carrier in &prepared.carrier_info.carriers {
if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
return Err(error_messages::format_error_pattern4_carrier_not_found(
&carrier.name,
));
}
}
// Phase 256.8.5: Use JoinModule.entry.params as SSOT (no hardcoded ValueIds)
use super::common::get_entry_function;
let main_func = get_entry_function(&join_module, "pattern4")?;
// SSOT: Use actual params allocated by JoinIR lowerer
let join_inputs = main_func.params.clone();
// Build host_inputs in same order (loop_var + carriers)
let mut host_inputs = vec![prepared.carrier_info.loop_var_id];
for carrier in &prepared.carrier_info.carriers {
host_inputs.push(carrier.host_id);
}
// Verify count consistency (fail-fast)
if join_inputs.len() != host_inputs.len() {
return Err(format!(
"[pattern4] Params count mismatch: join_inputs={}, host_inputs={}",
join_inputs.len(), host_inputs.len()
));
}
trace::trace().debug(
"pattern4",
&format!(
"join_inputs (SSOT): {:?}, host_inputs: {:?}",
join_inputs.iter().map(|v| v.0).collect::<Vec<_>>(),
host_inputs.iter().map(|v| v.0).collect::<Vec<_>>()
),
);
// Phase 284 P1: Build continuation set - include k_return if return_info exists
let continuation_funcs = {
use crate::mir::join_ir::lowering::canonical_names as cn;
let mut funcs = std::collections::BTreeSet::new();
funcs.insert(cn::K_EXIT.to_string());
if return_info.is_some() {
funcs.insert(cn::K_RETURN.to_string());
}
funcs
};
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs) // Dynamic carrier count
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(prepared.loop_var_name.clone()))
.with_carrier_info(prepared.carrier_info.clone())
.with_continuation_funcs(continuation_funcs) // Phase 284 P1
.build();
let _result_val = JoinIRConversionPipeline::execute(
builder,
join_module,
Some(&boundary),
"pattern4",
debug,
)?;
let void_val = crate::mir::builder::emission::constant::emit_void(builder);
trace::trace().debug(
"pattern4",
&format!("Loop complete, returning Void {:?}", void_val),
);
Ok(Some(void_val))
}

View File

@ -151,7 +151,7 @@ impl<'a> LoopPatternContext<'a> {
/// Phase 286 P2.2: Common helper for Plan line lowering
///
/// Extracts the common 3-line pattern used by Pattern6/7/4/1:
/// Extracts the common 3-line pattern used by Plan-based routing:
/// 1. Normalize DomainPlan → CorePlan
/// 2. Verify CorePlan invariants (fail-fast)
/// 3. Lower CorePlan → MIR
@ -180,7 +180,7 @@ fn lower_via_plan(
/// - Cons: Extraction can be expensive (but amortized over lowering)
///
/// ## StructureBased (Feature Classification)
/// - Used by: Pattern4 (legacy)
/// - Used by: Pattern6_NestedLoopMinimal (legacy)
/// - Strategy: Check pattern_kind (from LoopPatternContext), plus optional structural checks
/// - Pros: Fast classification, reuses centralized feature detection
/// - Cons: Two sources of truth (classify + structural checks)
@ -188,7 +188,7 @@ fn lower_via_plan(
/// ## Rationale for Dual Strategy:
/// - Pattern6/7: Complex extraction logic (variable step, carrier tracking)
/// → ExtractionBased avoids duplication between detect and extract
/// - Other patterns: Simple structural features (continue)
/// - Other patterns: Legacy structural features
/// → StructureBased leverages centralized LoopFeatures classification
///
/// This documentation prevents bugs like Phase 272 P0.2's Pattern7 issue
@ -201,7 +201,7 @@ pub(crate) enum CanLowerStrategy {
ExtractionBased,
/// Structure-based detection: Check pattern_kind from LoopPatternContext
/// Used by Pattern4
/// Used by legacy patterns
StructureBased,
}
@ -222,18 +222,13 @@ pub(crate) struct LoopPatternEntry {
///
/// **IMPORTANT**: Patterns are tried in array order (SSOT).
/// Array order defines priority - earlier entries are tried first.
/// Pattern6_NestedLoopMinimal → Pattern4
/// Pattern6_NestedLoopMinimal (legacy)
///
/// # Current Patterns (Structure-based detection, established Phase 131-11+)
///
/// Pattern detection strategies (updated Phase 286):
/// - Pattern6/7: ExtractionBased (Plan line, Phase 273+)
/// - Pattern8/9: ExtractionBased (extraction functions, Plan line Phase 286+)
/// - Pattern4: StructureBased (LoopFeatures classification, legacy)
///
/// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako)
/// - Detection: pattern_kind == Pattern4Continue
/// - Structure: has_continue && !has_break
///
/// - Pattern3: moved to plan-based routing (planner+composer SSOT)
///
@ -248,11 +243,6 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
detect: super::pattern6_nested_minimal::can_lower,
lower: super::pattern6_nested_minimal::lower,
},
LoopPatternEntry {
name: "Pattern4_WithContinue",
detect: super::pattern4_with_continue::can_lower,
lower: super::pattern4_with_continue::lower,
},
// Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing)
// Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer
// Phase 273 P2: Pattern7 entry removed (migrated to Plan-based routing)
@ -270,18 +260,18 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
/// This router uses multiple detection strategies:
/// - Plan-based (Pattern6/7): extract_*_plan() → DomainPlan (Phase 273+ SSOT)
/// - Extraction-based (Pattern8/9): extract_*() functions (already implemented)
/// - Structure-based (Pattern4): ctx.pattern_kind classification (legacy)
/// - Structure-based (Pattern6_NestedLoopMinimal): ctx.pattern_kind classification (legacy)
///
/// # Plan Line SSOT for Pattern6/7 (Phase 273+)
///
/// This function implements the following routing strategy:
/// 1. Try Plan-based Pattern6 (extract_scan_with_init_plan) → DomainPlan
/// 2. Try Plan-based Pattern7 (extract_split_scan_plan) → DomainPlan
/// 3. Fall through to legacy Pattern4 table for other patterns
/// 3. Fall through to legacy Pattern6_NestedLoopMinimal table for other patterns
///
/// The Plan line (Extractor → Normalizer → Verifier → Lowerer) is the
/// current operational SSOT for Pattern6/7. Legacy patterns (1/2/4/8/9) use
/// the traditional LoopPatternContext-based routing.
/// current operational SSOT for Pattern6/7. Legacy patterns
/// (Pattern6_NestedLoopMinimal) use the traditional LoopPatternContext-based routing.
///
/// Plan-based architecture (Phase 273 P1-P3):
/// - extract_*_plan() → DomainPlan (pure extraction, no builder)
@ -292,7 +282,6 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
/// SSOT Entry Points:
/// - Pattern6: src/mir/builder/control_flow/plan/normalizer.rs (ScanWithInit normalization)
/// - Pattern7: src/mir/builder/control_flow/plan/normalizer.rs (SplitScan normalization)
/// - Pattern4: src/mir/builder/control_flow/joinir/patterns/pattern*.rs (direct lowering)
/// - Pattern6_NestedLoopMinimal: src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs
pub(crate) fn route_loop_pattern(
builder: &mut MirBuilder,

View File

@ -23,6 +23,7 @@ run_filter "pattern2_subset" "phase29ai_pattern2_break_plan_subset_ok_min_vm" ||
run_filter "pattern2_release_adopt_vm" "phase29ao_pattern2_release_adopt_vm" || exit 1
run_filter "pattern3_ifphi_vm" "phase118_pattern3_if_sum_vm" || exit 1
run_filter "pattern3_release_adopt_vm" "phase29ao_pattern3_release_adopt_vm" || exit 1
run_filter "pattern4_continue_vm" "phase29ap_pattern4_continue_min_vm" || exit 1
run_filter "pattern1_strict_shadow_vm" "phase29ao_pattern1_strict_shadow_vm" || exit 1
run_filter "pattern1_subset_reject_extra_stmt_vm" "phase29ao_pattern1_subset_reject_extra_stmt_vm" || exit 1
run_filter "pattern1_stringutils_tolower_vm" "phase29ap_stringutils_tolower_vm" || exit 1

View File

@ -0,0 +1,16 @@
#!/bin/bash
# phase29ap_pattern4_continue_min_vm.sh - Pattern4 continue via plan routing (VM)
source "$(dirname "$0")/../../../lib/test_runner.sh"
require_env || exit 2
FIXTURE="$NYASH_ROOT/apps/tests/phase29ap_pattern4_continue_min.hako"
output=$(run_nyash_vm "$FIXTURE")
expected=$(cat << 'TXT'
6
TXT
)
compare_outputs "$expected" "$output" "phase29ap_pattern4_continue_min_vm" || exit 1