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                deduplicate,
81                merge_groups,
82                ..
83            } => {
84                let mutation = OrganizeImportsMutation::new()
85                    .with_deduplicate(*deduplicate)
86                    .with_merge_groups(*merge_groups);
87                Ok(vec![Box::new(mutation)])
88            }
89
90            MutationSpec::LoopToIterator { target_var, .. } => {
91                let mut mutation = LoopToIteratorMutation::new();
92                if let Some(var) = target_var {
93                    mutation = mutation.with_target(var);
94                }
95                Ok(vec![Box::new(mutation)])
96            }
97
98            MutationSpec::UnwrapToQuestion {
99                target_fn,
100                include_expect,
101                ..
102            } => {
103                let mut mutation = UnwrapToQuestionMutation::new();
104                if !include_expect {
105                    mutation = mutation.unwrap_only();
106                }
107                if let Some(fn_id) = target_fn {
108                    mutation = mutation.in_function(*fn_id);
109                }
110                Ok(vec![Box::new(mutation)])
111            }
112
113            MutationSpec::AssignOp { fn_id, .. } => {
114                let mut mutation = AssignOpMutation::new();
115                if let Some(id) = fn_id {
116                    mutation = mutation.in_function(*id);
117                }
118                Ok(vec![Box::new(mutation)])
119            }
120
121            MutationSpec::BoolSimplify { .. } => Ok(vec![Box::new(BoolSimplifyMutation::new())]),
122
123            MutationSpec::CloneOnCopy { .. } => Ok(vec![Box::new(CloneOnCopyMutation::new())]),
124
125            MutationSpec::CollapsibleIf { .. } => Ok(vec![Box::new(CollapsibleIfMutation::new())]),
126
127            MutationSpec::ComparisonToMethod { .. } => {
128                Ok(vec![Box::new(ComparisonToMethodMutation::new())])
129            }
130
131            MutationSpec::RedundantClosure { .. } => {
132                Ok(vec![Box::new(RedundantClosureMutation::new())])
133            }
134
135            MutationSpec::IntroduceVariable { expr, var_name, .. } => {
136                let syn_expr: syn::Expr = syn::parse_str(expr).map_err(|e| {
137                    ConvertError::Parse(format!("Failed to parse expression '{}': {}", expr, e))
138                })?;
139                let pure_expr = syn_expr.to_pure();
140                Ok(vec![Box::new(IntroduceVariableMutation::new(
141                    pure_expr, var_name,
142                ))])
143            }
144
145            MutationSpec::ManualMap { .. } => Ok(vec![Box::new(ManualMapMutation::new())]),
146
147            MutationSpec::MatchToIfLet { .. } => Ok(vec![Box::new(MatchToIfLetMutation::new())]),
148
149            MutationSpec::FilterNext { fn_id, .. } => {
150                let mut m = FilterNextMutation::new();
151                if let Some(id) = fn_id {
152                    m = m.in_function(*id);
153                }
154                Ok(vec![Box::new(m)])
155            }
156
157            MutationSpec::MapUnwrapOr { fn_id, .. } => {
158                let mut m = MapUnwrapOrMutation::new();
159                if let Some(id) = fn_id {
160                    m = m.in_function(*id);
161                }
162                Ok(vec![Box::new(m)])
163            }
164
165            MutationSpec::NoOpArmToTodo {
166                module_id,
167                replacement,
168            } => {
169                let mut m = NoOpArmToTodoMutation::new().with_replacement(replacement);
170                if let Some(id) = module_id {
171                    m = m.in_function(*id);
172                }
173                Ok(vec![Box::new(m)])
174            }
175
176            _ => Err(ConvertError::TypeMismatch {
177                expected: "Idiom transformation",
178                actual: spec.kind_name().to_string(),
179            }),
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_idiom_converter_spec_kinds() {
190        let converter = IdiomConverter::new();
191        assert_eq!(converter.spec_kinds().len(), 15); // Was 16, now 15 (MergeImplBlocks removed)
192        assert!(converter.spec_kinds().contains(&"OrganizeImports"));
193        assert!(converter.spec_kinds().contains(&"LoopToIterator"));
194        assert!(converter.spec_kinds().contains(&"MatchToIfLet"));
195        assert!(converter.spec_kinds().contains(&"NoOpArmToTodo"));
196    }
197}