Skip to main content

ryo_executor/engine/impls/
assign_op.rs

1//! V2 ASTRegApply implementation for AssignOpMutation
2//!
3//! Converts assignment patterns to compound operators:
4//! - `a = a + b` → `a += b`
5//! - `a = a - b` → `a -= b`
6//! - etc.
7
8use 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            // Target function specified: direct lookup
21            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            // No target: process all functions
30            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            // Process methods (from impl blocks)
50            // New design: methods are registered directly as SymbolKind::Method
51            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        // Find foo function's SymbolId
170        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}