1use ryo_source::pure::{PureBlock, PureExpr, PureStmt};
10use ryo_symbol::SymbolId;
11
12use crate::Mutation;
13
14#[derive(Debug, Clone, Default)]
26pub struct MapUnwrapOrMutation {
27 pub target_fn: Option<SymbolId>,
29}
30
31impl MapUnwrapOrMutation {
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn in_function(mut self, id: SymbolId) -> Self {
38 self.target_fn = Some(id);
39 self
40 }
41
42 fn is_map_unwrap_or_pattern(expr: &PureExpr) -> Option<(PureExpr, PureExpr, PureExpr, bool)> {
45 if let PureExpr::MethodCall {
46 receiver,
47 method,
48 args,
49 ..
50 } = expr
51 {
52 let (is_lazy, default_arg) = if method == "unwrap_or" && args.len() == 1 {
54 (false, &args[0])
55 } else if method == "unwrap_or_else" && args.len() == 1 {
56 (true, &args[0])
57 } else {
58 return None;
59 };
60
61 if let PureExpr::MethodCall {
63 receiver: map_receiver,
64 method: map_method,
65 args: map_args,
66 ..
67 } = receiver.as_ref()
68 {
69 if map_method == "map" && map_args.len() == 1 {
70 return Some((
71 map_receiver.as_ref().clone(),
72 map_args[0].clone(),
73 default_arg.clone(),
74 is_lazy,
75 ));
76 }
77 }
78 }
79 None
80 }
81
82 fn is_map_or_none_pattern(expr: &PureExpr) -> Option<(PureExpr, PureExpr)> {
85 if let PureExpr::MethodCall {
86 receiver,
87 method,
88 args,
89 ..
90 } = expr
91 {
92 if method == "unwrap_or"
94 && args.len() == 1
95 && matches!(&args[0], PureExpr::Path(p) if p == "None")
96 {
97 if let PureExpr::MethodCall {
99 receiver: map_receiver,
100 method: map_method,
101 args: map_args,
102 ..
103 } = receiver.as_ref()
104 {
105 if map_method == "map" && map_args.len() == 1 {
106 return Some((map_receiver.as_ref().clone(), map_args[0].clone()));
107 }
108 }
109 }
110 }
111 None
112 }
113
114 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
116 let mut changes = 0;
117
118 if let Some((receiver, map_fn)) = Self::is_map_or_none_pattern(expr) {
120 *expr = PureExpr::MethodCall {
121 receiver: Box::new(receiver),
122 method: "and_then".to_string(),
123 turbofish: None,
124 args: vec![map_fn],
125 };
126 return 1;
127 }
128
129 if let Some((receiver, map_fn, default_value, is_lazy)) =
131 Self::is_map_unwrap_or_pattern(expr)
132 {
133 let method = if is_lazy { "map_or_else" } else { "map_or" };
134 *expr = PureExpr::MethodCall {
135 receiver: Box::new(receiver),
136 method: method.to_string(),
137 turbofish: None,
138 args: vec![default_value, map_fn],
140 };
141 return 1;
142 }
143
144 match expr {
146 PureExpr::Binary { left, right, .. } => {
147 changes += self.transform_expr(left);
148 changes += self.transform_expr(right);
149 }
150 PureExpr::Unary { expr: inner, .. } => {
151 changes += self.transform_expr(inner);
152 }
153 PureExpr::Call { func, args } => {
154 changes += self.transform_expr(func);
155 for arg in args {
156 changes += self.transform_expr(arg);
157 }
158 }
159 PureExpr::MethodCall { receiver, args, .. } => {
160 changes += self.transform_expr(receiver);
161 for arg in args {
162 changes += self.transform_expr(arg);
163 }
164 }
165 PureExpr::Field { expr: inner, .. } => {
166 changes += self.transform_expr(inner);
167 }
168 PureExpr::Index { expr: inner, index } => {
169 changes += self.transform_expr(inner);
170 changes += self.transform_expr(index);
171 }
172 PureExpr::Block { block, .. } => {
173 changes += self.transform_block(block);
174 }
175 PureExpr::If {
176 cond,
177 then_branch,
178 else_branch,
179 } => {
180 changes += self.transform_expr(cond);
181 changes += self.transform_block(then_branch);
182 if let Some(else_expr) = else_branch {
183 changes += self.transform_expr(else_expr);
184 }
185 }
186 PureExpr::Match { expr: e, arms } => {
187 changes += self.transform_expr(e);
188 for arm in arms {
189 changes += self.transform_expr(&mut arm.body);
190 }
191 }
192 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
193 changes += self.transform_block(block);
194 }
195 PureExpr::For {
196 expr: iter_expr,
197 body,
198 ..
199 } => {
200 changes += self.transform_expr(iter_expr);
201 changes += self.transform_block(body);
202 }
203 PureExpr::Closure { body, .. } => {
204 changes += self.transform_expr(body);
205 }
206 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
207 for e in exprs {
208 changes += self.transform_expr(e);
209 }
210 }
211 PureExpr::Struct { fields, .. } => {
212 for (_, e) in fields {
213 changes += self.transform_expr(e);
214 }
215 }
216 PureExpr::Ref { expr: inner, .. } => {
217 changes += self.transform_expr(inner);
218 }
219 PureExpr::Return(Some(inner)) => {
220 changes += self.transform_expr(inner);
221 }
222 PureExpr::Try(inner) | PureExpr::Await(inner) => {
223 changes += self.transform_expr(inner);
224 }
225 _ => {}
226 }
227
228 changes
229 }
230
231 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
232 let mut changes = 0;
233 for stmt in &mut block.stmts {
234 changes += self.transform_stmt(stmt);
235 }
236 changes
237 }
238
239 fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
240 match stmt {
241 PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
242 PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
243 _ => 0,
244 }
245 }
246}
247
248impl Mutation for MapUnwrapOrMutation {
249 fn describe(&self) -> String {
250 "Convert .map().unwrap_or() to .map_or()".to_string()
251 }
252
253 fn mutation_type(&self) -> &'static str {
254 "MapUnwrapOr"
255 }
256
257 fn box_clone(&self) -> Box<dyn Mutation> {
258 Box::new(self.clone())
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_is_map_unwrap_or_pattern() {
268 let expr = PureExpr::MethodCall {
270 receiver: Box::new(PureExpr::MethodCall {
271 receiver: Box::new(PureExpr::Path("opt".to_string())),
272 method: "map".to_string(),
273 turbofish: None,
274 args: vec![PureExpr::Path("f".to_string())],
275 }),
276 method: "unwrap_or".to_string(),
277 turbofish: None,
278 args: vec![PureExpr::Lit("0".to_string())],
279 };
280
281 let result = MapUnwrapOrMutation::is_map_unwrap_or_pattern(&expr);
282 assert!(result.is_some());
283 let (_, _, _, is_lazy) = result.unwrap();
284 assert!(!is_lazy);
285 }
286
287 #[test]
288 fn test_is_map_unwrap_or_else_pattern() {
289 let expr = PureExpr::MethodCall {
291 receiver: Box::new(PureExpr::MethodCall {
292 receiver: Box::new(PureExpr::Path("opt".to_string())),
293 method: "map".to_string(),
294 turbofish: None,
295 args: vec![PureExpr::Path("f".to_string())],
296 }),
297 method: "unwrap_or_else".to_string(),
298 turbofish: None,
299 args: vec![PureExpr::Path("g".to_string())],
300 };
301
302 let result = MapUnwrapOrMutation::is_map_unwrap_or_pattern(&expr);
303 assert!(result.is_some());
304 let (_, _, _, is_lazy) = result.unwrap();
305 assert!(is_lazy);
306 }
307
308 #[test]
309 fn test_is_map_or_none_pattern() {
310 let expr = PureExpr::MethodCall {
312 receiver: Box::new(PureExpr::MethodCall {
313 receiver: Box::new(PureExpr::Path("opt".to_string())),
314 method: "map".to_string(),
315 turbofish: None,
316 args: vec![PureExpr::Path("f".to_string())],
317 }),
318 method: "unwrap_or".to_string(),
319 turbofish: None,
320 args: vec![PureExpr::Path("None".to_string())],
321 };
322
323 assert!(MapUnwrapOrMutation::is_map_or_none_pattern(&expr).is_some());
324 }
325}