diff --git a/src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs b/src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs index 9bc71fc5..252eac2f 100644 --- a/src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs +++ b/src/mir/loop_pattern_detection/mutable_accumulator_analyzer.rs @@ -6,8 +6,10 @@ //! - Whether RHS is read-only (delegated to ScopeManager) //! //! Minimal spec: `target = target + x` where x ∈ {Variable, Literal} +//! +//! Phase 100 P3-1: Added AccumulatorKind::{Int, String} for type-based routing -use crate::ast::{ASTNode, BinaryOperator}; +use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; /// RHS expression kind in accumulator pattern #[derive(Debug, Clone, PartialEq, Eq)] @@ -18,6 +20,15 @@ pub enum RhsExprKind { Literal, } +/// Accumulator kind (Phase 100 P3-1: Int vs String) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccumulatorKind { + /// Integer accumulator (e.g., count = count + 1) + Int, + /// String accumulator (e.g., out = out + ch) + String, +} + /// Accumulator specification detected from AST #[derive(Debug, Clone, PartialEq)] pub struct MutableAccumulatorSpec { @@ -29,6 +40,8 @@ pub struct MutableAccumulatorSpec { pub rhs_var_or_lit: String, /// Binary operator (currently only Add supported) pub op: BinaryOperator, + /// Accumulator kind (Phase 100 P3-1) + pub kind: AccumulatorKind, } /// Analyzer for detecting accumulator patterns in loop body AST. @@ -110,19 +123,29 @@ impl MutableAccumulatorAnalyzer { // Check RHS is Variable or Literal (no MethodCall) match right.as_ref() { ASTNode::Variable { name: rhs_name, .. } => { + // Phase 100 P3-1: Variable RHS - default to Int for backward compat + // Actual type validation happens in Pattern2 wiring (P3-3) return Ok(Some(MutableAccumulatorSpec { target_name, rhs_expr_kind: RhsExprKind::Var, rhs_var_or_lit: rhs_name.clone(), op: BinaryOperator::Add, + kind: AccumulatorKind::Int, // Will be refined in P3-3 })); } ASTNode::Literal { value, .. } => { + // Phase 100 P3-1: Detect kind from literal type + let kind = match value { + LiteralValue::String(_) => AccumulatorKind::String, + LiteralValue::Integer(_) => AccumulatorKind::Int, + _ => AccumulatorKind::Int, // Default to Int for other literals + }; return Ok(Some(MutableAccumulatorSpec { target_name, rhs_expr_kind: RhsExprKind::Literal, rhs_var_or_lit: format!("{:?}", value), op: BinaryOperator::Add, + kind, })); } ASTNode::MethodCall { .. } | ASTNode::Call { .. } => { @@ -250,6 +273,7 @@ mod tests { assert_eq!(spec.rhs_expr_kind, RhsExprKind::Var); assert_eq!(spec.rhs_var_or_lit, "ch"); assert_eq!(spec.op, BinaryOperator::Add); + assert_eq!(spec.kind, AccumulatorKind::Int); // Phase 100 P3-1: Variable defaults to Int } #[test] @@ -413,5 +437,79 @@ mod tests { assert_eq!(spec.target_name, "count"); assert_eq!(spec.rhs_expr_kind, RhsExprKind::Literal); assert_eq!(spec.op, BinaryOperator::Add); + assert_eq!(spec.kind, AccumulatorKind::Int); // Phase 100 P3-1: Integer literal + } + + #[test] + fn test_string_accumulator_spec() { + // Phase 100 P3-1: String accumulator with Variable RHS + // Build AST for: out = out + ch (ch is string-typed variable) + // Note: At AST-only stage, Variable defaults to Int + // Type refinement happens in Pattern2 wiring (P3-3) + let loop_body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "out".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "out".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Variable { + name: "ch".to_string(), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }]; + + let result = MutableAccumulatorAnalyzer::analyze(&loop_body).unwrap(); + assert!(result.is_some()); + + let spec = result.unwrap(); + assert_eq!(spec.target_name, "out"); + assert_eq!(spec.rhs_expr_kind, RhsExprKind::Var); + assert_eq!(spec.rhs_var_or_lit, "ch"); + assert_eq!(spec.op, BinaryOperator::Add); + // Phase 100 P3-1: Variable RHS defaults to Int at AST stage + // Will be refined to String in P3-3 based on actual types + assert_eq!(spec.kind, AccumulatorKind::Int); + } + + #[test] + fn test_int_accumulator_spec_unchanged() { + // Phase 100 P3-1: Integer accumulator (existing behavior) + // Build AST for: count = count + 1 + let loop_body = vec![ASTNode::Assignment { + target: Box::new(ASTNode::Variable { + name: "count".to_string(), + span: Span::unknown(), + }), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(ASTNode::Variable { + name: "count".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 = MutableAccumulatorAnalyzer::analyze(&loop_body).unwrap(); + assert!(result.is_some()); + + let spec = result.unwrap(); + assert_eq!(spec.target_name, "count"); + assert_eq!(spec.rhs_expr_kind, RhsExprKind::Literal); + assert_eq!(spec.op, BinaryOperator::Add); + assert_eq!(spec.kind, AccumulatorKind::Int); } }