1use crate::Mutation;
4use ryo_symbol::SymbolId;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15#[serde(rename_all = "snake_case")]
16pub enum EnumToTraitStrategy {
17 #[default]
23 Dynamic,
24
25 Static,
31
32 Generic,
38
39 MarkerOnly,
43}
44
45#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
51#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
52#[serde(rename_all = "snake_case")]
53pub enum MatchHandling {
54 #[default]
58 WarnOnly,
59
60 Downcast,
79
80 BlockOnMatch,
84}
85
86#[derive(Debug, Clone)]
104pub struct ExtractTraitMutation {
105 pub symbol_id: SymbolId,
107 pub trait_name: String,
109 pub methods: Option<Vec<String>>,
111}
112
113impl ExtractTraitMutation {
114 pub fn new(symbol_id: SymbolId, trait_name: impl Into<String>) -> Self {
115 Self {
116 symbol_id,
117 trait_name: trait_name.into(),
118 methods: None,
119 }
120 }
121
122 pub fn with_methods(mut self, methods: Vec<String>) -> Self {
123 self.methods = Some(methods);
124 self
125 }
126}
127
128impl Mutation for ExtractTraitMutation {
129 fn describe(&self) -> String {
130 format!(
131 "Extract trait '{}' from impl (SymbolId: {:?})",
132 self.trait_name, self.symbol_id
133 )
134 }
135
136 fn mutation_type(&self) -> &'static str {
137 "ExtractTrait"
138 }
139
140 fn box_clone(&self) -> Box<dyn Mutation> {
141 Box::new(self.clone())
142 }
143}
144
145#[derive(Debug, Clone)]
163pub struct InlineTraitMutation {
164 pub symbol_id: SymbolId,
166 pub struct_name: String,
167 pub remove_trait: bool, }
169
170impl InlineTraitMutation {
171 pub fn new(symbol_id: SymbolId, struct_name: impl Into<String>) -> Self {
172 Self {
173 symbol_id,
174 struct_name: struct_name.into(),
175 remove_trait: true,
176 }
177 }
178
179 pub fn keep_trait(mut self) -> Self {
180 self.remove_trait = false;
181 self
182 }
183}
184
185impl Mutation for InlineTraitMutation {
186 fn describe(&self) -> String {
187 format!(
188 "Inline trait {:?} into impl {}",
189 self.symbol_id, self.struct_name
190 )
191 }
192
193 fn mutation_type(&self) -> &'static str {
194 "InlineTrait"
195 }
196
197 fn box_clone(&self) -> Box<dyn Mutation> {
198 Box::new(self.clone())
199 }
200}
201
202#[derive(Debug, Clone)]
204pub struct RemoveTraitMutation {
205 pub trait_id: SymbolId,
207}
208
209impl RemoveTraitMutation {
210 pub fn new(trait_id: SymbolId) -> Self {
211 Self { trait_id }
212 }
213}
214
215impl Mutation for RemoveTraitMutation {
216 fn describe(&self) -> String {
217 format!("Remove trait {}", self.trait_id)
218 }
219
220 fn mutation_type(&self) -> &'static str {
221 "RemoveTrait"
222 }
223
224 fn box_clone(&self) -> Box<dyn Mutation> {
225 Box::new(self.clone())
226 }
227}
228
229#[derive(Debug, Clone)]
265pub struct EnumToTraitMutation {
266 pub symbol_id: ryo_symbol::SymbolId,
268 pub trait_name: Option<String>,
270 pub remove_enum: bool,
272 pub variants: Vec<VariantInfo>,
274 pub strategy: EnumToTraitStrategy,
276 pub match_handling: MatchHandling,
278}
279
280#[derive(Debug, Clone)]
282pub struct VariantInfo {
283 pub name: String,
284 pub fields: Vec<FieldInfo>,
285}
286
287#[derive(Debug, Clone)]
289pub struct FieldInfo {
290 pub name: String,
291 pub ty: String,
292}
293
294impl EnumToTraitMutation {
295 pub fn from_symbol_id(symbol_id: ryo_symbol::SymbolId) -> Self {
297 Self {
298 symbol_id,
299 trait_name: None,
300 remove_enum: true,
301 variants: Vec::new(),
302 strategy: EnumToTraitStrategy::default(),
303 match_handling: MatchHandling::default(),
304 }
305 }
306
307 pub fn with_trait_name(mut self, name: impl Into<String>) -> Self {
308 self.trait_name = Some(name.into());
309 self
310 }
311
312 pub fn keep_enum(mut self) -> Self {
313 self.remove_enum = false;
314 self
315 }
316
317 pub fn with_variants(mut self, variants: Vec<VariantInfo>) -> Self {
318 self.variants = variants;
319 self
320 }
321
322 pub fn with_strategy(mut self, strategy: EnumToTraitStrategy) -> Self {
324 self.strategy = strategy;
325 self
326 }
327
328 pub fn with_match_handling(mut self, handling: MatchHandling) -> Self {
330 self.match_handling = handling;
331 self
332 }
333}
334
335impl Mutation for EnumToTraitMutation {
336 fn describe(&self) -> String {
337 let trait_name = self.trait_name.as_deref().unwrap_or("<from_enum>");
338 format!(
339 "Convert enum (symbol:{:?}) to trait '{}' with {} struct implementations",
340 self.symbol_id,
341 trait_name,
342 self.variants.len()
343 )
344 }
345
346 fn mutation_type(&self) -> &'static str {
347 "EnumToTrait"
348 }
349
350 fn box_clone(&self) -> Box<dyn Mutation> {
351 Box::new(self.clone())
352 }
353}
354
355pub mod cross_crate_caller_pattern {
375 use ryo_source::pure::{
376 PureBlock, PureExpr, PureFn, PureGenericParam, PureGenerics, PureParam, PureStmt, PureType,
377 };
378
379 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
381 pub enum CallerPattern {
382 Dot,
383 Ufcs,
384 GenericBound,
385 DynDispatch,
386 Other,
387 }
388
389 pub fn classify_path_expr(path: &str, trait_name: &str) -> Option<CallerPattern> {
403 let needle_as = format!("as {}", trait_name);
404 let needle_prefix = format!("{}::", trait_name);
405 if path.contains(&needle_as) || path.starts_with(&needle_prefix) {
406 Some(CallerPattern::Ufcs)
407 } else {
408 None
409 }
410 }
411
412 pub fn classify_method_call(
416 expr: &PureExpr,
417 trait_methods: &[String],
418 ) -> Option<CallerPattern> {
419 if let PureExpr::MethodCall { method, .. } = expr {
420 if trait_methods.iter().any(|m| m == method) {
421 return Some(CallerPattern::Dot);
422 }
423 }
424 None
425 }
426
427 pub fn classify_fn_generic_bound(generics: &PureGenerics, trait_name: &str) -> bool {
431 for param in &generics.params {
432 if let PureGenericParam::Type { bounds, .. } = param {
433 if bounds.iter().any(|b| b == trait_name) {
434 return true;
435 }
436 }
437 }
438 for w in &generics.where_clause {
439 if w.contains(trait_name) {
440 return true;
441 }
442 }
443 false
444 }
445
446 pub fn classify_fn_dyn_param(params: &[PureParam], trait_name: &str) -> bool {
450 params.iter().any(|p| match p {
451 PureParam::Typed { ty, .. } => type_contains_dyn_trait(ty, trait_name),
452 _ => false,
453 })
454 }
455
456 fn type_contains_dyn_trait(ty: &PureType, trait_name: &str) -> bool {
457 match ty {
458 PureType::TraitObject(bounds) => bounds.iter().any(|b| b == trait_name),
459 PureType::Ref { ty, .. } => type_contains_dyn_trait(ty, trait_name),
460 _ => false,
461 }
462 }
463
464 pub fn classify_fn_signature(f: &PureFn, trait_name: &str) -> Vec<CallerPattern> {
468 let mut out = Vec::new();
469 if classify_fn_generic_bound(&f.generics, trait_name) {
470 out.push(CallerPattern::GenericBound);
471 }
472 if classify_fn_dyn_param(&f.params, trait_name) {
473 out.push(CallerPattern::DynDispatch);
474 }
475 out
476 }
477
478 pub fn walk_expr<F: FnMut(&PureExpr)>(expr: &PureExpr, visitor: &mut F) {
484 visitor(expr);
485 match expr {
486 PureExpr::Lit(_)
487 | PureExpr::Path(_)
488 | PureExpr::Macro { .. }
489 | PureExpr::Other(_)
490 | PureExpr::Verbatim(_)
491 | PureExpr::Continue { .. } => {}
492 PureExpr::Binary { left, right, .. } => {
493 walk_expr(left, visitor);
494 walk_expr(right, visitor);
495 }
496 PureExpr::Unary { expr, .. }
497 | PureExpr::Field { expr, .. }
498 | PureExpr::Await(expr)
499 | PureExpr::Try(expr)
500 | PureExpr::Ref { expr, .. }
501 | PureExpr::Cast { expr, .. }
502 | PureExpr::Let { expr, .. } => walk_expr(expr, visitor),
503 PureExpr::Call { func, args } => {
504 walk_expr(func, visitor);
505 for a in args {
506 walk_expr(a, visitor);
507 }
508 }
509 PureExpr::MethodCall { receiver, args, .. } => {
510 walk_expr(receiver, visitor);
511 for a in args {
512 walk_expr(a, visitor);
513 }
514 }
515 PureExpr::Index { expr, index } => {
516 walk_expr(expr, visitor);
517 walk_expr(index, visitor);
518 }
519 PureExpr::Block { block, .. }
520 | PureExpr::Async { body: block, .. }
521 | PureExpr::Unsafe(block) => walk_block(block, visitor),
522 PureExpr::If {
523 cond,
524 then_branch,
525 else_branch,
526 } => {
527 walk_expr(cond, visitor);
528 walk_block(then_branch, visitor);
529 if let Some(e) = else_branch {
530 walk_expr(e, visitor);
531 }
532 }
533 PureExpr::Match { expr, arms } => {
534 walk_expr(expr, visitor);
535 for arm in arms {
536 if let Some(g) = &arm.guard {
537 walk_expr(g, visitor);
538 }
539 walk_expr(&arm.body, visitor);
540 }
541 }
542 PureExpr::Loop { body, .. } => walk_block(body, visitor),
543 PureExpr::While { cond, body, .. } => {
544 walk_expr(cond, visitor);
545 walk_block(body, visitor);
546 }
547 PureExpr::For { expr, body, .. } => {
548 walk_expr(expr, visitor);
549 walk_block(body, visitor);
550 }
551 PureExpr::Return(opt) => {
552 if let Some(e) = opt {
553 walk_expr(e, visitor);
554 }
555 }
556 PureExpr::Break { expr, .. } => {
557 if let Some(e) = expr {
558 walk_expr(e, visitor);
559 }
560 }
561 PureExpr::Closure { body, .. } => walk_expr(body, visitor),
562 PureExpr::Struct { fields, rest, .. } => {
563 for (_, e) in fields {
564 walk_expr(e, visitor);
565 }
566 if let Some(r) = rest {
567 walk_expr(r, visitor);
568 }
569 }
570 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
571 for e in exprs {
572 walk_expr(e, visitor);
573 }
574 }
575 PureExpr::Range { start, end, .. } => {
576 if let Some(s) = start {
577 walk_expr(s, visitor);
578 }
579 if let Some(e) = end {
580 walk_expr(e, visitor);
581 }
582 }
583 PureExpr::Repeat { expr, len } => {
584 walk_expr(expr, visitor);
585 walk_expr(len, visitor);
586 }
587 }
588 }
589
590 pub fn walk_block<F: FnMut(&PureExpr)>(block: &PureBlock, visitor: &mut F) {
594 for stmt in &block.stmts {
595 match stmt {
596 PureStmt::Local {
597 init, else_branch, ..
598 } => {
599 if let Some(e) = init {
600 walk_expr(e, visitor);
601 }
602 if let Some(e) = else_branch {
603 walk_expr(e, visitor);
604 }
605 }
606 PureStmt::Semi(e) | PureStmt::Expr(e) => walk_expr(e, visitor),
607 PureStmt::Item(_) | PureStmt::Verbatim(_) => {}
608 }
609 }
610 }
611
612 pub fn scan_body_for_patterns(
616 body: &PureBlock,
617 trait_name: &str,
618 trait_methods: &[String],
619 ) -> Vec<CallerPattern> {
620 let mut found_dot = false;
621 let mut found_ufcs = false;
622 walk_block(body, &mut |e| {
623 if let PureExpr::Path(p) = e {
624 if classify_path_expr(p, trait_name).is_some() {
625 found_ufcs = true;
626 }
627 }
628 if classify_method_call(e, trait_methods).is_some() {
629 found_dot = true;
630 }
631 });
632 let mut out = Vec::new();
633 if found_dot {
634 out.push(CallerPattern::Dot);
635 }
636 if found_ufcs {
637 out.push(CallerPattern::Ufcs);
638 }
639 out
640 }
641
642 pub fn classify_fn(
645 f: &PureFn,
646 trait_name: &str,
647 trait_methods: &[String],
648 ) -> Vec<CallerPattern> {
649 let mut out = classify_fn_signature(f, trait_name);
650 out.extend(scan_body_for_patterns(&f.body, trait_name, trait_methods));
651 out
652 }
653
654 pub fn walk_expr_mut<F: FnMut(&mut PureExpr)>(expr: &mut PureExpr, visitor: &mut F) {
662 visitor(expr);
663 match expr {
664 PureExpr::Lit(_)
665 | PureExpr::Path(_)
666 | PureExpr::Macro { .. }
667 | PureExpr::Other(_)
668 | PureExpr::Verbatim(_)
669 | PureExpr::Continue { .. } => {}
670 PureExpr::Binary { left, right, .. } => {
671 walk_expr_mut(left, visitor);
672 walk_expr_mut(right, visitor);
673 }
674 PureExpr::Unary { expr, .. }
675 | PureExpr::Field { expr, .. }
676 | PureExpr::Await(expr)
677 | PureExpr::Try(expr)
678 | PureExpr::Ref { expr, .. }
679 | PureExpr::Cast { expr, .. }
680 | PureExpr::Let { expr, .. } => walk_expr_mut(expr, visitor),
681 PureExpr::Call { func, args } => {
682 walk_expr_mut(func, visitor);
683 for a in args {
684 walk_expr_mut(a, visitor);
685 }
686 }
687 PureExpr::MethodCall { receiver, args, .. } => {
688 walk_expr_mut(receiver, visitor);
689 for a in args {
690 walk_expr_mut(a, visitor);
691 }
692 }
693 PureExpr::Index { expr, index } => {
694 walk_expr_mut(expr, visitor);
695 walk_expr_mut(index, visitor);
696 }
697 PureExpr::Block { block, .. }
698 | PureExpr::Async { body: block, .. }
699 | PureExpr::Unsafe(block) => walk_block_mut(block, visitor),
700 PureExpr::If {
701 cond,
702 then_branch,
703 else_branch,
704 } => {
705 walk_expr_mut(cond, visitor);
706 walk_block_mut(then_branch, visitor);
707 if let Some(e) = else_branch {
708 walk_expr_mut(e, visitor);
709 }
710 }
711 PureExpr::Match { expr, arms } => {
712 walk_expr_mut(expr, visitor);
713 for arm in arms {
714 if let Some(g) = &mut arm.guard {
715 walk_expr_mut(g, visitor);
716 }
717 walk_expr_mut(&mut arm.body, visitor);
718 }
719 }
720 PureExpr::Loop { body, .. } => walk_block_mut(body, visitor),
721 PureExpr::While { cond, body, .. } => {
722 walk_expr_mut(cond, visitor);
723 walk_block_mut(body, visitor);
724 }
725 PureExpr::For { expr, body, .. } => {
726 walk_expr_mut(expr, visitor);
727 walk_block_mut(body, visitor);
728 }
729 PureExpr::Return(opt) => {
730 if let Some(e) = opt {
731 walk_expr_mut(e, visitor);
732 }
733 }
734 PureExpr::Break { expr, .. } => {
735 if let Some(e) = expr {
736 walk_expr_mut(e, visitor);
737 }
738 }
739 PureExpr::Closure { body, .. } => walk_expr_mut(body, visitor),
740 PureExpr::Struct { fields, rest, .. } => {
741 for (_, e) in fields {
742 walk_expr_mut(e, visitor);
743 }
744 if let Some(r) = rest {
745 walk_expr_mut(r, visitor);
746 }
747 }
748 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
749 for e in exprs {
750 walk_expr_mut(e, visitor);
751 }
752 }
753 PureExpr::Range { start, end, .. } => {
754 if let Some(s) = start {
755 walk_expr_mut(s, visitor);
756 }
757 if let Some(e) = end {
758 walk_expr_mut(e, visitor);
759 }
760 }
761 PureExpr::Repeat { expr, len } => {
762 walk_expr_mut(expr, visitor);
763 walk_expr_mut(len, visitor);
764 }
765 }
766 }
767
768 pub fn walk_block_mut<F: FnMut(&mut PureExpr)>(block: &mut PureBlock, visitor: &mut F) {
771 for stmt in &mut block.stmts {
772 match stmt {
773 PureStmt::Local {
774 init, else_branch, ..
775 } => {
776 if let Some(e) = init {
777 walk_expr_mut(e, visitor);
778 }
779 if let Some(e) = else_branch {
780 walk_expr_mut(e, visitor);
781 }
782 }
783 PureStmt::Semi(e) | PureStmt::Expr(e) => walk_expr_mut(e, visitor),
784 PureStmt::Item(_) | PureStmt::Verbatim(_) => {}
785 }
786 }
787 }
788
789 pub fn rewrite_path_expr_ufcs(path: &mut String, trait_name: &str, struct_name: &str) -> bool {
800 let prefix = format!("{}::", trait_name);
802 if let Some(rest) = path.strip_prefix(&prefix) {
803 let new_path = format!("{}::{}", struct_name, rest);
804 *path = new_path;
805 return true;
806 }
807 let needle_as = format!("as {}>::", trait_name);
809 if let Some(idx_as) = path.find(&needle_as) {
810 let tail_start = idx_as + needle_as.len();
813 let tail = path[tail_start..].to_string();
814 *path = format!("{}::{}", struct_name, tail);
815 return true;
816 }
817 false
818 }
819
820 pub fn rewrite_body_ufcs(body: &mut PureBlock, trait_name: &str, struct_name: &str) -> usize {
825 let mut count = 0usize;
826 walk_block_mut(body, &mut |e| {
827 if let PureExpr::Path(p) = e {
828 if rewrite_path_expr_ufcs(p, trait_name, struct_name) {
829 count += 1;
830 }
831 }
832 });
833 count
834 }
835
836 pub fn substitute_trait_in_type(
847 ty: &mut PureType,
848 trait_name: &str,
849 struct_name: &str,
850 ) -> bool {
851 match ty {
852 PureType::TraitObject(bounds) => {
853 if bounds.iter().any(|b| b == trait_name) {
854 *ty = PureType::Path(struct_name.to_string());
855 true
856 } else {
857 false
858 }
859 }
860 PureType::Ref {
861 ty: inner,
862 lifetime: _,
863 is_mut: _,
864 } => substitute_trait_in_type(inner, trait_name, struct_name),
865 PureType::Tuple(items) => {
866 let mut changed = false;
867 for t in items {
868 if substitute_trait_in_type(t, trait_name, struct_name) {
869 changed = true;
870 }
871 }
872 changed
873 }
874 PureType::Slice(inner) => substitute_trait_in_type(inner, trait_name, struct_name),
875 PureType::Array { ty: inner, .. } => {
876 substitute_trait_in_type(inner, trait_name, struct_name)
877 }
878 PureType::Fn { params, ret } => {
879 let mut changed = false;
880 for t in params {
881 if substitute_trait_in_type(t, trait_name, struct_name) {
882 changed = true;
883 }
884 }
885 if let Some(r) = ret {
886 if substitute_trait_in_type(r, trait_name, struct_name) {
887 changed = true;
888 }
889 }
890 changed
891 }
892 PureType::Path(_)
893 | PureType::ImplTrait(_)
894 | PureType::Infer
895 | PureType::Never
896 | PureType::Other(_) => false,
897 }
898 }
899
900 pub fn rewrite_fn_dyn_dispatch(f: &mut PureFn, trait_name: &str, struct_name: &str) -> usize {
909 let mut count = 0usize;
910 for p in &mut f.params {
911 if let PureParam::Typed { ty, .. } = p {
912 if substitute_trait_in_type(ty, trait_name, struct_name) {
913 count += 1;
914 }
915 }
916 }
917 if let Some(ret) = &mut f.ret {
918 if substitute_trait_in_type(ret, trait_name, struct_name) {
919 count += 1;
920 }
921 }
922 count
923 }
924
925 pub fn substitute_generic_in_type(
938 ty: &mut PureType,
939 generic_name: &str,
940 struct_name: &str,
941 ) -> bool {
942 match ty {
943 PureType::Path(p) => {
944 if p == generic_name {
945 *p = struct_name.to_string();
946 true
947 } else {
948 false
949 }
950 }
951 PureType::Ref { ty: inner, .. } => {
952 substitute_generic_in_type(inner, generic_name, struct_name)
953 }
954 PureType::Tuple(items) => {
955 let mut changed = false;
956 for t in items {
957 if substitute_generic_in_type(t, generic_name, struct_name) {
958 changed = true;
959 }
960 }
961 changed
962 }
963 PureType::Slice(inner) => substitute_generic_in_type(inner, generic_name, struct_name),
964 PureType::Array { ty: inner, .. } => {
965 substitute_generic_in_type(inner, generic_name, struct_name)
966 }
967 PureType::Fn { params, ret } => {
968 let mut changed = false;
969 for t in params {
970 if substitute_generic_in_type(t, generic_name, struct_name) {
971 changed = true;
972 }
973 }
974 if let Some(r) = ret {
975 if substitute_generic_in_type(r, generic_name, struct_name) {
976 changed = true;
977 }
978 }
979 changed
980 }
981 PureType::TraitObject(_)
982 | PureType::ImplTrait(_)
983 | PureType::Infer
984 | PureType::Never
985 | PureType::Other(_) => false,
986 }
987 }
988
989 pub fn rewrite_path_expr_generic(
1001 path: &mut String,
1002 generic_name: &str,
1003 struct_name: &str,
1004 ) -> bool {
1005 if path == generic_name {
1006 *path = struct_name.to_string();
1007 return true;
1008 }
1009 let prefix = format!("{}::", generic_name);
1010 if let Some(rest) = path.strip_prefix(&prefix) {
1011 let new_path = format!("{}::{}", struct_name, rest);
1012 *path = new_path;
1013 return true;
1014 }
1015 false
1016 }
1017
1018 pub fn rewrite_body_generic_bound(
1032 body: &mut PureBlock,
1033 generic_name: &str,
1034 struct_name: &str,
1035 ) -> usize {
1036 let mut count = 0usize;
1037
1038 walk_block_mut(body, &mut |e| {
1040 if let PureExpr::Path(p) = e {
1041 if rewrite_path_expr_generic(p, generic_name, struct_name) {
1042 count += 1;
1043 }
1044 }
1045 });
1046
1047 for stmt in &mut body.stmts {
1050 if let PureStmt::Local { ty: Some(ty), .. } = stmt {
1051 if substitute_generic_in_type(ty, generic_name, struct_name) {
1052 count += 1;
1053 }
1054 }
1055 }
1056
1057 count
1058 }
1059
1060 pub fn rewrite_turbofish_generic(
1073 s: &mut String,
1074 generic_name: &str,
1075 struct_name: &str,
1076 ) -> usize {
1077 let chars: Vec<char> = s.chars().collect();
1078 let mut out = String::with_capacity(s.len());
1079 let mut count = 0usize;
1080 let mut i = 0usize;
1081 while i < chars.len() {
1082 let c = chars[i];
1083 if c.is_alphabetic() || c == '_' {
1084 let start = i;
1085 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
1086 i += 1;
1087 }
1088 let ident: String = chars[start..i].iter().collect();
1089 if ident == generic_name {
1090 out.push_str(struct_name);
1091 count += 1;
1092 } else {
1093 out.push_str(&ident);
1094 }
1095 } else {
1096 out.push(c);
1097 i += 1;
1098 }
1099 }
1100 *s = out;
1101 count
1102 }
1103
1104 pub fn rewrite_body_turbofish_generic(
1109 body: &mut PureBlock,
1110 generic_name: &str,
1111 struct_name: &str,
1112 ) -> usize {
1113 let mut count = 0usize;
1114 walk_block_mut(body, &mut |e| {
1115 if let PureExpr::MethodCall {
1116 turbofish: Some(s), ..
1117 } = e
1118 {
1119 count += rewrite_turbofish_generic(s, generic_name, struct_name);
1120 }
1121 });
1122 count
1123 }
1124
1125 pub fn rewrite_where_clause_generic(
1148 clause: &mut Vec<String>,
1149 generic_name: &str,
1150 struct_name: &str,
1151 ) -> usize {
1152 let mut count = 0usize;
1153 clause.retain_mut(|predicate| {
1154 let trimmed = predicate.trim_start();
1157 let lhs_end = trimmed.find(':').unwrap_or(trimmed.len());
1158 let lhs = trimmed[..lhs_end].trim();
1159 if lhs == generic_name {
1160 count += 1;
1161 return false;
1162 }
1163
1164 let chars: Vec<char> = predicate.chars().collect();
1167 let mut out = String::with_capacity(predicate.len());
1168 let mut i = 0usize;
1169 while i < chars.len() {
1170 let c = chars[i];
1171 if c.is_alphabetic() || c == '_' {
1172 let start = i;
1173 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
1174 i += 1;
1175 }
1176 let ident: String = chars[start..i].iter().collect();
1177 if ident == generic_name {
1178 out.push_str(struct_name);
1179 count += 1;
1180 } else {
1181 out.push_str(&ident);
1182 }
1183 } else {
1184 out.push(c);
1185 i += 1;
1186 }
1187 }
1188 *predicate = out;
1189 true
1190 });
1191 count
1192 }
1193
1194 pub fn rewrite_fn_generic_bound(f: &mut PureFn, trait_name: &str, struct_name: &str) -> usize {
1212 let bound_names: Vec<String> = f
1214 .generics
1215 .params
1216 .iter()
1217 .filter_map(|p| match p {
1218 PureGenericParam::Type { name, bounds }
1219 if bounds.iter().any(|b| b == trait_name) =>
1220 {
1221 Some(name.clone())
1222 }
1223 _ => None,
1224 })
1225 .collect();
1226
1227 if bound_names.is_empty() {
1228 return 0;
1229 }
1230
1231 let before = f.generics.params.len();
1233 f.generics.params.retain(|p| match p {
1234 PureGenericParam::Type { name, .. } => !bound_names.contains(name),
1235 _ => true,
1236 });
1237 let removed = before - f.generics.params.len();
1238
1239 let mut substitutions = 0usize;
1242 for bound_name in &bound_names {
1243 for p in &mut f.params {
1244 if let PureParam::Typed { ty, .. } = p {
1245 if substitute_generic_in_type(ty, bound_name, struct_name) {
1246 substitutions += 1;
1247 }
1248 }
1249 }
1250 if let Some(ret) = &mut f.ret {
1251 if substitute_generic_in_type(ret, bound_name, struct_name) {
1252 substitutions += 1;
1253 }
1254 }
1255 }
1256
1257 let mut body_edits = 0usize;
1262 for bound_name in &bound_names {
1263 body_edits += rewrite_body_generic_bound(&mut f.body, bound_name, struct_name);
1264 body_edits += rewrite_body_turbofish_generic(&mut f.body, bound_name, struct_name);
1265 body_edits +=
1266 rewrite_where_clause_generic(&mut f.generics.where_clause, bound_name, struct_name);
1267 }
1268
1269 removed + substitutions + body_edits
1270 }
1271}
1272
1273#[cfg(test)]
1274mod cross_crate_caller_pattern_tests {
1275 use super::cross_crate_caller_pattern::*;
1276 use ryo_source::pure::{
1277 PureBlock, PureExpr, PureFn, PureGenericParam, PureGenerics, PureParam, PureType, PureVis,
1278 };
1279
1280 fn mk_fn(name: &str, generics: PureGenerics, params: Vec<PureParam>) -> PureFn {
1281 PureFn {
1282 attrs: Vec::new(),
1283 vis: PureVis::Public,
1284 is_async: false,
1285 is_async_inferred: false,
1286 is_const: false,
1287 is_unsafe: false,
1288 abi: None,
1289 name: name.to_string(),
1290 generics,
1291 params,
1292 ret: None,
1293 body: PureBlock { stmts: Vec::new() },
1294 }
1295 }
1296
1297 #[test]
1298 fn ufcs_path_detected() {
1299 let p = "<Case01 as Case01Trait>::foo";
1300 assert_eq!(
1301 classify_path_expr(p, "Case01Trait"),
1302 Some(CallerPattern::Ufcs)
1303 );
1304 assert_eq!(classify_path_expr(p, "OtherTrait"), None);
1305 }
1306
1307 #[test]
1308 fn ufcs_path_negative_case() {
1309 let p = "Case01::foo";
1310 assert_eq!(classify_path_expr(p, "Case01Trait"), None);
1311 }
1312
1313 #[test]
1314 fn ufcs_path_trait_qualified_form_detected() {
1315 let p = "Case01Trait::foo";
1317 assert_eq!(
1318 classify_path_expr(p, "Case01Trait"),
1319 Some(CallerPattern::Ufcs)
1320 );
1321 }
1322
1323 #[test]
1324 fn dot_method_call_detected() {
1325 let receiver = PureExpr::Path("s".to_string());
1326 let expr = PureExpr::MethodCall {
1327 receiver: Box::new(receiver),
1328 method: "foo".to_string(),
1329 turbofish: None,
1330 args: Vec::new(),
1331 };
1332 let methods = vec!["foo".to_string(), "bar".to_string()];
1333 assert_eq!(
1334 classify_method_call(&expr, &methods),
1335 Some(CallerPattern::Dot)
1336 );
1337 }
1338
1339 #[test]
1340 fn dot_method_call_negative_when_name_mismatch() {
1341 let expr = PureExpr::MethodCall {
1342 receiver: Box::new(PureExpr::Path("s".to_string())),
1343 method: "qux".to_string(),
1344 turbofish: None,
1345 args: Vec::new(),
1346 };
1347 let methods = vec!["foo".to_string()];
1348 assert_eq!(classify_method_call(&expr, &methods), None);
1349 }
1350
1351 #[test]
1352 fn dot_method_call_negative_when_not_method_call() {
1353 let expr = PureExpr::Path("foo".to_string());
1354 let methods = vec!["foo".to_string()];
1355 assert_eq!(classify_method_call(&expr, &methods), None);
1356 }
1357
1358 #[test]
1359 fn generic_bound_detected_inline() {
1360 let g = PureGenerics {
1361 params: vec![PureGenericParam::Type {
1362 name: "T".to_string(),
1363 bounds: vec!["Case01Trait".to_string()],
1364 }],
1365 where_clause: Vec::new(),
1366 };
1367 assert!(classify_fn_generic_bound(&g, "Case01Trait"));
1368 assert!(!classify_fn_generic_bound(&g, "OtherTrait"));
1369 }
1370
1371 #[test]
1372 fn generic_bound_detected_where_clause() {
1373 let g = PureGenerics {
1374 params: vec![PureGenericParam::Type {
1375 name: "T".to_string(),
1376 bounds: Vec::new(),
1377 }],
1378 where_clause: vec!["T: Case01Trait".to_string()],
1379 };
1380 assert!(classify_fn_generic_bound(&g, "Case01Trait"));
1381 }
1382
1383 #[test]
1384 fn dyn_param_detected() {
1385 let params = vec![PureParam::Typed {
1386 name: "t".to_string(),
1387 ty: PureType::Ref {
1388 lifetime: None,
1389 is_mut: false,
1390 ty: Box::new(PureType::TraitObject(vec!["Case01Trait".to_string()])),
1391 },
1392 is_mut: false,
1393 pat: None,
1394 }];
1395 assert!(classify_fn_dyn_param(¶ms, "Case01Trait"));
1396 assert!(!classify_fn_dyn_param(¶ms, "OtherTrait"));
1397 }
1398
1399 #[test]
1400 fn dyn_param_negative_when_no_dyn() {
1401 let params = vec![PureParam::Typed {
1402 name: "t".to_string(),
1403 ty: PureType::Path("Case01".to_string()),
1404 is_mut: false,
1405 pat: None,
1406 }];
1407 assert!(!classify_fn_dyn_param(¶ms, "Case01Trait"));
1408 }
1409
1410 #[test]
1411 fn classify_fn_signature_combines_axes() {
1412 let g = PureGenerics {
1413 params: vec![PureGenericParam::Type {
1414 name: "T".to_string(),
1415 bounds: vec!["Case01Trait".to_string()],
1416 }],
1417 where_clause: Vec::new(),
1418 };
1419 let params = vec![PureParam::Typed {
1420 name: "t".to_string(),
1421 ty: PureType::Ref {
1422 lifetime: None,
1423 is_mut: false,
1424 ty: Box::new(PureType::TraitObject(vec!["Case01Trait".to_string()])),
1425 },
1426 is_mut: false,
1427 pat: None,
1428 }];
1429 let f = mk_fn("use_both", g, params);
1430 let out = classify_fn_signature(&f, "Case01Trait");
1431 assert!(out.contains(&CallerPattern::GenericBound));
1432 assert!(out.contains(&CallerPattern::DynDispatch));
1433 assert_eq!(out.len(), 2);
1434 }
1435
1436 #[test]
1437 fn classify_fn_signature_empty_for_unrelated_trait() {
1438 let g = PureGenerics {
1439 params: vec![PureGenericParam::Type {
1440 name: "T".to_string(),
1441 bounds: vec!["Case01Trait".to_string()],
1442 }],
1443 where_clause: Vec::new(),
1444 };
1445 let f = mk_fn("use_generic", g, Vec::new());
1446 let out = classify_fn_signature(&f, "OtherTrait");
1447 assert!(out.is_empty());
1448 }
1449
1450 use ryo_source::pure::PureStmt;
1453
1454 fn empty_generics() -> PureGenerics {
1455 PureGenerics {
1456 params: Vec::new(),
1457 where_clause: Vec::new(),
1458 }
1459 }
1460
1461 fn mk_method_call(receiver_name: &str, method: &str) -> PureExpr {
1462 PureExpr::MethodCall {
1463 receiver: Box::new(PureExpr::Path(receiver_name.to_string())),
1464 method: method.to_string(),
1465 turbofish: None,
1466 args: Vec::new(),
1467 }
1468 }
1469
1470 fn fn_with_body(name: &str, body: PureBlock) -> PureFn {
1471 PureFn {
1472 attrs: Vec::new(),
1473 vis: PureVis::Public,
1474 is_async: false,
1475 is_async_inferred: false,
1476 is_const: false,
1477 is_unsafe: false,
1478 abi: None,
1479 name: name.to_string(),
1480 generics: empty_generics(),
1481 params: Vec::new(),
1482 ret: None,
1483 body,
1484 }
1485 }
1486
1487 #[test]
1488 fn walk_expr_visits_method_call_arguments() {
1489 let inner = mk_method_call("s", "bar");
1491 let outer = PureExpr::Call {
1492 func: Box::new(PureExpr::Path("foo".to_string())),
1493 args: vec![inner],
1494 };
1495 let mut visited = 0usize;
1496 walk_expr(&outer, &mut |e| {
1497 if matches!(e, PureExpr::MethodCall { .. }) {
1498 visited += 1;
1499 }
1500 });
1501 assert_eq!(visited, 1);
1502 }
1503
1504 #[test]
1505 fn scan_body_detects_dot() {
1506 let body = PureBlock {
1508 stmts: vec![PureStmt::Expr(mk_method_call("s", "foo"))],
1509 };
1510 let methods = vec!["foo".to_string()];
1511 let out = scan_body_for_patterns(&body, "Case01Trait", &methods);
1512 assert_eq!(out, vec![CallerPattern::Dot]);
1513 }
1514
1515 #[test]
1516 fn scan_body_detects_ufcs_path() {
1517 let body = PureBlock {
1519 stmts: vec![PureStmt::Expr(PureExpr::Call {
1520 func: Box::new(PureExpr::Path("<Case01 as Case01Trait>::foo".to_string())),
1521 args: vec![PureExpr::Path("s".to_string())],
1522 })],
1523 };
1524 let out = scan_body_for_patterns(&body, "Case01Trait", &[]);
1525 assert_eq!(out, vec![CallerPattern::Ufcs]);
1526 }
1527
1528 #[test]
1529 fn scan_body_detects_both_dot_and_ufcs() {
1530 let body = PureBlock {
1532 stmts: vec![
1533 PureStmt::Semi(mk_method_call("s", "foo")),
1534 PureStmt::Expr(PureExpr::Path("<Case01 as Case01Trait>::bar".to_string())),
1535 ],
1536 };
1537 let methods = vec!["foo".to_string()];
1538 let out = scan_body_for_patterns(&body, "Case01Trait", &methods);
1539 assert!(out.contains(&CallerPattern::Dot));
1540 assert!(out.contains(&CallerPattern::Ufcs));
1541 assert_eq!(out.len(), 2);
1542 }
1543
1544 #[test]
1545 fn scan_body_detects_dot_in_nested_block() {
1546 let inner_if = PureExpr::If {
1548 cond: Box::new(PureExpr::Lit("true".to_string())),
1549 then_branch: PureBlock {
1550 stmts: vec![PureStmt::Expr(mk_method_call("s", "foo"))],
1551 },
1552 else_branch: Some(Box::new(PureExpr::Lit("0".to_string()))),
1553 };
1554 let body = PureBlock {
1555 stmts: vec![PureStmt::Expr(inner_if)],
1556 };
1557 let methods = vec!["foo".to_string()];
1558 let out = scan_body_for_patterns(&body, "Case01Trait", &methods);
1559 assert_eq!(out, vec![CallerPattern::Dot]);
1560 }
1561
1562 #[test]
1563 fn scan_body_no_pattern_when_unrelated() {
1564 let body = PureBlock {
1566 stmts: vec![PureStmt::Expr(mk_method_call("s", "qux"))],
1567 };
1568 let methods = vec!["foo".to_string()];
1569 let out = scan_body_for_patterns(&body, "Case01Trait", &methods);
1570 assert!(out.is_empty());
1571 }
1572
1573 #[test]
1576 fn rewrite_path_expr_ufcs_trait_qualified() {
1577 let mut p = String::from("Case01Trait::foo");
1578 let changed = rewrite_path_expr_ufcs(&mut p, "Case01Trait", "Case01");
1579 assert!(changed);
1580 assert_eq!(p, "Case01::foo");
1581 }
1582
1583 #[test]
1584 fn rewrite_path_expr_ufcs_literal_envelope() {
1585 let mut p = String::from("<Case01 as Case01Trait>::foo");
1586 let changed = rewrite_path_expr_ufcs(&mut p, "Case01Trait", "Case01");
1587 assert!(changed);
1588 assert_eq!(p, "Case01::foo");
1589 }
1590
1591 #[test]
1592 fn rewrite_path_expr_ufcs_no_match_returns_false() {
1593 let mut p = String::from("Case01::foo");
1594 let changed = rewrite_path_expr_ufcs(&mut p, "Case01Trait", "Case01");
1595 assert!(!changed);
1596 assert_eq!(p, "Case01::foo");
1597 }
1598
1599 #[test]
1600 fn rewrite_path_expr_ufcs_unrelated_trait_no_change() {
1601 let mut p = String::from("OtherTrait::foo");
1602 let changed = rewrite_path_expr_ufcs(&mut p, "Case01Trait", "Case01");
1603 assert!(!changed);
1604 assert_eq!(p, "OtherTrait::foo");
1605 }
1606
1607 #[test]
1608 fn rewrite_body_ufcs_single_path() {
1609 let mut body = PureBlock {
1610 stmts: vec![PureStmt::Expr(PureExpr::Call {
1611 func: Box::new(PureExpr::Path("Case01Trait::foo".to_string())),
1612 args: vec![PureExpr::Path("s".to_string())],
1613 })],
1614 };
1615 let count = rewrite_body_ufcs(&mut body, "Case01Trait", "Case01");
1616 assert_eq!(count, 1);
1617 if let PureStmt::Expr(PureExpr::Call { func, .. }) = &body.stmts[0] {
1618 if let PureExpr::Path(p) = func.as_ref() {
1619 assert_eq!(p, "Case01::foo");
1620 } else {
1621 panic!("expected Path");
1622 }
1623 } else {
1624 panic!("expected Call");
1625 }
1626 }
1627
1628 #[test]
1629 fn rewrite_body_ufcs_multiple_paths_in_nested_block() {
1630 let inner_if = PureExpr::If {
1632 cond: Box::new(PureExpr::Lit("true".to_string())),
1633 then_branch: PureBlock {
1634 stmts: vec![PureStmt::Expr(PureExpr::Call {
1635 func: Box::new(PureExpr::Path("Case01Trait::bar".to_string())),
1636 args: Vec::new(),
1637 })],
1638 },
1639 else_branch: Some(Box::new(PureExpr::Lit("0".to_string()))),
1640 };
1641 let mut body = PureBlock {
1642 stmts: vec![
1643 PureStmt::Semi(PureExpr::Call {
1644 func: Box::new(PureExpr::Path("Case01Trait::foo".to_string())),
1645 args: vec![PureExpr::Path("s".to_string())],
1646 }),
1647 PureStmt::Expr(inner_if),
1648 ],
1649 };
1650 let count = rewrite_body_ufcs(&mut body, "Case01Trait", "Case01");
1651 assert_eq!(count, 2);
1652 }
1653
1654 #[test]
1655 fn rewrite_body_ufcs_leaves_unrelated_paths_alone() {
1656 let mut body = PureBlock {
1657 stmts: vec![PureStmt::Expr(PureExpr::Call {
1658 func: Box::new(PureExpr::Path("OtherTrait::foo".to_string())),
1659 args: Vec::new(),
1660 })],
1661 };
1662 let count = rewrite_body_ufcs(&mut body, "Case01Trait", "Case01");
1663 assert_eq!(count, 0);
1664 }
1665
1666 #[test]
1669 fn substitute_trait_in_type_dyn_object() {
1670 let mut ty = PureType::TraitObject(vec!["Case01Trait".to_string()]);
1671 let changed = substitute_trait_in_type(&mut ty, "Case01Trait", "Case01");
1672 assert!(changed);
1673 assert_eq!(ty, PureType::Path("Case01".to_string()));
1674 }
1675
1676 #[test]
1677 fn substitute_trait_in_type_ref_dyn() {
1678 let mut ty = PureType::Ref {
1680 lifetime: None,
1681 is_mut: false,
1682 ty: Box::new(PureType::TraitObject(vec!["Case01Trait".to_string()])),
1683 };
1684 let changed = substitute_trait_in_type(&mut ty, "Case01Trait", "Case01");
1685 assert!(changed);
1686 match ty {
1687 PureType::Ref { ty: inner, .. } => {
1688 assert_eq!(*inner, PureType::Path("Case01".to_string()));
1689 }
1690 _ => panic!("expected Ref"),
1691 }
1692 }
1693
1694 #[test]
1695 fn substitute_trait_in_type_unrelated() {
1696 let mut ty = PureType::Path("u32".to_string());
1697 let changed = substitute_trait_in_type(&mut ty, "Case01Trait", "Case01");
1698 assert!(!changed);
1699 assert_eq!(ty, PureType::Path("u32".to_string()));
1700 }
1701
1702 #[test]
1703 fn substitute_trait_in_type_tuple_mixed() {
1704 let mut ty = PureType::Tuple(vec![
1706 PureType::Ref {
1707 lifetime: None,
1708 is_mut: false,
1709 ty: Box::new(PureType::TraitObject(vec!["Case01Trait".to_string()])),
1710 },
1711 PureType::Path("u32".to_string()),
1712 ]);
1713 let changed = substitute_trait_in_type(&mut ty, "Case01Trait", "Case01");
1714 assert!(changed);
1715 }
1716
1717 #[test]
1718 fn rewrite_fn_dyn_dispatch_signature_param_and_ret() {
1719 let dyn_ref = |bounds: &str| PureType::Ref {
1721 lifetime: None,
1722 is_mut: false,
1723 ty: Box::new(PureType::TraitObject(vec![bounds.to_string()])),
1724 };
1725 let mut f = mk_fn(
1726 "use_both",
1727 PureGenerics {
1728 params: Vec::new(),
1729 where_clause: Vec::new(),
1730 },
1731 vec![PureParam::Typed {
1732 name: "t".to_string(),
1733 ty: dyn_ref("Case01Trait"),
1734 is_mut: false,
1735 pat: None,
1736 }],
1737 );
1738 f.ret = Some(dyn_ref("Case01Trait"));
1739
1740 let count = rewrite_fn_dyn_dispatch(&mut f, "Case01Trait", "Case01");
1741 assert_eq!(count, 2);
1742
1743 if let PureParam::Typed { ty, .. } = &f.params[0] {
1745 match ty {
1746 PureType::Ref { ty: inner, .. } => {
1747 assert_eq!(**inner, PureType::Path("Case01".to_string()));
1748 }
1749 _ => panic!("expected Ref"),
1750 }
1751 }
1752 match &f.ret {
1754 Some(PureType::Ref { ty: inner, .. }) => {
1755 assert_eq!(**inner, PureType::Path("Case01".to_string()));
1756 }
1757 _ => panic!("expected Ref ret"),
1758 }
1759 }
1760
1761 #[test]
1762 fn rewrite_fn_dyn_dispatch_no_change_when_unrelated() {
1763 let mut f = mk_fn(
1764 "use_nothing",
1765 PureGenerics {
1766 params: Vec::new(),
1767 where_clause: Vec::new(),
1768 },
1769 vec![PureParam::Typed {
1770 name: "x".to_string(),
1771 ty: PureType::Path("u32".to_string()),
1772 is_mut: false,
1773 pat: None,
1774 }],
1775 );
1776 let count = rewrite_fn_dyn_dispatch(&mut f, "Case01Trait", "Case01");
1777 assert_eq!(count, 0);
1778 }
1779
1780 #[test]
1783 fn substitute_generic_in_type_direct_path() {
1784 let mut ty = PureType::Path("T".to_string());
1785 let changed = substitute_generic_in_type(&mut ty, "T", "Case01");
1786 assert!(changed);
1787 assert_eq!(ty, PureType::Path("Case01".to_string()));
1788 }
1789
1790 #[test]
1791 fn substitute_generic_in_type_ref_path() {
1792 let mut ty = PureType::Ref {
1794 lifetime: None,
1795 is_mut: false,
1796 ty: Box::new(PureType::Path("T".to_string())),
1797 };
1798 let changed = substitute_generic_in_type(&mut ty, "T", "Case01");
1799 assert!(changed);
1800 }
1801
1802 #[test]
1803 fn substitute_generic_in_type_unrelated_name_unchanged() {
1804 let mut ty = PureType::Path("u32".to_string());
1805 let changed = substitute_generic_in_type(&mut ty, "T", "Case01");
1806 assert!(!changed);
1807 assert_eq!(ty, PureType::Path("u32".to_string()));
1808 }
1809
1810 #[test]
1811 fn rewrite_fn_generic_bound_strips_param_and_substitutes_type() {
1812 let g = PureGenerics {
1814 params: vec![PureGenericParam::Type {
1815 name: "T".to_string(),
1816 bounds: vec!["Case01Trait".to_string()],
1817 }],
1818 where_clause: Vec::new(),
1819 };
1820 let mut f = mk_fn(
1821 "use_generic",
1822 g,
1823 vec![PureParam::Typed {
1824 name: "t".to_string(),
1825 ty: PureType::Path("T".to_string()),
1826 is_mut: false,
1827 pat: None,
1828 }],
1829 );
1830 f.ret = Some(PureType::Path("u32".to_string()));
1831
1832 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
1833 assert_eq!(count, 2);
1835 assert!(f.generics.params.is_empty(), "generic params must be empty");
1836 if let PureParam::Typed { ty, .. } = &f.params[0] {
1837 assert_eq!(*ty, PureType::Path("Case01".to_string()));
1838 } else {
1839 panic!("expected Typed param");
1840 }
1841 }
1842
1843 #[test]
1844 fn rewrite_fn_generic_bound_leaves_unrelated_generics_alone() {
1845 let g = PureGenerics {
1847 params: vec![
1848 PureGenericParam::Type {
1849 name: "U".to_string(),
1850 bounds: vec!["OtherTrait".to_string()],
1851 },
1852 PureGenericParam::Type {
1853 name: "T".to_string(),
1854 bounds: vec!["Case01Trait".to_string()],
1855 },
1856 ],
1857 where_clause: Vec::new(),
1858 };
1859 let mut f = mk_fn(
1860 "use_other",
1861 g,
1862 vec![
1863 PureParam::Typed {
1864 name: "t".to_string(),
1865 ty: PureType::Path("T".to_string()),
1866 is_mut: false,
1867 pat: None,
1868 },
1869 PureParam::Typed {
1870 name: "u".to_string(),
1871 ty: PureType::Path("U".to_string()),
1872 is_mut: false,
1873 pat: None,
1874 },
1875 ],
1876 );
1877 f.ret = Some(PureType::Path("T".to_string()));
1878
1879 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
1880 assert_eq!(count, 3);
1882 assert_eq!(f.generics.params.len(), 1, "U generic must remain");
1883 if let PureGenericParam::Type { name, .. } = &f.generics.params[0] {
1884 assert_eq!(name, "U");
1885 } else {
1886 panic!("expected Type param");
1887 }
1888 if let PureParam::Typed { ty, .. } = &f.params[1] {
1889 assert_eq!(*ty, PureType::Path("U".to_string()));
1890 }
1891 }
1892
1893 #[test]
1896 fn rewrite_path_expr_generic_bare() {
1897 let mut p = String::from("T");
1898 let changed = rewrite_path_expr_generic(&mut p, "T", "Case01");
1899 assert!(changed);
1900 assert_eq!(p, "Case01");
1901 }
1902
1903 #[test]
1904 fn rewrite_path_expr_generic_prefixed() {
1905 let mut p = String::from("T::default");
1906 let changed = rewrite_path_expr_generic(&mut p, "T", "Case01");
1907 assert!(changed);
1908 assert_eq!(p, "Case01::default");
1909 }
1910
1911 #[test]
1912 fn rewrite_path_expr_generic_nested_segments() {
1913 let mut p = String::from("T::Item::sub");
1914 let changed = rewrite_path_expr_generic(&mut p, "T", "Case01");
1915 assert!(changed);
1916 assert_eq!(p, "Case01::Item::sub");
1917 }
1918
1919 #[test]
1920 fn rewrite_path_expr_generic_no_match_when_unrelated() {
1921 let mut p = String::from("Type::method");
1924 let changed = rewrite_path_expr_generic(&mut p, "T", "Case01");
1925 assert!(!changed);
1926 assert_eq!(p, "Type::method");
1927 }
1928
1929 #[test]
1930 fn rewrite_path_expr_generic_no_match_different_generic_name() {
1931 let mut p = String::from("U::new");
1932 let changed = rewrite_path_expr_generic(&mut p, "T", "Case01");
1933 assert!(!changed);
1934 assert_eq!(p, "U::new");
1935 }
1936
1937 #[test]
1938 fn rewrite_body_generic_bound_path_in_call() {
1939 let mut body = PureBlock {
1941 stmts: vec![PureStmt::Expr(PureExpr::Call {
1942 func: Box::new(PureExpr::Path("T::default".to_string())),
1943 args: Vec::new(),
1944 })],
1945 };
1946 let count = rewrite_body_generic_bound(&mut body, "T", "Case01");
1947 assert_eq!(count, 1);
1948 if let PureStmt::Expr(PureExpr::Call { func, .. }) = &body.stmts[0] {
1949 if let PureExpr::Path(p) = func.as_ref() {
1950 assert_eq!(p, "Case01::default");
1951 } else {
1952 panic!("expected Path");
1953 }
1954 } else {
1955 panic!("expected Call");
1956 }
1957 }
1958
1959 #[test]
1960 fn rewrite_body_generic_bound_local_type_annotation() {
1961 let mut body = PureBlock {
1963 stmts: vec![PureStmt::Local {
1964 pattern: ryo_source::pure::PurePattern::Ident {
1965 name: "x".to_string(),
1966 is_mut: false,
1967 by_ref: false,
1968 },
1969 ty: Some(PureType::Path("T".to_string())),
1970 init: None,
1971 else_branch: None,
1972 }],
1973 };
1974 let count = rewrite_body_generic_bound(&mut body, "T", "Case01");
1975 assert_eq!(count, 1);
1976 if let PureStmt::Local { ty: Some(ty), .. } = &body.stmts[0] {
1977 assert_eq!(*ty, PureType::Path("Case01".to_string()));
1978 } else {
1979 panic!("expected Local with ty");
1980 }
1981 }
1982
1983 #[test]
1984 fn rewrite_body_generic_bound_combined_path_and_local_ty() {
1985 let mut body = PureBlock {
1987 stmts: vec![PureStmt::Local {
1988 pattern: ryo_source::pure::PurePattern::Ident {
1989 name: "x".to_string(),
1990 is_mut: false,
1991 by_ref: false,
1992 },
1993 ty: Some(PureType::Path("T".to_string())),
1994 init: Some(PureExpr::Call {
1995 func: Box::new(PureExpr::Path("T::default".to_string())),
1996 args: Vec::new(),
1997 }),
1998 else_branch: None,
1999 }],
2000 };
2001 let count = rewrite_body_generic_bound(&mut body, "T", "Case01");
2002 assert_eq!(count, 2);
2004 }
2005
2006 #[test]
2007 fn rewrite_body_generic_bound_no_change_when_unrelated() {
2008 let mut body = PureBlock {
2010 stmts: vec![PureStmt::Expr(PureExpr::Call {
2011 func: Box::new(PureExpr::Path("U::new".to_string())),
2012 args: Vec::new(),
2013 })],
2014 };
2015 let count = rewrite_body_generic_bound(&mut body, "T", "Case01");
2016 assert_eq!(count, 0);
2017 }
2018
2019 #[test]
2020 fn rewrite_fn_generic_bound_with_body_path() {
2021 let g = PureGenerics {
2026 params: vec![PureGenericParam::Type {
2027 name: "T".to_string(),
2028 bounds: vec!["Case01Trait".to_string()],
2029 }],
2030 where_clause: Vec::new(),
2031 };
2032 let body = PureBlock {
2033 stmts: vec![PureStmt::Local {
2034 pattern: ryo_source::pure::PurePattern::Ident {
2035 name: "x".to_string(),
2036 is_mut: false,
2037 by_ref: false,
2038 },
2039 ty: None,
2040 init: Some(PureExpr::Call {
2041 func: Box::new(PureExpr::Path("T::default".to_string())),
2042 args: Vec::new(),
2043 }),
2044 else_branch: None,
2045 }],
2046 };
2047 let mut f = fn_with_body("use_generic", body);
2048 f.generics = g;
2049 f.params = vec![PureParam::Typed {
2050 name: "t".to_string(),
2051 ty: PureType::Path("T".to_string()),
2052 is_mut: false,
2053 pat: None,
2054 }];
2055 f.ret = Some(PureType::Path("u32".to_string()));
2056
2057 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
2058 assert_eq!(count, 3);
2060 assert!(f.generics.params.is_empty());
2061 if let PureStmt::Local {
2062 init: Some(PureExpr::Call { func, .. }),
2063 ..
2064 } = &f.body.stmts[0]
2065 {
2066 if let PureExpr::Path(p) = func.as_ref() {
2067 assert_eq!(p, "Case01::default");
2068 } else {
2069 panic!("expected Path");
2070 }
2071 } else {
2072 panic!("expected Local with init");
2073 }
2074 }
2075
2076 #[test]
2079 fn rewrite_turbofish_generic_bare_token() {
2080 let mut s = String::from("< T >");
2081 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2082 assert_eq!(count, 1);
2083 assert_eq!(s, "< Case01 >");
2084 }
2085
2086 #[test]
2087 fn rewrite_turbofish_generic_multi_args_only_t_changes() {
2088 let mut s = String::from("< T , U >");
2089 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2090 assert_eq!(count, 1);
2091 assert_eq!(s, "< Case01 , U >");
2092 }
2093
2094 #[test]
2095 fn rewrite_turbofish_generic_nested() {
2096 let mut s = String::from("< Vec < T > >");
2097 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2098 assert_eq!(count, 1);
2099 assert_eq!(s, "< Vec < Case01 > >");
2100 }
2101
2102 #[test]
2103 fn rewrite_turbofish_generic_does_not_match_prefix_identifier() {
2104 let mut s = String::from("< Type >");
2105 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2106 assert_eq!(count, 0);
2107 assert_eq!(s, "< Type >");
2108 }
2109
2110 #[test]
2111 fn rewrite_turbofish_generic_does_not_match_extended_identifier() {
2112 let mut s = String::from("< Tx >");
2113 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2114 assert_eq!(count, 0);
2115 assert_eq!(s, "< Tx >");
2116 }
2117
2118 #[test]
2119 fn rewrite_turbofish_generic_repeated_occurrences() {
2120 let mut s = String::from("< T , T , T >");
2121 let count = rewrite_turbofish_generic(&mut s, "T", "Case01");
2122 assert_eq!(count, 3);
2123 assert_eq!(s, "< Case01 , Case01 , Case01 >");
2124 }
2125
2126 #[test]
2127 fn rewrite_body_turbofish_generic_in_method_call() {
2128 let mut body = PureBlock {
2129 stmts: vec![PureStmt::Expr(PureExpr::MethodCall {
2130 receiver: Box::new(PureExpr::Path("vec".to_string())),
2131 method: "iter".to_string(),
2132 turbofish: Some("< T >".to_string()),
2133 args: Vec::new(),
2134 })],
2135 };
2136 let count = rewrite_body_turbofish_generic(&mut body, "T", "Case01");
2137 assert_eq!(count, 1);
2138 if let PureStmt::Expr(PureExpr::MethodCall {
2139 turbofish: Some(s), ..
2140 }) = &body.stmts[0]
2141 {
2142 assert_eq!(s, "< Case01 >");
2143 } else {
2144 panic!("expected MethodCall with turbofish");
2145 }
2146 }
2147
2148 #[test]
2149 fn rewrite_body_turbofish_generic_skips_unrelated() {
2150 let mut body = PureBlock {
2151 stmts: vec![PureStmt::Expr(PureExpr::MethodCall {
2152 receiver: Box::new(PureExpr::Path("vec".to_string())),
2153 method: "iter".to_string(),
2154 turbofish: Some("< U >".to_string()),
2155 args: Vec::new(),
2156 })],
2157 };
2158 let count = rewrite_body_turbofish_generic(&mut body, "T", "Case01");
2159 assert_eq!(count, 0);
2160 }
2161
2162 #[test]
2163 fn rewrite_fn_generic_bound_with_body_turbofish() {
2164 let g = PureGenerics {
2165 params: vec![PureGenericParam::Type {
2166 name: "T".to_string(),
2167 bounds: vec!["Case01Trait".to_string()],
2168 }],
2169 where_clause: Vec::new(),
2170 };
2171 let body = PureBlock {
2172 stmts: vec![PureStmt::Expr(PureExpr::MethodCall {
2173 receiver: Box::new(PureExpr::Path("vec".to_string())),
2174 method: "iter".to_string(),
2175 turbofish: Some("< T >".to_string()),
2176 args: Vec::new(),
2177 })],
2178 };
2179 let mut f = fn_with_body("use_generic", body);
2180 f.generics = g;
2181 f.params = vec![PureParam::Typed {
2182 name: "t".to_string(),
2183 ty: PureType::Path("T".to_string()),
2184 is_mut: false,
2185 pat: None,
2186 }];
2187
2188 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
2189 assert_eq!(count, 3);
2191 assert!(f.generics.params.is_empty());
2192 if let PureStmt::Expr(PureExpr::MethodCall {
2193 turbofish: Some(s), ..
2194 }) = &f.body.stmts[0]
2195 {
2196 assert_eq!(s, "< Case01 >");
2197 } else {
2198 panic!("expected MethodCall with turbofish");
2199 }
2200 }
2201
2202 #[test]
2205 fn rewrite_where_clause_drops_predicate_whose_lhs_is_stripped() {
2206 let mut clause = vec!["T: Clone".to_string()];
2208 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2209 assert_eq!(count, 1);
2210 assert!(clause.is_empty(), "predicate should be dropped");
2211 }
2212
2213 #[test]
2214 fn rewrite_where_clause_keeps_predicate_with_different_lhs() {
2215 let mut clause = vec!["U: Foo".to_string()];
2217 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2218 assert_eq!(count, 0);
2219 assert_eq!(clause, vec!["U: Foo".to_string()]);
2220 }
2221
2222 #[test]
2223 fn rewrite_where_clause_substitutes_in_predicate_rhs() {
2224 let mut clause = vec!["U: Foo<T>".to_string()];
2226 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2227 assert_eq!(count, 1);
2228 assert_eq!(clause, vec!["U: Foo<Case01>".to_string()]);
2229 }
2230
2231 #[test]
2232 fn rewrite_where_clause_substitutes_repeated_in_rhs() {
2233 let mut clause = vec!["U: Foo<T> + Bar<T>".to_string()];
2235 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2236 assert_eq!(count, 2);
2237 assert_eq!(clause, vec!["U: Foo<Case01> + Bar<Case01>".to_string()]);
2238 }
2239
2240 #[test]
2241 fn rewrite_where_clause_does_not_match_prefix_identifier_in_rhs() {
2242 let mut clause = vec!["U: Foo<Type>".to_string()];
2244 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2245 assert_eq!(count, 0);
2246 assert_eq!(clause, vec!["U: Foo<Type>".to_string()]);
2247 }
2248
2249 #[test]
2250 fn rewrite_where_clause_mixed_drop_and_substitute() {
2251 let mut clause = vec![
2254 "T: Clone".to_string(),
2255 "U: Foo<T>".to_string(),
2256 "V: Bar".to_string(),
2257 ];
2258 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2259 assert_eq!(count, 2);
2261 assert_eq!(
2262 clause,
2263 vec!["U: Foo<Case01>".to_string(), "V: Bar".to_string()]
2264 );
2265 }
2266
2267 #[test]
2268 fn rewrite_where_clause_leading_whitespace_lhs() {
2269 let mut clause = vec![" T: Clone".to_string()];
2272 let count = rewrite_where_clause_generic(&mut clause, "T", "Case01");
2273 assert_eq!(count, 1);
2274 assert!(clause.is_empty());
2275 }
2276
2277 #[test]
2278 fn rewrite_fn_generic_bound_with_where_clause() {
2279 let g = PureGenerics {
2283 params: vec![PureGenericParam::Type {
2284 name: "T".to_string(),
2285 bounds: vec!["Case01Trait".to_string()],
2286 }],
2287 where_clause: vec!["T: Clone".to_string()],
2288 };
2289 let mut f = mk_fn(
2290 "use_generic",
2291 g,
2292 vec![PureParam::Typed {
2293 name: "t".to_string(),
2294 ty: PureType::Path("T".to_string()),
2295 is_mut: false,
2296 pat: None,
2297 }],
2298 );
2299
2300 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
2301 assert_eq!(count, 3);
2304 assert!(f.generics.params.is_empty());
2305 assert!(f.generics.where_clause.is_empty());
2306 }
2307
2308 #[test]
2309 fn rewrite_fn_generic_bound_no_change_when_no_matching_bound() {
2310 let g = PureGenerics {
2311 params: vec![PureGenericParam::Type {
2312 name: "T".to_string(),
2313 bounds: vec!["OtherTrait".to_string()],
2314 }],
2315 where_clause: Vec::new(),
2316 };
2317 let mut f = mk_fn("use_unrelated", g, Vec::new());
2318 let count = rewrite_fn_generic_bound(&mut f, "Case01Trait", "Case01");
2319 assert_eq!(count, 0);
2320 assert_eq!(f.generics.params.len(), 1);
2321 }
2322
2323 #[test]
2324 fn classify_fn_combines_signature_and_body() {
2325 let g = PureGenerics {
2330 params: vec![PureGenericParam::Type {
2331 name: "T".to_string(),
2332 bounds: vec!["Case01Trait".to_string()],
2333 }],
2334 where_clause: Vec::new(),
2335 };
2336 let dyn_param = PureParam::Typed {
2337 name: "t".to_string(),
2338 ty: PureType::Ref {
2339 lifetime: None,
2340 is_mut: false,
2341 ty: Box::new(PureType::TraitObject(vec!["Case01Trait".to_string()])),
2342 },
2343 is_mut: false,
2344 pat: None,
2345 };
2346 let body = PureBlock {
2347 stmts: vec![
2348 PureStmt::Semi(PureExpr::Path("<Case01 as Case01Trait>::foo".to_string())),
2349 PureStmt::Expr(mk_method_call("t", "foo")),
2350 ],
2351 };
2352 let mut f = fn_with_body("use_all", body);
2353 f.generics = g;
2354 f.params = vec![dyn_param];
2355
2356 let methods = vec!["foo".to_string()];
2357 let out = classify_fn(&f, "Case01Trait", &methods);
2358 assert!(out.contains(&CallerPattern::GenericBound));
2359 assert!(out.contains(&CallerPattern::DynDispatch));
2360 assert!(out.contains(&CallerPattern::Dot));
2361 assert!(out.contains(&CallerPattern::Ufcs));
2362 assert_eq!(out.len(), 4);
2363 }
2364}