feat(joinir): Phase 286 P2.6 - Pattern3 Plan 基盤 + Pattern1 退行修正

## Pattern1 退行修正(構造的 Fail-Fast)
- Router に pattern_kind ガード追加(ctx.pattern_kind != Pattern1SimpleWhile → skip)
- has_if_else_statement() ヘルパー追加(再帰版、ScopeBox/Loop 内もチェック)
- Pattern1 extractor に if-else 拒否追加

## Pattern3 Plan 基盤
- DomainPlan: Pattern3IfPhiPlan 構造体追加
- Extractor: extract_pattern3_plan() 追加
- Normalizer: normalize_pattern3_if_phi() スタブ追加(レガシー JoinIR へフォールバック)
- Router: PLAN_EXTRACTORS に Pattern3 追加

## テスト結果
- quick 154 PASS
- Phase 118 smoke PASS(出力 12、退行解消)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 04:01:11 +09:00
parent 21daf1b7dd
commit 4d17e3d812
7 changed files with 307 additions and 2 deletions

View File

@ -144,6 +144,62 @@ pub(crate) fn has_control_flow_statement(body: &[ASTNode]) -> bool {
counts.break_count > 0 || counts.continue_count > 0
}
/// Phase 286 P2.6: Check if body has ANY if statement (recursive)
///
/// This is a supplementary helper for Pattern1 extraction to prevent
/// Pattern1 from incorrectly matching Pattern3 fixtures (if-else-phi).
///
/// # Returns
/// - true if any if statement found in body (recursively)
/// - false otherwise
pub(crate) fn has_if_statement(body: &[ASTNode]) -> bool {
for node in body {
match node {
ASTNode::If { .. } => return true,
ASTNode::ScopeBox { body, .. } => {
if has_if_statement(body) {
return true;
}
}
ASTNode::Loop { body, .. } => {
if has_if_statement(body) {
return true;
}
}
_ => {}
}
}
false
}
/// Phase 286 P2.6: Check if body has ANY if-else statement (recursive)
///
/// This is more specific than has_if_statement - it only detects if statements
/// with else branches, which are Pattern3 territory (if-phi merge).
///
/// # Returns
/// - true if any if-else statement found in body (recursively)
/// - false otherwise
pub(crate) fn has_if_else_statement(body: &[ASTNode]) -> bool {
for node in body {
match node {
ASTNode::If { else_body: Some(_), .. } => return true,
ASTNode::ScopeBox { body, .. } => {
if has_if_else_statement(body) {
return true;
}
}
ASTNode::Loop { body, .. } => {
if has_if_else_statement(body) {
return true;
}
}
_ => {}
}
}
false
}
/// ============================================================
/// Group 3: Condition Validation (比較演算検証)
/// ============================================================

View File

@ -48,6 +48,13 @@ pub(crate) fn extract_simple_while_parts(
return Ok(None);
}
// Phase 286 P2.6: Reject if-else statements (Pattern3 territory)
// Pattern1 allows simple if without else, but not if-else (which is Pattern3)
if super::common_helpers::has_if_else_statement(body) {
// Has if-else statement → Pattern3 (if-phi merge)
return Ok(None);
}
// Phase 3: Validate step pattern (単純増減のみ)
if !has_simple_step_pattern(body, &loop_var) {
// Step が複雑 or 存在しない → Not Pattern1

View File

@ -507,3 +507,97 @@ mod tests {
assert!(result.unwrap().is_none()); // Has break → Not Pattern3
}
}
// ============================================================================
// Phase 286 P2.6: Pattern3 → Plan/Frag SSOT extractor
// ============================================================================
/// Phase 286 P2.6: Extract Pattern3 (Loop with If-Phi) Plan
///
/// Leverages existing `extract_loop_with_if_phi_parts()` for validation,
/// then extracts detailed AST components for Plan normalization.
///
/// # Returns
/// - Ok(Some(DomainPlan::Pattern3IfPhi)) if Pattern3 match confirmed
/// - Ok(None) if not Pattern3 (structural mismatch)
/// - Err if malformed AST (rare)
pub(crate) fn extract_pattern3_plan(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String> {
use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern3IfPhiPlan};
// Step 1: Validate via existing extractor
let parts = extract_loop_with_if_phi_parts(condition, body)?;
if parts.is_none() {
return Ok(None); // Not Pattern3 → fallback
}
let parts = parts.unwrap();
// Step 2: Extract if-else statement (validated to exist)
let if_stmt = find_if_else_statement(body)
.ok_or_else(|| "Pattern3: if-else statement not found after validation".to_string())?;
// Step 3: Extract if condition and branch updates
let (if_condition, then_update, else_update) = match if_stmt {
ASTNode::If {
condition: if_cond,
then_body,
else_body: Some(else_body),
..
} => {
// Extract carrier update from then branch
let then_update = extract_single_update(then_body, &parts.merged_var)?;
// Extract carrier update from else branch
let else_update = extract_single_update(else_body, &parts.merged_var)?;
(if_cond.as_ref().clone(), then_update, else_update)
}
_ => {
return Err("Pattern3: if-else structure mismatch after validation".to_string());
}
};
// Step 4: Extract loop increment
let loop_increment = match super::common_helpers::extract_loop_increment_plan(body, &parts.loop_var)? {
Some(inc) => inc,
None => {
return Err(format!(
"Pattern3: loop increment not found for variable '{}'",
parts.loop_var
));
}
};
Ok(Some(DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan {
loop_var: parts.loop_var,
carrier_var: parts.merged_var,
condition: condition.clone(),
if_condition,
then_update,
else_update,
loop_increment,
})))
}
/// Extract update expression from a branch body
///
/// Expects a single assignment: `carrier = <expr>`
/// Returns the RHS expression AST
fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Result<ASTNode, String> {
for stmt in body {
if let ASTNode::Assignment { target, value, .. } = stmt {
if let ASTNode::Variable { name, .. } = target.as_ref() {
if name == carrier_var {
return Ok(value.as_ref().clone());
}
}
}
}
Err(format!(
"Pattern3: carrier update not found for variable '{}' in branch",
carrier_var
))
}

View File

@ -190,6 +190,7 @@ enum PlanExtractorVariant {
/// Order is important: more specific patterns first
/// - Pattern6/7: Need fn_body for capture analysis
/// - Pattern8: Bool predicate scan (early-exit return, more specific)
/// - Pattern3: If-phi merge (carrier with conditional update, more specific than Pattern4)
/// - Pattern4: Continue loop (more specific than Pattern1)
/// - Pattern9: Accum const loop (2 carriers, more specific than Pattern1)
/// - Pattern1: Simple while (fallback)
@ -206,6 +207,10 @@ static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[
name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan),
},
PlanExtractorEntry {
name: "Pattern3_IfPhi (Phase 286 P2.6)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern3::extract_pattern3_plan),
},
PlanExtractorEntry {
name: "Pattern4_Continue (Phase 286 P2)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern4::extract_pattern4_plan),
@ -232,6 +237,16 @@ fn try_plan_extractors(
use super::super::trace;
for entry in PLAN_EXTRACTORS {
// Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast)
// Pattern1 should only match Pattern1SimpleWhile pattern_kind
// This prevents Pattern1 from incorrectly matching Pattern3 fixtures
if entry.name.contains("Pattern1") {
use crate::mir::loop_pattern_detection::LoopPatternKind;
if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile {
continue;
}
}
// Try extraction based on variant
let plan_opt = match &entry.extractor {
PlanExtractorVariant::Simple(extractor) => {
@ -266,6 +281,23 @@ fn try_plan_extractors(
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);
// Phase 286 P2.6: Pattern3 PoC exception - allow fallback to legacy
// Pattern3 extractor validates structure, but normalizer is stub
if entry.name.contains("Pattern3") {
match lower_via_plan(builder, domain_plan, ctx) {
Ok(value_id) => return Ok(value_id),
Err(_) => {
if ctx.debug {
trace::trace().debug(
"route/plan",
"Pattern3 Plan normalization stub - fallback to legacy Pattern3",
);
}
continue; // Try next extractor (will eventually reach legacy Pattern3)
}
}
}
// Phase 286 P2.4.1: Fail-Fast - extract 成功 → normalize/lower 失敗は即 Err
// 構造的 Fail-Fast: 文字列判定なし、extract が Some なら fallback 禁止
return lower_via_plan(builder, domain_plan, ctx);

View File

@ -53,6 +53,8 @@ pub(in crate::mir::builder) enum DomainPlan {
Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan),
/// Pattern8: Boolean Predicate Scan (Phase 286 P2.4)
Pattern8BoolPredicateScan(Pattern8BoolPredicateScanPlan),
/// Pattern3: Loop with If-Phi (Phase 286 P2.6)
Pattern3IfPhi(Pattern3IfPhiPlan),
}
/// Phase 273 P0: Scan direction for forward/reverse scan
@ -176,6 +178,53 @@ pub(in crate::mir::builder) struct Pattern8BoolPredicateScanPlan {
pub step_lit: i64,
}
/// Phase 286 P2.6: Extracted structure for Pattern3 (Loop with If-Phi)
///
/// This structure contains all the information needed to lower an if-phi merge loop.
/// Pattern3 is a loop with conditional carrier update via if-else branching.
///
/// # Structure
/// ```text
/// loop(i < N) {
/// if (condition) {
/// carrier = then_update
/// } else {
/// carrier = else_update
/// }
/// i = i + step
/// }
/// ```
///
/// # CFG Layout
/// ```text
/// preheader → header(PHI: i, carrier) → body(if_condition)
/// ↓ ↓
/// after then | else
/// ↓ ↓
/// merge(PHI: carrier)
/// ↓
/// step(i_next)
/// ↓
/// back-edge to header
/// ```
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern3IfPhiPlan {
/// Loop variable name (e.g., "i")
pub loop_var: String,
/// Carrier variable name (e.g., "sum")
pub carrier_var: String,
/// Loop condition AST (e.g., `i < 3`)
pub condition: ASTNode,
/// If condition AST (e.g., `i > 0`)
pub if_condition: ASTNode,
/// Then branch update AST (e.g., `sum + 1`)
pub then_update: ASTNode,
/// Else branch update AST (e.g., `sum + 0`)
pub else_update: ASTNode,
/// Loop increment expression AST (e.g., `i + 1`)
pub loop_increment: ASTNode,
}
// ============================================================================
// CorePlan (固定語彙 - 構造ノードのみ)
// ============================================================================

View File

@ -14,7 +14,7 @@
use super::{
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan,
ScanWithInitPlan, SplitScanPlan, Pattern1SimpleWhilePlan, Pattern9AccumConstLoopPlan,
Pattern8BoolPredicateScanPlan,
Pattern8BoolPredicateScanPlan, Pattern3IfPhiPlan,
};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
@ -44,6 +44,7 @@ impl PlanNormalizer {
DomainPlan::Pattern1SimpleWhile(parts) => Self::normalize_pattern1_simple_while(builder, parts, ctx),
DomainPlan::Pattern9AccumConstLoop(parts) => Self::normalize_pattern9_accum_const_loop(builder, parts, ctx),
DomainPlan::Pattern8BoolPredicateScan(parts) => Self::normalize_pattern8_bool_predicate_scan(builder, parts, ctx),
DomainPlan::Pattern3IfPhi(parts) => Self::normalize_pattern3_if_phi(builder, parts, ctx),
}
}
@ -2044,4 +2045,35 @@ impl PlanNormalizer {
Ok(CorePlan::Loop(loop_plan))
}
/// Phase 286 P2.6: Pattern3 (Loop with If-Phi) → CorePlan 変換
///
/// # Status: PoC Stub
///
/// This is a minimal stub implementation for Phase 286 P2.6 PoC.
/// The extractor works and will be called, but this normalizer
/// returns an error to fallback to the legacy Pattern3 implementation.
///
/// # CFG Structure (Design - not yet implemented)
/// ```text
/// preheader → header(PHI: i, carrier) → body(if_condition)
/// ↓ ↓
/// after then | else
/// ↓ ↓
/// merge(PHI: carrier)
/// ↓
/// step(i_next)
/// ↓
/// back-edge to header
/// ```
fn normalize_pattern3_if_phi(
_builder: &mut MirBuilder,
_parts: Pattern3IfPhiPlan,
_ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
// Phase 286 P2.6 PoC: Stub implementation
// Extractor validates Pattern3 structure, but normalization is deferred
// Fallback to legacy Pattern3 implementation via Ok(None) in router
Err("Pattern3 Plan normalization not yet implemented (PoC stub - fallback to legacy)".to_string())
}
}