Skip to main content

ryo_executor/engine/impls/
stmt_ops.rs

1//! V2 ASTRegApply implementations for statement/expression operations
2//!
3//! - ReplaceExpr: Replace expressions matching a pattern
4//! - ReplaceExprAt: Replace expression at specific position indices
5//! - WrapExpr: Wrap expressions with macro calls
6//! - RemoveStatement: Remove statements matching a pattern
7//! - InsertStatement: Insert statements at specific positions
8//! - ReplaceStatement: Replace statements matching a pattern
9//!
10//! # Implementation Strategy
11//!
12//! All operations work on function bodies in the ASTRegistry:
13//! 1. Find target function(s)
14//! 2. Traverse/modify statements or expressions
15//! 3. Update the AST in registry
16
17use 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
27// =============================================================================
28// ReplaceExprMutation
29// =============================================================================
30
31impl 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
71// =============================================================================
72// WrapExprMutation
73// =============================================================================
74
75impl 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
118// =============================================================================
119// RemoveStatementMutation
120// =============================================================================
121
122impl 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
157// =============================================================================
158// InsertStatementMutation
159// =============================================================================
160
161impl 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                    // Insert before return statement if present, otherwise at end
183                    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
223// =============================================================================
224// ReplaceStatementMutation
225// =============================================================================
226
227impl 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
263// =============================================================================
264// Helper functions
265// =============================================================================
266
267/// Replace expressions in a block
268fn 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
284/// Wrap expressions in a block
285fn 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
640/// Check if two statements match structurally (using PartialEq).
641///
642/// Used by `InsertStatement` (BeforePattern/AfterPattern) for **exact** position matching.
643/// InsertStatement needs to identify a specific statement to insert next to,
644/// so structural equality is required (e.g., `let x = compute()` must NOT match `let x = other()`).
645///
646/// This is intentionally different from `stmt_matches_pattern` (used by RemoveStatement),
647/// which uses fuzzy string-based matching appropriate for "remove anything matching this pattern".
648fn stmt_matches(target: &PureStmt, stmt: &PureStmt) -> bool {
649    target == stmt
650}
651
652/// Check if a statement matches a pattern (fuzzy, string-based).
653///
654/// Used by `RemoveStatement` for flexible pattern matching.
655/// Also suitable for any operation where approximate matching is acceptable.
656///
657/// Pattern formats:
658/// - `macro_name!(..)` or `macro_name!` - matches macro calls by name
659/// - `let var_name = ...` - matches let statements by variable name
660/// - Other patterns - substring match against Debug format
661fn stmt_matches_pattern(stmt: &PureStmt, pattern: &str) -> bool {
662    // Check for macro pattern: "name!(..)` or "name!"
663    let macro_name = pattern
664        .strip_suffix("!(..)")
665        .or_else(|| pattern.strip_suffix('!'));
666
667    if let Some(name) = macro_name {
668        // Check if the statement is a macro call with this name
669        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    // Check for let pattern: "let var_name = ..." or "let var_name"
679    if let Some(after_let) = pattern.strip_prefix("let ") {
680        // Extract variable name (before "=" if present)
681        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    // Fallback: match against Debug format
701    let stmt_str = format!("{:?}", stmt);
702    stmt_str.contains(pattern)
703}
704
705/// Remove statements matching a pattern from a block
706fn 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; // Keep this one, already removed one
714            }
715            removed += 1;
716            false // Remove
717        } else {
718            true // Keep
719        }
720    });
721
722    // Also recurse into nested blocks
723    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
768/// Replace statements in a block
769fn 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            // Recurse into nested blocks
777            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
821/// Find the index where return statement starts (for End insertion)
822fn 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                // Tail expression - insert before it
830                return i;
831            }
832            _ => {}
833        }
834    }
835    stmts.len() // Insert at end if no return found
836}
837
838/// Insert statement relative to a reference statement (exact structural match).
839///
840/// Uses `stmt_matches` (PartialEq) — NOT `stmt_matches_pattern`.
841/// InsertStatement は「この特定の文の隣に挿入」なので、正確な位置特定が必要。
842/// `reference_stmt` は converter が `syn::parse_str` → `to_pure()` で生成し、
843/// 元ソースも同一パイプラインを経由するため、同一コードからは同一 PureStmt になる。
844fn 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
860// =============================================================================
861// ReplaceExprAtMutation
862// =============================================================================
863
864impl 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            // First index is the statement index
888            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 index
948        replace_child_at(expr, path[0], new_expr)
949    } else {
950        // Navigate deeper
951        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        // Find the function SymbolId
1061        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        // Find the function SymbolId
1098        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        // Parse the statement to insert
1152        let new_stmt: syn::Stmt = syn::parse_str("let z = 3;").unwrap();
1153        // Parse the reference statement
1154        let ref_stmt: syn::Stmt = syn::parse_str("let x = 1;").unwrap();
1155
1156        // Debug: print the PureStmt representations
1157        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        // Parse the statement to insert
1213        let new_stmt: syn::Stmt = syn::parse_str("println!(\"inserted\");").unwrap();
1214        // Parse the reference statement
1215        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        // Parse the reference: method call with nested call arg
1273        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}