ryo_executor/engine/impls/
redundant_closure.rs1use 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 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 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 if self.target_fn.is_some() {
53 continue; }
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 assert_eq!(result.result.changes, 0);
139 }
140}