1use ryo_analysis::SymbolKind;
18use ryo_mutations::basic::stmt::{
19 InsertPosition, InsertStatementMutation, RemoveStatementMutation, ReplaceExprAtMutation,
20 ReplaceExprMutation, ReplaceStatementMutation, WrapExprMutation,
21};
22use ryo_mutations::{Mutation, MutationResult};
23use ryo_source::pure::{MacroDelimiter, PureBlock, PureExpr, PureItem, PureStmt};
24
25use crate::engine::{ASTMutationContext, ASTRegApply, ModificationType};
26
27impl ASTRegApply for ReplaceExprMutation {
32 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
33 let fn_id = self.target_fn;
34
35 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
36 return MutationResult {
37 mutation_type: self.mutation_type().to_string(),
38 changes: 0,
39 description: format!("Target function '{:?}' not found", fn_id),
40 };
41 }
42
43 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
44 let mut new_func = func.clone();
45 let replacements = replace_expr_in_block(
46 &mut new_func.body,
47 &self.old_expr,
48 &self.new_expr,
49 self.replace_all,
50 );
51
52 if replacements > 0 {
53 ctx.set_ast(fn_id, PureItem::Fn(new_func));
54 ctx.emit_modified(fn_id, ModificationType::BodyModified);
55 return MutationResult {
56 mutation_type: self.mutation_type().to_string(),
57 changes: replacements,
58 description: format!("Replaced {} expression(s)", replacements),
59 };
60 }
61 }
62
63 MutationResult {
64 mutation_type: self.mutation_type().to_string(),
65 changes: 0,
66 description: "No matching expressions found".to_string(),
67 }
68 }
69}
70
71impl ASTRegApply for WrapExprMutation {
76 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
77 let fn_id = self.target_fn;
78
79 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
80 return MutationResult {
81 mutation_type: self.mutation_type().to_string(),
82 changes: 0,
83 description: format!("Target function '{:?}' not found", fn_id),
84 };
85 }
86
87 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
88 let mut new_func = func.clone();
89 let wraps = wrap_expr_in_block(
90 &mut new_func.body,
91 &self.target_expr,
92 &self.wrapper_macro,
93 self.wrap_all,
94 );
95
96 if wraps > 0 {
97 ctx.set_ast(fn_id, PureItem::Fn(new_func));
98 ctx.emit_modified(fn_id, ModificationType::BodyModified);
99 return MutationResult {
100 mutation_type: self.mutation_type().to_string(),
101 changes: wraps,
102 description: format!(
103 "Wrapped {} expression(s) with {}!()",
104 wraps, self.wrapper_macro
105 ),
106 };
107 }
108 }
109
110 MutationResult {
111 mutation_type: self.mutation_type().to_string(),
112 changes: 0,
113 description: "No matching expressions found".to_string(),
114 }
115 }
116}
117
118impl ASTRegApply for RemoveStatementMutation {
123 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
124 let fn_id = self.target_fn;
125
126 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
127 return MutationResult {
128 mutation_type: self.mutation_type().to_string(),
129 changes: 0,
130 description: format!("Target function '{:?}' not found", fn_id),
131 };
132 }
133
134 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
135 let mut new_func = func.clone();
136 let removed = remove_stmts_in_block(&mut new_func.body, &self.pattern, self.remove_all);
137
138 if removed > 0 {
139 ctx.set_ast(fn_id, PureItem::Fn(new_func));
140 ctx.emit_modified(fn_id, ModificationType::BodyModified);
141 return MutationResult {
142 mutation_type: self.mutation_type().to_string(),
143 changes: removed,
144 description: format!("Removed {} statement(s)", removed),
145 };
146 }
147 }
148
149 MutationResult {
150 mutation_type: self.mutation_type().to_string(),
151 changes: 0,
152 description: "No matching statements found".to_string(),
153 }
154 }
155}
156
157impl ASTRegApply for InsertStatementMutation {
162 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
163 let fn_id = self.target_fn;
164
165 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
166 return MutationResult {
167 mutation_type: self.mutation_type().to_string(),
168 changes: 0,
169 description: format!("Target function '{:?}' not found", fn_id),
170 };
171 }
172
173 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
174 let mut new_func = func.clone();
175
176 let inserted = match self.position {
177 InsertPosition::Start => {
178 new_func.body.stmts.insert(0, self.stmt.clone());
179 true
180 }
181 InsertPosition::End => {
182 let insert_idx = find_return_index(&new_func.body.stmts);
184 new_func.body.stmts.insert(insert_idx, self.stmt.clone());
185 true
186 }
187 InsertPosition::BeforePattern | InsertPosition::AfterPattern => {
188 if let Some(ref reference_stmt) = self.reference_stmt {
189 insert_relative_to_stmt(
190 &mut new_func.body.stmts,
191 &self.stmt,
192 reference_stmt,
193 self.position == InsertPosition::AfterPattern,
194 )
195 } else {
196 false
197 }
198 }
199 };
200
201 if inserted {
202 ctx.set_ast(fn_id, PureItem::Fn(new_func));
203 ctx.emit_modified(fn_id, ModificationType::BodyModified);
204 return MutationResult {
205 mutation_type: self.mutation_type().to_string(),
206 changes: 1,
207 description: format!(
208 "Inserted statement in '{}' at {:?}",
209 self.target_fn, self.position
210 ),
211 };
212 }
213 }
214
215 MutationResult {
216 mutation_type: self.mutation_type().to_string(),
217 changes: 0,
218 description: "Failed to insert statement".to_string(),
219 }
220 }
221}
222
223impl ASTRegApply for ReplaceStatementMutation {
228 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
229 let fn_id = self.target_fn;
230
231 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
232 return MutationResult {
233 mutation_type: self.mutation_type().to_string(),
234 changes: 0,
235 description: format!("Target function '{:?}' not found", fn_id),
236 };
237 }
238
239 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
240 let mut new_func = func.clone();
241 let replaced =
242 replace_stmts_in_block(&mut new_func.body, &self.old_stmt, &self.new_stmt);
243
244 if replaced > 0 {
245 ctx.set_ast(fn_id, PureItem::Fn(new_func));
246 ctx.emit_modified(fn_id, ModificationType::BodyModified);
247 return MutationResult {
248 mutation_type: self.mutation_type().to_string(),
249 changes: replaced,
250 description: format!("Replaced {} statement(s)", replaced),
251 };
252 }
253 }
254
255 MutationResult {
256 mutation_type: self.mutation_type().to_string(),
257 changes: 0,
258 description: "No matching statements found".to_string(),
259 }
260 }
261}
262
263fn replace_expr_in_block(
269 block: &mut PureBlock,
270 old: &PureExpr,
271 new: &PureExpr,
272 replace_all: bool,
273) -> usize {
274 let mut count = 0;
275 for stmt in &mut block.stmts {
276 count += replace_expr_in_stmt(stmt, old, new, replace_all);
277 if !replace_all && count > 0 {
278 return count;
279 }
280 }
281 count
282}
283
284fn wrap_expr_in_block(
286 block: &mut PureBlock,
287 target: &PureExpr,
288 wrapper_macro: &str,
289 wrap_all: bool,
290) -> usize {
291 let mut count = 0;
292 for stmt in &mut block.stmts {
293 count += wrap_expr_in_stmt(stmt, target, wrapper_macro, wrap_all);
294 if !wrap_all && count > 0 {
295 return count;
296 }
297 }
298 count
299}
300
301fn wrap_expr_in_stmt(
302 stmt: &mut PureStmt,
303 target: &PureExpr,
304 wrapper_macro: &str,
305 wrap_all: bool,
306) -> usize {
307 match stmt {
308 PureStmt::Local { init, .. } => {
309 if let Some(expr) = init {
310 wrap_expr(expr, target, wrapper_macro, wrap_all)
311 } else {
312 0
313 }
314 }
315 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
316 wrap_expr(expr, target, wrapper_macro, wrap_all)
317 }
318 PureStmt::Item(_) => 0,
319 }
320}
321
322fn wrap_expr(expr: &mut PureExpr, target: &PureExpr, wrapper_macro: &str, wrap_all: bool) -> usize {
323 if expr == target {
324 let wrapped = PureExpr::Macro {
325 name: wrapper_macro.to_string(),
326 delimiter: MacroDelimiter::Paren,
327 tokens: format!("{:?}", expr),
328 };
329 *expr = wrapped;
330 return 1;
331 }
332
333 match expr {
334 PureExpr::Binary { left, right, .. } => {
335 let mut c = wrap_expr(left, target, wrapper_macro, wrap_all);
336 if wrap_all || c == 0 {
337 c += wrap_expr(right, target, wrapper_macro, wrap_all);
338 }
339 c
340 }
341 PureExpr::Unary { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
342 PureExpr::Call { func, args } => {
343 let mut c = wrap_expr(func, target, wrapper_macro, wrap_all);
344 for arg in args {
345 if wrap_all || c == 0 {
346 c += wrap_expr(arg, target, wrapper_macro, wrap_all);
347 }
348 }
349 c
350 }
351 PureExpr::MethodCall { receiver, args, .. } => {
352 let mut c = wrap_expr(receiver, target, wrapper_macro, wrap_all);
353 for arg in args {
354 if wrap_all || c == 0 {
355 c += wrap_expr(arg, target, wrapper_macro, wrap_all);
356 }
357 }
358 c
359 }
360 PureExpr::Field { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
361 PureExpr::Index { expr: e, index } => {
362 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
363 if wrap_all || c == 0 {
364 c += wrap_expr(index, target, wrapper_macro, wrap_all);
365 }
366 c
367 }
368 PureExpr::Block { block: b, .. } => wrap_expr_in_block(b, target, wrapper_macro, wrap_all),
369 PureExpr::If {
370 cond,
371 then_branch,
372 else_branch,
373 } => {
374 let mut c = wrap_expr(cond, target, wrapper_macro, wrap_all);
375 if wrap_all || c == 0 {
376 c += wrap_expr_in_block(then_branch, target, wrapper_macro, wrap_all);
377 }
378 if let Some(else_expr) = else_branch {
379 if wrap_all || c == 0 {
380 c += wrap_expr(else_expr, target, wrapper_macro, wrap_all);
381 }
382 }
383 c
384 }
385 PureExpr::Match { expr: e, arms } => {
386 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
387 for arm in arms {
388 if wrap_all || c == 0 {
389 c += wrap_expr(&mut arm.body, target, wrapper_macro, wrap_all);
390 }
391 if let Some(guard) = &mut arm.guard {
392 if wrap_all || c == 0 {
393 c += wrap_expr(guard, target, wrapper_macro, wrap_all);
394 }
395 }
396 }
397 c
398 }
399 PureExpr::Loop { body: b, .. } => wrap_expr_in_block(b, target, wrapper_macro, wrap_all),
400 PureExpr::While { cond, body, .. } => {
401 let mut c = wrap_expr(cond, target, wrapper_macro, wrap_all);
402 if wrap_all || c == 0 {
403 c += wrap_expr_in_block(body, target, wrapper_macro, wrap_all);
404 }
405 c
406 }
407 PureExpr::For { expr: e, body, .. } => {
408 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
409 if wrap_all || c == 0 {
410 c += wrap_expr_in_block(body, target, wrapper_macro, wrap_all);
411 }
412 c
413 }
414 PureExpr::Return(Some(inner))
415 | PureExpr::Break {
416 expr: Some(inner), ..
417 } => wrap_expr(inner, target, wrapper_macro, wrap_all),
418 PureExpr::Closure { body, .. } => wrap_expr(body, target, wrapper_macro, wrap_all),
419 PureExpr::Struct { fields, .. } => {
420 let mut c = 0;
421 for (_, field_expr) in fields {
422 if wrap_all || c == 0 {
423 c += wrap_expr(field_expr, target, wrapper_macro, wrap_all);
424 }
425 }
426 c
427 }
428 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
429 let mut c = 0;
430 for e in exprs {
431 if wrap_all || c == 0 {
432 c += wrap_expr(e, target, wrapper_macro, wrap_all);
433 }
434 }
435 c
436 }
437 PureExpr::Ref { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
438 PureExpr::Await(inner) | PureExpr::Try(inner) => {
439 wrap_expr(inner, target, wrapper_macro, wrap_all)
440 }
441 PureExpr::Range { start, end, .. } => {
442 let mut c = 0;
443 if let Some(s) = start {
444 c += wrap_expr(s, target, wrapper_macro, wrap_all);
445 }
446 if let Some(e) = end {
447 if wrap_all || c == 0 {
448 c += wrap_expr(e, target, wrapper_macro, wrap_all);
449 }
450 }
451 c
452 }
453 PureExpr::Cast { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
454 PureExpr::Let { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
455 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
456 wrap_expr_in_block(body, target, wrapper_macro, wrap_all)
457 }
458 PureExpr::Repeat { expr: e, len } => {
459 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
460 if wrap_all || c == 0 {
461 c += wrap_expr(len, target, wrapper_macro, wrap_all);
462 }
463 c
464 }
465 PureExpr::Lit(_)
466 | PureExpr::Path(_)
467 | PureExpr::Macro { .. }
468 | PureExpr::Return(None)
469 | PureExpr::Break { expr: None, .. }
470 | PureExpr::Continue { .. }
471 | PureExpr::Other(_) => 0,
472 }
473}
474
475fn replace_expr_in_stmt(
476 stmt: &mut PureStmt,
477 old: &PureExpr,
478 new: &PureExpr,
479 replace_all: bool,
480) -> usize {
481 match stmt {
482 PureStmt::Local { init, .. } => {
483 if let Some(expr) = init {
484 replace_expr(expr, old, new, replace_all)
485 } else {
486 0
487 }
488 }
489 PureStmt::Semi(expr) | PureStmt::Expr(expr) => replace_expr(expr, old, new, replace_all),
490 PureStmt::Item(_) => 0,
491 }
492}
493
494fn replace_expr(expr: &mut PureExpr, old: &PureExpr, new: &PureExpr, replace_all: bool) -> usize {
495 if expr == old {
496 *expr = new.clone();
497 return 1;
498 }
499
500 match expr {
501 PureExpr::Binary { left, right, .. } => {
502 let mut c = replace_expr(left, old, new, replace_all);
503 if replace_all || c == 0 {
504 c += replace_expr(right, old, new, replace_all);
505 }
506 c
507 }
508 PureExpr::Unary { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
509 PureExpr::Call { func, args } => {
510 let mut c = replace_expr(func, old, new, replace_all);
511 for arg in args {
512 if replace_all || c == 0 {
513 c += replace_expr(arg, old, new, replace_all);
514 }
515 }
516 c
517 }
518 PureExpr::MethodCall { receiver, args, .. } => {
519 let mut c = replace_expr(receiver, old, new, replace_all);
520 for arg in args {
521 if replace_all || c == 0 {
522 c += replace_expr(arg, old, new, replace_all);
523 }
524 }
525 c
526 }
527 PureExpr::Field { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
528 PureExpr::Index { expr: e, index } => {
529 let mut c = replace_expr(e, old, new, replace_all);
530 if replace_all || c == 0 {
531 c += replace_expr(index, old, new, replace_all);
532 }
533 c
534 }
535 PureExpr::Block { block: b, .. } => replace_expr_in_block(b, old, new, replace_all),
536 PureExpr::If {
537 cond,
538 then_branch,
539 else_branch,
540 } => {
541 let mut c = replace_expr(cond, old, new, replace_all);
542 if replace_all || c == 0 {
543 c += replace_expr_in_block(then_branch, old, new, replace_all);
544 }
545 if let Some(else_expr) = else_branch {
546 if replace_all || c == 0 {
547 c += replace_expr(else_expr, old, new, replace_all);
548 }
549 }
550 c
551 }
552 PureExpr::Match { expr: e, arms } => {
553 let mut c = replace_expr(e, old, new, replace_all);
554 for arm in arms {
555 if replace_all || c == 0 {
556 c += replace_expr(&mut arm.body, old, new, replace_all);
557 }
558 if let Some(guard) = &mut arm.guard {
559 if replace_all || c == 0 {
560 c += replace_expr(guard, old, new, replace_all);
561 }
562 }
563 }
564 c
565 }
566 PureExpr::Loop { body: b, .. } => replace_expr_in_block(b, old, new, replace_all),
567 PureExpr::While { cond, body, .. } => {
568 let mut c = replace_expr(cond, old, new, replace_all);
569 if replace_all || c == 0 {
570 c += replace_expr_in_block(body, old, new, replace_all);
571 }
572 c
573 }
574 PureExpr::For { expr: e, body, .. } => {
575 let mut c = replace_expr(e, old, new, replace_all);
576 if replace_all || c == 0 {
577 c += replace_expr_in_block(body, old, new, replace_all);
578 }
579 c
580 }
581 PureExpr::Return(Some(inner))
582 | PureExpr::Break {
583 expr: Some(inner), ..
584 } => replace_expr(inner, old, new, replace_all),
585 PureExpr::Closure { body, .. } => replace_expr(body, old, new, replace_all),
586 PureExpr::Struct { fields, .. } => {
587 let mut c = 0;
588 for (_, field_expr) in fields {
589 if replace_all || c == 0 {
590 c += replace_expr(field_expr, old, new, replace_all);
591 }
592 }
593 c
594 }
595 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
596 let mut c = 0;
597 for e in exprs {
598 if replace_all || c == 0 {
599 c += replace_expr(e, old, new, replace_all);
600 }
601 }
602 c
603 }
604 PureExpr::Ref { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
605 PureExpr::Await(inner) | PureExpr::Try(inner) => replace_expr(inner, old, new, replace_all),
606 PureExpr::Range { start, end, .. } => {
607 let mut c = 0;
608 if let Some(s) = start {
609 c += replace_expr(s, old, new, replace_all);
610 }
611 if let Some(e) = end {
612 if replace_all || c == 0 {
613 c += replace_expr(e, old, new, replace_all);
614 }
615 }
616 c
617 }
618 PureExpr::Cast { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
619 PureExpr::Let { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
620 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
621 replace_expr_in_block(body, old, new, replace_all)
622 }
623 PureExpr::Repeat { expr: e, len } => {
624 let mut c = replace_expr(e, old, new, replace_all);
625 if replace_all || c == 0 {
626 c += replace_expr(len, old, new, replace_all);
627 }
628 c
629 }
630 PureExpr::Lit(_)
631 | PureExpr::Path(_)
632 | PureExpr::Macro { .. }
633 | PureExpr::Return(None)
634 | PureExpr::Break { expr: None, .. }
635 | PureExpr::Continue { .. }
636 | PureExpr::Other(_) => 0,
637 }
638}
639
640fn stmt_matches(target: &PureStmt, stmt: &PureStmt) -> bool {
649 target == stmt
650}
651
652fn stmt_matches_pattern(stmt: &PureStmt, pattern: &str) -> bool {
662 let macro_name = pattern
664 .strip_suffix("!(..)")
665 .or_else(|| pattern.strip_suffix('!'));
666
667 if let Some(name) = macro_name {
668 match stmt {
670 PureStmt::Semi(PureExpr::Macro { name: macro_n, .. })
671 | PureStmt::Expr(PureExpr::Macro { name: macro_n, .. }) => {
672 return macro_n == name;
673 }
674 _ => {}
675 }
676 }
677
678 if let Some(after_let) = pattern.strip_prefix("let ") {
680 let var_name = if let Some(eq_pos) = after_let.find(" = ") {
682 after_let[..eq_pos].trim()
683 } else if let Some(eq_pos) = after_let.find("=") {
684 after_let[..eq_pos].trim()
685 } else {
686 after_let.trim()
687 };
688
689 if let PureStmt::Local {
690 pattern: ryo_source::pure::PurePattern::Ident { name, .. },
691 ..
692 } = stmt
693 {
694 if name == var_name {
695 return true;
696 }
697 }
698 }
699
700 let stmt_str = format!("{:?}", stmt);
702 stmt_str.contains(pattern)
703}
704
705fn remove_stmts_in_block(block: &mut PureBlock, pattern: &str, remove_all: bool) -> usize {
707 let initial_len = block.stmts.len();
708 let mut removed = 0;
709
710 block.stmts.retain(|stmt| {
711 if stmt_matches_pattern(stmt, pattern) {
712 if !remove_all && removed > 0 {
713 return true; }
715 removed += 1;
716 false } else {
718 true }
720 });
721
722 for stmt in &mut block.stmts {
724 match stmt {
725 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
726 removed += remove_stmts_in_expr(expr, pattern, remove_all);
727 }
728 _ => {}
729 }
730 }
731
732 removed.min(initial_len - block.stmts.len() + removed)
733}
734
735fn remove_stmts_in_expr(expr: &mut PureExpr, pattern: &str, remove_all: bool) -> usize {
736 match expr {
737 PureExpr::Block { block, .. } => remove_stmts_in_block(block, pattern, remove_all),
738 PureExpr::If {
739 then_branch,
740 else_branch,
741 ..
742 } => {
743 let mut c = remove_stmts_in_block(then_branch, pattern, remove_all);
744 if let Some(else_expr) = else_branch {
745 c += remove_stmts_in_expr(else_expr, pattern, remove_all);
746 }
747 c
748 }
749 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
750 remove_stmts_in_block(block, pattern, remove_all)
751 }
752 PureExpr::For { body, .. } => remove_stmts_in_block(body, pattern, remove_all),
753 PureExpr::Match { arms, .. } => {
754 let mut c = 0;
755 for arm in arms {
756 c += remove_stmts_in_expr(&mut arm.body, pattern, remove_all);
757 }
758 c
759 }
760 PureExpr::Closure { body, .. } => remove_stmts_in_expr(body, pattern, remove_all),
761 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
762 remove_stmts_in_block(body, pattern, remove_all)
763 }
764 _ => 0,
765 }
766}
767
768fn replace_stmts_in_block(block: &mut PureBlock, old: &PureStmt, new: &PureStmt) -> usize {
770 let mut count = 0;
771 for stmt in &mut block.stmts {
772 if stmt == old {
773 *stmt = new.clone();
774 count += 1;
775 } else {
776 match stmt {
778 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
779 count += replace_stmts_in_expr(expr, old, new);
780 }
781 _ => {}
782 }
783 }
784 }
785 count
786}
787
788fn replace_stmts_in_expr(expr: &mut PureExpr, old: &PureStmt, new: &PureStmt) -> usize {
789 match expr {
790 PureExpr::Block { block, .. } => replace_stmts_in_block(block, old, new),
791 PureExpr::If {
792 then_branch,
793 else_branch,
794 ..
795 } => {
796 let mut c = replace_stmts_in_block(then_branch, old, new);
797 if let Some(else_expr) = else_branch {
798 c += replace_stmts_in_expr(else_expr, old, new);
799 }
800 c
801 }
802 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
803 replace_stmts_in_block(block, old, new)
804 }
805 PureExpr::For { body, .. } => replace_stmts_in_block(body, old, new),
806 PureExpr::Match { arms, .. } => {
807 let mut c = 0;
808 for arm in arms {
809 c += replace_stmts_in_expr(&mut arm.body, old, new);
810 }
811 c
812 }
813 PureExpr::Closure { body, .. } => replace_stmts_in_expr(body, old, new),
814 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
815 replace_stmts_in_block(body, old, new)
816 }
817 _ => 0,
818 }
819}
820
821fn find_return_index(stmts: &[PureStmt]) -> usize {
823 for (i, stmt) in stmts.iter().enumerate().rev() {
824 match stmt {
825 PureStmt::Expr(PureExpr::Return(_)) | PureStmt::Semi(PureExpr::Return(_)) => {
826 return i;
827 }
828 PureStmt::Expr(_) if i == stmts.len() - 1 => {
829 return i;
831 }
832 _ => {}
833 }
834 }
835 stmts.len() }
837
838fn insert_relative_to_stmt(
845 stmts: &mut Vec<PureStmt>,
846 stmt: &PureStmt,
847 reference_stmt: &PureStmt,
848 after: bool,
849) -> bool {
850 for i in 0..stmts.len() {
851 if stmt_matches(reference_stmt, &stmts[i]) {
852 let insert_idx = if after { i + 1 } else { i };
853 stmts.insert(insert_idx, stmt.clone());
854 return true;
855 }
856 }
857 false
858}
859
860impl ASTRegApply for ReplaceExprAtMutation {
865 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
866 let fn_id = self.target_fn;
867
868 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
869 return MutationResult {
870 mutation_type: "ReplaceExprAt".to_string(),
871 changes: 0,
872 description: format!("Target function '{:?}' not found", fn_id),
873 };
874 }
875
876 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
877 let mut new_func = func.clone();
878
879 if self.body_indices.is_empty() {
880 return MutationResult {
881 mutation_type: "ReplaceExprAt".to_string(),
882 changes: 0,
883 description: "Empty body indices".to_string(),
884 };
885 }
886
887 let stmt_idx = self.body_indices[0];
889 if let Some(stmt) = new_func.body.stmts.get_mut(stmt_idx) {
890 let replaced = if self.body_indices.len() == 1 {
891 replace_stmt_expr_at(stmt, &self.new_expr)
892 } else {
893 replace_in_stmt_at_path(stmt, &self.body_indices[1..], &self.new_expr)
894 };
895
896 if replaced {
897 ctx.set_ast(fn_id, PureItem::Fn(new_func));
898 ctx.emit_modified(fn_id, ModificationType::BodyModified);
899 return MutationResult {
900 mutation_type: "ReplaceExprAt".to_string(),
901 changes: 1,
902 description: format!(
903 "Replaced expression at {:?} in '{}'",
904 self.body_indices, self.target_fn
905 ),
906 };
907 }
908 }
909 }
910
911 MutationResult {
912 mutation_type: "ReplaceExprAt".to_string(),
913 changes: 0,
914 description: "Failed to replace expression at position".to_string(),
915 }
916 }
917}
918
919fn replace_stmt_expr_at(stmt: &mut PureStmt, new_expr: &PureExpr) -> bool {
920 match stmt {
921 PureStmt::Local { init, .. } => {
922 if init.is_some() {
923 *init = Some(new_expr.clone());
924 true
925 } else {
926 false
927 }
928 }
929 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
930 *expr = new_expr.clone();
931 true
932 }
933 PureStmt::Item(_) => false,
934 }
935}
936
937fn replace_in_stmt_at_path(stmt: &mut PureStmt, path: &[usize], new_expr: &PureExpr) -> bool {
938 let expr = match stmt {
939 PureStmt::Local {
940 init: Some(expr), ..
941 } => expr,
942 PureStmt::Semi(expr) | PureStmt::Expr(expr) => expr,
943 _ => return false,
944 };
945
946 if path.len() == 1 {
947 replace_child_at(expr, path[0], new_expr)
949 } else {
950 if let Some(child) = navigate_expr_mut(expr, &path[..path.len() - 1]) {
952 replace_child_at(child, path[path.len() - 1], new_expr)
953 } else {
954 false
955 }
956 }
957}
958
959fn navigate_expr_mut<'a>(expr: &'a mut PureExpr, path: &[usize]) -> Option<&'a mut PureExpr> {
960 if path.is_empty() {
961 return Some(expr);
962 }
963
964 let idx = path[0];
965 let child: Option<&'a mut PureExpr> = match expr {
966 PureExpr::Binary { left, right, .. } => match idx {
967 0 => Some(left.as_mut()),
968 1 => Some(right.as_mut()),
969 _ => None,
970 },
971 PureExpr::Unary { expr: inner, .. } => {
972 if idx == 0 {
973 Some(inner.as_mut())
974 } else {
975 None
976 }
977 }
978 PureExpr::Call { args, .. } => args.get_mut(idx),
979 PureExpr::MethodCall { receiver, args, .. } => {
980 if idx == 0 {
981 Some(receiver.as_mut())
982 } else {
983 args.get_mut(idx - 1)
984 }
985 }
986 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get_mut(idx),
987 _ => None,
988 };
989
990 child.and_then(|c| navigate_expr_mut(c, &path[1..]))
991}
992
993fn replace_child_at(expr: &mut PureExpr, idx: usize, new_expr: &PureExpr) -> bool {
994 match expr {
995 PureExpr::Binary { left, right, .. } => match idx {
996 0 => {
997 **left = new_expr.clone();
998 true
999 }
1000 1 => {
1001 **right = new_expr.clone();
1002 true
1003 }
1004 _ => false,
1005 },
1006 PureExpr::Unary { expr: inner, .. } if idx == 0 => {
1007 **inner = new_expr.clone();
1008 true
1009 }
1010 PureExpr::Call { args, .. } => {
1011 if let Some(arg) = args.get_mut(idx) {
1012 *arg = new_expr.clone();
1013 true
1014 } else {
1015 false
1016 }
1017 }
1018 PureExpr::MethodCall { receiver, args, .. } => {
1019 if idx == 0 {
1020 **receiver = new_expr.clone();
1021 true
1022 } else if let Some(arg) = args.get_mut(idx - 1) {
1023 *arg = new_expr.clone();
1024 true
1025 } else {
1026 false
1027 }
1028 }
1029 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
1030 if let Some(e) = exprs.get_mut(idx) {
1031 *e = new_expr.clone();
1032 true
1033 } else {
1034 false
1035 }
1036 }
1037 _ => false,
1038 }
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043 use super::*;
1044 use crate::engine::ASTMutationEngine;
1045 use ryo_analysis::testing::ContextBuilder;
1046
1047 #[test]
1048 fn test_v2_replace_expr() {
1049 let mut ctx = ContextBuilder::new()
1050 .with_file(
1051 "src/lib.rs",
1052 r#"
1053fn compute() -> i32 {
1054 1 + 2
1055}
1056"#,
1057 )
1058 .build();
1059
1060 let compute_id = ctx
1062 .registry
1063 .iter()
1064 .find(|(id, path)| {
1065 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1066 && path.name() == "compute"
1067 })
1068 .map(|(id, _)| id)
1069 .expect("compute function not found");
1070
1071 let old = PureExpr::Binary {
1072 op: "+".to_string(),
1073 left: Box::new(PureExpr::Lit("1".to_string())),
1074 right: Box::new(PureExpr::Lit("2".to_string())),
1075 };
1076 let new = PureExpr::Lit("3".to_string());
1077
1078 let mutation = ReplaceExprMutation::new(old, new, compute_id);
1079
1080 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1081 println!("ReplaceExpr result: {:?}", result.result);
1082 }
1083
1084 #[test]
1085 fn test_v2_insert_statement_start() {
1086 let mut ctx = ContextBuilder::new()
1087 .with_file(
1088 "src/lib.rs",
1089 r#"
1090fn greet() {
1091 println!("World");
1092}
1093"#,
1094 )
1095 .build();
1096
1097 let greet_id = ctx
1099 .registry
1100 .iter()
1101 .find(|(id, path)| {
1102 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1103 && path.name() == "greet"
1104 })
1105 .map(|(id, _)| id)
1106 .expect("greet function not found");
1107
1108 let stmt = PureStmt::Semi(PureExpr::Macro {
1109 name: "println".to_string(),
1110 delimiter: MacroDelimiter::Paren,
1111 tokens: "\"Hello\"".to_string(),
1112 });
1113
1114 let mutation = InsertStatementMutation {
1115 stmt,
1116 target_fn: greet_id,
1117 position: InsertPosition::Start,
1118 reference_stmt: None,
1119 };
1120
1121 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1122 assert_eq!(result.result.changes, 1);
1123 }
1124
1125 #[test]
1126 fn test_v2_insert_statement_after_pattern_let() {
1127 use ryo_source::pure::ToPure;
1128
1129 let mut ctx = ContextBuilder::new()
1130 .with_file(
1131 "src/lib.rs",
1132 r#"
1133fn process() {
1134 let x = 1;
1135 let y = 2;
1136}
1137"#,
1138 )
1139 .build();
1140
1141 let process_id = ctx
1142 .registry
1143 .iter()
1144 .find(|(id, path)| {
1145 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1146 && path.name() == "process"
1147 })
1148 .map(|(id, _)| id)
1149 .expect("process function not found");
1150
1151 let new_stmt: syn::Stmt = syn::parse_str("let z = 3;").unwrap();
1153 let ref_stmt: syn::Stmt = syn::parse_str("let x = 1;").unwrap();
1155
1156 let ref_pure = ref_stmt.to_pure();
1158 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(process_id) {
1159 eprintln!("=== AST body stmts ===");
1160 for (i, s) in func.body.stmts.iter().enumerate() {
1161 eprintln!(" [{}] {:?}", i, s);
1162 }
1163 eprintln!("=== reference_stmt ===");
1164 eprintln!(" {:?}", ref_pure);
1165 eprintln!("=== match result ===");
1166 for (i, s) in func.body.stmts.iter().enumerate() {
1167 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1168 }
1169 }
1170
1171 let mutation = InsertStatementMutation {
1172 stmt: new_stmt.to_pure(),
1173 target_fn: process_id,
1174 position: InsertPosition::AfterPattern,
1175 reference_stmt: Some(ref_pure),
1176 };
1177
1178 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1179 assert_eq!(
1180 result.result.changes, 1,
1181 "AfterPattern should insert 1 statement, got description: {}",
1182 result.result.description
1183 );
1184 }
1185
1186 #[test]
1187 fn test_v2_insert_statement_after_pattern_macro() {
1188 use ryo_source::pure::ToPure;
1189
1190 let mut ctx = ContextBuilder::new()
1191 .with_file(
1192 "src/lib.rs",
1193 r#"
1194fn greet() {
1195 println!("hello");
1196 println!("world");
1197}
1198"#,
1199 )
1200 .build();
1201
1202 let greet_id = ctx
1203 .registry
1204 .iter()
1205 .find(|(id, path)| {
1206 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1207 && path.name() == "greet"
1208 })
1209 .map(|(id, _)| id)
1210 .expect("greet function not found");
1211
1212 let new_stmt: syn::Stmt = syn::parse_str("println!(\"inserted\");").unwrap();
1214 let ref_stmt: syn::Stmt = syn::parse_str("println!(\"hello\");").unwrap();
1216
1217 let ref_pure = ref_stmt.to_pure();
1218 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(greet_id) {
1219 eprintln!("=== AST body stmts (macro test) ===");
1220 for (i, s) in func.body.stmts.iter().enumerate() {
1221 eprintln!(" [{}] {:?}", i, s);
1222 }
1223 eprintln!("=== reference_stmt ===");
1224 eprintln!(" {:?}", ref_pure);
1225 eprintln!("=== match result ===");
1226 for (i, s) in func.body.stmts.iter().enumerate() {
1227 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1228 }
1229 }
1230
1231 let mutation = InsertStatementMutation {
1232 stmt: new_stmt.to_pure(),
1233 target_fn: greet_id,
1234 position: InsertPosition::AfterPattern,
1235 reference_stmt: Some(ref_pure),
1236 };
1237
1238 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1239 assert_eq!(
1240 result.result.changes, 1,
1241 "AfterPattern should insert 1 statement, got description: {}",
1242 result.result.description
1243 );
1244 }
1245
1246 #[test]
1247 fn test_v2_insert_statement_after_pattern_method_chain() {
1248 use ryo_source::pure::ToPure;
1249
1250 let mut ctx = ContextBuilder::new()
1251 .with_file(
1252 "src/lib.rs",
1253 r#"
1254fn process(config: &mut Config) {
1255 config.set_timeout(Duration::from_secs(30));
1256 config.set_retries(3);
1257}
1258"#,
1259 )
1260 .build();
1261
1262 let process_id = ctx
1263 .registry
1264 .iter()
1265 .find(|(id, path)| {
1266 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1267 && path.name() == "process"
1268 })
1269 .map(|(id, _)| id)
1270 .expect("process function not found");
1271
1272 let ref_stmt: syn::Stmt =
1274 syn::parse_str("config.set_timeout(Duration::from_secs(30));").unwrap();
1275 let new_stmt: syn::Stmt = syn::parse_str("config.enable_logging();").unwrap();
1276
1277 let ref_pure = ref_stmt.to_pure();
1278 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(process_id) {
1279 eprintln!("=== AST body stmts (method chain test) ===");
1280 for (i, s) in func.body.stmts.iter().enumerate() {
1281 eprintln!(" [{}] {:?}", i, s);
1282 }
1283 eprintln!("=== reference_stmt ===");
1284 eprintln!(" {:?}", ref_pure);
1285 eprintln!("=== match ===");
1286 for (i, s) in func.body.stmts.iter().enumerate() {
1287 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1288 }
1289 }
1290
1291 let mutation = InsertStatementMutation {
1292 stmt: new_stmt.to_pure(),
1293 target_fn: process_id,
1294 position: InsertPosition::AfterPattern,
1295 reference_stmt: Some(ref_pure),
1296 };
1297
1298 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1299 assert_eq!(
1300 result.result.changes, 1,
1301 "AfterPattern for method chain should work, got: {}",
1302 result.result.description
1303 );
1304 }
1305
1306 #[test]
1307 fn test_v2_remove_statement_no_match() {
1308 let mut ctx = ContextBuilder::new()
1309 .with_file(
1310 "src/lib.rs",
1311 r#"
1312fn simple() -> i32 {
1313 42
1314}
1315"#,
1316 )
1317 .build();
1318
1319 use ryo_analysis::SymbolKind;
1320 use ryo_source::pure::ToPure;
1321
1322 let simple_id = ctx
1323 .registry
1324 .iter()
1325 .find(|(id, path)| {
1326 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1327 && path.name() == "simple"
1328 })
1329 .map(|(id, _)| id)
1330 .expect("simple function not found");
1331
1332 let target_stmt: syn::Stmt = syn::parse_str("nonexistent;").unwrap();
1333 let mutation = RemoveStatementMutation::new(
1334 target_stmt.to_pure(),
1335 "nonexistent;".to_string(),
1336 simple_id,
1337 );
1338
1339 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1340 assert_eq!(result.result.changes, 0);
1341 }
1342}