Files
hakorune/src/mir/join_ir/lowering/loop_update_summary.rs
nyash-codex c9458ef11e 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>
2025-12-07 14:17:58 +09:00

212 lines
6.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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);
}
}