ryo_executor/engine/impls/
clone_on_copy.rs1use ryo_analysis::SymbolKind;
7use ryo_mutations::idiom::CloneOnCopyMutation;
8use ryo_mutations::{Mutation, MutationResult};
9use ryo_source::pure::{PureImplItem, PureItem};
10
11use crate::engine::{ASTMutationContext, ASTRegApply, ModificationType};
12
13impl ASTRegApply for CloneOnCopyMutation {
14 fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
15 let mut total_changes = 0;
16
17 if let Some(target_id) = self.target_fn {
18 if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(target_id) {
23 let changes = self.transform_fn(f);
24 if changes > 0 {
25 ctx.emit_modified(target_id, ModificationType::BodyModified);
26 total_changes += changes;
27 }
28 }
29 } else {
30 let fn_ids: Vec<_> = ctx
32 .symbol_registry
33 .iter()
34 .filter(|(id, _)| {
35 matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
36 })
37 .map(|(id, _)| id)
38 .collect();
39
40 for id in fn_ids {
41 if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
42 let changes = self.transform_fn(f);
43 if changes > 0 {
44 ctx.emit_modified(id, ModificationType::BodyModified);
45 total_changes += changes;
46 }
47 }
48 }
49
50 let impl_ids: Vec<_> = ctx
52 .symbol_registry
53 .iter()
54 .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
55 .map(|(id, _)| id)
56 .collect();
57
58 for id in impl_ids {
59 let mut impl_changes = 0;
60 if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
61 for impl_item in &mut imp.items {
62 if let PureImplItem::Fn(f) = impl_item {
63 impl_changes += self.transform_fn(f);
64 }
65 }
66 }
67 if impl_changes > 0 {
68 ctx.emit_modified(id, ModificationType::BodyModified);
69 total_changes += impl_changes;
70 }
71 }
72 }
73
74 MutationResult {
75 mutation_type: self.mutation_type().to_string(),
76 changes: total_changes,
77 description: if total_changes > 0 {
78 format!("Removed {} unnecessary .clone() call(s)", total_changes)
79 } else {
80 "No .clone() calls removed".to_string()
81 },
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::engine::ASTMutationEngine;
90 use ryo_analysis::testing::ContextBuilder;
91
92 #[test]
93 fn test_v2_clone_on_copy_target_fn_scopes_to_target_only() {
94 let mut ctx = ContextBuilder::new()
98 .with_file(
99 "src/lib.rs",
100 r#"
101fn foo(x: i32) -> i32 {
102 x.clone()
103}
104
105fn bar(y: i32) -> i32 {
106 y.clone()
107}
108"#,
109 )
110 .build();
111
112 let foo_id = ctx
113 .registry
114 .iter()
115 .find(|(id, path)| {
116 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function)) && path.name() == "foo"
117 })
118 .map(|(id, _)| id)
119 .expect("foo function not found");
120
121 let mutation = CloneOnCopyMutation::new().in_function(foo_id);
122 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
123
124 assert_eq!(result.result.changes, 1, "target_fn must scope to foo only");
125 }
126
127 #[test]
128 fn test_v2_clone_on_copy_literal() {
129 let mut ctx = ContextBuilder::new()
130 .with_file(
131 "src/lib.rs",
132 r#"
133fn process() -> i32 {
134 let x = 42.clone();
135 x
136}
137"#,
138 )
139 .build();
140
141 let mutation = CloneOnCopyMutation::new();
142 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
143
144 assert_eq!(result.result.changes, 1);
145 }
146
147 #[test]
148 fn test_v2_clone_on_copy_param() {
149 let mut ctx = ContextBuilder::new()
150 .with_file(
151 "src/lib.rs",
152 r#"
153fn process(x: i32) -> i32 {
154 x.clone()
155}
156"#,
157 )
158 .build();
159
160 let mutation = CloneOnCopyMutation::new();
161 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
162
163 assert_eq!(result.result.changes, 1);
165 }
166
167 #[test]
168 fn test_v2_clone_on_copy_no_changes() {
169 let mut ctx = ContextBuilder::new()
170 .with_file(
171 "src/lib.rs",
172 r#"
173fn process(s: String) -> String {
174 s.clone()
175}
176"#,
177 )
178 .build();
179
180 let mutation = CloneOnCopyMutation::new();
181 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
182
183 assert_eq!(result.result.changes, 0);
185 }
186
187 #[test]
188 fn test_v2_clone_on_copy_aggressive() {
189 let mut ctx = ContextBuilder::new()
190 .with_file(
191 "src/lib.rs",
192 r#"
193fn process(s: String) -> String {
194 s.clone()
195}
196"#,
197 )
198 .build();
199
200 let mutation = CloneOnCopyMutation::new().aggressive();
201 let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
202
203 assert_eq!(result.result.changes, 1);
205 }
206}