feat(joinir): Phase 282 P3 - Pattern1 ExtractionBased Migration

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<Option<Parts>, 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 <noreply@anthropic.com>
This commit is contained in:
2025-12-23 06:09:57 +09:00
parent a2be79b875
commit 8d0808b6d4
4 changed files with 325 additions and 3 deletions

View File

@ -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<Option<Parts>, 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

View File

@ -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<Option<Pattern1Parts>, 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<String> {
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)
}
}

View File

@ -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

View File

@ -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<Option<ValueId>, 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)
}