feat(joinir): Phase 170-C-2 LoopUpdateSummary skeleton and design doc
Add UpdateKind/LoopUpdateSummary types for loop update pattern analysis. Currently uses carrier name heuristics internally (same as 170-C-1), but provides clean interface for future AST/MIR-based analysis. New types: - UpdateKind: CounterLike | AccumulationLike | Other - CarrierUpdateInfo: name + kind pair - LoopUpdateSummary: collection with helper methods Helper methods: - has_single_counter(): for StringExamination detection - has_accumulation(): for ArrayAccumulation detection Design doc: docs/development/current/main/phase170-c2-update-summary-design.md - Describes LoopFeatures.update_summary integration plan - Migration path to AST/MIR analysis 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -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<CarrierUpdateInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LoopUpdateSummary>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 なので影響最小
|
||||||
211
src/mir/join_ir/lowering/loop_update_summary.rs
Normal file
211
src/mir/join_ir/lowering/loop_update_summary.rs
Normal file
@ -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<CarrierUpdateInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering helper
|
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_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 exit_args_resolver;
|
||||||
pub mod funcscanner_append_defs;
|
pub mod funcscanner_append_defs;
|
||||||
pub mod funcscanner_trim;
|
pub mod funcscanner_trim;
|
||||||
|
|||||||
Reference in New Issue
Block a user