Skip to main content

ryo_executor/executor/registry/converters/
idiom.rs

1//! IdiomConverter: Converts idiom transformation MutationSpecs to Mutations
2//!
3//! Handles 16 idiom transformation variants:
4//! - OrganizeImports: Sort, dedupe, merge imports
5//! - LoopToIterator: Convert for loops to iterator chains
6//! - UnwrapToQuestion: Convert unwrap/expect to ? operator
7//! - AssignOp: Simplify `a = a + b` to `a += b`
8//! - BoolSimplify: Simplify `x == true` to `x`
9//! - CloneOnCopy: Remove redundant .clone() on Copy types
10//! - CollapsibleIf: Merge nested if statements
11//! - ComparisonToMethod: Convert `s == ""` to `s.is_empty()`
12//! - RedundantClosure: Remove redundant closures `|x| f(x)` to `f`
13//! - IntroduceVariable: Extract repeated expressions to variables
14//! - ManualMap: Convert manual match to .map()
15//! - MatchToIfLet: Convert simple match to if let
16//! - FilterNext: Convert .filter().next() to .find()
17//! - MapUnwrapOr: Convert .map().unwrap_or() to .map_or()
18//! - NoOpArmToTodo: Replace `_ => {}` with `_ => todo!()`
19
20use crate::engine::ASTRegApply;
21use crate::executor::registry::converter::{ConvertError, MutationConverter};
22use crate::executor::registry::converters::ResolveTargetSymbol;
23use crate::executor::spec::MutationSpec;
24use ryo_analysis::AnalysisContext;
25use ryo_mutations::{
26    AssignOpMutation, BoolSimplifyMutation, CloneOnCopyMutation, CollapsibleIfMutation,
27    ComparisonToMethodMutation, FilterNextMutation, IntroduceVariableMutation,
28    LoopToIteratorMutation, ManualMapMutation, MapUnwrapOrMutation, MatchToIfLetMutation,
29    NoOpArmToTodoMutation, OrganizeImportsMutation, RedundantClosureMutation,
30    UnwrapToQuestionMutation,
31};
32use ryo_source::pure::ToPure;
33
34/// Converter for idiom transformation MutationSpecs
35pub struct IdiomConverter;
36
37impl IdiomConverter {
38    pub fn new() -> Self {
39        Self
40    }
41}
42
43impl Default for IdiomConverter {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49// IdiomConverter uses the default implementation of ResolveTargetSymbol
50impl ResolveTargetSymbol for IdiomConverter {}
51
52impl MutationConverter for IdiomConverter {
53    fn spec_kinds(&self) -> &'static [&'static str] {
54        &[
55            "OrganizeImports",
56            "LoopToIterator",
57            "UnwrapToQuestion",
58            "AssignOp",
59            "BoolSimplify",
60            "CloneOnCopy",
61            "CollapsibleIf",
62            "ComparisonToMethod",
63            "RedundantClosure",
64            "IntroduceVariable",
65            "ManualMap",
66            "MatchToIfLet",
67            "FilterNext",
68            "MapUnwrapOr",
69            "NoOpArmToTodo",
70        ]
71    }
72
73    fn convert_v2(
74        &self,
75        spec: &MutationSpec,
76        _ctx: &AnalysisContext,
77    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
78        match spec {
79            MutationSpec::OrganizeImports {
80                module_id,
81                deduplicate,
82                merge_groups,
83            } => {
84                let mut mutation = OrganizeImportsMutation::new()
85                    .with_deduplicate(*deduplicate)
86                    .with_merge_groups(*merge_groups);
87                if let Some(id) = module_id {
88                    mutation = mutation.in_module(*id);
89                }
90                Ok(vec![Box::new(mutation)])
91            }
92
93            MutationSpec::LoopToIterator {
94                module_id,
95                target_var,
96            } => {
97                let mut mutation = LoopToIteratorMutation::new();
98                if let Some(var) = target_var {
99                    mutation = mutation.with_target(var);
100                }
101                if let Some(id) = module_id {
102                    mutation = mutation.in_function(*id);
103                }
104                Ok(vec![Box::new(mutation)])
105            }
106
107            MutationSpec::UnwrapToQuestion {
108                target_fn,
109                include_expect,
110                ..
111            } => {
112                let mut mutation = UnwrapToQuestionMutation::new();
113                if !include_expect {
114                    mutation = mutation.unwrap_only();
115                }
116                if let Some(fn_id) = target_fn {
117                    mutation = mutation.in_function(*fn_id);
118                }
119                Ok(vec![Box::new(mutation)])
120            }
121
122            MutationSpec::AssignOp { fn_id, .. } => {
123                let mut mutation = AssignOpMutation::new();
124                if let Some(id) = fn_id {
125                    mutation = mutation.in_function(*id);
126                }
127                Ok(vec![Box::new(mutation)])
128            }
129
130            MutationSpec::BoolSimplify { module_id } => {
131                let mut m = BoolSimplifyMutation::new();
132                if let Some(id) = module_id {
133                    m = m.in_function(*id);
134                }
135                Ok(vec![Box::new(m)])
136            }
137
138            MutationSpec::CloneOnCopy { module_id } => {
139                let mut m = CloneOnCopyMutation::new();
140                if let Some(id) = module_id {
141                    m = m.in_function(*id);
142                }
143                Ok(vec![Box::new(m)])
144            }
145
146            MutationSpec::CollapsibleIf { module_id } => {
147                let mut m = CollapsibleIfMutation::new();
148                if let Some(id) = module_id {
149                    m = m.in_function(*id);
150                }
151                Ok(vec![Box::new(m)])
152            }
153
154            MutationSpec::ComparisonToMethod { module_id } => {
155                let mut m = ComparisonToMethodMutation::new();
156                if let Some(id) = module_id {
157                    m = m.in_function(*id);
158                }
159                Ok(vec![Box::new(m)])
160            }
161
162            MutationSpec::RedundantClosure { module_id } => {
163                let mut m = RedundantClosureMutation::new();
164                if let Some(id) = module_id {
165                    m = m.in_function(*id);
166                }
167                Ok(vec![Box::new(m)])
168            }
169
170            MutationSpec::IntroduceVariable { expr, var_name, .. } => {
171                let syn_expr: syn::Expr = syn::parse_str(expr).map_err(|e| {
172                    ConvertError::Parse(format!("Failed to parse expression '{}': {}", expr, e))
173                })?;
174                let pure_expr = syn_expr.to_pure();
175                Ok(vec![Box::new(IntroduceVariableMutation::new(
176                    pure_expr, var_name,
177                ))])
178            }
179
180            MutationSpec::ManualMap { module_id } => {
181                let mut m = ManualMapMutation::new();
182                if let Some(id) = module_id {
183                    m = m.in_function(*id);
184                }
185                Ok(vec![Box::new(m)])
186            }
187
188            MutationSpec::MatchToIfLet { module_id } => {
189                let mut m = MatchToIfLetMutation::new();
190                if let Some(id) = module_id {
191                    m = m.in_function(*id);
192                }
193                Ok(vec![Box::new(m)])
194            }
195
196            MutationSpec::FilterNext { fn_id, .. } => {
197                let mut m = FilterNextMutation::new();
198                if let Some(id) = fn_id {
199                    m = m.in_function(*id);
200                }
201                Ok(vec![Box::new(m)])
202            }
203
204            MutationSpec::MapUnwrapOr { fn_id, .. } => {
205                let mut m = MapUnwrapOrMutation::new();
206                if let Some(id) = fn_id {
207                    m = m.in_function(*id);
208                }
209                Ok(vec![Box::new(m)])
210            }
211
212            MutationSpec::NoOpArmToTodo {
213                module_id,
214                replacement,
215            } => {
216                let mut m = NoOpArmToTodoMutation::new().with_replacement(replacement);
217                if let Some(id) = module_id {
218                    m = m.in_function(*id);
219                }
220                Ok(vec![Box::new(m)])
221            }
222
223            _ => Err(ConvertError::TypeMismatch {
224                expected: "Idiom transformation",
225                actual: spec.kind_name().to_string(),
226            }),
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_idiom_converter_spec_kinds() {
237        let converter = IdiomConverter::new();
238        assert_eq!(converter.spec_kinds().len(), 15); // Was 16, now 15 (MergeImplBlocks removed)
239        assert!(converter.spec_kinds().contains(&"OrganizeImports"));
240        assert!(converter.spec_kinds().contains(&"LoopToIterator"));
241        assert!(converter.spec_kinds().contains(&"MatchToIfLet"));
242        assert!(converter.spec_kinds().contains(&"NoOpArmToTodo"));
243    }
244}