use ryo_analysis::SymbolKind;
use ryo_mutations::idiom::AssignOpMutation;
use ryo_mutations::{Mutation, MutationResult};
use ryo_source::pure::PureItem;
use crate::engine::{ASTMutationContext, ASTRegApply};
impl ASTRegApply for AssignOpMutation {
fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
let mut total_changes = 0;
if let Some(target_id) = self.target_fn {
if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(target_id) {
total_changes += self.transform_block(&mut f.body);
}
} else {
let fn_ids: Vec<_> = ctx
.symbol_registry
.iter()
.filter(|(id, _)| {
matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
})
.map(|(id, _)| id)
.collect();
for id in fn_ids {
if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
total_changes += self.transform_block(&mut f.body);
}
}
let method_ids: Vec<_> = ctx
.symbol_registry
.iter()
.filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Method)))
.map(|(id, _)| id)
.collect();
for id in method_ids {
if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
total_changes += self.transform_block(&mut f.body);
}
}
}
MutationResult {
mutation_type: self.mutation_type().to_string(),
changes: total_changes,
description: if total_changes > 0 {
format!(
"Converted {} assignment(s) to compound operators",
total_changes
)
} else {
"No assignments converted".to_string()
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::ASTMutationEngine;
use ryo_analysis::testing::ContextBuilder;
#[test]
fn test_v2_assign_op_basic() {
let mut ctx = ContextBuilder::new()
.with_file(
"src/lib.rs",
r#"
fn increment(x: &mut i32) {
*x = *x + 1;
}
"#,
)
.build();
let mutation = AssignOpMutation::new();
let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
assert_eq!(result.result.changes, 1);
}
#[test]
fn test_v2_assign_op_multiple() {
let mut ctx = ContextBuilder::new()
.with_file(
"src/lib.rs",
r#"
fn math(a: &mut i32, b: &mut i32) {
*a = *a + 1;
*b = *b * 2;
}
"#,
)
.build();
let mutation = AssignOpMutation::new();
let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
assert_eq!(result.result.changes, 2);
}
#[test]
fn test_v2_assign_op_in_method() {
let mut ctx = ContextBuilder::new()
.with_file(
"src/lib.rs",
r#"
struct Counter { value: i32 }
impl Counter {
fn increment(&mut self) {
self.value = self.value + 1;
}
}
"#,
)
.build();
let mutation = AssignOpMutation::new();
let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
assert_eq!(result.result.changes, 1);
}
#[test]
fn test_v2_assign_op_target_fn() {
let mut ctx = ContextBuilder::new()
.with_file(
"src/lib.rs",
r#"
fn foo(x: &mut i32) {
*x = *x + 1;
}
fn bar(x: &mut i32) {
*x = *x + 2;
}
"#,
)
.build();
let foo_id = ctx
.registry
.iter()
.find(|(id, path)| {
matches!(ctx.registry.kind(*id), Some(SymbolKind::Function)) && path.name() == "foo"
})
.map(|(id, _)| id)
.expect("foo function not found");
let mutation = AssignOpMutation::new().in_function(foo_id);
let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
assert_eq!(result.result.changes, 1);
}
#[test]
fn test_v2_assign_op_no_changes() {
let mut ctx = ContextBuilder::new()
.with_file(
"src/lib.rs",
r#"
fn already_compound(x: &mut i32) {
*x += 1;
}
"#,
)
.build();
let mutation = AssignOpMutation::new();
let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
assert_eq!(result.result.changes, 0);
}
}