Files
hakorune/src/mir/loop_canonicalizer/mod.rs
nyash-codex e404746612 refactor(mir): Phase 139-P3-B - RoutingDecision を enum 対応 + レガシー削除
- RoutingDecision の missing_caps を Vec<CapabilityTag> に変更(型安全化)
- error_tags は to_tag() メソッドで自動生成
- 全 callsite を enum variant に修正
- capability_tags モジュール(文字列定数群)を完全削除
- 全テスト PASS(型安全性向上を確認)
- フォーマット適用
2025-12-16 07:02:14 +09:00

152 lines
4.8 KiB
Rust

//! Loop Canonicalizer - AST Level Loop Preprocessing
//!
//! ## Purpose
//!
//! Decomposes AST-level loops into a normalized "skeleton" representation
//! to prevent combinatorial explosion in pattern detection and lowering.
//!
//! ## Design Principle
//!
//! - **Input**: AST (LoopExpr)
//! - **Output**: LoopSkeleton only (no JoinIR generation)
//! - **Boundary**: No JoinIR-specific information (BlockId, ValueId, etc.)
//!
//! ## Architecture
//!
//! ```
//! AST → LoopSkeleton → Capability Guard → RoutingDecision → Pattern Lowerer
//! ```
//!
//! ## Module Structure (Phase 138 Refactoring)
//!
//! - `skeleton_types` - Core data structures (LoopSkeleton, SkeletonStep, etc.)
//! - `capability_guard` - Routing decisions and capability tags
//! - `pattern_recognizer` - Pattern detection logic (skip_whitespace, etc.)
//! - `canonicalizer` - Main canonicalization entry point
//!
//! ## References
//!
//! - Design SSOT: `docs/development/current/main/design/loop-canonicalizer.md`
//! - JoinIR Architecture: `docs/development/current/main/joinir-architecture-overview.md`
//! - Pattern Space: `docs/development/current/main/loop_pattern_space.md`
// ============================================================================
// Module Declarations
// ============================================================================
mod canonicalizer;
mod capability_guard;
mod pattern_recognizer;
mod skeleton_types;
// ============================================================================
// Public Re-exports
// ============================================================================
// Skeleton Types
pub use skeleton_types::{
CapturedSlot, CarrierRole, CarrierSlot, ExitContract, LoopSkeleton, SkeletonStep, UpdateKind,
};
// Capability Guard
pub use capability_guard::{CapabilityTag, RoutingDecision};
// Canonicalization Entry Point
pub use canonicalizer::canonicalize_loop_expr;
// ============================================================================
// Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Span;
#[test]
fn test_skeleton_creation() {
let skeleton = LoopSkeleton::new(Span::unknown());
assert_eq!(skeleton.steps.len(), 0);
assert_eq!(skeleton.carriers.len(), 0);
assert!(!skeleton.exits.has_any_exit());
}
#[test]
fn test_exit_contract() {
let mut contract = ExitContract::none();
assert!(!contract.has_any_exit());
contract.has_break = true;
assert!(contract.has_any_exit());
}
#[test]
fn test_routing_decision() {
use crate::mir::loop_pattern_detection::LoopPatternKind;
let success = RoutingDecision::success(LoopPatternKind::Pattern1SimpleWhile);
assert!(success.is_success());
assert!(!success.is_fail_fast());
let fail =
RoutingDecision::fail_fast(vec![CapabilityTag::ConstStep], "Test failure".to_string());
assert!(!fail.is_success());
assert!(fail.is_fail_fast());
assert_eq!(fail.missing_caps.len(), 1);
assert_eq!(fail.missing_caps[0], CapabilityTag::ConstStep);
}
#[test]
fn test_carrier_role_display() {
assert_eq!(CarrierRole::Counter.to_string(), "Counter");
assert_eq!(CarrierRole::Accumulator.to_string(), "Accumulator");
assert_eq!(CarrierRole::ConditionVar.to_string(), "ConditionVar");
assert_eq!(CarrierRole::Derived.to_string(), "Derived");
}
#[test]
fn test_skeleton_count_helpers() {
use crate::ast::{ASTNode, LiteralValue};
let mut skeleton = LoopSkeleton::new(Span::unknown());
skeleton.steps.push(SkeletonStep::BreakCheck {
cond: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
has_value: false,
});
skeleton.steps.push(SkeletonStep::ContinueCheck {
cond: Box::new(ASTNode::Literal {
value: LiteralValue::Bool(true),
span: Span::unknown(),
}),
});
assert_eq!(skeleton.count_break_checks(), 1);
assert_eq!(skeleton.count_continue_checks(), 1);
}
#[test]
fn test_skeleton_carrier_names() {
let mut skeleton = LoopSkeleton::new(Span::unknown());
skeleton.carriers.push(CarrierSlot {
name: "i".to_string(),
role: CarrierRole::Counter,
update_kind: UpdateKind::ConstStep { delta: 1 },
});
skeleton.carriers.push(CarrierSlot {
name: "sum".to_string(),
role: CarrierRole::Accumulator,
update_kind: UpdateKind::Arbitrary,
});
let names = skeleton.carrier_names();
assert_eq!(names, vec!["i", "sum"]);
}
}