Skip to main content

ryo_executor/engine/impls/
rename.rs

1//! ASTRegApply implementation for rename mutations
2
3use ryo_analysis::SymbolKind;
4use ryo_mutations::basic::RenameMutation;
5use ryo_mutations::MutationResult;
6use ryo_source::pure::{PureItem, PureType, PureUseTree};
7
8use crate::engine::events::ModificationType;
9use crate::engine::{ASTMutationContext, ASTRegApply};
10
11impl ASTRegApply for RenameMutation {
12    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
13        // O(1) lookup using pre-resolved symbol_id
14        let old_path = match ctx.symbol_registry.path(self.symbol_id) {
15            Some(path) => path.clone(),
16            None => {
17                return MutationResult {
18                    mutation_type: "Rename".to_string(),
19                    changes: 0,
20                    description: format!("Symbol path not found for SymbolId {:?}", self.symbol_id),
21                };
22            }
23        };
24
25        // Get the original name from the symbol path
26        let from = old_path.name().to_string();
27
28        // Get the AST for this symbol
29        let item = match ctx.ast_registry.get(self.symbol_id) {
30            Some(item) => item.clone(),
31            None => {
32                return MutationResult {
33                    mutation_type: "Rename".to_string(),
34                    changes: 0,
35                    description: format!("AST not found for '{}'", from),
36                };
37            }
38        };
39
40        // Rename the item
41        let (new_item, renamed) = rename_item(item, &from, &self.to);
42
43        let mut changes = 0;
44
45        if renamed {
46            // Update the AST registry
47            ctx.set_ast(self.symbol_id, new_item);
48            changes += 1;
49
50            // Compute new path and rename the symbol in the registry
51            let new_path = old_path.with_renamed_last_segment(&from, &self.to);
52
53            // Actually rename the symbol in the registry (not just emit event)
54            let _ = ctx.rename_symbol(self.symbol_id, new_path);
55
56            // Also update any impl blocks that reference this type as self_ty
57            // (e.g., `impl TodoItem { ... }` when renaming TodoItem to Task)
58            let impl_updates: Vec<_> = ctx
59                .symbol_registry
60                .iter()
61                .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
62                .filter_map(|(id, path)| {
63                    if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get(id) {
64                        // Check if self_ty matches the renamed type
65                        if imp.self_ty == from {
66                            return Some((id, path.clone()));
67                        }
68                    }
69                    None
70                })
71                .collect();
72
73            for (impl_id, impl_path) in impl_updates {
74                if let Some(PureItem::Impl(mut imp)) = ctx.ast_registry.get(impl_id).cloned() {
75                    imp.self_ty = self.to.clone();
76                    ctx.set_ast(impl_id, PureItem::Impl(imp));
77
78                    // Also rename the impl's SymbolPath (e.g., <impl TodoItem> -> <impl Task>)
79                    let old_impl_name = impl_path.name();
80                    let new_impl_name = old_impl_name.replace(&from, &self.to);
81                    if old_impl_name != new_impl_name {
82                        let new_impl_path =
83                            impl_path.with_renamed_last_segment(old_impl_name, &new_impl_name);
84                        let _ = ctx.rename_symbol(impl_id, new_impl_path);
85                    }
86
87                    changes += 1;
88                }
89            }
90
91            // Cascade: Update use statements and references across all modules
92            changes += update_references_across_modules(ctx, &from, &self.to);
93        }
94
95        MutationResult {
96            mutation_type: "Rename".to_string(),
97            changes,
98            description: if changes > 0 {
99                format!("Renamed '{}' to '{}'", from, self.to)
100            } else {
101                format!("Failed to rename '{}' to '{}'", from, self.to)
102            },
103        }
104    }
105}
106
107/// Get the name of a PureItem if it has one
108fn get_item_name(item: &PureItem) -> Option<String> {
109    match item {
110        PureItem::Fn(f) => Some(f.name.clone()),
111        PureItem::Struct(s) => Some(s.name.clone()),
112        PureItem::Enum(e) => Some(e.name.clone()),
113        PureItem::Const(c) => Some(c.name.clone()),
114        PureItem::Static(s) => Some(s.name.clone()),
115        PureItem::Type(t) => Some(t.name.clone()),
116        PureItem::Trait(t) => Some(t.name.clone()),
117        PureItem::Mod(m) => Some(m.name.clone()),
118        // Impl blocks have names like "<impl TypeName>" or "<impl Trait for TypeName>"
119        PureItem::Impl(imp) => {
120            if let Some(trait_name) = &imp.trait_ {
121                Some(format!("<impl {} for {}>", trait_name, imp.self_ty))
122            } else {
123                Some(format!("<impl {}>", imp.self_ty))
124            }
125        }
126        _ => None,
127    }
128}
129
130/// Rename an item's name field
131fn rename_item(item: PureItem, from: &str, to: &str) -> (PureItem, bool) {
132    match item {
133        PureItem::Fn(mut f) if f.name == from => {
134            f.name = to.to_string();
135            (PureItem::Fn(f), true)
136        }
137        PureItem::Struct(mut s) if s.name == from => {
138            s.name = to.to_string();
139            (PureItem::Struct(s), true)
140        }
141        PureItem::Enum(mut e) if e.name == from => {
142            e.name = to.to_string();
143            (PureItem::Enum(e), true)
144        }
145        PureItem::Const(mut c) if c.name == from => {
146            c.name = to.to_string();
147            (PureItem::Const(c), true)
148        }
149        PureItem::Static(mut s) if s.name == from => {
150            s.name = to.to_string();
151            (PureItem::Static(s), true)
152        }
153        PureItem::Type(mut t) if t.name == from => {
154            t.name = to.to_string();
155            (PureItem::Type(t), true)
156        }
157        PureItem::Trait(mut t) if t.name == from => {
158            t.name = to.to_string();
159            (PureItem::Trait(t), true)
160        }
161        PureItem::Mod(mut m) if m.name == from => {
162            m.name = to.to_string();
163            (PureItem::Mod(m), true)
164        }
165        _ => (item, false),
166    }
167}
168
169/// Update references across all modules (use statements, type references, path expressions)
170fn update_references_across_modules(ctx: &mut ASTMutationContext, from: &str, to: &str) -> usize {
171    let mut changes = 0;
172
173    // Collect all module IDs that have module_items
174    let module_ids: Vec<_> = ctx
175        .ast_registry
176        .iter_module_items()
177        .map(|(id, _)| id)
178        .collect();
179
180    for module_id in module_ids {
181        // Clone items to release the borrow on ctx
182        let items = match ctx.ast_registry.get_module_items(module_id) {
183            Some(items) => items.clone(),
184            None => continue,
185        };
186
187        // Get the module path for symbol lookup
188        let module_path = ctx.symbol_registry.path(module_id).cloned();
189
190        let mut updated_items = Vec::new();
191        let mut module_changed = false;
192
193        for item in items {
194            let (new_item, changed) = update_item_references(item.clone(), from, to);
195            if changed {
196                module_changed = true;
197                changes += 1;
198
199                // Also update the AST registry entry for this symbol
200                // (FileDumper looks up AST by symbol ID, not from module_items)
201                if let Some(item_name) = get_item_name(&new_item) {
202                    if let Some(ref mp) = module_path {
203                        if let Ok(item_path) = mp.child(&item_name) {
204                            if let Some(symbol_id) = ctx.symbol_registry.lookup(&item_path) {
205                                ctx.set_ast(symbol_id, new_item.clone());
206                            }
207                        }
208                    }
209                }
210            }
211            updated_items.push(new_item);
212        }
213
214        if module_changed {
215            ctx.ast_registry.set_module_items(module_id, updated_items);
216            // Emit MutationEvent so generate_affected includes this module's file
217            ctx.emit_modified(module_id, ModificationType::BodyModified);
218        }
219    }
220
221    changes
222}
223
224/// Update references within a single PureItem
225fn update_item_references(item: PureItem, from: &str, to: &str) -> (PureItem, bool) {
226    match item {
227        PureItem::Use(mut u) => {
228            let (new_tree, changed) = update_use_tree(u.tree, from, to);
229            u.tree = new_tree;
230            (PureItem::Use(u), changed)
231        }
232        PureItem::Fn(mut f) => {
233            let mut changed = false;
234            // Update parameter types
235            let new_params: Vec<_> = f
236                .params
237                .into_iter()
238                .map(|param| {
239                    let (new_param, c) = update_param_type(param, from, to);
240                    if c {
241                        changed = true;
242                    }
243                    new_param
244                })
245                .collect();
246            f.params = new_params;
247            // Update return type
248            if let Some(ret) = f.ret {
249                let (new_ret, c) = update_type_references(ret, from, to);
250                if c {
251                    changed = true;
252                }
253                f.ret = Some(new_ret);
254            }
255            // Update body
256            let (new_body, c) = update_block_references(f.body, from, to);
257            if c {
258                changed = true;
259            }
260            f.body = new_body;
261            (PureItem::Fn(f), changed)
262        }
263        PureItem::Impl(mut imp) => {
264            let mut changed = false;
265            // Update self_ty if it matches
266            if imp.self_ty == from {
267                imp.self_ty = to.to_string();
268                changed = true;
269            }
270            // Update impl items (methods)
271            let mut new_items = Vec::new();
272            for impl_item in imp.items {
273                let (new_item, item_changed) = update_impl_item_references(impl_item, from, to);
274                if item_changed {
275                    changed = true;
276                }
277                new_items.push(new_item);
278            }
279            imp.items = new_items;
280            (PureItem::Impl(imp), changed)
281        }
282        _ => (item, false),
283    }
284}
285
286/// Update use tree references
287fn update_use_tree(tree: PureUseTree, from: &str, to: &str) -> (PureUseTree, bool) {
288    match tree {
289        PureUseTree::Name(name) if name == from => (PureUseTree::Name(to.to_string()), true),
290        PureUseTree::Rename { name, rename } if name == from => (
291            PureUseTree::Rename {
292                name: to.to_string(),
293                rename,
294            },
295            true,
296        ),
297        PureUseTree::Path { path, tree } => {
298            let (new_tree, changed) = update_use_tree(*tree, from, to);
299            (
300                PureUseTree::Path {
301                    path,
302                    tree: Box::new(new_tree),
303                },
304                changed,
305            )
306        }
307        PureUseTree::Group(items) => {
308            let mut changed = false;
309            let new_items: Vec<_> = items
310                .into_iter()
311                .map(|t| {
312                    let (new_t, c) = update_use_tree(t, from, to);
313                    if c {
314                        changed = true;
315                    }
316                    new_t
317                })
318                .collect();
319            (PureUseTree::Group(new_items), changed)
320        }
321        _ => (tree, false),
322    }
323}
324
325/// Update references in impl item (method)
326fn update_impl_item_references(
327    item: ryo_source::pure::PureImplItem,
328    from: &str,
329    to: &str,
330) -> (ryo_source::pure::PureImplItem, bool) {
331    use ryo_source::pure::PureImplItem;
332
333    match item {
334        PureImplItem::Fn(mut f) => {
335            let mut changed = false;
336            // Update parameter types
337            let new_params: Vec<_> = f
338                .params
339                .into_iter()
340                .map(|param| {
341                    let (new_param, c) = update_param_type(param, from, to);
342                    if c {
343                        changed = true;
344                    }
345                    new_param
346                })
347                .collect();
348            f.params = new_params;
349            // Update return type
350            if let Some(ret) = f.ret {
351                let (new_ret, c) = update_type_references(ret, from, to);
352                if c {
353                    changed = true;
354                }
355                f.ret = Some(new_ret);
356            }
357            // Update body
358            let (new_body, c) = update_block_references(f.body, from, to);
359            if c {
360                changed = true;
361            }
362            f.body = new_body;
363            (PureImplItem::Fn(f), changed)
364        }
365        _ => (item, false),
366    }
367}
368
369/// Update references in a block
370fn update_block_references(
371    mut block: ryo_source::pure::PureBlock,
372    from: &str,
373    to: &str,
374) -> (ryo_source::pure::PureBlock, bool) {
375    let mut changed = false;
376
377    let new_stmts: Vec<_> = block
378        .stmts
379        .into_iter()
380        .map(|stmt| {
381            let (new_stmt, c) = update_stmt_references(stmt, from, to);
382            if c {
383                changed = true;
384            }
385            new_stmt
386        })
387        .collect();
388    block.stmts = new_stmts;
389
390    (block, changed)
391}
392
393/// Update references in a statement
394fn update_stmt_references(
395    stmt: ryo_source::pure::PureStmt,
396    from: &str,
397    to: &str,
398) -> (ryo_source::pure::PureStmt, bool) {
399    use ryo_source::pure::PureStmt;
400
401    match stmt {
402        PureStmt::Expr(expr) => {
403            let (new_expr, changed) = update_expr_references(expr, from, to);
404            (PureStmt::Expr(new_expr), changed)
405        }
406        PureStmt::Semi(expr) => {
407            let (new_expr, changed) = update_expr_references(expr, from, to);
408            (PureStmt::Semi(new_expr), changed)
409        }
410        PureStmt::Local { pattern, ty, init } => {
411            let mut changed = false;
412            // Update type annotation if present
413            let new_ty = ty.map(|t| {
414                let (new_t, c) = update_type_references(t, from, to);
415                if c {
416                    changed = true;
417                }
418                new_t
419            });
420            // Update init expression if present
421            let new_init = init.map(|e| {
422                let (new_e, c) = update_expr_references(e, from, to);
423                if c {
424                    changed = true;
425                }
426                new_e
427            });
428            (
429                PureStmt::Local {
430                    pattern,
431                    ty: new_ty,
432                    init: new_init,
433                },
434                changed,
435            )
436        }
437        _ => (stmt, false),
438    }
439}
440
441/// Update references in an expression
442fn update_expr_references(
443    expr: ryo_source::pure::PureExpr,
444    from: &str,
445    to: &str,
446) -> (ryo_source::pure::PureExpr, bool) {
447    use ryo_source::pure::PureExpr;
448
449    match expr {
450        PureExpr::Path(path) if path.contains(from) => {
451            (PureExpr::Path(path.replace(from, to)), true)
452        }
453        PureExpr::Call { func, args } => {
454            let (new_func, func_changed) = update_expr_references(*func, from, to);
455            let mut args_changed = false;
456            let new_args: Vec<_> = args
457                .into_iter()
458                .map(|a| {
459                    let (new_a, c) = update_expr_references(a, from, to);
460                    if c {
461                        args_changed = true;
462                    }
463                    new_a
464                })
465                .collect();
466            (
467                PureExpr::Call {
468                    func: Box::new(new_func),
469                    args: new_args,
470                },
471                func_changed || args_changed,
472            )
473        }
474        PureExpr::Match { expr, arms } => {
475            let (new_expr, expr_changed) = update_expr_references(*expr, from, to);
476            let mut arms_changed = false;
477            let new_arms: Vec<_> = arms
478                .into_iter()
479                .map(|mut arm| {
480                    // Update pattern
481                    let (new_pattern, pat_changed) =
482                        update_pattern_references(arm.pattern, from, to);
483                    arm.pattern = new_pattern;
484                    if pat_changed {
485                        arms_changed = true;
486                    }
487                    // Update body
488                    let (new_body, c) = update_expr_references(arm.body, from, to);
489                    if c {
490                        arms_changed = true;
491                    }
492                    arm.body = new_body;
493                    arm
494                })
495                .collect();
496            (
497                PureExpr::Match {
498                    expr: Box::new(new_expr),
499                    arms: new_arms,
500                },
501                expr_changed || arms_changed,
502            )
503        }
504        PureExpr::Macro {
505            name,
506            delimiter,
507            tokens,
508        } if tokens.contains(from) => {
509            // Update references in macro tokens (e.g., matches!(self, Status::Completed))
510            let new_tokens = tokens.replace(from, to);
511            (
512                PureExpr::Macro {
513                    name,
514                    delimiter,
515                    tokens: new_tokens,
516                },
517                true,
518            )
519        }
520        _ => (expr, false),
521    }
522}
523
524/// Update references in a pattern
525fn update_pattern_references(
526    pattern: ryo_source::pure::PurePattern,
527    from: &str,
528    to: &str,
529) -> (ryo_source::pure::PurePattern, bool) {
530    use ryo_source::pure::PurePattern;
531
532    match pattern {
533        PurePattern::Path(path) if path.contains(from) => {
534            (PurePattern::Path(path.replace(from, to)), true)
535        }
536        PurePattern::Struct { path, fields, rest } if path.contains(from) => (
537            PurePattern::Struct {
538                path: path.replace(from, to),
539                fields,
540                rest,
541            },
542            true,
543        ),
544        _ => (pattern, false),
545    }
546}
547
548/// Update references in a function parameter
549fn update_param_type(
550    param: ryo_source::pure::PureParam,
551    from: &str,
552    to: &str,
553) -> (ryo_source::pure::PureParam, bool) {
554    use ryo_source::pure::PureParam;
555
556    match param {
557        PureParam::Typed { name, ty } => {
558            let (new_ty, changed) = update_type_references(ty, from, to);
559            (PureParam::Typed { name, ty: new_ty }, changed)
560        }
561        PureParam::SelfValue { .. } => (param, false),
562    }
563}
564
565/// Update references in a type
566fn update_type_references(ty: PureType, from: &str, to: &str) -> (PureType, bool) {
567    match ty {
568        PureType::Path(ref path) if path.contains(from) => {
569            (PureType::Path(path.replace(from, to)), true)
570        }
571        PureType::Ref {
572            lifetime,
573            is_mut,
574            ty,
575        } => {
576            let (new_ty, changed) = update_type_references(*ty, from, to);
577            (
578                PureType::Ref {
579                    lifetime,
580                    is_mut,
581                    ty: Box::new(new_ty),
582                },
583                changed,
584            )
585        }
586        PureType::Tuple(tys) => {
587            let mut changed = false;
588            let new_tys: Vec<_> = tys
589                .into_iter()
590                .map(|t| {
591                    let (new_t, c) = update_type_references(t, from, to);
592                    if c {
593                        changed = true;
594                    }
595                    new_t
596                })
597                .collect();
598            (PureType::Tuple(new_tys), changed)
599        }
600        PureType::Array { ty, len } => {
601            let (new_ty, changed) = update_type_references(*ty, from, to);
602            (
603                PureType::Array {
604                    ty: Box::new(new_ty),
605                    len,
606                },
607                changed,
608            )
609        }
610        PureType::Slice(ty) => {
611            let (new_ty, changed) = update_type_references(*ty, from, to);
612            (PureType::Slice(Box::new(new_ty)), changed)
613        }
614        PureType::Fn { params, ret } => {
615            let mut changed = false;
616            let new_params: Vec<_> = params
617                .into_iter()
618                .map(|t| {
619                    let (new_t, c) = update_type_references(t, from, to);
620                    if c {
621                        changed = true;
622                    }
623                    new_t
624                })
625                .collect();
626            let new_ret = ret.map(|r| {
627                let (new_r, c) = update_type_references(*r, from, to);
628                if c {
629                    changed = true;
630                }
631                Box::new(new_r)
632            });
633            (
634                PureType::Fn {
635                    params: new_params,
636                    ret: new_ret,
637                },
638                changed,
639            )
640        }
641        PureType::ImplTrait(traits) => {
642            let mut changed = false;
643            let new_traits: Vec<_> = traits
644                .into_iter()
645                .map(|t| {
646                    if t.contains(from) {
647                        changed = true;
648                        t.replace(from, to)
649                    } else {
650                        t
651                    }
652                })
653                .collect();
654            (PureType::ImplTrait(new_traits), changed)
655        }
656        PureType::TraitObject(traits) => {
657            let mut changed = false;
658            let new_traits: Vec<_> = traits
659                .into_iter()
660                .map(|t| {
661                    if t.contains(from) {
662                        changed = true;
663                        t.replace(from, to)
664                    } else {
665                        t
666                    }
667                })
668                .collect();
669            (PureType::TraitObject(new_traits), changed)
670        }
671        _ => (ty, false),
672    }
673}