Skip to main content

ryo_mutations/idiom/
introduce_variable.rs

1//! IntroduceVariableMutation: Extract complex expressions into named variables
2//!
3//! Converts:
4//! ```ignore
5//! foo(a + b * c, a + b * c);
6//! ```
7//! Into:
8//! ```ignore
9//! let product = a + b * c;
10//! foo(product, product);
11//! ```
12//!
13//! Use cases:
14//! - Extract repeated sub-expressions
15//! - Improve readability of complex expressions
16//! - Enable debugging by naming intermediate values
17
18use ryo_source::pure::PureExpr;
19use ryo_symbol::SymbolId;
20
21use crate::Mutation;
22
23/// Extract an expression into a named variable
24#[derive(Debug, Clone)]
25pub struct IntroduceVariableMutation {
26    /// The expression to extract (matched by structure)
27    pub target_expr: PureExpr,
28    /// Name for the new variable
29    pub var_name: String,
30    /// Target function SymbolId. If None, applies to all functions.
31    pub target_fn: Option<SymbolId>,
32    /// Make the variable mutable
33    pub is_mut: bool,
34}
35
36impl IntroduceVariableMutation {
37    pub fn new(target_expr: PureExpr, var_name: impl Into<String>) -> Self {
38        Self {
39            target_expr,
40            var_name: var_name.into(),
41            target_fn: None,
42            is_mut: false,
43        }
44    }
45
46    /// Only apply in a specific function
47    pub fn in_function(mut self, id: SymbolId) -> Self {
48        self.target_fn = Some(id);
49        self
50    }
51
52    /// Make the introduced variable mutable
53    pub fn mutable(mut self) -> Self {
54        self.is_mut = true;
55        self
56    }
57}
58
59impl Mutation for IntroduceVariableMutation {
60    fn describe(&self) -> String {
61        format!("Introduce variable '{}'", self.var_name)
62    }
63
64    fn mutation_type(&self) -> &'static str {
65        "IntroduceVariable"
66    }
67
68    fn box_clone(&self) -> Box<dyn Mutation> {
69        Box::new(self.clone())
70    }
71}
72
73/// Find duplicate expressions in code that could be extracted
74#[derive(Debug, Clone, Default)]
75pub struct FindDuplicateExpressions {
76    /// Minimum complexity to consider (number of nodes)
77    pub min_complexity: usize,
78    /// Minimum occurrences to report
79    pub min_occurrences: usize,
80}
81
82impl FindDuplicateExpressions {
83    pub fn new() -> Self {
84        Self {
85            min_complexity: 3,
86            min_occurrences: 2,
87        }
88    }
89
90    /// Calculate expression complexity (number of nodes)
91    pub fn complexity(expr: &PureExpr) -> usize {
92        let mut count = 1;
93        match expr {
94            PureExpr::Binary { left, right, .. } => {
95                count += Self::complexity(left) + Self::complexity(right);
96            }
97            PureExpr::Unary { expr: inner, .. } => {
98                count += Self::complexity(inner);
99            }
100            PureExpr::Call { func, args } => {
101                count += Self::complexity(func);
102                for arg in args {
103                    count += Self::complexity(arg);
104                }
105            }
106            PureExpr::MethodCall { receiver, args, .. } => {
107                count += Self::complexity(receiver);
108                for arg in args {
109                    count += Self::complexity(arg);
110                }
111            }
112            PureExpr::Field { expr: inner, .. } => {
113                count += Self::complexity(inner);
114            }
115            PureExpr::Index { expr: inner, index } => {
116                count += Self::complexity(inner) + Self::complexity(index);
117            }
118            _ => {}
119        }
120        count
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    fn make_binary_expr() -> PureExpr {
129        // a + b * c
130        PureExpr::Binary {
131            op: "+".to_string(),
132            left: Box::new(PureExpr::Path("a".to_string())),
133            right: Box::new(PureExpr::Binary {
134                op: "*".to_string(),
135                left: Box::new(PureExpr::Path("b".to_string())),
136                right: Box::new(PureExpr::Path("c".to_string())),
137            }),
138        }
139    }
140
141    #[test]
142    fn test_complexity() {
143        let simple = PureExpr::Path("x".to_string());
144        assert_eq!(FindDuplicateExpressions::complexity(&simple), 1);
145
146        let complex = make_binary_expr();
147        assert_eq!(FindDuplicateExpressions::complexity(&complex), 5); // +, a, *, b, c
148    }
149}