From 8d0808b6d42cc612a25850a8db21a51a4ec6a5fb Mon Sep 17 00:00:00 2001 From: tomoaki Date: Tue, 23 Dec 2025 06:09:57 +0900 Subject: [PATCH] feat(joinir): Phase 282 P3 - Pattern1 ExtractionBased Migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrated Pattern1 (Simple While Loop) from StructureBased to ExtractionBased detection, creating a migration template for Pattern2-5. **Changes**: 1. Created extractors/mod.rs - Module entry point with design principles 2. Created extractors/pattern1.rs - Pure extraction functions with 4-phase validation 3. Updated pattern1_minimal.rs - can_lower() + lower() use extraction 4. Updated mod.rs - Registered extractors module **Pattern1Parts Design** (Lightweight): - Stores only loop_var (AST reused from ctx, no heavy copies) - Validation: 比較条件 + no break/continue/if-else + 単純step (i = i ± const) - Return statements allowed (not loop control flow) **4-Phase Validation**: 1. Validate condition structure (比較演算, left=variable) 2. Validate body (no break/continue/if-else-phi) 3. Validate step pattern (simple i = i ± const only) 4. Extract loop variable **Safety Valve Strategy**: - pattern_kind as O(1) early rejection guard - Extraction as SSOT authoritative check - Re-extraction in lower() (no caching from can_lower) **Testing**: - Unit tests: 3/3 PASS - Build: 0 errors - Regression: 45/46 PASS (zero regression) **Migration Template**: - Result, String> return type (Pattern8/9 model) - Pure functions (no builder mutations) - Fail-Fast error handling (Err for logic bugs, Ok(None) for non-matches) 🎉 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../joinir/patterns/extractors/mod.rs | 22 ++ .../joinir/patterns/extractors/pattern1.rs | 244 ++++++++++++++++++ .../control_flow/joinir/patterns/mod.rs | 1 + .../joinir/patterns/pattern1_minimal.rs | 61 ++++- 4 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs create mode 100644 src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs new file mode 100644 index 00000000..b6298ea3 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs @@ -0,0 +1,22 @@ +//! Phase 282 P3: Common extraction interfaces for loop patterns +//! +//! This module provides pure extraction functions for pattern detection. +//! Extraction functions follow the ExtractionBased strategy (Phase 272 P0.2). +//! +//! # Design Principles +//! +//! - **Pure Functions**: No builder mutations, no side effects +//! - **Fail-Fast**: `Err` for logic bugs, `Ok(None)` for non-matches +//! - **SSOT**: Extraction is re-run in `lower()` (no caching assumptions) +//! - **Result, String>**: Pattern8/9 model +//! +//! # Pattern Migration Status +//! +//! - Pattern1: ✅ Migrated (Phase 282 P3) +//! - Pattern2-5: ⏸ Pending (future phases) +//! - Pattern6-7: ✅ Plan-based (Phase 273, different path) +//! - Pattern8-9: ✅ Already ExtractionBased (Phase 259/270) + +pub(crate) mod pattern1; + +// Future: pattern2, pattern3, pattern4, pattern5 diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs new file mode 100644 index 00000000..8c919fe4 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern1.rs @@ -0,0 +1,244 @@ +//! Phase 282 P3: Pattern1 (Simple While Loop) Extraction + +use crate::ast::{ASTNode, BinaryOperator}; + +#[derive(Debug, Clone)] +pub(crate) struct Pattern1Parts { + pub loop_var: String, + // Note: condition/body は ctx から再利用(AST 丸コピー不要) +} + +/// Extract Pattern1 (Simple While Loop) parts +/// +/// # Detection Criteria (誤マッチ防止強化版) +/// +/// 1. **Condition**: 比較演算(<, <=, >, >=, ==, !=)で左辺が変数 +/// 2. **Body**: No break/continue/if-else-phi (return is allowed - it's not loop control flow) +/// 3. **Step**: 単純な増減パターン (i = i + 1, i = i - 1 など) +/// +/// # Four-Phase Validation +/// +/// **Phase 1**: Validate condition structure (比較 + 左が変数) +/// **Phase 2**: Validate body (control flow check) +/// **Phase 3**: Validate step pattern (単純増減のみ) +/// **Phase 4**: Extract loop variable +/// +/// # Fail-Fast Rules +/// +/// - `Ok(Some(parts))`: Pattern1 match confirmed +/// - `Ok(None)`: Not Pattern1 (構造不一致 or control flow) +/// - `Err(msg)`: Logic bug (malformed AST) +pub(crate) fn extract_simple_while_parts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, String> { + // Phase 1: Validate condition structure (比較 + 左が変数) + let loop_var = match validate_condition_structure(condition) { + Some(var) => var, + None => return Ok(None), // 条件が Pattern1 形式でない + }; + + // Phase 2: Validate body (reject control flow) + if has_control_flow_statement(body) { + // Has break/continue/return → Not Pattern1 + return Ok(None); + } + + // Phase 3: Validate step pattern (単純増減のみ) + if !has_simple_step_pattern(body, &loop_var) { + // Step が複雑 or 存在しない → Not Pattern1 + return Ok(None); + } + + // Phase 4: Return extracted info + Ok(Some(Pattern1Parts { loop_var })) +} + +/// Validate condition: 比較演算 (左辺が変数) +fn validate_condition_structure(condition: &ASTNode) -> Option { + match condition { + ASTNode::BinaryOp { operator, left, .. } => { + // 比較演算子チェック + if !matches!( + operator, + BinaryOperator::Less + | BinaryOperator::LessEqual + | BinaryOperator::Greater + | BinaryOperator::GreaterEqual + | BinaryOperator::Equal + | BinaryOperator::NotEqual + ) { + return None; // 比較でない(算術演算など) + } + + // 左辺が変数であることを確認 + if let ASTNode::Variable { name, .. } = left.as_ref() { + return Some(name.clone()); + } + + None // 左辺が変数でない + } + _ => None, // 比較演算でない + } +} + +/// Validate step: 単純増減パターン (i = i ± const) +fn has_simple_step_pattern(body: &[ASTNode], loop_var: &str) -> bool { + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + // target が loop_var か確認 + if let ASTNode::Variable { name, .. } = target.as_ref() { + if name != loop_var { + continue; // 他の変数への代入 + } + + // value が "loop_var ± const" 形式か確認 + if let ASTNode::BinaryOp { + operator, + left, + right, + .. + } = value.as_ref() + { + // 演算子が + or - + if !matches!(operator, BinaryOperator::Add | BinaryOperator::Subtract) { + return false; // 複雑な演算 + } + + // 左辺が loop_var + if let ASTNode::Variable { name: left_var, .. } = left.as_ref() { + if left_var != loop_var { + return false; // i = j + 1 みたいなパターン + } + } else { + return false; // 左辺が変数でない + } + + // 右辺が定数 + if !matches!(right.as_ref(), ASTNode::Literal { .. }) { + return false; // i = i + j みたいなパターン + } + + return true; // ✅ 単純増減パターン確認 + } + + // value が他の形式(複雑な代入) + return false; + } + } + } + + // Step が見つからない → 単純ループでない + false +} + +/// Check if body has control flow statements +fn has_control_flow_statement(body: &[ASTNode]) -> bool { + for stmt in body { + if has_control_flow_recursive(stmt) { + return true; + } + } + false +} + +fn has_control_flow_recursive(node: &ASTNode) -> bool { + match node { + // Only break/continue are loop control flow - return is just early function exit (allowed) + ASTNode::Break { .. } | ASTNode::Continue { .. } => true, + ASTNode::If { + then_body, + else_body, + .. + } => { + // Check for if-else-phi pattern (Pattern3 territory) + if else_body.is_some() { + // Has else branch - potential Pattern3 + return true; + } + // Check nested statements + then_body.iter().any(has_control_flow_recursive) + || else_body + .as_ref() + .map_or(false, |b| b.iter().any(has_control_flow_recursive)) + } + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{LiteralValue, Span}; + + #[test] + fn test_extract_simple_while_success() { + // loop(i < 10) { i = i + 1 } + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(10), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "i".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_some()); + } + + #[test] + fn test_extract_with_break_returns_none() { + let condition = ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }; + let body = vec![ASTNode::Break { + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has break → Ok(None) + } + + #[test] + fn test_extract_with_continue_returns_none() { + let condition = ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }; + let body = vec![ASTNode::Continue { + span: Span::unknown(), + }]; + + let result = extract_simple_while_parts(&condition, &body); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); // Has continue → Ok(None) + } +} diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index 869e6284..c335e55f 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -53,6 +53,7 @@ //! - common/: Shared helper functions (var() etc.) to eliminate code duplication pub(in crate::mir::builder) mod common; // Phase 255 P2: Common AST helpers +pub(in crate::mir::builder) mod extractors; // Phase 282 P3: Common extraction interfaces pub(in crate::mir::builder) mod ast_feature_extractor; pub(in crate::mir::builder) mod policies; // Phase 93/94: Pattern routing policies (future expansion) pub(in crate::mir::builder) mod body_local_policy; // Phase 92 P3: promotion vs slot routing diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs index c0d5a690..de211f7f 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs @@ -7,22 +7,77 @@ use crate::mir::ValueId; /// Phase 194: Detection function for Pattern 1 /// -/// Phase 192: Updated to structure-based detection +/// Phase 282 P3: Updated to ExtractionBased detection with safety valve /// /// Pattern 1 matches: -/// - Pattern kind is Pattern1SimpleWhile (no break, no continue, no if-else PHI) +/// - Pattern kind is Pattern1SimpleWhile (safety valve, O(1) early rejection) +/// - Extraction validates: 比較条件 + no control flow + 単純step pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use crate::mir::loop_pattern_detection::LoopPatternKind; - ctx.pattern_kind == LoopPatternKind::Pattern1SimpleWhile + + // Step 1: Early rejection guard (safety valve, O(1)) + if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile { + if ctx.debug { + trace::trace().debug( + "pattern1/can_lower", + &format!("reject: pattern_kind={:?}", ctx.pattern_kind), + ); + } + return false; + } + + // Step 2: ExtractionBased validation (SSOT, deep check) + use super::extractors::pattern1::extract_simple_while_parts; + + match extract_simple_while_parts(ctx.condition, ctx.body) { + Ok(Some(_)) => { + if ctx.debug { + trace::trace().debug("pattern1/can_lower", "accept: extractable (Phase 282 P3)"); + } + true + } + Ok(None) => { + if ctx.debug { + trace::trace().debug( + "pattern1/can_lower", + "reject: not simple while (has control flow or complex step)", + ); + } + false + } + Err(e) => { + if ctx.debug { + trace::trace().debug("pattern1/can_lower", &format!("error: {}", e)); + } + false + } + } } /// Phase 194: Lowering function for Pattern 1 /// +/// Phase 282 P3: Re-extracts for SSOT (no caching from can_lower) +/// /// Wrapper around cf_loop_pattern1_minimal to match router signature pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { + use super::extractors::pattern1::extract_simple_while_parts; + + // Re-extract (SSOT principle - no caching from can_lower) + let parts = extract_simple_while_parts(ctx.condition, ctx.body)? + .ok_or_else(|| "[pattern1] Not a simple while pattern in lower()".to_string())?; + + if ctx.debug { + trace::trace().debug( + "pattern1/lower", + &format!("loop_var={} (Phase 282 P3)", parts.loop_var), + ); + } + + // Call existing internal lowerer (ctx.condition/ctx.body を直接使用) + // Note: parts は検証結果のみで、AST は ctx から再利用 builder.cf_loop_pattern1_minimal(ctx.condition, ctx.body, ctx.func_name, ctx.debug) }