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};
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                total_changes += self.transform_block(&mut f.body);
34            }
35        }
36
37        // Process impl blocks (methods)
38        let impl_ids: Vec<_> = ctx
39            .symbol_registry
40            .iter()
41            .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
42            .map(|(id, _)| id)
43            .collect();
44
45        for id in impl_ids {
46            if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
47                for impl_item in &mut imp.items {
48                    if let PureImplItem::Fn(f) = impl_item {
49                        // Note: target_fn filtering not implemented for impl methods
50                        // as we don't have direct SymbolId access for individual methods
51                        // TODO: Add method SymbolId resolution for proper targeting
52                        if self.target_fn.is_some() {
53                            continue; // Skip impl methods when target_fn is specified
54                        }
55                        total_changes += self.transform_block(&mut f.body);
56                    }
57                }
58            }
59        }
60
61        MutationResult {
62            mutation_type: self.mutation_type().to_string(),
63            changes: total_changes,
64            description: if total_changes > 0 {
65                format!("Simplified {} redundant closure(s)", total_changes)
66            } else {
67                "No redundant closures simplified".to_string()
68            },
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::engine::ASTMutationEngine;
77    use ryo_analysis::testing::ContextBuilder;
78
79    #[test]
80    fn test_v2_redundant_closure_single_param() {
81        let mut ctx = ContextBuilder::new()
82            .with_file(
83                "src/lib.rs",
84                r#"
85fn process(items: Vec<i32>) -> Vec<i32> {
86    items.into_iter().map(|x| foo(x)).collect()
87}
88
89fn foo(x: i32) -> i32 { x + 1 }
90"#,
91            )
92            .build();
93
94        let mutation = RedundantClosureMutation::new();
95        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
96
97        assert_eq!(result.result.changes, 1);
98    }
99
100    #[test]
101    fn test_v2_redundant_closure_no_changes() {
102        let mut ctx = ContextBuilder::new()
103            .with_file(
104                "src/lib.rs",
105                r#"
106fn process(items: Vec<i32>) -> Vec<i32> {
107    items.into_iter().map(foo).collect()
108}
109
110fn foo(x: i32) -> i32 { x + 1 }
111"#,
112            )
113            .build();
114
115        let mutation = RedundantClosureMutation::new();
116        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
117
118        assert_eq!(result.result.changes, 0);
119    }
120
121    #[test]
122    fn test_v2_redundant_closure_not_redundant() {
123        let mut ctx = ContextBuilder::new()
124            .with_file(
125                "src/lib.rs",
126                r#"
127fn process(items: Vec<i32>) -> Vec<i32> {
128    items.into_iter().map(|x| x + 1).collect()
129}
130"#,
131            )
132            .build();
133
134        let mutation = RedundantClosureMutation::new();
135        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
136
137        // |x| x + 1 is not a redundant closure (not a simple function call)
138        assert_eq!(result.result.changes, 0);
139    }
140}