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 by_ref: false,
52 },
53 ty: None, init: Some(self.target_expr.clone()),
55 else_branch: None,
56 };
57 new_func.body.stmts.insert(0, let_stmt);
58
59 ctx.set_ast(fn_id, PureItem::Fn(new_func));
60 total_replacements += replacements;
61 }
62 }
63 }
64
65 MutationResult {
66 mutation_type: self.mutation_type().to_string(),
67 changes: total_replacements,
68 description: if total_replacements > 0 {
69 format!(
70 "Introduced variable '{}' ({} replacements)",
71 self.var_name, total_replacements
72 )
73 } else {
74 format!("No matching expressions found for '{}'", self.var_name)
75 },
76 }
77 }
78}
79
80fn replace_expr_in_block(block: &mut PureBlock, target: &PureExpr, var_name: &str) -> usize {
83 let mut count = 0;
84 for stmt in &mut block.stmts {
85 count += replace_expr_in_stmt(stmt, target, var_name);
86 }
87 count
88}
89
90fn replace_expr_in_stmt(stmt: &mut PureStmt, target: &PureExpr, var_name: &str) -> usize {
92 match stmt {
93 PureStmt::Local { init, .. } => {
94 if let Some(expr) = init {
95 replace_expr(expr, target, var_name)
96 } else {
97 0
98 }
99 }
100 PureStmt::Semi(expr) | PureStmt::Expr(expr) => replace_expr(expr, target, var_name),
101 PureStmt::Item(_) => 0, PureStmt::Verbatim(_) => 0,
104 }
105}
106
107fn replace_expr(expr: &mut PureExpr, target: &PureExpr, var_name: &str) -> usize {
110 if expr == target {
112 *expr = PureExpr::Path(var_name.to_string());
113 return 1;
114 }
115
116 match expr {
118 PureExpr::Binary { left, right, .. } => {
119 replace_expr(left, target, var_name) + replace_expr(right, target, var_name)
120 }
121 PureExpr::Unary { expr: inner, .. } => replace_expr(inner, target, var_name),
122 PureExpr::Call { func, args } => {
123 let mut count = replace_expr(func, target, var_name);
124 for arg in args {
125 count += replace_expr(arg, target, var_name);
126 }
127 count
128 }
129 PureExpr::MethodCall { receiver, args, .. } => {
130 let mut count = replace_expr(receiver, target, var_name);
131 for arg in args {
132 count += replace_expr(arg, target, var_name);
133 }
134 count
135 }
136 PureExpr::Field { expr: inner, .. } => replace_expr(inner, target, var_name),
137 PureExpr::Index { expr: e, index } => {
138 replace_expr(e, target, var_name) + replace_expr(index, target, var_name)
139 }
140 PureExpr::Block { block, .. } => replace_expr_in_block(block, target, var_name),
141 PureExpr::If {
142 cond,
143 then_branch,
144 else_branch,
145 } => {
146 let mut count = replace_expr(cond, target, var_name);
147 count += replace_expr_in_block(then_branch, target, var_name);
148 if let Some(else_expr) = else_branch {
149 count += replace_expr(else_expr, target, var_name);
150 }
151 count
152 }
153 PureExpr::Match { expr: e, arms } => {
154 let mut count = replace_expr(e, target, var_name);
155 for arm in arms {
156 count += replace_expr(&mut arm.body, target, var_name);
157 if let Some(guard) = &mut arm.guard {
158 count += replace_expr(guard, target, var_name);
159 }
160 }
161 count
162 }
163 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
164 replace_expr_in_block(block, target, var_name)
165 }
166 PureExpr::For { expr: e, body, .. } => {
167 replace_expr(e, target, var_name) + replace_expr_in_block(body, target, var_name)
168 }
169 PureExpr::Return(Some(inner))
170 | PureExpr::Break {
171 expr: Some(inner), ..
172 } => replace_expr(inner, target, var_name),
173 PureExpr::Closure { body, .. } => replace_expr(body, target, var_name),
174 PureExpr::Struct { fields, .. } => {
175 let mut count = 0;
176 for (_, field_expr) in fields {
177 count += replace_expr(field_expr, target, var_name);
178 }
179 count
180 }
181 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
182 let mut count = 0;
183 for e in exprs {
184 count += replace_expr(e, target, var_name);
185 }
186 count
187 }
188 PureExpr::Ref { expr: inner, .. } => replace_expr(inner, target, var_name),
189 PureExpr::Await(inner) | PureExpr::Try(inner) => replace_expr(inner, target, var_name),
190 PureExpr::Range { start, end, .. } => {
191 let mut count = 0;
192 if let Some(s) = start {
193 count += replace_expr(s, target, var_name);
194 }
195 if let Some(e) = end {
196 count += replace_expr(e, target, var_name);
197 }
198 count
199 }
200 PureExpr::Cast { expr: inner, .. } => replace_expr(inner, target, var_name),
201 PureExpr::Let { expr: inner, .. } => replace_expr(inner, target, var_name),
202 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
203 replace_expr_in_block(body, target, var_name)
204 }
205 PureExpr::Repeat { expr: e, len } => {
206 replace_expr(e, target, var_name) + replace_expr(len, target, var_name)
207 }
208 PureExpr::Lit(_)
210 | PureExpr::Path(_)
211 | PureExpr::Macro { .. }
212 | PureExpr::Return(None)
213 | PureExpr::Break { expr: None, .. }
214 | PureExpr::Continue { .. }
215 | PureExpr::Other(_)
216 | PureExpr::Verbatim(_) => 0,
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::engine::ASTMutationEngine;
226 use ryo_analysis::testing::ContextBuilder;
227
228 #[test]
229 fn test_v2_introduce_variable() {
230 let mut ctx = ContextBuilder::new()
231 .with_file(
232 "src/lib.rs",
233 r#"
234fn compute() -> i32 {
235 let x = 1 + 2;
236 let y = 1 + 2;
237 x + y
238}
239"#,
240 )
241 .build();
242
243 let target = PureExpr::Binary {
245 op: "+".to_string(),
246 left: Box::new(PureExpr::Lit("1".to_string())),
247 right: Box::new(PureExpr::Lit("2".to_string())),
248 };
249
250 let mutation = IntroduceVariableMutation {
251 target_expr: target,
252 var_name: "sum".to_string(),
253 target_fn: None, is_mut: false,
255 };
256
257 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
258
259 println!("Result: {:?}", result.result);
262 }
263
264 #[test]
265 fn test_v2_introduce_variable_no_match() {
266 let mut ctx = ContextBuilder::new()
267 .with_file(
268 "src/lib.rs",
269 r#"
270fn simple() -> i32 {
271 42
272}
273"#,
274 )
275 .build();
276
277 let target = PureExpr::Binary {
278 op: "+".to_string(),
279 left: Box::new(PureExpr::Lit("1".to_string())),
280 right: Box::new(PureExpr::Lit("2".to_string())),
281 };
282
283 let mutation = IntroduceVariableMutation {
284 target_expr: target,
285 var_name: "sum".to_string(),
286 target_fn: None,
287 is_mut: false,
288 };
289
290 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
291
292 assert_eq!(result.result.changes, 0);
294 }
295}