1use ryo_source::pure::{PureBlock, PureExpr, PureMatchArm, PurePattern, PureStmt};
10use ryo_symbol::SymbolId;
11
12use crate::Mutation;
13
14#[derive(Debug, Clone, Default)]
33pub struct MatchToIfLetMutation {
34 pub target_fn: Option<SymbolId>,
36}
37
38impl MatchToIfLetMutation {
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 pub fn in_function(mut self, id: SymbolId) -> Self {
44 self.target_fn = Some(id);
45 self
46 }
47
48 fn is_some_pattern(pattern: &PurePattern) -> Option<String> {
50 match pattern {
51 PurePattern::Struct { path, fields, .. } => {
52 if (path == "Some" || path.ends_with("::Some")) && fields.len() == 1 {
53 if let Some((_, PurePattern::Ident { name, .. })) = fields.first() {
54 return Some(name.clone());
55 }
56 }
57 None
58 }
59 _ => None,
60 }
61 }
62
63 fn is_none_pattern(pattern: &PurePattern) -> bool {
65 match pattern {
66 PurePattern::Path(p) => p == "None" || p.ends_with("::None"),
67 PurePattern::Ident { name, .. } => name == "None",
68 _ => false,
69 }
70 }
71
72 fn is_ok_pattern(pattern: &PurePattern) -> Option<String> {
74 match pattern {
75 PurePattern::Struct { path, fields, .. } => {
76 if (path == "Ok" || path.ends_with("::Ok")) && fields.len() == 1 {
77 if let Some((_, PurePattern::Ident { name, .. })) = fields.first() {
78 return Some(name.clone());
79 }
80 }
81 None
82 }
83 _ => None,
84 }
85 }
86
87 fn is_err_pattern(pattern: &PurePattern) -> bool {
89 match pattern {
90 PurePattern::Struct { path, fields, .. } => {
91 if (path == "Err" || path.ends_with("::Err")) && fields.len() == 1 {
92 return true;
94 }
95 false
96 }
97 _ => false,
98 }
99 }
100
101 fn is_empty_body(expr: &PureExpr) -> bool {
103 match expr {
104 PureExpr::Block { block, .. } => block.stmts.is_empty(),
105 PureExpr::Tuple(elems) if elems.is_empty() => true, PureExpr::Path(p) if p == "()" => true,
107 _ => false,
108 }
109 }
110
111 fn try_convert_match(scrutinee: &PureExpr, arms: &[PureMatchArm]) -> Option<PureExpr> {
113 if arms.len() != 2 {
114 return None;
115 }
116
117 if let Some(var_name) = Self::is_some_pattern(&arms[0].pattern) {
119 if Self::is_none_pattern(&arms[1].pattern) && Self::is_empty_body(&arms[1].body) {
120 return Some(Self::create_if_let(
121 scrutinee.clone(),
122 "Some".to_string(),
123 var_name,
124 arms[0].body.clone(),
125 ));
126 }
127 }
128
129 if Self::is_none_pattern(&arms[0].pattern) && Self::is_empty_body(&arms[0].body) {
131 if let Some(var_name) = Self::is_some_pattern(&arms[1].pattern) {
132 return Some(Self::create_if_let(
133 scrutinee.clone(),
134 "Some".to_string(),
135 var_name,
136 arms[1].body.clone(),
137 ));
138 }
139 }
140
141 if let Some(var_name) = Self::is_ok_pattern(&arms[0].pattern) {
143 if Self::is_err_pattern(&arms[1].pattern) && Self::is_empty_body(&arms[1].body) {
144 return Some(Self::create_if_let(
145 scrutinee.clone(),
146 "Ok".to_string(),
147 var_name,
148 arms[0].body.clone(),
149 ));
150 }
151 }
152
153 if Self::is_err_pattern(&arms[0].pattern) && Self::is_empty_body(&arms[0].body) {
155 if let Some(var_name) = Self::is_ok_pattern(&arms[1].pattern) {
156 return Some(Self::create_if_let(
157 scrutinee.clone(),
158 "Ok".to_string(),
159 var_name,
160 arms[1].body.clone(),
161 ));
162 }
163 }
164
165 None
166 }
167
168 fn create_if_let(
179 scrutinee: PureExpr,
180 variant: String,
181 var_name: String,
182 body: PureExpr,
183 ) -> PureExpr {
184 let then_block = match body {
186 PureExpr::Block { block, .. } => block,
187 other => PureBlock {
188 stmts: vec![PureStmt::Expr(other)],
189 },
190 };
191
192 let let_expr = PureExpr::Let {
194 pattern: PurePattern::Struct {
195 path: variant,
196 fields: vec![(
197 "0".to_string(),
198 PurePattern::Ident {
199 name: var_name,
200 is_mut: false,
201 },
202 )],
203 rest: false,
204 },
205 expr: Box::new(scrutinee),
206 };
207
208 PureExpr::If {
210 cond: Box::new(let_expr),
211 then_branch: then_block,
212 else_branch: None,
213 }
214 }
215
216 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
218 let mut changes = 0;
219
220 if let PureExpr::Match {
222 expr: scrutinee,
223 arms,
224 } = expr
225 {
226 if let Some(if_let) = Self::try_convert_match(scrutinee, arms) {
227 *expr = if_let;
228 return 1;
229 }
230 }
231
232 match expr {
234 PureExpr::Binary { left, right, .. } => {
235 changes += self.transform_expr(left);
236 changes += self.transform_expr(right);
237 }
238 PureExpr::Unary { expr: inner, .. } => {
239 changes += self.transform_expr(inner);
240 }
241 PureExpr::Call { func, args } => {
242 changes += self.transform_expr(func);
243 for arg in args {
244 changes += self.transform_expr(arg);
245 }
246 }
247 PureExpr::MethodCall { receiver, args, .. } => {
248 changes += self.transform_expr(receiver);
249 for arg in args {
250 changes += self.transform_expr(arg);
251 }
252 }
253 PureExpr::Block { block, .. } => {
254 changes += self.transform_block(block);
255 }
256 PureExpr::If {
257 cond,
258 then_branch,
259 else_branch,
260 } => {
261 changes += self.transform_expr(cond);
262 changes += self.transform_block(then_branch);
263 if let Some(else_expr) = else_branch {
264 changes += self.transform_expr(else_expr);
265 }
266 }
267 PureExpr::Match { expr: e, arms } => {
268 changes += self.transform_expr(e);
269 for arm in arms {
270 changes += self.transform_expr(&mut arm.body);
271 }
272 }
273 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
274 changes += self.transform_block(block);
275 }
276 PureExpr::For {
277 expr: iter_expr,
278 body,
279 ..
280 } => {
281 changes += self.transform_expr(iter_expr);
282 changes += self.transform_block(body);
283 }
284 PureExpr::Closure { body, .. } => {
285 changes += self.transform_expr(body);
286 }
287 _ => {}
288 }
289
290 changes
291 }
292
293 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
294 let mut changes = 0;
295 for stmt in &mut block.stmts {
296 changes += self.transform_stmt(stmt);
297 }
298 changes
299 }
300
301 fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
302 match stmt {
303 PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
304 PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
305 _ => 0,
306 }
307 }
308}
309
310impl Mutation for MatchToIfLetMutation {
311 fn describe(&self) -> String {
312 "Convert match to if let".to_string()
313 }
314
315 fn mutation_type(&self) -> &'static str {
316 "MatchToIfLet"
317 }
318
319 fn box_clone(&self) -> Box<dyn Mutation> {
320 Box::new(self.clone())
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_is_some_pattern() {
330 let pattern = PurePattern::Struct {
331 path: "Some".to_string(),
332 fields: vec![(
333 "0".to_string(),
334 PurePattern::Ident {
335 name: "x".to_string(),
336 is_mut: false,
337 },
338 )],
339 rest: false,
340 };
341 assert_eq!(
342 MatchToIfLetMutation::is_some_pattern(&pattern),
343 Some("x".to_string())
344 );
345 }
346
347 #[test]
348 fn test_is_none_pattern_ident() {
349 let pattern = PurePattern::Ident {
350 name: "None".to_string(),
351 is_mut: false,
352 };
353 assert!(MatchToIfLetMutation::is_none_pattern(&pattern));
354 }
355
356 #[test]
357 fn test_is_empty_body() {
358 let empty_block = PureExpr::Block {
359 label: None,
360 block: PureBlock { stmts: vec![] },
361 };
362 assert!(MatchToIfLetMutation::is_empty_body(&empty_block));
363 }
364}