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 PureStmt::Verbatim(_) => 0,
322 }
323}
324
325fn wrap_expr(expr: &mut PureExpr, target: &PureExpr, wrapper_macro: &str, wrap_all: bool) -> usize {
326 if expr == target {
327 let wrapped = PureExpr::Macro {
328 name: wrapper_macro.to_string(),
329 delimiter: MacroDelimiter::Paren,
330 tokens: format!("{:?}", expr),
331 };
332 *expr = wrapped;
333 return 1;
334 }
335
336 match expr {
337 PureExpr::Binary { left, right, .. } => {
338 let mut c = wrap_expr(left, target, wrapper_macro, wrap_all);
339 if wrap_all || c == 0 {
340 c += wrap_expr(right, target, wrapper_macro, wrap_all);
341 }
342 c
343 }
344 PureExpr::Unary { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
345 PureExpr::Call { func, args } => {
346 let mut c = wrap_expr(func, target, wrapper_macro, wrap_all);
347 for arg in args {
348 if wrap_all || c == 0 {
349 c += wrap_expr(arg, target, wrapper_macro, wrap_all);
350 }
351 }
352 c
353 }
354 PureExpr::MethodCall { receiver, args, .. } => {
355 let mut c = wrap_expr(receiver, target, wrapper_macro, wrap_all);
356 for arg in args {
357 if wrap_all || c == 0 {
358 c += wrap_expr(arg, target, wrapper_macro, wrap_all);
359 }
360 }
361 c
362 }
363 PureExpr::Field { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
364 PureExpr::Index { expr: e, index } => {
365 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
366 if wrap_all || c == 0 {
367 c += wrap_expr(index, target, wrapper_macro, wrap_all);
368 }
369 c
370 }
371 PureExpr::Block { block: b, .. } => wrap_expr_in_block(b, target, wrapper_macro, wrap_all),
372 PureExpr::If {
373 cond,
374 then_branch,
375 else_branch,
376 } => {
377 let mut c = wrap_expr(cond, target, wrapper_macro, wrap_all);
378 if wrap_all || c == 0 {
379 c += wrap_expr_in_block(then_branch, target, wrapper_macro, wrap_all);
380 }
381 if let Some(else_expr) = else_branch {
382 if wrap_all || c == 0 {
383 c += wrap_expr(else_expr, target, wrapper_macro, wrap_all);
384 }
385 }
386 c
387 }
388 PureExpr::Match { expr: e, arms } => {
389 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
390 for arm in arms {
391 if wrap_all || c == 0 {
392 c += wrap_expr(&mut arm.body, target, wrapper_macro, wrap_all);
393 }
394 if let Some(guard) = &mut arm.guard {
395 if wrap_all || c == 0 {
396 c += wrap_expr(guard, target, wrapper_macro, wrap_all);
397 }
398 }
399 }
400 c
401 }
402 PureExpr::Loop { body: b, .. } => wrap_expr_in_block(b, target, wrapper_macro, wrap_all),
403 PureExpr::While { cond, body, .. } => {
404 let mut c = wrap_expr(cond, target, wrapper_macro, wrap_all);
405 if wrap_all || c == 0 {
406 c += wrap_expr_in_block(body, target, wrapper_macro, wrap_all);
407 }
408 c
409 }
410 PureExpr::For { expr: e, body, .. } => {
411 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
412 if wrap_all || c == 0 {
413 c += wrap_expr_in_block(body, target, wrapper_macro, wrap_all);
414 }
415 c
416 }
417 PureExpr::Return(Some(inner))
418 | PureExpr::Break {
419 expr: Some(inner), ..
420 } => wrap_expr(inner, target, wrapper_macro, wrap_all),
421 PureExpr::Closure { body, .. } => wrap_expr(body, target, wrapper_macro, wrap_all),
422 PureExpr::Struct { fields, .. } => {
423 let mut c = 0;
424 for (_, field_expr) in fields {
425 if wrap_all || c == 0 {
426 c += wrap_expr(field_expr, target, wrapper_macro, wrap_all);
427 }
428 }
429 c
430 }
431 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
432 let mut c = 0;
433 for e in exprs {
434 if wrap_all || c == 0 {
435 c += wrap_expr(e, target, wrapper_macro, wrap_all);
436 }
437 }
438 c
439 }
440 PureExpr::Ref { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
441 PureExpr::Await(inner) | PureExpr::Try(inner) => {
442 wrap_expr(inner, target, wrapper_macro, wrap_all)
443 }
444 PureExpr::Range { start, end, .. } => {
445 let mut c = 0;
446 if let Some(s) = start {
447 c += wrap_expr(s, target, wrapper_macro, wrap_all);
448 }
449 if let Some(e) = end {
450 if wrap_all || c == 0 {
451 c += wrap_expr(e, target, wrapper_macro, wrap_all);
452 }
453 }
454 c
455 }
456 PureExpr::Cast { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
457 PureExpr::Let { expr: inner, .. } => wrap_expr(inner, target, wrapper_macro, wrap_all),
458 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
459 wrap_expr_in_block(body, target, wrapper_macro, wrap_all)
460 }
461 PureExpr::Repeat { expr: e, len } => {
462 let mut c = wrap_expr(e, target, wrapper_macro, wrap_all);
463 if wrap_all || c == 0 {
464 c += wrap_expr(len, target, wrapper_macro, wrap_all);
465 }
466 c
467 }
468 PureExpr::Lit(_)
469 | PureExpr::Path(_)
470 | PureExpr::Macro { .. }
471 | PureExpr::Return(None)
472 | PureExpr::Break { expr: None, .. }
473 | PureExpr::Continue { .. }
474 | PureExpr::Other(_)
475 | PureExpr::Verbatim(_) => 0,
477 }
478}
479
480fn replace_expr_in_stmt(
481 stmt: &mut PureStmt,
482 old: &PureExpr,
483 new: &PureExpr,
484 replace_all: bool,
485) -> usize {
486 match stmt {
487 PureStmt::Local { init, .. } => {
488 if let Some(expr) = init {
489 replace_expr(expr, old, new, replace_all)
490 } else {
491 0
492 }
493 }
494 PureStmt::Semi(expr) | PureStmt::Expr(expr) => replace_expr(expr, old, new, replace_all),
495 PureStmt::Item(_) => 0,
496 PureStmt::Verbatim(_) => 0,
499 }
500}
501
502fn replace_expr(expr: &mut PureExpr, old: &PureExpr, new: &PureExpr, replace_all: bool) -> usize {
503 if expr == old {
504 *expr = new.clone();
505 return 1;
506 }
507
508 match expr {
509 PureExpr::Binary { left, right, .. } => {
510 let mut c = replace_expr(left, old, new, replace_all);
511 if replace_all || c == 0 {
512 c += replace_expr(right, old, new, replace_all);
513 }
514 c
515 }
516 PureExpr::Unary { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
517 PureExpr::Call { func, args } => {
518 let mut c = replace_expr(func, old, new, replace_all);
519 for arg in args {
520 if replace_all || c == 0 {
521 c += replace_expr(arg, old, new, replace_all);
522 }
523 }
524 c
525 }
526 PureExpr::MethodCall { receiver, args, .. } => {
527 let mut c = replace_expr(receiver, old, new, replace_all);
528 for arg in args {
529 if replace_all || c == 0 {
530 c += replace_expr(arg, old, new, replace_all);
531 }
532 }
533 c
534 }
535 PureExpr::Field { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
536 PureExpr::Index { expr: e, index } => {
537 let mut c = replace_expr(e, old, new, replace_all);
538 if replace_all || c == 0 {
539 c += replace_expr(index, old, new, replace_all);
540 }
541 c
542 }
543 PureExpr::Block { block: b, .. } => replace_expr_in_block(b, old, new, replace_all),
544 PureExpr::If {
545 cond,
546 then_branch,
547 else_branch,
548 } => {
549 let mut c = replace_expr(cond, old, new, replace_all);
550 if replace_all || c == 0 {
551 c += replace_expr_in_block(then_branch, old, new, replace_all);
552 }
553 if let Some(else_expr) = else_branch {
554 if replace_all || c == 0 {
555 c += replace_expr(else_expr, old, new, replace_all);
556 }
557 }
558 c
559 }
560 PureExpr::Match { expr: e, arms } => {
561 let mut c = replace_expr(e, old, new, replace_all);
562 for arm in arms {
563 if replace_all || c == 0 {
564 c += replace_expr(&mut arm.body, old, new, replace_all);
565 }
566 if let Some(guard) = &mut arm.guard {
567 if replace_all || c == 0 {
568 c += replace_expr(guard, old, new, replace_all);
569 }
570 }
571 }
572 c
573 }
574 PureExpr::Loop { body: b, .. } => replace_expr_in_block(b, old, new, replace_all),
575 PureExpr::While { cond, body, .. } => {
576 let mut c = replace_expr(cond, old, new, replace_all);
577 if replace_all || c == 0 {
578 c += replace_expr_in_block(body, old, new, replace_all);
579 }
580 c
581 }
582 PureExpr::For { expr: e, body, .. } => {
583 let mut c = replace_expr(e, old, new, replace_all);
584 if replace_all || c == 0 {
585 c += replace_expr_in_block(body, old, new, replace_all);
586 }
587 c
588 }
589 PureExpr::Return(Some(inner))
590 | PureExpr::Break {
591 expr: Some(inner), ..
592 } => replace_expr(inner, old, new, replace_all),
593 PureExpr::Closure { body, .. } => replace_expr(body, old, new, replace_all),
594 PureExpr::Struct { fields, .. } => {
595 let mut c = 0;
596 for (_, field_expr) in fields {
597 if replace_all || c == 0 {
598 c += replace_expr(field_expr, old, new, replace_all);
599 }
600 }
601 c
602 }
603 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
604 let mut c = 0;
605 for e in exprs {
606 if replace_all || c == 0 {
607 c += replace_expr(e, old, new, replace_all);
608 }
609 }
610 c
611 }
612 PureExpr::Ref { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
613 PureExpr::Await(inner) | PureExpr::Try(inner) => replace_expr(inner, old, new, replace_all),
614 PureExpr::Range { start, end, .. } => {
615 let mut c = 0;
616 if let Some(s) = start {
617 c += replace_expr(s, old, new, replace_all);
618 }
619 if let Some(e) = end {
620 if replace_all || c == 0 {
621 c += replace_expr(e, old, new, replace_all);
622 }
623 }
624 c
625 }
626 PureExpr::Cast { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
627 PureExpr::Let { expr: inner, .. } => replace_expr(inner, old, new, replace_all),
628 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
629 replace_expr_in_block(body, old, new, replace_all)
630 }
631 PureExpr::Repeat { expr: e, len } => {
632 let mut c = replace_expr(e, old, new, replace_all);
633 if replace_all || c == 0 {
634 c += replace_expr(len, old, new, replace_all);
635 }
636 c
637 }
638 PureExpr::Lit(_)
639 | PureExpr::Path(_)
640 | PureExpr::Macro { .. }
641 | PureExpr::Return(None)
642 | PureExpr::Break { expr: None, .. }
643 | PureExpr::Continue { .. }
644 | PureExpr::Other(_)
645 | PureExpr::Verbatim(_) => 0,
647 }
648}
649
650fn stmt_matches(target: &PureStmt, stmt: &PureStmt) -> bool {
659 target == stmt
660}
661
662fn stmt_matches_pattern(stmt: &PureStmt, pattern: &str) -> bool {
672 let macro_name = pattern
674 .strip_suffix("!(..)")
675 .or_else(|| pattern.strip_suffix('!'));
676
677 if let Some(name) = macro_name {
678 match stmt {
680 PureStmt::Semi(PureExpr::Macro { name: macro_n, .. })
681 | PureStmt::Expr(PureExpr::Macro { name: macro_n, .. }) => {
682 return macro_n == name;
683 }
684 _ => {}
685 }
686 }
687
688 if let Some(after_let) = pattern.strip_prefix("let ") {
690 let var_name = if let Some(eq_pos) = after_let.find(" = ") {
692 after_let[..eq_pos].trim()
693 } else if let Some(eq_pos) = after_let.find("=") {
694 after_let[..eq_pos].trim()
695 } else {
696 after_let.trim()
697 };
698
699 if let PureStmt::Local {
700 pattern: ryo_source::pure::PurePattern::Ident { name, .. },
701 ..
702 } = stmt
703 {
704 if name == var_name {
705 return true;
706 }
707 }
708 }
709
710 let stmt_str = format!("{:?}", stmt);
712 stmt_str.contains(pattern)
713}
714
715fn remove_stmts_in_block(block: &mut PureBlock, pattern: &str, remove_all: bool) -> usize {
717 let initial_len = block.stmts.len();
718 let mut removed = 0;
719
720 block.stmts.retain(|stmt| {
721 if stmt_matches_pattern(stmt, pattern) {
722 if !remove_all && removed > 0 {
723 return true; }
725 removed += 1;
726 false } else {
728 true }
730 });
731
732 for stmt in &mut block.stmts {
734 match stmt {
735 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
736 removed += remove_stmts_in_expr(expr, pattern, remove_all);
737 }
738 _ => {}
739 }
740 }
741
742 removed.min(initial_len - block.stmts.len() + removed)
743}
744
745fn remove_stmts_in_expr(expr: &mut PureExpr, pattern: &str, remove_all: bool) -> usize {
746 match expr {
747 PureExpr::Block { block, .. } => remove_stmts_in_block(block, pattern, remove_all),
748 PureExpr::If {
749 then_branch,
750 else_branch,
751 ..
752 } => {
753 let mut c = remove_stmts_in_block(then_branch, pattern, remove_all);
754 if let Some(else_expr) = else_branch {
755 c += remove_stmts_in_expr(else_expr, pattern, remove_all);
756 }
757 c
758 }
759 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
760 remove_stmts_in_block(block, pattern, remove_all)
761 }
762 PureExpr::For { body, .. } => remove_stmts_in_block(body, pattern, remove_all),
763 PureExpr::Match { arms, .. } => {
764 let mut c = 0;
765 for arm in arms {
766 c += remove_stmts_in_expr(&mut arm.body, pattern, remove_all);
767 }
768 c
769 }
770 PureExpr::Closure { body, .. } => remove_stmts_in_expr(body, pattern, remove_all),
771 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
772 remove_stmts_in_block(body, pattern, remove_all)
773 }
774 _ => 0,
775 }
776}
777
778fn replace_stmts_in_block(block: &mut PureBlock, old: &PureStmt, new: &PureStmt) -> usize {
780 let mut count = 0;
781 for stmt in &mut block.stmts {
782 if stmt == old {
783 *stmt = new.clone();
784 count += 1;
785 } else {
786 match stmt {
788 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
789 count += replace_stmts_in_expr(expr, old, new);
790 }
791 _ => {}
792 }
793 }
794 }
795 count
796}
797
798fn replace_stmts_in_expr(expr: &mut PureExpr, old: &PureStmt, new: &PureStmt) -> usize {
799 match expr {
800 PureExpr::Block { block, .. } => replace_stmts_in_block(block, old, new),
801 PureExpr::If {
802 then_branch,
803 else_branch,
804 ..
805 } => {
806 let mut c = replace_stmts_in_block(then_branch, old, new);
807 if let Some(else_expr) = else_branch {
808 c += replace_stmts_in_expr(else_expr, old, new);
809 }
810 c
811 }
812 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
813 replace_stmts_in_block(block, old, new)
814 }
815 PureExpr::For { body, .. } => replace_stmts_in_block(body, old, new),
816 PureExpr::Match { arms, .. } => {
817 let mut c = 0;
818 for arm in arms {
819 c += replace_stmts_in_expr(&mut arm.body, old, new);
820 }
821 c
822 }
823 PureExpr::Closure { body, .. } => replace_stmts_in_expr(body, old, new),
824 PureExpr::Async { body, .. } | PureExpr::Unsafe(body) => {
825 replace_stmts_in_block(body, old, new)
826 }
827 _ => 0,
828 }
829}
830
831fn find_return_index(stmts: &[PureStmt]) -> usize {
833 for (i, stmt) in stmts.iter().enumerate().rev() {
834 match stmt {
835 PureStmt::Expr(PureExpr::Return(_)) | PureStmt::Semi(PureExpr::Return(_)) => {
836 return i;
837 }
838 PureStmt::Expr(_) if i == stmts.len() - 1 => {
839 return i;
841 }
842 _ => {}
843 }
844 }
845 stmts.len() }
847
848fn insert_relative_to_stmt(
855 stmts: &mut Vec<PureStmt>,
856 stmt: &PureStmt,
857 reference_stmt: &PureStmt,
858 after: bool,
859) -> bool {
860 for i in 0..stmts.len() {
861 if stmt_matches(reference_stmt, &stmts[i]) {
862 let insert_idx = if after { i + 1 } else { i };
863 stmts.insert(insert_idx, stmt.clone());
864 return true;
865 }
866 }
867 false
868}
869
870impl ASTRegApply for ReplaceExprAtMutation {
875 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
876 let fn_id = self.target_fn;
877
878 if !matches!(ctx.symbol_registry.kind(fn_id), Some(SymbolKind::Function)) {
879 return MutationResult {
880 mutation_type: "ReplaceExprAt".to_string(),
881 changes: 0,
882 description: format!("Target function '{:?}' not found", fn_id),
883 };
884 }
885
886 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
887 let mut new_func = func.clone();
888
889 if self.body_indices.is_empty() {
890 return MutationResult {
891 mutation_type: "ReplaceExprAt".to_string(),
892 changes: 0,
893 description: "Empty body indices".to_string(),
894 };
895 }
896
897 let stmt_idx = self.body_indices[0];
899 if let Some(stmt) = new_func.body.stmts.get_mut(stmt_idx) {
900 let replaced = if self.body_indices.len() == 1 {
901 replace_stmt_expr_at(stmt, &self.new_expr)
902 } else {
903 replace_in_stmt_at_path(stmt, &self.body_indices[1..], &self.new_expr)
904 };
905
906 if replaced {
907 ctx.set_ast(fn_id, PureItem::Fn(new_func));
908 ctx.emit_modified(fn_id, ModificationType::BodyModified);
909 return MutationResult {
910 mutation_type: "ReplaceExprAt".to_string(),
911 changes: 1,
912 description: format!(
913 "Replaced expression at {:?} in '{}'",
914 self.body_indices, self.target_fn
915 ),
916 };
917 }
918 }
919 }
920
921 MutationResult {
922 mutation_type: "ReplaceExprAt".to_string(),
923 changes: 0,
924 description: "Failed to replace expression at position".to_string(),
925 }
926 }
927}
928
929fn replace_stmt_expr_at(stmt: &mut PureStmt, new_expr: &PureExpr) -> bool {
930 match stmt {
931 PureStmt::Local { init, .. } => {
932 if init.is_some() {
933 *init = Some(new_expr.clone());
934 true
935 } else {
936 false
937 }
938 }
939 PureStmt::Semi(expr) | PureStmt::Expr(expr) => {
940 *expr = new_expr.clone();
941 true
942 }
943 PureStmt::Item(_) => false,
944 PureStmt::Verbatim(_) => false,
946 }
947}
948
949fn replace_in_stmt_at_path(stmt: &mut PureStmt, path: &[usize], new_expr: &PureExpr) -> bool {
950 let expr = match stmt {
951 PureStmt::Local {
952 init: Some(expr), ..
953 } => expr,
954 PureStmt::Semi(expr) | PureStmt::Expr(expr) => expr,
955 _ => return false,
956 };
957
958 if path.len() == 1 {
959 replace_child_at(expr, path[0], new_expr)
961 } else {
962 if let Some(child) = navigate_expr_mut(expr, &path[..path.len() - 1]) {
964 replace_child_at(child, path[path.len() - 1], new_expr)
965 } else {
966 false
967 }
968 }
969}
970
971fn navigate_expr_mut<'a>(expr: &'a mut PureExpr, path: &[usize]) -> Option<&'a mut PureExpr> {
972 if path.is_empty() {
973 return Some(expr);
974 }
975
976 let idx = path[0];
977 let child: Option<&'a mut PureExpr> = match expr {
978 PureExpr::Binary { left, right, .. } => match idx {
979 0 => Some(left.as_mut()),
980 1 => Some(right.as_mut()),
981 _ => None,
982 },
983 PureExpr::Unary { expr: inner, .. } => {
984 if idx == 0 {
985 Some(inner.as_mut())
986 } else {
987 None
988 }
989 }
990 PureExpr::Call { args, .. } => args.get_mut(idx),
991 PureExpr::MethodCall { receiver, args, .. } => {
992 if idx == 0 {
993 Some(receiver.as_mut())
994 } else {
995 args.get_mut(idx - 1)
996 }
997 }
998 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => exprs.get_mut(idx),
999 _ => None,
1000 };
1001
1002 child.and_then(|c| navigate_expr_mut(c, &path[1..]))
1003}
1004
1005fn replace_child_at(expr: &mut PureExpr, idx: usize, new_expr: &PureExpr) -> bool {
1006 match expr {
1007 PureExpr::Binary { left, right, .. } => match idx {
1008 0 => {
1009 **left = new_expr.clone();
1010 true
1011 }
1012 1 => {
1013 **right = new_expr.clone();
1014 true
1015 }
1016 _ => false,
1017 },
1018 PureExpr::Unary { expr: inner, .. } if idx == 0 => {
1019 **inner = new_expr.clone();
1020 true
1021 }
1022 PureExpr::Call { args, .. } => {
1023 if let Some(arg) = args.get_mut(idx) {
1024 *arg = new_expr.clone();
1025 true
1026 } else {
1027 false
1028 }
1029 }
1030 PureExpr::MethodCall { receiver, args, .. } => {
1031 if idx == 0 {
1032 **receiver = new_expr.clone();
1033 true
1034 } else if let Some(arg) = args.get_mut(idx - 1) {
1035 *arg = new_expr.clone();
1036 true
1037 } else {
1038 false
1039 }
1040 }
1041 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
1042 if let Some(e) = exprs.get_mut(idx) {
1043 *e = new_expr.clone();
1044 true
1045 } else {
1046 false
1047 }
1048 }
1049 _ => false,
1050 }
1051}
1052
1053#[cfg(test)]
1054mod tests {
1055 use super::*;
1056 use crate::engine::ASTMutationEngine;
1057 use ryo_analysis::testing::ContextBuilder;
1058
1059 #[test]
1060 fn test_v2_replace_expr() {
1061 let mut ctx = ContextBuilder::new()
1062 .with_file(
1063 "src/lib.rs",
1064 r#"
1065fn compute() -> i32 {
1066 1 + 2
1067}
1068"#,
1069 )
1070 .build();
1071
1072 let compute_id = ctx
1074 .registry
1075 .iter()
1076 .find(|(id, path)| {
1077 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1078 && path.name() == "compute"
1079 })
1080 .map(|(id, _)| id)
1081 .expect("compute function not found");
1082
1083 let old = PureExpr::Binary {
1084 op: "+".to_string(),
1085 left: Box::new(PureExpr::Lit("1".to_string())),
1086 right: Box::new(PureExpr::Lit("2".to_string())),
1087 };
1088 let new = PureExpr::Lit("3".to_string());
1089
1090 let mutation = ReplaceExprMutation::new(old, new, compute_id);
1091
1092 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1093 println!("ReplaceExpr result: {:?}", result.result);
1094 }
1095
1096 #[test]
1097 fn test_v2_insert_statement_start() {
1098 let mut ctx = ContextBuilder::new()
1099 .with_file(
1100 "src/lib.rs",
1101 r#"
1102fn greet() {
1103 println!("World");
1104}
1105"#,
1106 )
1107 .build();
1108
1109 let greet_id = ctx
1111 .registry
1112 .iter()
1113 .find(|(id, path)| {
1114 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1115 && path.name() == "greet"
1116 })
1117 .map(|(id, _)| id)
1118 .expect("greet function not found");
1119
1120 let stmt = PureStmt::Semi(PureExpr::Macro {
1121 name: "println".to_string(),
1122 delimiter: MacroDelimiter::Paren,
1123 tokens: "\"Hello\"".to_string(),
1124 });
1125
1126 let mutation = InsertStatementMutation {
1127 stmt,
1128 target_fn: greet_id,
1129 position: InsertPosition::Start,
1130 reference_stmt: None,
1131 };
1132
1133 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1134 assert_eq!(result.result.changes, 1);
1135 }
1136
1137 #[test]
1138 fn test_v2_insert_statement_after_pattern_let() {
1139 use ryo_source::pure::ToPure;
1140
1141 let mut ctx = ContextBuilder::new()
1142 .with_file(
1143 "src/lib.rs",
1144 r#"
1145fn process() {
1146 let x = 1;
1147 let y = 2;
1148}
1149"#,
1150 )
1151 .build();
1152
1153 let process_id = ctx
1154 .registry
1155 .iter()
1156 .find(|(id, path)| {
1157 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1158 && path.name() == "process"
1159 })
1160 .map(|(id, _)| id)
1161 .expect("process function not found");
1162
1163 let new_stmt: syn::Stmt = syn::parse_str("let z = 3;").unwrap();
1165 let ref_stmt: syn::Stmt = syn::parse_str("let x = 1;").unwrap();
1167
1168 let ref_pure = ref_stmt.to_pure();
1170 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(process_id) {
1171 eprintln!("=== AST body stmts ===");
1172 for (i, s) in func.body.stmts.iter().enumerate() {
1173 eprintln!(" [{}] {:?}", i, s);
1174 }
1175 eprintln!("=== reference_stmt ===");
1176 eprintln!(" {:?}", ref_pure);
1177 eprintln!("=== match result ===");
1178 for (i, s) in func.body.stmts.iter().enumerate() {
1179 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1180 }
1181 }
1182
1183 let mutation = InsertStatementMutation {
1184 stmt: new_stmt.to_pure(),
1185 target_fn: process_id,
1186 position: InsertPosition::AfterPattern,
1187 reference_stmt: Some(ref_pure),
1188 };
1189
1190 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1191 assert_eq!(
1192 result.result.changes, 1,
1193 "AfterPattern should insert 1 statement, got description: {}",
1194 result.result.description
1195 );
1196 }
1197
1198 #[test]
1199 fn test_v2_insert_statement_after_pattern_macro() {
1200 use ryo_source::pure::ToPure;
1201
1202 let mut ctx = ContextBuilder::new()
1203 .with_file(
1204 "src/lib.rs",
1205 r#"
1206fn greet() {
1207 println!("hello");
1208 println!("world");
1209}
1210"#,
1211 )
1212 .build();
1213
1214 let greet_id = ctx
1215 .registry
1216 .iter()
1217 .find(|(id, path)| {
1218 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1219 && path.name() == "greet"
1220 })
1221 .map(|(id, _)| id)
1222 .expect("greet function not found");
1223
1224 let new_stmt: syn::Stmt = syn::parse_str("println!(\"inserted\");").unwrap();
1226 let ref_stmt: syn::Stmt = syn::parse_str("println!(\"hello\");").unwrap();
1228
1229 let ref_pure = ref_stmt.to_pure();
1230 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(greet_id) {
1231 eprintln!("=== AST body stmts (macro test) ===");
1232 for (i, s) in func.body.stmts.iter().enumerate() {
1233 eprintln!(" [{}] {:?}", i, s);
1234 }
1235 eprintln!("=== reference_stmt ===");
1236 eprintln!(" {:?}", ref_pure);
1237 eprintln!("=== match result ===");
1238 for (i, s) in func.body.stmts.iter().enumerate() {
1239 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1240 }
1241 }
1242
1243 let mutation = InsertStatementMutation {
1244 stmt: new_stmt.to_pure(),
1245 target_fn: greet_id,
1246 position: InsertPosition::AfterPattern,
1247 reference_stmt: Some(ref_pure),
1248 };
1249
1250 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1251 assert_eq!(
1252 result.result.changes, 1,
1253 "AfterPattern should insert 1 statement, got description: {}",
1254 result.result.description
1255 );
1256 }
1257
1258 #[test]
1259 fn test_v2_insert_statement_after_pattern_method_chain() {
1260 use ryo_source::pure::ToPure;
1261
1262 let mut ctx = ContextBuilder::new()
1263 .with_file(
1264 "src/lib.rs",
1265 r#"
1266fn process(config: &mut Config) {
1267 config.set_timeout(Duration::from_secs(30));
1268 config.set_retries(3);
1269}
1270"#,
1271 )
1272 .build();
1273
1274 let process_id = ctx
1275 .registry
1276 .iter()
1277 .find(|(id, path)| {
1278 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1279 && path.name() == "process"
1280 })
1281 .map(|(id, _)| id)
1282 .expect("process function not found");
1283
1284 let ref_stmt: syn::Stmt =
1286 syn::parse_str("config.set_timeout(Duration::from_secs(30));").unwrap();
1287 let new_stmt: syn::Stmt = syn::parse_str("config.enable_logging();").unwrap();
1288
1289 let ref_pure = ref_stmt.to_pure();
1290 if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(process_id) {
1291 eprintln!("=== AST body stmts (method chain test) ===");
1292 for (i, s) in func.body.stmts.iter().enumerate() {
1293 eprintln!(" [{}] {:?}", i, s);
1294 }
1295 eprintln!("=== reference_stmt ===");
1296 eprintln!(" {:?}", ref_pure);
1297 eprintln!("=== match ===");
1298 for (i, s) in func.body.stmts.iter().enumerate() {
1299 eprintln!(" [{}] == ref? {}", i, &ref_pure == s);
1300 }
1301 }
1302
1303 let mutation = InsertStatementMutation {
1304 stmt: new_stmt.to_pure(),
1305 target_fn: process_id,
1306 position: InsertPosition::AfterPattern,
1307 reference_stmt: Some(ref_pure),
1308 };
1309
1310 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1311 assert_eq!(
1312 result.result.changes, 1,
1313 "AfterPattern for method chain should work, got: {}",
1314 result.result.description
1315 );
1316 }
1317
1318 #[test]
1319 fn test_v2_remove_statement_no_match() {
1320 let mut ctx = ContextBuilder::new()
1321 .with_file(
1322 "src/lib.rs",
1323 r#"
1324fn simple() -> i32 {
1325 42
1326}
1327"#,
1328 )
1329 .build();
1330
1331 use ryo_analysis::SymbolKind;
1332 use ryo_source::pure::ToPure;
1333
1334 let simple_id = ctx
1335 .registry
1336 .iter()
1337 .find(|(id, path)| {
1338 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
1339 && path.name() == "simple"
1340 })
1341 .map(|(id, _)| id)
1342 .expect("simple function not found");
1343
1344 let target_stmt: syn::Stmt = syn::parse_str("nonexistent;").unwrap();
1345 let mutation = RemoveStatementMutation::new(
1346 target_stmt.to_pure(),
1347 "nonexistent;".to_string(),
1348 simple_id,
1349 );
1350
1351 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
1352 assert_eq!(result.result.changes, 0);
1353 }
1354}