1use ryo_analysis::SymbolKind;
14use ryo_mutations::idiom::IntroduceVariableMutation;
15use ryo_mutations::{Mutation, MutationResult};
16use ryo_source::pure::{PureBlock, PureExpr, PureItem, PurePattern, PureStmt};
17
18use crate::engine::{ASTMutationContext, ASTRegApply};
19
20impl ASTRegApply for IntroduceVariableMutation {
21 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
22 let mut total_replacements = 0;
23
24 let fn_ids: Vec<_> = if let Some(target_id) = self.target_fn {
26 vec![target_id]
28 } else {
29 ctx.symbol_registry
31 .iter()
32 .filter(|(id, _)| {
33 matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
34 })
35 .map(|(id, _)| id)
36 .collect()
37 };
38
39 for fn_id in fn_ids {
40 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
41 let mut new_func = func.clone();
42 let replacements =
43 replace_expr_in_block(&mut new_func.body, &self.target_expr, &self.var_name);
44
45 if replacements > 0 {
46 let let_stmt = PureStmt::Local {
48 pattern: PurePattern::Ident {
49 name: self.var_name.clone(),
50 is_mut: self.is_mut,
51 },
52 ty: None, init: Some(self.target_expr.clone()),
54 };
55 new_func.body.stmts.insert(0, let_stmt);
56
57 ctx.set_ast(fn_id, PureItem::Fn(new_func));
58 total_replacements += replacements;
59 }
60 }
61 }
62
63 MutationResult {
64 mutation_type: self.mutation_type().to_string(),
65 changes: total_replacements,
66 description: if total_replacements > 0 {
67 format!(
68 "Introduced variable '{}' ({} replacements)",
69 self.var_name, total_replacements
70 )
71 } else {
72 format!("No matching expressions found for '{}'", self.var_name)
73 },
74 }
75 }
76}
77
78fn replace_expr_in_block(block: &mut PureBlock, target: &PureExpr, var_name: &str) -> usize {
81 let mut count = 0;
82 for stmt in &mut block.stmts {
83 count += replace_expr_in_stmt(stmt, target, var_name);
84 }
85 count
86}
87
88fn replace_expr_in_stmt(stmt: &mut PureStmt, target: &PureExpr, var_name: &str) -> usize {
90 match stmt {
91 PureStmt::Local { init, .. } => {
92 if let Some(expr) = init {
93 replace_expr(expr, target, var_name)
94 } else {
95 0
96 }
97 }
98 PureStmt::Semi(expr) | PureStmt::Expr(expr) => replace_expr(expr, target, var_name),
99 PureStmt::Item(_) => 0, }
101}
102
103fn replace_expr(expr: &mut PureExpr, target: &PureExpr, var_name: &str) -> usize {
106 if expr == target {
108 *expr = PureExpr::Path(var_name.to_string());
109 return 1;
110 }
111
112 match expr {
114 PureExpr::Binary { left, right, .. } => {
115 replace_expr(left, target, var_name) + replace_expr(right, target, var_name)
116 }
117 PureExpr::Unary { expr: inner, .. } => replace_expr(inner, target, var_name),
118 PureExpr::Call { func, args } => {
119 let mut count = replace_expr(func, target, var_name);
120 for arg in args {
121 count += replace_expr(arg, target, var_name);
122 }
123 count
124 }
125 PureExpr::MethodCall { receiver, args, .. } => {
126 let mut count = replace_expr(receiver, target, var_name);
127 for arg in args {
128 count += replace_expr(arg, target, var_name);
129 }
130 count
131 }
132 PureExpr::Field { expr: inner, .. } => replace_expr(inner, target, var_name),
133 PureExpr::Index { expr: e, index } => {
134 replace_expr(e, target, var_name) + replace_expr(index, target, var_name)
135 }
136 PureExpr::Block { block, .. } => replace_expr_in_block(block, target, var_name),
137 PureExpr::If {
138 cond,
139 then_branch,
140 else_branch,
141 } => {
142 let mut count = replace_expr(cond, target, var_name);
143 count += replace_expr_in_block(then_branch, target, var_name);
144 if let Some(else_expr) = else_branch {
145 count += replace_expr(else_expr, target, var_name);
146 }
147 count
148 }
149 PureExpr::Match { expr: e, arms } => {
150 let mut count = replace_expr(e, target, var_name);
151 for arm in arms {
152 count += replace_expr(&mut arm.body, target, var_name);
153 if let Some(guard) = &mut arm.guard {
154 count += replace_expr(guard, target, var_name);
155 }
156 }
157 count
158 }
159 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
160 replace_expr_in_block(block, target, var_name)
161 }
162 PureExpr::For { expr: e, body, .. } => {
163 replace_expr(e, target, var_name) + replace_expr_in_block(body, target, var_name)
164 }
165 PureExpr::Return(Some(inner))
166 | PureExpr::Break {
167 expr: Some(inner), ..
168 } => replace_expr(inner, target, var_name),
169 PureExpr::Closure { body, .. } => replace_expr(body, target, var_name),
170 PureExpr::Struct { fields, .. } => {
171 let mut count = 0;
172 for (_, field_expr) in fields {
173 count += replace_expr(field_expr, target, var_name);
174 }
175 count
176 }
177 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
178 let mut count = 0;
179 for e in exprs {
180 count += replace_expr(e, target, var_name);
181 }
182 count
183 }
184 PureExpr::Ref { expr: inner, .. } => replace_expr(inner, target, var_name),
185 PureExpr::Await(inner) | PureExpr::Try(inner) => replace_expr(inner, target, var_name),
186 PureExpr::Range { start, end, .. } => {
187 let mut count = 0;
188 if let Some(s) = start {
189 count += replace_expr(s, target, var_name);
190 }
191 if let Some(e) = end {
192 count += replace_expr(e, target, var_name);
193 }
194 count
195 }
196 PureExpr::Cast { expr: inner, .. } => replace_expr(inner, target, var_name),
197 PureExpr::Let { expr: inner, .. } => replace_expr(inner, target, var_name),
198 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
199 replace_expr_in_block(body, target, var_name)
200 }
201 PureExpr::Repeat { expr: e, len } => {
202 replace_expr(e, target, var_name) + replace_expr(len, target, var_name)
203 }
204 PureExpr::Lit(_)
206 | PureExpr::Path(_)
207 | PureExpr::Macro { .. }
208 | PureExpr::Return(None)
209 | PureExpr::Break { expr: None, .. }
210 | PureExpr::Continue { .. }
211 | PureExpr::Other(_) => 0,
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::engine::ASTMutationEngine;
219 use ryo_analysis::testing::ContextBuilder;
220
221 #[test]
222 fn test_v2_introduce_variable() {
223 let mut ctx = ContextBuilder::new()
224 .with_file(
225 "src/lib.rs",
226 r#"
227fn compute() -> i32 {
228 let x = 1 + 2;
229 let y = 1 + 2;
230 x + y
231}
232"#,
233 )
234 .build();
235
236 let target = PureExpr::Binary {
238 op: "+".to_string(),
239 left: Box::new(PureExpr::Lit("1".to_string())),
240 right: Box::new(PureExpr::Lit("2".to_string())),
241 };
242
243 let mutation = IntroduceVariableMutation {
244 target_expr: target,
245 var_name: "sum".to_string(),
246 target_fn: None, is_mut: false,
248 };
249
250 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
251
252 println!("Result: {:?}", result.result);
255 }
256
257 #[test]
258 fn test_v2_introduce_variable_no_match() {
259 let mut ctx = ContextBuilder::new()
260 .with_file(
261 "src/lib.rs",
262 r#"
263fn simple() -> i32 {
264 42
265}
266"#,
267 )
268 .build();
269
270 let target = PureExpr::Binary {
271 op: "+".to_string(),
272 left: Box::new(PureExpr::Lit("1".to_string())),
273 right: Box::new(PureExpr::Lit("2".to_string())),
274 };
275
276 let mutation = IntroduceVariableMutation {
277 target_expr: target,
278 var_name: "sum".to_string(),
279 target_fn: None,
280 is_mut: false,
281 };
282
283 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
284
285 assert_eq!(result.result.changes, 0);
287 }
288}