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(_) => {}
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(types) => {
150                for ty in types {
151                    self.visit_type(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 { pattern, init, ty } => {
282                self.visit_pattern(pattern);
283                if let Some(ty) = ty {
284                    self.visit_type(ty);
285                }
286                if let Some(expr) = init {
287                    self.visit_expr(expr);
288                }
289            }
290            PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
291                self.visit_expr(expr);
292            }
293            PureStmt::Item(item) => {
294                self.visit_item(item);
295            }
296        }
297    }
298
299    fn visit_pattern(&mut self, pattern: &mut PurePattern) {
300        match pattern {
301            PurePattern::Ident { name, .. } => {
302                self.maybe_rename(name);
303            }
304            PurePattern::Tuple(pats) => {
305                for pat in pats {
306                    self.visit_pattern(pat);
307                }
308            }
309            PurePattern::Struct { path, fields, .. } => {
310                self.maybe_rename_qualified_path(path);
311                for (_, pat) in fields {
312                    self.visit_pattern(pat);
313                }
314            }
315            PurePattern::Ref { pattern, .. } => {
316                self.visit_pattern(pattern);
317            }
318            PurePattern::Or(pats) => {
319                for pat in pats {
320                    self.visit_pattern(pat);
321                }
322            }
323            PurePattern::Slice(pats) => {
324                for pat in pats {
325                    self.visit_pattern(pat);
326                }
327            }
328            PurePattern::Path(path) => {
329                // Rename qualified path if first segment matches (type name)
330                // or last segment matches (variant/field name)
331                self.maybe_rename_qualified_path(path);
332            }
333            PurePattern::Wild
334            | PurePattern::Lit(_)
335            | PurePattern::Range { .. }
336            | PurePattern::Rest
337            | PurePattern::Other(_) => {}
338        }
339    }
340
341    /// Rename a qualified path if first segment matches (for type names)
342    /// or last segment matches (for variant/field names).
343    /// e.g., "Status::Pending" -> "TaskStatus::Pending" when renaming "Status" to "TaskStatus"
344    fn maybe_rename_qualified_path(&mut self, path: &mut String) {
345        if path.contains("::") {
346            // Check first segment (type name)
347            if let Some(first) = path.split("::").next() {
348                if first == self.old_name {
349                    let rest = &path[first.len()..];
350                    *path = format!("{}{}", self.new_name, rest);
351                    self.count += 1;
352                    return;
353                }
354            }
355            // Check last segment (variant/field name)
356            if let Some(last) = path.rsplit("::").next() {
357                if last == self.old_name {
358                    if let Some(sep_idx) = path.rfind("::") {
359                        let prefix = &path[..sep_idx + 2];
360                        *path = format!("{}{}", prefix, self.new_name);
361                        self.count += 1;
362                    } else {
363                        // Path is just the bare name (no "::"); rename the whole path.
364                        *path = self.new_name.to_string();
365                        self.count += 1;
366                    }
367                }
368            }
369        } else {
370            self.maybe_rename(path);
371        }
372    }
373
374    fn visit_expr(&mut self, expr: &mut PureExpr) {
375        match expr {
376            PureExpr::Path(path) => {
377                self.maybe_rename_qualified_path(path);
378            }
379            PureExpr::Binary { left, right, .. } => {
380                self.visit_expr(left);
381                self.visit_expr(right);
382            }
383            PureExpr::Unary { expr, .. } => {
384                self.visit_expr(expr);
385            }
386            PureExpr::Call { func, args } => {
387                self.visit_expr(func);
388                for arg in args {
389                    self.visit_expr(arg);
390                }
391            }
392            PureExpr::MethodCall { receiver, args, .. } => {
393                self.visit_expr(receiver);
394                for arg in args {
395                    self.visit_expr(arg);
396                }
397            }
398            PureExpr::Field { expr, .. } => {
399                self.visit_expr(expr);
400            }
401            PureExpr::Index { expr, index } => {
402                self.visit_expr(expr);
403                self.visit_expr(index);
404            }
405            PureExpr::Block { block, .. } => {
406                self.visit_block(block);
407            }
408            PureExpr::If {
409                cond,
410                then_branch,
411                else_branch,
412            } => {
413                self.visit_expr(cond);
414                self.visit_block(then_branch);
415                if let Some(else_expr) = else_branch {
416                    self.visit_expr(else_expr);
417                }
418            }
419            PureExpr::Match { expr, arms } => {
420                self.visit_expr(expr);
421                for arm in arms {
422                    self.visit_pattern(&mut arm.pattern);
423                    if let Some(guard) = &mut arm.guard {
424                        self.visit_expr(guard);
425                    }
426                    self.visit_expr(&mut arm.body);
427                }
428            }
429            PureExpr::Loop { body: block, .. } => {
430                self.visit_block(block);
431            }
432            PureExpr::While { cond, body, .. } => {
433                self.visit_expr(cond);
434                self.visit_block(body);
435            }
436            PureExpr::For {
437                pat, expr, body, ..
438            } => {
439                self.visit_expr(expr);
440                self.visit_pattern(pat);
441                self.visit_block(body);
442            }
443            PureExpr::Return(Some(expr))
444            | PureExpr::Break {
445                expr: Some(expr), ..
446            } => {
447                self.visit_expr(expr);
448            }
449            PureExpr::Closure { params, body, .. } => {
450                for param in params {
451                    self.visit_pattern(&mut param.pattern);
452                }
453                self.visit_expr(body);
454            }
455            PureExpr::Struct { path, fields } => {
456                if !path.contains("::") {
457                    self.maybe_rename(path);
458                }
459                for (_, expr) in fields {
460                    self.visit_expr(expr);
461                }
462            }
463            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
464                for expr in exprs {
465                    self.visit_expr(expr);
466                }
467            }
468            PureExpr::Ref { expr, .. } => {
469                self.visit_expr(expr);
470            }
471            PureExpr::Await(expr) | PureExpr::Try(expr) => {
472                self.visit_expr(expr);
473            }
474            PureExpr::Macro { tokens, .. } => {
475                // Rename occurrences in macro tokens (text-based)
476                self.rename_in_tokens(tokens);
477            }
478            _ => {}
479        }
480    }
481
482    /// Rename occurrences of the symbol in macro tokens.
483    /// This is a text-based replacement since macro tokens are stored as strings.
484    fn rename_in_tokens(&mut self, tokens: &mut String) {
485        // Pattern: match first segment of qualified path or standalone name
486        // e.g., "Status::Pending" -> "TaskStatus::Pending"
487        // or "Status" -> "TaskStatus"
488
489        // First, try to rename qualified paths
490        let qualified_pattern = format!("{}::", self.old_name);
491        let qualified_replacement = format!("{}::", self.new_name);
492        if tokens.contains(&qualified_pattern) {
493            let count = tokens.matches(&qualified_pattern).count();
494            *tokens = tokens.replace(&qualified_pattern, &qualified_replacement);
495            self.count += count;
496        }
497
498        // Then try standalone names (with word boundaries)
499        // Use regex-like matching for word boundaries
500        let mut new_tokens = String::with_capacity(tokens.len());
501        let mut last_end = 0;
502        let old_bytes = self.old_name.as_bytes();
503        let token_bytes = tokens.as_bytes();
504
505        let mut i = 0;
506        while i <= token_bytes.len().saturating_sub(old_bytes.len()) {
507            if &token_bytes[i..i + old_bytes.len()] == old_bytes {
508                // Check if it's a word boundary before
509                let is_word_start = i == 0 || !is_ident_char(token_bytes[i - 1] as char);
510                // Check if it's a word boundary after
511                let is_word_end = i + old_bytes.len() >= token_bytes.len()
512                    || !is_ident_char(token_bytes[i + old_bytes.len()] as char);
513
514                // Don't match if it's part of a qualified path (already handled above)
515                let is_qualified = i + old_bytes.len() < token_bytes.len()
516                    && token_bytes[i + old_bytes.len()] == b':';
517
518                if is_word_start && is_word_end && !is_qualified {
519                    new_tokens.push_str(&tokens[last_end..i]);
520                    new_tokens.push_str(&self.new_name);
521                    last_end = i + old_bytes.len();
522                    self.count += 1;
523                    i += old_bytes.len();
524                    continue;
525                }
526            }
527            i += 1;
528        }
529        new_tokens.push_str(&tokens[last_end..]);
530        *tokens = new_tokens;
531    }
532}
533
534/// Check if a character is a valid identifier character.
535fn is_ident_char(c: char) -> bool {
536    c.is_alphanumeric() || c == '_'
537}
538
539/// Visitor that renames a local variable only within a specific function.
540struct ScopedRenamer {
541    target_fn: String,
542    old_name: String,
543    new_name: String,
544    count: usize,
545    in_target_fn: bool,
546}
547
548impl ScopedRenamer {
549    fn new(target_fn: &str, old_name: &str, new_name: &str) -> Self {
550        Self {
551            target_fn: target_fn.to_string(),
552            old_name: old_name.to_string(),
553            new_name: new_name.to_string(),
554            count: 0,
555            in_target_fn: false,
556        }
557    }
558
559    fn maybe_rename(&mut self, name: &mut String) {
560        if self.in_target_fn && *name == self.old_name {
561            *name = self.new_name.clone();
562            self.count += 1;
563        }
564    }
565
566    fn visit_file(&mut self, file: &mut PureFile) {
567        for item in &mut file.items {
568            self.visit_item(item);
569        }
570    }
571
572    fn visit_item(&mut self, item: &mut PureItem) {
573        if let PureItem::Fn(f) = item {
574            let was_in_target = self.in_target_fn;
575
576            if f.name == self.target_fn {
577                self.in_target_fn = true;
578            }
579
580            self.visit_fn(f);
581
582            self.in_target_fn = was_in_target;
583        }
584    }
585
586    fn visit_fn(&mut self, f: &mut PureFn) {
587        // Rename parameters (if scoped)
588        for param in &mut f.params {
589            if let PureParam::Typed { name, .. } = param {
590                self.maybe_rename(name);
591            }
592        }
593
594        // Visit body
595        self.visit_block(&mut f.body);
596    }
597
598    fn visit_block(&mut self, block: &mut PureBlock) {
599        for stmt in &mut block.stmts {
600            self.visit_stmt(stmt);
601        }
602    }
603
604    fn visit_stmt(&mut self, stmt: &mut PureStmt) {
605        match stmt {
606            PureStmt::Local { pattern, init, .. } => {
607                self.visit_pattern(pattern);
608                if let Some(expr) = init {
609                    self.visit_expr(expr);
610                }
611            }
612            PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
613                self.visit_expr(expr);
614            }
615            PureStmt::Item(_) => {}
616        }
617    }
618
619    fn visit_pattern(&mut self, pattern: &mut PurePattern) {
620        match pattern {
621            PurePattern::Ident { name, .. } => {
622                self.maybe_rename(name);
623            }
624            PurePattern::Tuple(pats) => {
625                for pat in pats {
626                    self.visit_pattern(pat);
627                }
628            }
629            PurePattern::Struct { fields, .. } => {
630                for (_, pat) in fields {
631                    self.visit_pattern(pat);
632                }
633            }
634            PurePattern::Ref { pattern, .. } => {
635                self.visit_pattern(pattern);
636            }
637            PurePattern::Or(pats) => {
638                for pat in pats {
639                    self.visit_pattern(pat);
640                }
641            }
642            PurePattern::Slice(pats) => {
643                for pat in pats {
644                    self.visit_pattern(pat);
645                }
646            }
647            PurePattern::Path(path) => {
648                // Rename the last segment of a qualified path if it matches
649                if let Some(last) = path.rsplit("::").next() {
650                    if last == self.old_name {
651                        if let Some(sep_idx) = path.rfind("::") {
652                            let prefix = &path[..sep_idx + 2];
653                            *path = format!("{}{}", prefix, self.new_name);
654                        } else {
655                            *path = self.new_name.to_string();
656                        }
657                    }
658                }
659            }
660            PurePattern::Wild
661            | PurePattern::Lit(_)
662            | PurePattern::Range { .. }
663            | PurePattern::Rest
664            | PurePattern::Other(_) => {}
665        }
666    }
667
668    fn visit_expr(&mut self, expr: &mut PureExpr) {
669        match expr {
670            PureExpr::Path(path) if !path.contains("::") => {
671                self.maybe_rename(path);
672            }
673            PureExpr::Binary { left, right, .. } => {
674                self.visit_expr(left);
675                self.visit_expr(right);
676            }
677            PureExpr::Unary { expr, .. } => {
678                self.visit_expr(expr);
679            }
680            PureExpr::Call { func, args } => {
681                self.visit_expr(func);
682                for arg in args {
683                    self.visit_expr(arg);
684                }
685            }
686            PureExpr::MethodCall { receiver, args, .. } => {
687                self.visit_expr(receiver);
688                for arg in args {
689                    self.visit_expr(arg);
690                }
691            }
692            PureExpr::Field { expr, .. } => {
693                self.visit_expr(expr);
694            }
695            PureExpr::Index { expr, index } => {
696                self.visit_expr(expr);
697                self.visit_expr(index);
698            }
699            PureExpr::Block { block, .. } => {
700                self.visit_block(block);
701            }
702            PureExpr::If {
703                cond,
704                then_branch,
705                else_branch,
706            } => {
707                self.visit_expr(cond);
708                self.visit_block(then_branch);
709                if let Some(else_expr) = else_branch {
710                    self.visit_expr(else_expr);
711                }
712            }
713            PureExpr::Match { expr, arms } => {
714                self.visit_expr(expr);
715                for arm in arms {
716                    self.visit_pattern(&mut arm.pattern);
717                    if let Some(guard) = &mut arm.guard {
718                        self.visit_expr(guard);
719                    }
720                    self.visit_expr(&mut arm.body);
721                }
722            }
723            PureExpr::Loop { body: block, .. } => {
724                self.visit_block(block);
725            }
726            PureExpr::While { cond, body, .. } => {
727                self.visit_expr(cond);
728                self.visit_block(body);
729            }
730            PureExpr::For {
731                pat, expr, body, ..
732            } => {
733                self.visit_expr(expr);
734                self.visit_pattern(pat);
735                self.visit_block(body);
736            }
737            PureExpr::Return(Some(expr))
738            | PureExpr::Break {
739                expr: Some(expr), ..
740            } => {
741                self.visit_expr(expr);
742            }
743            PureExpr::Closure { params, body, .. } => {
744                for param in params {
745                    self.visit_pattern(&mut param.pattern);
746                }
747                self.visit_expr(body);
748            }
749            PureExpr::Struct { fields, .. } => {
750                for (_, expr) in fields {
751                    self.visit_expr(expr);
752                }
753            }
754            PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
755                for expr in exprs {
756                    self.visit_expr(expr);
757                }
758            }
759            PureExpr::Ref { expr, .. } => {
760                self.visit_expr(expr);
761            }
762            PureExpr::Await(expr) | PureExpr::Try(expr) => {
763                self.visit_expr(expr);
764            }
765            _ => {}
766        }
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773
774    #[test]
775    fn test_rename_local_var() {
776        let mut file = PureFile::from_source(
777            r#"
778            fn main() {
779                let x = 1;
780                let y = x + 1;
781            }
782            "#,
783        )
784        .unwrap();
785
786        let result = PureRename::apply(&mut file, "x", "value");
787        assert_eq!(result.count, 2); // 1 def + 1 use
788
789        // Verify the change
790        let f = file.functions().into_iter().next().unwrap();
791        assert!(f.body.stmts.iter().any(|stmt| {
792            if let PureStmt::Local {
793                pattern: PurePattern::Ident { name, .. },
794                ..
795            } = stmt
796            {
797                name == "value"
798            } else {
799                false
800            }
801        }));
802    }
803
804    #[test]
805    fn test_rename_function() {
806        let mut file = PureFile::from_source(
807            r#"
808            fn foo() {}
809            fn main() {
810                foo();
811            }
812            "#,
813        )
814        .unwrap();
815
816        let result = PureRename::apply(&mut file, "foo", "bar");
817        assert_eq!(result.count, 2); // 1 def + 1 call
818
819        let fns = file.functions();
820        assert!(fns.iter().any(|f| f.name == "bar"));
821        assert!(!fns.iter().any(|f| f.name == "foo"));
822    }
823
824    #[test]
825    fn test_rename_struct() {
826        let mut file = PureFile::from_source(
827            r#"
828            struct Point { x: i32, y: i32 }
829            fn main() {
830                let p: Point = Point { x: 0, y: 0 };
831            }
832            "#,
833        )
834        .unwrap();
835
836        let result = PureRename::apply(&mut file, "Point", "Vec2");
837        assert!(result.count >= 2); // def + uses
838
839        let structs = file.structs();
840        assert!(structs.iter().any(|s| s.name == "Vec2"));
841        assert!(!structs.iter().any(|s| s.name == "Point"));
842    }
843
844    #[test]
845    fn test_scoped_rename() {
846        let mut file = PureFile::from_source(
847            r#"
848            fn foo() {
849                let x = 1;
850            }
851            fn bar() {
852                let x = 2;
853            }
854            "#,
855        )
856        .unwrap();
857
858        let result = PureRename::rename_local_in_fn(&mut file, "foo", "x", "renamed");
859        assert_eq!(result.count, 1);
860
861        // Verify foo's x is renamed
862        let foo = file
863            .functions()
864            .into_iter()
865            .find(|f| f.name == "foo")
866            .unwrap();
867        assert!(foo.body.stmts.iter().any(|stmt| {
868            if let PureStmt::Local {
869                pattern: PurePattern::Ident { name, .. },
870                ..
871            } = stmt
872            {
873                name == "renamed"
874            } else {
875                false
876            }
877        }));
878
879        // Verify bar's x is unchanged
880        let bar = file
881            .functions()
882            .into_iter()
883            .find(|f| f.name == "bar")
884            .unwrap();
885        assert!(bar.body.stmts.iter().any(|stmt| {
886            if let PureStmt::Local {
887                pattern: PurePattern::Ident { name, .. },
888                ..
889            } = stmt
890            {
891                name == "x"
892            } else {
893                false
894            }
895        }));
896    }
897
898    #[test]
899    fn test_cow_rename() {
900        let original = PureFile::from_source("fn foo() {}").unwrap();
901
902        let (renamed, result) = PureRename::apply_cow(&original, "foo", "bar");
903
904        assert_eq!(result.count, 1);
905
906        // Original unchanged
907        assert!(original.functions().iter().any(|f| f.name == "foo"));
908
909        // New copy is renamed
910        assert!(renamed.functions().iter().any(|f| f.name == "bar"));
911    }
912
913    #[test]
914    fn test_parallel_rename() {
915        use std::sync::Arc;
916        use std::thread;
917
918        let file = PureFile::from_source(
919            r#"
920            fn alpha() {}
921            fn beta() {}
922            fn gamma() {}
923            "#,
924        )
925        .unwrap();
926
927        let shared = Arc::new(file);
928
929        // Multiple threads can read and create renamed copies
930        let handles: Vec<_> = vec!["alpha", "beta", "gamma"]
931            .into_iter()
932            .map(|old_name| {
933                let f = Arc::clone(&shared);
934                let new_name = format!("{}_renamed", old_name);
935                thread::spawn(move || {
936                    let (renamed, result) = PureRename::apply_cow(&f, old_name, &new_name);
937                    (renamed.functions().len(), result.count)
938                })
939            })
940            .collect();
941
942        for handle in handles {
943            let (fn_count, rename_count) = handle.join().unwrap();
944            assert_eq!(fn_count, 3);
945            assert_eq!(rename_count, 1);
946        }
947    }
948}