Skip to main content

ryo_source/pure/
rename.rs

1//! Rename operation for PureFile.
2//!
3//! Thread-safe rename operation that works on PureFile AST.
4
5use super::ast::*;
6
7/// Result of a rename operation.
8#[derive(Debug, Clone)]
9pub struct PureRenameResult {
10    /// Number of occurrences renamed.
11    pub count: usize,
12    /// Old name.
13    pub old_name: String,
14    /// New name.
15    pub new_name: String,
16}
17
18/// Rename operation for PureFile.
19pub struct PureRename;
20
21impl PureRename {
22    /// Rename all occurrences of a symbol in a PureFile.
23    ///
24    /// This renames:
25    /// - Local variable definitions and uses
26    /// - Function definitions and calls
27    /// - Struct/enum definitions and uses
28    /// - Type references
29    pub fn apply(file: &mut PureFile, old_name: &str, new_name: &str) -> PureRenameResult {
30        let mut renamer = SymbolRenamer::new(old_name, new_name);
31        renamer.visit_file(file);
32
33        PureRenameResult {
34            count: renamer.count,
35            old_name: old_name.to_string(),
36            new_name: new_name.to_string(),
37        }
38    }
39
40    /// Rename a local variable within a specific function.
41    pub fn rename_local_in_fn(
42        file: &mut PureFile,
43        fn_name: &str,
44        old_name: &str,
45        new_name: &str,
46    ) -> PureRenameResult {
47        let mut renamer = ScopedRenamer::new(fn_name, old_name, new_name);
48        renamer.visit_file(file);
49
50        PureRenameResult {
51            count: renamer.count,
52            old_name: old_name.to_string(),
53            new_name: new_name.to_string(),
54        }
55    }
56
57    /// Create a renamed copy of a PureFile (for COW pattern).
58    pub fn apply_cow(
59        file: &PureFile,
60        old_name: &str,
61        new_name: &str,
62    ) -> (PureFile, PureRenameResult) {
63        let mut cloned = file.clone();
64        let result = Self::apply(&mut cloned, old_name, new_name);
65        (cloned, result)
66    }
67}
68
69/// Visitor that renames all occurrences of a symbol.
70struct SymbolRenamer {
71    old_name: String,
72    new_name: String,
73    count: usize,
74}
75
76impl SymbolRenamer {
77    fn new(old_name: &str, new_name: &str) -> Self {
78        Self {
79            old_name: old_name.to_string(),
80            new_name: new_name.to_string(),
81            count: 0,
82        }
83    }
84
85    fn maybe_rename(&mut self, name: &mut String) -> bool {
86        if *name == self.old_name {
87            *name = self.new_name.clone();
88            self.count += 1;
89            true
90        } else {
91            false
92        }
93    }
94
95    fn visit_file(&mut self, file: &mut PureFile) {
96        for item in &mut file.items {
97            self.visit_item(item);
98        }
99    }
100
101    fn visit_item(&mut self, item: &mut PureItem) {
102        match item {
103            PureItem::Fn(f) => self.visit_fn(f),
104            PureItem::Struct(s) => self.visit_struct(s),
105            PureItem::Enum(e) => self.visit_enum(e),
106            PureItem::Impl(i) => self.visit_impl(i),
107            PureItem::Trait(t) => self.visit_trait(t),
108            PureItem::Const(c) => self.visit_const(c),
109            PureItem::Static(s) => self.visit_static(s),
110            PureItem::Type(t) => self.visit_type_alias(t),
111            PureItem::Mod(m) => self.visit_mod(m),
112            PureItem::Use(u) => self.visit_use(u),
113            PureItem::Macro(_) | PureItem::Other(_) | PureItem::Verbatim(_) => {}
114        }
115    }
116
117    fn visit_fn(&mut self, f: &mut PureFn) {
118        self.maybe_rename(&mut f.name);
119
120        // Rename parameters
121        for param in &mut f.params {
122            if let PureParam::Typed { name, ty, .. } = param {
123                self.maybe_rename(name);
124                self.visit_type(ty);
125            }
126        }
127
128        // Rename return type
129        if let Some(ty) = &mut f.ret {
130            self.visit_type(ty);
131        }
132
133        // Visit body
134        self.visit_block(&mut f.body);
135    }
136
137    fn visit_struct(&mut self, s: &mut PureStruct) {
138        self.maybe_rename(&mut s.name);
139        self.visit_fields(&mut s.fields);
140    }
141
142    fn visit_fields(&mut self, fields: &mut PureFields) {
143        match fields {
144            PureFields::Named(named) => {
145                for field in named {
146                    self.visit_type(&mut field.ty);
147                }
148            }
149            PureFields::Tuple(fields) => {
150                for f in fields {
151                    self.visit_type(&mut f.ty);
152                }
153            }
154            PureFields::Unit => {}
155        }
156    }
157
158    fn visit_enum(&mut self, e: &mut PureEnum) {
159        self.maybe_rename(&mut e.name);
160        for variant in &mut e.variants {
161            self.visit_fields(&mut variant.fields);
162        }
163    }
164
165    fn visit_impl(&mut self, i: &mut PureImpl) {
166        self.maybe_rename(&mut i.self_ty);
167        if let Some(trait_name) = &mut i.trait_ {
168            self.maybe_rename(trait_name);
169        }
170        for item in &mut i.items {
171            if let PureImplItem::Fn(f) = item {
172                self.visit_fn(f);
173            }
174        }
175    }
176
177    fn visit_trait(&mut self, t: &mut PureTrait) {
178        self.maybe_rename(&mut t.name);
179    }
180
181    fn visit_const(&mut self, c: &mut PureConst) {
182        self.maybe_rename(&mut c.name);
183        self.visit_type(&mut c.ty);
184        if let Some(v) = &mut c.value {
185            self.visit_expr(v);
186        }
187    }
188
189    fn visit_static(&mut self, s: &mut PureStatic) {
190        self.maybe_rename(&mut s.name);
191        self.visit_type(&mut s.ty);
192        self.visit_expr(&mut s.value);
193    }
194
195    fn visit_type_alias(&mut self, t: &mut PureTypeAlias) {
196        self.maybe_rename(&mut t.name);
197        self.visit_type(&mut t.ty);
198    }
199
200    fn visit_mod(&mut self, m: &mut PureMod) {
201        self.maybe_rename(&mut m.name);
202        for item in &mut m.items {
203            self.visit_item(item);
204        }
205    }
206
207    fn visit_use(&mut self, u: &mut PureUse) {
208        self.visit_use_tree(&mut u.tree);
209    }
210
211    fn visit_use_tree(&mut self, tree: &mut PureUseTree) {
212        match tree {
213            PureUseTree::Path { tree, .. } => {
214                self.visit_use_tree(tree);
215            }
216            PureUseTree::Name(name) => {
217                self.maybe_rename(name);
218            }
219            PureUseTree::Rename { name, .. } => {
220                self.maybe_rename(name);
221            }
222            PureUseTree::Glob => {}
223            PureUseTree::Group(trees) => {
224                for t in trees {
225                    self.visit_use_tree(t);
226                }
227            }
228        }
229    }
230
231    fn visit_type(&mut self, ty: &mut PureType) {
232        match ty {
233            PureType::Path(path) => {
234                // Rename type references (including qualified paths like std::vec::Vec)
235                self.maybe_rename_qualified_path(path);
236            }
237            PureType::Ref { ty, .. } => {
238                self.visit_type(ty);
239            }
240            PureType::Slice(ty) => {
241                self.visit_type(ty);
242            }
243            PureType::Array { ty, .. } => {
244                self.visit_type(ty);
245            }
246            PureType::Tuple(types) => {
247                for ty in types {
248                    self.visit_type(ty);
249                }
250            }
251            PureType::Fn { params, ret } => {
252                for ty in params {
253                    self.visit_type(ty);
254                }
255                if let Some(ret_ty) = ret {
256                    self.visit_type(ret_ty);
257                }
258            }
259            PureType::ImplTrait(bounds) => {
260                for bound in bounds {
261                    self.maybe_rename_qualified_path(bound);
262                }
263            }
264            PureType::TraitObject(bounds) => {
265                for bound in bounds {
266                    self.maybe_rename_qualified_path(bound);
267                }
268            }
269            PureType::Infer | PureType::Never | PureType::Other(_) => {}
270        }
271    }
272
273    fn visit_block(&mut self, block: &mut PureBlock) {
274        for stmt in &mut block.stmts {
275            self.visit_stmt(stmt);
276        }
277    }
278
279    fn visit_stmt(&mut self, stmt: &mut PureStmt) {
280        match stmt {
281            PureStmt::Local {
282                pattern,
283                init,
284                ty,
285                else_branch,
286            } => {
287                self.visit_pattern(pattern);
288                if let Some(ty) = ty {
289                    self.visit_type(ty);
290                }
291                if let Some(expr) = init {
292                    self.visit_expr(expr);
293                }
294                if let Some(eb) = else_branch {
295                    self.visit_expr(eb);
296                }
297            }
298            PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
299                self.visit_expr(expr);
300            }
301            PureStmt::Item(item) => {
302                self.visit_item(item);
303            }
304            // Verbatim is opaque raw bytes — rename pass treats it
305            // as a leaf (B-3-cont carrier).
306            PureStmt::Verbatim(_) => {}
307        }
308    }
309
310    fn visit_pattern(&mut self, pattern: &mut PurePattern) {
311        match pattern {
312            PurePattern::Ident { name, .. } => {
313                self.maybe_rename(name);
314            }
315            PurePattern::Tuple(pats) => {
316                for pat in pats {
317                    self.visit_pattern(pat);
318                }
319            }
320            PurePattern::Struct { path, fields, .. } => {
321                self.maybe_rename_qualified_path(path);
322                for (_, pat) in fields {
323                    self.visit_pattern(pat);
324                }
325            }
326            PurePattern::Ref { pattern, .. } => {
327                self.visit_pattern(pattern);
328            }
329            PurePattern::Or(pats) => {
330                for pat in pats {
331                    self.visit_pattern(pat);
332                }
333            }
334            PurePattern::Slice(pats) => {
335                for pat in pats {
336                    self.visit_pattern(pat);
337                }
338            }
339            PurePattern::Path(path) => {
340                // Rename qualified path if first segment matches (type name)
341                // or last segment matches (variant/field name)
342                self.maybe_rename_qualified_path(path);
343            }
344            PurePattern::Wild
345            | PurePattern::Lit(_)
346            | PurePattern::Range { .. }
347            | PurePattern::Rest
348            | PurePattern::Other(_) => {}
349        }
350    }
351
352    /// Rename a qualified path if first segment matches (for type names)
353    /// or last segment matches (for variant/field names).
354    /// e.g., "Status::Pending" -> "TaskStatus::Pending" when renaming "Status" to "TaskStatus"
355    fn maybe_rename_qualified_path(&mut self, path: &mut String) {
356        if path.contains("::") {
357            // Check first segment (type name)
358            if let Some(first) = path.split("::").next() {
359                if first == self.old_name {
360                    let rest = &path[first.len()..];
361                    *path = format!("{}{}", self.new_name, rest);
362                    self.count += 1;
363                    return;
364                }
365            }
366            // Check last segment (variant/field name)
367            if let Some(last) = path.rsplit("::").next() {
368                if last == self.old_name {
369                    if let Some(sep_idx) = path.rfind("::") {
370                        let prefix = &path[..sep_idx + 2];
371                        *path = format!("{}{}", prefix, self.new_name);
372                        self.count += 1;
373                    } else {
374                        // Path is just the bare name (no "::"); rename the whole path.
375                        *path = self.new_name.to_string();
376                        self.count += 1;
377                    }
378                }
379            }
380        } else {
381            self.maybe_rename(path);
382        }
383    }
384
385    fn visit_expr(&mut self, expr: &mut PureExpr) {
386        match expr {
387            PureExpr::Path(path) => {
388                self.maybe_rename_qualified_path(path);
389            }
390            PureExpr::Binary { left, right, .. } => {
391                self.visit_expr(left);
392                self.visit_expr(right);
393            }
394            PureExpr::Unary { expr, .. } => {
395                self.visit_expr(expr);
396            }
397            PureExpr::Call { func, args } => {
398                self.visit_expr(func);
399                for arg in args {
400                    self.visit_expr(arg);
401                }
402            }
403            PureExpr::MethodCall { receiver, args, .. } => {
404                self.visit_expr(receiver);
405                for arg in args {
406                    self.visit_expr(arg);
407                }
408            }
409            PureExpr::Field { expr, .. } => {
410                self.visit_expr(expr);
411            }
412            PureExpr::Index { expr, index } => {
413                self.visit_expr(expr);
414                self.visit_expr(index);
415            }
416            PureExpr::Block { block, .. } => {
417                self.visit_block(block);
418            }
419            PureExpr::If {
420                cond,
421                then_branch,
422                else_branch,
423            } => {
424                self.visit_expr(cond);
425                self.visit_block(then_branch);
426                if let Some(else_expr) = else_branch {
427                    self.visit_expr(else_expr);
428                }
429            }
430            PureExpr::Match { expr, arms } => {
431                self.visit_expr(expr);
432                for arm in arms {
433                    self.visit_pattern(&mut arm.pattern);
434                    if let Some(guard) = &mut arm.guard {
435                        self.visit_expr(guard);
436                    }
437                    self.visit_expr(&mut arm.body);
438                }
439            }
440            PureExpr::Loop { body: block, .. } => {
441                self.visit_block(block);
442            }
443            PureExpr::While { cond, body, .. } => {
444                self.visit_expr(cond);
445                self.visit_block(body);
446            }
447            PureExpr::For {
448                pat, expr, body, ..
449            } => {
450                self.visit_expr(expr);
451                self.visit_pattern(pat);
452                self.visit_block(body);
453            }
454            PureExpr::Return(Some(expr))
455            | PureExpr::Break {
456                expr: Some(expr), ..
457            } => {
458                self.visit_expr(expr);
459            }
460            PureExpr::Closure { params, body, .. } => {
461                for param in params {
462                    self.visit_pattern(&mut param.pattern);
463                }
464                self.visit_expr(body);
465            }
466            PureExpr::Struct { path, fields, rest } => {
467                if !path.contains("::") {
468                    self.maybe_rename(path);
469                }
470                for (_, expr) in fields {
471                    self.visit_expr(expr);
472                }
473                if let Some(r) = rest {
474                    self.visit_expr(r);
475                }
476            }
477            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
478                for expr in exprs {
479                    self.visit_expr(expr);
480                }
481            }
482            PureExpr::Ref { expr, .. } => {
483                self.visit_expr(expr);
484            }
485            PureExpr::Await(expr) | PureExpr::Try(expr) => {
486                self.visit_expr(expr);
487            }
488            PureExpr::Macro { tokens, .. } => {
489                // Rename occurrences in macro tokens (text-based)
490                self.rename_in_tokens(tokens);
491            }
492            _ => {}
493        }
494    }
495
496    /// Rename occurrences of the symbol in macro tokens.
497    /// This is a text-based replacement since macro tokens are stored as strings.
498    fn rename_in_tokens(&mut self, tokens: &mut String) {
499        // Pattern: match first segment of qualified path or standalone name
500        // e.g., "Status::Pending" -> "TaskStatus::Pending"
501        // or "Status" -> "TaskStatus"
502
503        // First, try to rename qualified paths
504        let qualified_pattern = format!("{}::", self.old_name);
505        let qualified_replacement = format!("{}::", self.new_name);
506        if tokens.contains(&qualified_pattern) {
507            let count = tokens.matches(&qualified_pattern).count();
508            *tokens = tokens.replace(&qualified_pattern, &qualified_replacement);
509            self.count += count;
510        }
511
512        // Then try standalone names (with word boundaries)
513        // Use regex-like matching for word boundaries
514        let mut new_tokens = String::with_capacity(tokens.len());
515        let mut last_end = 0;
516        let old_bytes = self.old_name.as_bytes();
517        let token_bytes = tokens.as_bytes();
518
519        let mut i = 0;
520        while i <= token_bytes.len().saturating_sub(old_bytes.len()) {
521            if &token_bytes[i..i + old_bytes.len()] == old_bytes {
522                // Check if it's a word boundary before
523                let is_word_start = i == 0 || !is_ident_char(token_bytes[i - 1] as char);
524                // Check if it's a word boundary after
525                let is_word_end = i + old_bytes.len() >= token_bytes.len()
526                    || !is_ident_char(token_bytes[i + old_bytes.len()] as char);
527
528                // Don't match if it's part of a qualified path (already handled above)
529                let is_qualified = i + old_bytes.len() < token_bytes.len()
530                    && token_bytes[i + old_bytes.len()] == b':';
531
532                if is_word_start && is_word_end && !is_qualified {
533                    new_tokens.push_str(&tokens[last_end..i]);
534                    new_tokens.push_str(&self.new_name);
535                    last_end = i + old_bytes.len();
536                    self.count += 1;
537                    i += old_bytes.len();
538                    continue;
539                }
540            }
541            i += 1;
542        }
543        new_tokens.push_str(&tokens[last_end..]);
544        *tokens = new_tokens;
545    }
546}
547
548/// Check if a character is a valid identifier character.
549fn is_ident_char(c: char) -> bool {
550    c.is_alphanumeric() || c == '_'
551}
552
553/// Visitor that renames a local variable only within a specific function.
554struct ScopedRenamer {
555    target_fn: String,
556    old_name: String,
557    new_name: String,
558    count: usize,
559    in_target_fn: bool,
560}
561
562impl ScopedRenamer {
563    fn new(target_fn: &str, old_name: &str, new_name: &str) -> Self {
564        Self {
565            target_fn: target_fn.to_string(),
566            old_name: old_name.to_string(),
567            new_name: new_name.to_string(),
568            count: 0,
569            in_target_fn: false,
570        }
571    }
572
573    fn maybe_rename(&mut self, name: &mut String) {
574        if self.in_target_fn && *name == self.old_name {
575            *name = self.new_name.clone();
576            self.count += 1;
577        }
578    }
579
580    fn visit_file(&mut self, file: &mut PureFile) {
581        for item in &mut file.items {
582            self.visit_item(item);
583        }
584    }
585
586    fn visit_item(&mut self, item: &mut PureItem) {
587        if let PureItem::Fn(f) = item {
588            let was_in_target = self.in_target_fn;
589
590            if f.name == self.target_fn {
591                self.in_target_fn = true;
592            }
593
594            self.visit_fn(f);
595
596            self.in_target_fn = was_in_target;
597        }
598    }
599
600    fn visit_fn(&mut self, f: &mut PureFn) {
601        // Rename parameters (if scoped)
602        for param in &mut f.params {
603            if let PureParam::Typed { name, .. } = param {
604                self.maybe_rename(name);
605            }
606        }
607
608        // Visit body
609        self.visit_block(&mut f.body);
610    }
611
612    fn visit_block(&mut self, block: &mut PureBlock) {
613        for stmt in &mut block.stmts {
614            self.visit_stmt(stmt);
615        }
616    }
617
618    fn visit_stmt(&mut self, stmt: &mut PureStmt) {
619        match stmt {
620            PureStmt::Local { pattern, init, .. } => {
621                self.visit_pattern(pattern);
622                if let Some(expr) = init {
623                    self.visit_expr(expr);
624                }
625            }
626            PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
627                self.visit_expr(expr);
628            }
629            PureStmt::Item(_) => {}
630            // Verbatim is opaque raw bytes — rename pass treats it
631            // as a leaf (B-3-cont carrier).
632            PureStmt::Verbatim(_) => {}
633        }
634    }
635
636    fn visit_pattern(&mut self, pattern: &mut PurePattern) {
637        match pattern {
638            PurePattern::Ident { name, .. } => {
639                self.maybe_rename(name);
640            }
641            PurePattern::Tuple(pats) => {
642                for pat in pats {
643                    self.visit_pattern(pat);
644                }
645            }
646            PurePattern::Struct { fields, .. } => {
647                for (_, pat) in fields {
648                    self.visit_pattern(pat);
649                }
650            }
651            PurePattern::Ref { pattern, .. } => {
652                self.visit_pattern(pattern);
653            }
654            PurePattern::Or(pats) => {
655                for pat in pats {
656                    self.visit_pattern(pat);
657                }
658            }
659            PurePattern::Slice(pats) => {
660                for pat in pats {
661                    self.visit_pattern(pat);
662                }
663            }
664            PurePattern::Path(path) => {
665                // Rename the last segment of a qualified path if it matches
666                if let Some(last) = path.rsplit("::").next() {
667                    if last == self.old_name {
668                        if let Some(sep_idx) = path.rfind("::") {
669                            let prefix = &path[..sep_idx + 2];
670                            *path = format!("{}{}", prefix, self.new_name);
671                        } else {
672                            *path = self.new_name.to_string();
673                        }
674                    }
675                }
676            }
677            PurePattern::Wild
678            | PurePattern::Lit(_)
679            | PurePattern::Range { .. }
680            | PurePattern::Rest
681            | PurePattern::Other(_) => {}
682        }
683    }
684
685    fn visit_expr(&mut self, expr: &mut PureExpr) {
686        match expr {
687            PureExpr::Path(path) if !path.contains("::") => {
688                self.maybe_rename(path);
689            }
690            PureExpr::Binary { left, right, .. } => {
691                self.visit_expr(left);
692                self.visit_expr(right);
693            }
694            PureExpr::Unary { expr, .. } => {
695                self.visit_expr(expr);
696            }
697            PureExpr::Call { func, args } => {
698                self.visit_expr(func);
699                for arg in args {
700                    self.visit_expr(arg);
701                }
702            }
703            PureExpr::MethodCall { receiver, args, .. } => {
704                self.visit_expr(receiver);
705                for arg in args {
706                    self.visit_expr(arg);
707                }
708            }
709            PureExpr::Field { expr, .. } => {
710                self.visit_expr(expr);
711            }
712            PureExpr::Index { expr, index } => {
713                self.visit_expr(expr);
714                self.visit_expr(index);
715            }
716            PureExpr::Block { block, .. } => {
717                self.visit_block(block);
718            }
719            PureExpr::If {
720                cond,
721                then_branch,
722                else_branch,
723            } => {
724                self.visit_expr(cond);
725                self.visit_block(then_branch);
726                if let Some(else_expr) = else_branch {
727                    self.visit_expr(else_expr);
728                }
729            }
730            PureExpr::Match { expr, arms } => {
731                self.visit_expr(expr);
732                for arm in arms {
733                    self.visit_pattern(&mut arm.pattern);
734                    if let Some(guard) = &mut arm.guard {
735                        self.visit_expr(guard);
736                    }
737                    self.visit_expr(&mut arm.body);
738                }
739            }
740            PureExpr::Loop { body: block, .. } => {
741                self.visit_block(block);
742            }
743            PureExpr::While { cond, body, .. } => {
744                self.visit_expr(cond);
745                self.visit_block(body);
746            }
747            PureExpr::For {
748                pat, expr, body, ..
749            } => {
750                self.visit_expr(expr);
751                self.visit_pattern(pat);
752                self.visit_block(body);
753            }
754            PureExpr::Return(Some(expr))
755            | PureExpr::Break {
756                expr: Some(expr), ..
757            } => {
758                self.visit_expr(expr);
759            }
760            PureExpr::Closure { params, body, .. } => {
761                for param in params {
762                    self.visit_pattern(&mut param.pattern);
763                }
764                self.visit_expr(body);
765            }
766            PureExpr::Struct { fields, .. } => {
767                for (_, expr) in fields {
768                    self.visit_expr(expr);
769                }
770            }
771            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
772                for expr in exprs {
773                    self.visit_expr(expr);
774                }
775            }
776            PureExpr::Ref { expr, .. } => {
777                self.visit_expr(expr);
778            }
779            PureExpr::Await(expr) | PureExpr::Try(expr) => {
780                self.visit_expr(expr);
781            }
782            _ => {}
783        }
784    }
785}
786
787#[cfg(test)]
788mod tests {
789    use super::*;
790
791    #[test]
792    fn test_rename_local_var() {
793        let mut file = PureFile::from_source(
794            r#"
795            fn main() {
796                let x = 1;
797                let y = x + 1;
798            }
799            "#,
800        )
801        .unwrap();
802
803        let result = PureRename::apply(&mut file, "x", "value");
804        assert_eq!(result.count, 2); // 1 def + 1 use
805
806        // Verify the change
807        let f = file.functions().into_iter().next().unwrap();
808        assert!(f.body.stmts.iter().any(|stmt| {
809            if let PureStmt::Local {
810                pattern: PurePattern::Ident { name, .. },
811                ..
812            } = stmt
813            {
814                name == "value"
815            } else {
816                false
817            }
818        }));
819    }
820
821    #[test]
822    fn test_rename_function() {
823        let mut file = PureFile::from_source(
824            r#"
825            fn foo() {}
826            fn main() {
827                foo();
828            }
829            "#,
830        )
831        .unwrap();
832
833        let result = PureRename::apply(&mut file, "foo", "bar");
834        assert_eq!(result.count, 2); // 1 def + 1 call
835
836        let fns = file.functions();
837        assert!(fns.iter().any(|f| f.name == "bar"));
838        assert!(!fns.iter().any(|f| f.name == "foo"));
839    }
840
841    #[test]
842    fn test_rename_struct() {
843        let mut file = PureFile::from_source(
844            r#"
845            struct Point { x: i32, y: i32 }
846            fn main() {
847                let p: Point = Point { x: 0, y: 0 };
848            }
849            "#,
850        )
851        .unwrap();
852
853        let result = PureRename::apply(&mut file, "Point", "Vec2");
854        assert!(result.count >= 2); // def + uses
855
856        let structs = file.structs();
857        assert!(structs.iter().any(|s| s.name == "Vec2"));
858        assert!(!structs.iter().any(|s| s.name == "Point"));
859    }
860
861    #[test]
862    fn test_scoped_rename() {
863        let mut file = PureFile::from_source(
864            r#"
865            fn foo() {
866                let x = 1;
867            }
868            fn bar() {
869                let x = 2;
870            }
871            "#,
872        )
873        .unwrap();
874
875        let result = PureRename::rename_local_in_fn(&mut file, "foo", "x", "renamed");
876        assert_eq!(result.count, 1);
877
878        // Verify foo's x is renamed
879        let foo = file
880            .functions()
881            .into_iter()
882            .find(|f| f.name == "foo")
883            .unwrap();
884        assert!(foo.body.stmts.iter().any(|stmt| {
885            if let PureStmt::Local {
886                pattern: PurePattern::Ident { name, .. },
887                ..
888            } = stmt
889            {
890                name == "renamed"
891            } else {
892                false
893            }
894        }));
895
896        // Verify bar's x is unchanged
897        let bar = file
898            .functions()
899            .into_iter()
900            .find(|f| f.name == "bar")
901            .unwrap();
902        assert!(bar.body.stmts.iter().any(|stmt| {
903            if let PureStmt::Local {
904                pattern: PurePattern::Ident { name, .. },
905                ..
906            } = stmt
907            {
908                name == "x"
909            } else {
910                false
911            }
912        }));
913    }
914
915    #[test]
916    fn test_cow_rename() {
917        let original = PureFile::from_source("fn foo() {}").unwrap();
918
919        let (renamed, result) = PureRename::apply_cow(&original, "foo", "bar");
920
921        assert_eq!(result.count, 1);
922
923        // Original unchanged
924        assert!(original.functions().iter().any(|f| f.name == "foo"));
925
926        // New copy is renamed
927        assert!(renamed.functions().iter().any(|f| f.name == "bar"));
928    }
929
930    #[test]
931    fn test_parallel_rename() {
932        use std::sync::Arc;
933        use std::thread;
934
935        let file = PureFile::from_source(
936            r#"
937            fn alpha() {}
938            fn beta() {}
939            fn gamma() {}
940            "#,
941        )
942        .unwrap();
943
944        let shared = Arc::new(file);
945
946        // Multiple threads can read and create renamed copies
947        let handles: Vec<_> = vec!["alpha", "beta", "gamma"]
948            .into_iter()
949            .map(|old_name| {
950                let f = Arc::clone(&shared);
951                let new_name = format!("{}_renamed", old_name);
952                thread::spawn(move || {
953                    let (renamed, result) = PureRename::apply_cow(&f, old_name, &new_name);
954                    (renamed.functions().len(), result.count)
955                })
956            })
957            .collect();
958
959        for handle in handles {
960            let (fn_count, rename_count) = handle.join().unwrap();
961            assert_eq!(fn_count, 3);
962            assert_eq!(rename_count, 1);
963        }
964    }
965}