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