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, ModificationType};
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 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 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 if self.target_fn.is_some() {
58 continue; }
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 assert_eq!(result.result.changes, 0);
148 }
149}