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 by_ref: false,
202 },
203 )],
204 rest: false,
205 },
206 expr: Box::new(scrutinee),
207 };
208
209 PureExpr::If {
211 cond: Box::new(let_expr),
212 then_branch: then_block,
213 else_branch: None,
214 }
215 }
216
217 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
219 let mut changes = 0;
220
221 if let PureExpr::Match {
223 expr: scrutinee,
224 arms,
225 } = expr
226 {
227 if let Some(if_let) = Self::try_convert_match(scrutinee, arms) {
228 *expr = if_let;
229 return 1;
230 }
231 }
232
233 match expr {
235 PureExpr::Binary { left, right, .. } => {
236 changes += self.transform_expr(left);
237 changes += self.transform_expr(right);
238 }
239 PureExpr::Unary { expr: inner, .. } => {
240 changes += self.transform_expr(inner);
241 }
242 PureExpr::Call { func, args } => {
243 changes += self.transform_expr(func);
244 for arg in args {
245 changes += self.transform_expr(arg);
246 }
247 }
248 PureExpr::MethodCall { receiver, args, .. } => {
249 changes += self.transform_expr(receiver);
250 for arg in args {
251 changes += self.transform_expr(arg);
252 }
253 }
254 PureExpr::Block { block, .. } => {
255 changes += self.transform_block(block);
256 }
257 PureExpr::If {
258 cond,
259 then_branch,
260 else_branch,
261 } => {
262 changes += self.transform_expr(cond);
263 changes += self.transform_block(then_branch);
264 if let Some(else_expr) = else_branch {
265 changes += self.transform_expr(else_expr);
266 }
267 }
268 PureExpr::Match { expr: e, arms } => {
269 changes += self.transform_expr(e);
270 for arm in arms {
271 changes += self.transform_expr(&mut arm.body);
272 }
273 }
274 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
275 changes += self.transform_block(block);
276 }
277 PureExpr::For {
278 expr: iter_expr,
279 body,
280 ..
281 } => {
282 changes += self.transform_expr(iter_expr);
283 changes += self.transform_block(body);
284 }
285 PureExpr::Closure { body, .. } => {
286 changes += self.transform_expr(body);
287 }
288 _ => {}
289 }
290
291 changes
292 }
293
294 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
295 let mut changes = 0;
296 for stmt in &mut block.stmts {
297 changes += self.transform_stmt(stmt);
298 }
299 changes
300 }
301
302 fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
303 match stmt {
304 PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
305 PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
306 _ => 0,
307 }
308 }
309}
310
311impl Mutation for MatchToIfLetMutation {
312 fn describe(&self) -> String {
313 "Convert match to if let".to_string()
314 }
315
316 fn mutation_type(&self) -> &'static str {
317 "MatchToIfLet"
318 }
319
320 fn box_clone(&self) -> Box<dyn Mutation> {
321 Box::new(self.clone())
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_is_some_pattern() {
331 let pattern = PurePattern::Struct {
332 path: "Some".to_string(),
333 fields: vec![(
334 "0".to_string(),
335 PurePattern::Ident {
336 name: "x".to_string(),
337 is_mut: false,
338 by_ref: false,
339 },
340 )],
341 rest: false,
342 };
343 assert_eq!(
344 MatchToIfLetMutation::is_some_pattern(&pattern),
345 Some("x".to_string())
346 );
347 }
348
349 #[test]
350 fn test_is_none_pattern_ident() {
351 let pattern = PurePattern::Ident {
352 name: "None".to_string(),
353 is_mut: false,
354 by_ref: false,
355 };
356 assert!(MatchToIfLetMutation::is_none_pattern(&pattern));
357 }
358
359 #[test]
360 fn test_is_empty_body() {
361 let empty_block = PureExpr::Block {
362 label: None,
363 block: PureBlock { stmts: vec![] },
364 };
365 assert!(MatchToIfLetMutation::is_empty_body(&empty_block));
366 }
367}