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};
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                total_changes += self.transform_block(&mut f.body);
23            }
24        } else {
25            // No target: process all functions
26            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            // Process methods (from impl blocks)
42            // New design: methods are registered directly as SymbolKind::Method
43            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        // Find foo function's SymbolId
158        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}