diff --git a/docs/development/current/main/phase170-c2-update-summary-design.md b/docs/development/current/main/phase170-c2-update-summary-design.md new file mode 100644 index 00000000..ffabc5ac --- /dev/null +++ b/docs/development/current/main/phase170-c2-update-summary-design.md @@ -0,0 +1,186 @@ +# Phase 170-C-2: LoopUpdateSummaryBox 設計 + +## 概要 + +CaseALoweringShape の検出精度を向上させるため、ループの更新パターンを解析する専用 Box を導入する。 + +## 背景 + +### 現状 (Phase 170-C-1) +- `detect_with_carrier_name()` で carrier 名ヒューリスティックを使用 +- `i`, `e`, `idx` → StringExamination +- その他 → ArrayAccumulation + +### 問題点 +- 名前だけでは不正確(`sum` という名前でも CounterLike かもしれない) +- 実際の更新式を見ていない + +## 設計 + +### 1. UpdateKind 列挙型 + +```rust +/// キャリア変数の更新パターン +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateKind { + /// カウンタ系: i = i + 1, i = i - 1, i += 1 + /// 典型的な skip/trim パターン + CounterLike, + + /// 蓄積系: result = result + x, arr.push(x), list.append(x) + /// 典型的な collect/filter パターン + AccumulationLike, + + /// 判定不能 + Other, +} +``` + +### 2. CarrierUpdateInfo 構造体 + +```rust +/// 単一キャリアの更新情報 +#[derive(Debug, Clone)] +pub struct CarrierUpdateInfo { + /// キャリア変数名 + pub name: String, + + /// 更新パターン + pub kind: UpdateKind, +} +``` + +### 3. LoopUpdateSummary 構造体 + +```rust +/// ループ全体の更新サマリ +#[derive(Debug, Clone, Default)] +pub struct LoopUpdateSummary { + /// 各キャリアの更新情報 + pub carriers: Vec, +} + +impl LoopUpdateSummary { + /// 単一 CounterLike キャリアを持つか + pub fn has_single_counter(&self) -> bool { + self.carriers.len() == 1 + && self.carriers[0].kind == UpdateKind::CounterLike + } + + /// AccumulationLike キャリアを含むか + pub fn has_accumulation(&self) -> bool { + self.carriers.iter().any(|c| c.kind == UpdateKind::AccumulationLike) + } +} +``` + +### 4. analyze_loop_updates_ast 関数 + +```rust +/// AST からループ更新パターンを解析 +/// +/// # Phase 170-C-2 暫定実装 +/// - 名前ヒューリスティックを内部で使用 +/// - 将来的に AST 解析に置き換え +pub fn analyze_loop_updates_ast( + _condition: &ASTNode, + _body: &[ASTNode], + carrier_names: &[String], +) -> LoopUpdateSummary { + let carriers = carrier_names + .iter() + .map(|name| { + let kind = if is_typical_index_name(name) { + UpdateKind::CounterLike + } else { + UpdateKind::AccumulationLike + }; + CarrierUpdateInfo { + name: name.clone(), + kind, + } + }) + .collect(); + + LoopUpdateSummary { carriers } +} +``` + +## LoopFeatures への統合 + +```rust +pub struct LoopFeatures { + pub has_break: bool, + pub has_continue: bool, + pub has_if: bool, + pub has_if_else_phi: bool, + pub carrier_count: usize, + pub break_count: usize, + pub continue_count: usize, + + // Phase 170-C-2 追加 + pub update_summary: Option, +} +``` + +## CaseALoweringShape での利用 + +```rust +pub fn detect_from_features( + features: &LoopFeatures, + carrier_count: usize, + has_progress_carrier: bool, +) -> Self { + // ... 既存チェック ... + + // Phase 170-C-2: UpdateSummary を優先 + if let Some(ref summary) = features.update_summary { + if summary.has_single_counter() { + return CaseALoweringShape::StringExamination; + } + if summary.has_accumulation() { + return CaseALoweringShape::ArrayAccumulation; + } + } + + // フォールバック: carrier 数のみ + match carrier_count { + 1 => CaseALoweringShape::Generic, + 2.. => CaseALoweringShape::IterationWithAccumulation, + _ => CaseALoweringShape::NotCaseA, + } +} +``` + +## ファイル配置 + +``` +src/mir/join_ir/lowering/ +├── loop_update_summary.rs # 新規: UpdateKind, LoopUpdateSummary +├── loop_to_join.rs # 更新: analyze_loop_updates_ast 呼び出し +└── loop_scope_shape/ + └── case_a_lowering_shape.rs # 更新: UpdateSummary 参照 +``` + +## 移行計画 + +### Phase 170-C-2a (今回) +- `loop_update_summary.rs` 骨格作成 +- 名前ヒューリスティックを内部に閉じ込め +- LoopFeatures への統合は保留 + +### Phase 170-C-2b (将来) +- AST 解析で実際の更新式を判定 +- `i = i + 1` → CounterLike +- `result.push(x)` → AccumulationLike + +### Phase 170-C-3 (将来) +- MIR ベース解析(AST が使えない場合の代替) +- BinOp 命令パターンマッチング + +## 利点 + +1. **関心の分離**: 更新パターン解析が独立した Box に +2. **差し替え容易**: 名前 → AST → MIR と段階的に精度向上可能 +3. **テスト容易**: LoopUpdateSummary を直接テスト可能 +4. **後方互換**: LoopFeatures.update_summary は Option なので影響最小 diff --git a/src/mir/join_ir/lowering/loop_update_summary.rs b/src/mir/join_ir/lowering/loop_update_summary.rs new file mode 100644 index 00000000..0a6c9d9a --- /dev/null +++ b/src/mir/join_ir/lowering/loop_update_summary.rs @@ -0,0 +1,211 @@ +//! Phase 170-C-2: LoopUpdateSummary - ループ更新パターン解析 +//! +//! キャリア変数の更新パターン(CounterLike / AccumulationLike)を判定し、 +//! CaseALoweringShape の検出精度を向上させる。 +//! +//! ## 設計思想 +//! +//! - 責務: AST のループ body から「各キャリアがどう更新されているか」を判定 +//! - 差し替え可能: 名前ヒューリスティック → AST 解析 → MIR 解析と段階的に精度向上 +//! - LoopFeatures / CaseALoweringShape から独立したモジュール +//! +//! ## 使用例 +//! +//! ```ignore +//! let summary = analyze_loop_updates(&carrier_names); +//! if summary.has_single_counter() { +//! // StringExamination パターン +//! } +//! ``` + +/// キャリア変数の更新パターン +/// +/// Phase 170-C-2: 3種類のパターンを区別 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateKind { + /// カウンタ系: i = i + 1, i = i - 1, i += 1 + /// + /// 典型的な skip/trim パターン。進捗変数として使われる。 + CounterLike, + + /// 蓄積系: result = result + x, arr.push(x), list.append(x) + /// + /// 典型的な collect/filter パターン。結果を蓄積する変数。 + AccumulationLike, + + /// 判定不能 + /// + /// 複雑な更新パターン、または解析できなかった場合。 + Other, +} + +impl UpdateKind { + /// デバッグ用の名前を返す + pub fn name(&self) -> &'static str { + match self { + UpdateKind::CounterLike => "CounterLike", + UpdateKind::AccumulationLike => "AccumulationLike", + UpdateKind::Other => "Other", + } + } +} + +/// 単一キャリアの更新情報 +#[derive(Debug, Clone)] +pub struct CarrierUpdateInfo { + /// キャリア変数名 + pub name: String, + + /// 更新パターン + pub kind: UpdateKind, +} + +/// ループ全体の更新サマリ +/// +/// Phase 170-C-2: CaseALoweringShape の判定入力として使用 +#[derive(Debug, Clone, Default)] +pub struct LoopUpdateSummary { + /// 各キャリアの更新情報 + pub carriers: Vec, +} + +impl LoopUpdateSummary { + /// 空のサマリを作成 + pub fn empty() -> Self { + Self { carriers: vec![] } + } + + /// 単一 CounterLike キャリアを持つか + /// + /// StringExamination パターンの判定に使用 + pub fn has_single_counter(&self) -> bool { + self.carriers.len() == 1 && self.carriers[0].kind == UpdateKind::CounterLike + } + + /// AccumulationLike キャリアを含むか + /// + /// ArrayAccumulation パターンの判定に使用 + pub fn has_accumulation(&self) -> bool { + self.carriers + .iter() + .any(|c| c.kind == UpdateKind::AccumulationLike) + } + + /// CounterLike キャリアの数 + pub fn counter_count(&self) -> usize { + self.carriers + .iter() + .filter(|c| c.kind == UpdateKind::CounterLike) + .count() + } + + /// AccumulationLike キャリアの数 + pub fn accumulation_count(&self) -> usize { + self.carriers + .iter() + .filter(|c| c.kind == UpdateKind::AccumulationLike) + .count() + } +} + +/// キャリア名から UpdateKind を推定(暫定実装) +/// +/// Phase 170-C-2a: 名前ヒューリスティック +/// Phase 170-C-2b: AST 解析に置き換え予定 +fn infer_update_kind_from_name(name: &str) -> UpdateKind { + // 典型的なインデックス変数名 → CounterLike + if is_typical_index_name(name) { + return UpdateKind::CounterLike; + } + + // その他 → AccumulationLike(蓄積系と推定) + UpdateKind::AccumulationLike +} + +/// 典型的なインデックス変数名か判定 +/// +/// Phase 170-C-1 から移植 +fn is_typical_index_name(name: &str) -> bool { + matches!( + name, + "i" | "e" | "idx" | "index" | "pos" | "position" | "start" | "end" | "n" | "j" | "k" + ) +} + +/// キャリア名リストからループ更新サマリを作成 +/// +/// # Phase 170-C-2 暫定実装 +/// +/// 現在は名前ヒューリスティックを使用。 +/// 将来的に AST/MIR 解析に置き換え可能。 +/// +/// # Arguments +/// +/// * `carrier_names` - キャリア変数名のリスト +/// +/// # Returns +/// +/// 各キャリアの更新パターンをまとめた LoopUpdateSummary +pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary { + let carriers = carrier_names + .iter() + .map(|name| CarrierUpdateInfo { + name: name.clone(), + kind: infer_update_kind_from_name(name), + }) + .collect(); + + LoopUpdateSummary { carriers } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_update_kind_name() { + assert_eq!(UpdateKind::CounterLike.name(), "CounterLike"); + assert_eq!(UpdateKind::AccumulationLike.name(), "AccumulationLike"); + assert_eq!(UpdateKind::Other.name(), "Other"); + } + + #[test] + fn test_typical_index_names() { + assert!(is_typical_index_name("i")); + assert!(is_typical_index_name("idx")); + assert!(is_typical_index_name("pos")); + assert!(!is_typical_index_name("result")); + assert!(!is_typical_index_name("sum")); + } + + #[test] + fn test_analyze_single_counter() { + let names = vec!["i".to_string()]; + let summary = analyze_loop_updates(&names); + + assert!(summary.has_single_counter()); + assert!(!summary.has_accumulation()); + assert_eq!(summary.counter_count(), 1); + } + + #[test] + fn test_analyze_accumulation() { + let names = vec!["result".to_string()]; + let summary = analyze_loop_updates(&names); + + assert!(!summary.has_single_counter()); + assert!(summary.has_accumulation()); + assert_eq!(summary.accumulation_count(), 1); + } + + #[test] + fn test_analyze_mixed() { + let names = vec!["i".to_string(), "sum".to_string()]; + let summary = analyze_loop_updates(&names); + + assert!(!summary.has_single_counter()); // 2つあるので false + assert!(summary.has_accumulation()); + assert_eq!(summary.counter_count(), 1); + assert_eq!(summary.accumulation_count(), 1); + } +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 1d0f5660..5bf8014f 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -24,6 +24,7 @@ pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering pub mod common; pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering helper pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics +pub mod loop_update_summary; // Phase 170-C-2: Update pattern summary for shape detection pub mod exit_args_resolver; pub mod funcscanner_append_defs; pub mod funcscanner_trim;