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