Skip to main content

ryo_executor/engine/impls/
redundant_closure.rs

1//! V2 ASTRegApply implementation for RedundantClosureMutation
2//!
3//! Simplifies redundant closures to function references:
4//! - `|x| foo(x)` → `foo`
5//! - `|a, b| func(a, b)` → `func`
6
7use ryo_analysis::SymbolKind;
8use ryo_mutations::idiom::RedundantClosureMutation;
9use ryo_mutations::{Mutation, MutationResult};
10use ryo_source::pure::{PureImplItem, PureItem};
11
12use crate::engine::{ASTMutationContext, ASTRegApply, ModificationType};
13
14impl ASTRegApply for RedundantClosureMutation {
15    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
16        let mut total_changes = 0;
17
18        // Process standalone functions
19        let fn_ids: Vec<_> = ctx
20            .symbol_registry
21            .iter()
22            .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function)))
23            .map(|(id, _)| id)
24            .collect();
25
26        for id in fn_ids {
27            if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
28                if let Some(target) = self.target_fn {
29                    if id != target {
30                        continue;
31                    }
32                }
33                let changes = self.transform_block(&mut f.body);
34                if changes > 0 {
35                    ctx.emit_modified(id, ModificationType::BodyModified);
36                    total_changes += changes;
37                }
38            }
39        }
40
41        // Process impl blocks (methods)
42        let impl_ids: Vec<_> = ctx
43            .symbol_registry
44            .iter()
45            .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
46            .map(|(id, _)| id)
47            .collect();
48
49        for id in impl_ids {
50            let mut impl_changes = 0;
51            if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
52                for impl_item in &mut imp.items {
53                    if let PureImplItem::Fn(f) = impl_item {
54                        // Note: target_fn filtering not implemented for impl methods
55                        // as we don't have direct SymbolId access for individual methods
56                        // TODO: Add method SymbolId resolution for proper targeting
57                        if self.target_fn.is_some() {
58                            continue; // Skip impl methods when target_fn is specified
59                        }
60                        impl_changes += self.transform_block(&mut f.body);
61                    }
62                }
63            }
64            if impl_changes > 0 {
65                ctx.emit_modified(id, ModificationType::BodyModified);
66                total_changes += impl_changes;
67            }
68        }
69
70        MutationResult {
71            mutation_type: self.mutation_type().to_string(),
72            changes: total_changes,
73            description: if total_changes > 0 {
74                format!("Simplified {} redundant closure(s)", total_changes)
75            } else {
76                "No redundant closures simplified".to_string()
77            },
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use crate::engine::ASTMutationEngine;
86    use ryo_analysis::testing::ContextBuilder;
87
88    #[test]
89    fn test_v2_redundant_closure_single_param() {
90        let mut ctx = ContextBuilder::new()
91            .with_file(
92                "src/lib.rs",
93                r#"
94fn process(items: Vec<i32>) -> Vec<i32> {
95    items.into_iter().map(|x| foo(x)).collect()
96}
97
98fn foo(x: i32) -> i32 { x + 1 }
99"#,
100            )
101            .build();
102
103        let mutation = RedundantClosureMutation::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_redundant_closure_no_changes() {
111        let mut ctx = ContextBuilder::new()
112            .with_file(
113                "src/lib.rs",
114                r#"
115fn process(items: Vec<i32>) -> Vec<i32> {
116    items.into_iter().map(foo).collect()
117}
118
119fn foo(x: i32) -> i32 { x + 1 }
120"#,
121            )
122            .build();
123
124        let mutation = RedundantClosureMutation::new();
125        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
126
127        assert_eq!(result.result.changes, 0);
128    }
129
130    #[test]
131    fn test_v2_redundant_closure_not_redundant() {
132        let mut ctx = ContextBuilder::new()
133            .with_file(
134                "src/lib.rs",
135                r#"
136fn process(items: Vec<i32>) -> Vec<i32> {
137    items.into_iter().map(|x| x + 1).collect()
138}
139"#,
140            )
141            .build();
142
143        let mutation = RedundantClosureMutation::new();
144        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
145
146        // |x| x + 1 is not a redundant closure (not a simple function call)
147        assert_eq!(result.result.changes, 0);
148    }
149}