Skip to main content

ryo_executor/engine/impls/
comparison_to_method.rs

1//! V2 ASTRegApply implementation for ComparisonToMethodMutation
2//!
3//! Converts comparisons to idiomatic method calls:
4//! - `s == ""` → `s.is_empty()`
5//! - `s != ""` → `!s.is_empty()`
6//! - `v.len() == 0` → `v.is_empty()`
7//! - `v.len() != 0` → `!v.is_empty()`
8
9use ryo_analysis::SymbolKind;
10use ryo_mutations::idiom::ComparisonToMethodMutation;
11use ryo_mutations::{Mutation, MutationResult};
12use ryo_source::pure::{PureImplItem, PureItem};
13
14use crate::engine::{ASTMutationContext, ASTRegApply, ModificationType};
15
16impl ASTRegApply for ComparisonToMethodMutation {
17    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
18        let mut total_changes = 0;
19
20        if let Some(target_id) = self.target_fn {
21            // Target function specified: direct lookup
22            if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(target_id) {
23                let changes = self.transform_block(&mut f.body);
24                if changes > 0 {
25                    ctx.emit_modified(target_id, ModificationType::BodyModified);
26                    total_changes += changes;
27                }
28            }
29        } else {
30            // No target: process all functions
31            let fn_ids: Vec<_> = ctx
32                .symbol_registry
33                .iter()
34                .filter(|(id, _)| {
35                    matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
36                })
37                .map(|(id, _)| id)
38                .collect();
39
40            for id in fn_ids {
41                if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
42                    let changes = self.transform_block(&mut f.body);
43                    if changes > 0 {
44                        ctx.emit_modified(id, ModificationType::BodyModified);
45                        total_changes += changes;
46                    }
47                }
48            }
49
50            // Process impl blocks (methods)
51            let impl_ids: Vec<_> = ctx
52                .symbol_registry
53                .iter()
54                .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
55                .map(|(id, _)| id)
56                .collect();
57
58            for id in impl_ids {
59                let mut impl_changes = 0;
60                if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
61                    for impl_item in &mut imp.items {
62                        if let PureImplItem::Fn(f) = impl_item {
63                            impl_changes += self.transform_block(&mut f.body);
64                        }
65                    }
66                }
67                if impl_changes > 0 {
68                    ctx.emit_modified(id, ModificationType::BodyModified);
69                    total_changes += impl_changes;
70                }
71            }
72        }
73
74        MutationResult {
75            mutation_type: self.mutation_type().to_string(),
76            changes: total_changes,
77            description: if total_changes > 0 {
78                format!("Converted {} comparison(s) to method calls", total_changes)
79            } else {
80                "No comparisons converted".to_string()
81            },
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use crate::engine::ASTMutationEngine;
90    use ryo_analysis::testing::ContextBuilder;
91
92    #[test]
93    fn test_v2_comparison_empty_string() {
94        let mut ctx = ContextBuilder::new()
95            .with_file(
96                "src/lib.rs",
97                r#"
98fn check(s: &str) -> bool {
99    s == ""
100}
101"#,
102            )
103            .build();
104
105        let mutation = ComparisonToMethodMutation::new();
106        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
107
108        assert_eq!(result.result.changes, 1);
109    }
110
111    #[test]
112    fn test_v2_comparison_len_zero() {
113        let mut ctx = ContextBuilder::new()
114            .with_file(
115                "src/lib.rs",
116                r#"
117fn check(v: &Vec<i32>) -> bool {
118    v.len() == 0
119}
120"#,
121            )
122            .build();
123
124        let mutation = ComparisonToMethodMutation::new();
125        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
126
127        assert_eq!(result.result.changes, 1);
128    }
129
130    #[test]
131    fn test_v2_comparison_no_changes() {
132        let mut ctx = ContextBuilder::new()
133            .with_file(
134                "src/lib.rs",
135                r#"
136fn check(s: &str) -> bool {
137    s.is_empty()
138}
139"#,
140            )
141            .build();
142
143        let mutation = ComparisonToMethodMutation::new();
144        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
145
146        assert_eq!(result.result.changes, 0);
147    }
148}