ryo_mutations/idiom/
noop_arm.rs1use ryo_source::pure::{MacroDelimiter, PureBlock, PureExpr, PureMatchArm, PureStmt};
11use ryo_symbol::SymbolId;
12
13use crate::Mutation;
14
15#[derive(Debug, Clone)]
35pub struct NoOpArmToTodoMutation {
36 pub target_fn: Option<SymbolId>,
38 pub replacement: String,
40}
41
42impl Default for NoOpArmToTodoMutation {
43 fn default() -> Self {
44 Self {
45 target_fn: None,
46 replacement: "todo".to_string(),
47 }
48 }
49}
50
51impl NoOpArmToTodoMutation {
52 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn in_function(mut self, id: SymbolId) -> Self {
58 self.target_fn = Some(id);
59 self
60 }
61
62 pub fn with_replacement(mut self, replacement: impl Into<String>) -> Self {
64 self.replacement = replacement.into();
65 self
66 }
67
68 fn is_noop_body(expr: &PureExpr) -> bool {
70 match expr {
71 PureExpr::Block { block, .. } => block.stmts.is_empty(),
73 PureExpr::Tuple(items) => items.is_empty(),
75 _ => false,
76 }
77 }
78
79 fn create_replacement_expr(&self) -> PureExpr {
81 PureExpr::Macro {
82 name: self.replacement.clone(),
83 delimiter: MacroDelimiter::Paren,
84 tokens: "".to_string(),
85 }
86 }
87
88 fn transform_arms(&self, arms: &mut [PureMatchArm]) -> usize {
90 let mut changes = 0;
91
92 for arm in arms.iter_mut() {
93 changes += self.transform_expr(&mut arm.body);
95
96 if Self::is_noop_body(&arm.body) {
98 arm.body = self.create_replacement_expr();
99 changes += 1;
100 }
101 }
102
103 changes
104 }
105
106 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
108 let mut changes = 0;
109
110 match expr {
111 PureExpr::Match { arms, expr: inner } => {
112 changes += self.transform_expr(inner);
114 changes += self.transform_arms(arms);
116 }
117 PureExpr::If {
118 cond,
119 then_branch,
120 else_branch,
121 } => {
122 changes += self.transform_expr(cond);
123 changes += self.transform_block(then_branch);
124 if let Some(else_expr) = else_branch {
125 changes += self.transform_expr(else_expr);
126 }
127 }
128 PureExpr::Block { block, .. } => {
129 changes += self.transform_block(block);
130 }
131 PureExpr::Call { func, args } => {
132 changes += self.transform_expr(func);
133 for arg in args {
134 changes += self.transform_expr(arg);
135 }
136 }
137 PureExpr::MethodCall { receiver, args, .. } => {
138 changes += self.transform_expr(receiver);
139 for arg in args {
140 changes += self.transform_expr(arg);
141 }
142 }
143 PureExpr::Binary { left, right, .. } => {
144 changes += self.transform_expr(left);
145 changes += self.transform_expr(right);
146 }
147 PureExpr::Unary { expr: inner, .. } => {
148 changes += self.transform_expr(inner);
149 }
150 PureExpr::Closure { body, .. } => {
151 changes += self.transform_expr(body);
152 }
153 PureExpr::While { cond, body, .. } => {
154 changes += self.transform_expr(cond);
155 changes += self.transform_block(body);
156 }
157 PureExpr::Loop { body, .. } => {
158 changes += self.transform_block(body);
159 }
160 PureExpr::For { expr, body, .. } => {
161 changes += self.transform_expr(expr);
162 changes += self.transform_block(body);
163 }
164 PureExpr::Tuple(items) | PureExpr::Array(items) => {
165 for item in items {
166 changes += self.transform_expr(item);
167 }
168 }
169 PureExpr::Struct { fields, .. } => {
170 for (_, value) in fields {
171 changes += self.transform_expr(value);
172 }
173 }
174 PureExpr::Index { expr, index } => {
175 changes += self.transform_expr(expr);
176 changes += self.transform_expr(index);
177 }
178 PureExpr::Field { expr, .. } => {
179 changes += self.transform_expr(expr);
180 }
181 PureExpr::Cast { expr, .. } => {
182 changes += self.transform_expr(expr);
183 }
184 PureExpr::Return(Some(inner))
185 | PureExpr::Break {
186 expr: Some(inner), ..
187 }
188 | PureExpr::Await(inner)
189 | PureExpr::Try(inner)
190 | PureExpr::Ref { expr: inner, .. }
191 | PureExpr::Let { expr: inner, .. } => {
192 changes += self.transform_expr(inner);
193 }
194 PureExpr::Range { start, end, .. } => {
195 if let Some(s) = start {
196 changes += self.transform_expr(s);
197 }
198 if let Some(e) = end {
199 changes += self.transform_expr(e);
200 }
201 }
202 PureExpr::Unsafe(block) | PureExpr::Async { body: block, .. } => {
203 changes += self.transform_block(block);
204 }
205 PureExpr::Repeat { expr, len } => {
206 changes += self.transform_expr(expr);
207 changes += self.transform_expr(len);
208 }
209 _ => {}
211 }
212
213 changes
214 }
215
216 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
218 let mut changes = 0;
219
220 for stmt in &mut block.stmts {
221 match stmt {
222 PureStmt::Local {
223 init: Some(init_expr),
224 ..
225 } => {
226 changes += self.transform_expr(init_expr);
227 }
228 PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
229 changes += self.transform_expr(expr);
230 }
231 _ => {}
232 }
233 }
234
235 changes
236 }
237}
238
239impl Mutation for NoOpArmToTodoMutation {
240 fn describe(&self) -> String {
241 format!(
242 "Replace empty match arms with {}!() (_ => {{}} → _ => {}!())",
243 self.replacement, self.replacement
244 )
245 }
246
247 fn mutation_type(&self) -> &'static str {
248 "NoOpArmToTodo"
249 }
250
251 fn box_clone(&self) -> Box<dyn Mutation> {
252 Box::new(self.clone())
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use ryo_source::pure::PurePattern;
260
261 fn create_empty_block() -> PureExpr {
262 PureExpr::Block {
263 label: None,
264 block: PureBlock { stmts: vec![] },
265 }
266 }
267
268 fn create_unit_tuple() -> PureExpr {
269 PureExpr::Tuple(vec![])
270 }
271
272 #[test]
273 fn test_is_noop_body_empty_block() {
274 assert!(NoOpArmToTodoMutation::is_noop_body(&create_empty_block()));
275 }
276
277 #[test]
278 fn test_is_noop_body_unit_tuple() {
279 assert!(NoOpArmToTodoMutation::is_noop_body(&create_unit_tuple()));
280 }
281
282 #[test]
283 fn test_is_noop_body_non_empty() {
284 let non_empty = PureExpr::Path("something".to_string());
285 assert!(!NoOpArmToTodoMutation::is_noop_body(&non_empty));
286 }
287
288 #[test]
289 fn test_create_replacement_expr_default() {
290 let mutation = NoOpArmToTodoMutation::new();
291 let expr = mutation.create_replacement_expr();
292 match expr {
293 PureExpr::Macro { name, tokens, .. } => {
294 assert_eq!(name, "todo");
295 assert_eq!(tokens, "");
296 }
297 _ => panic!("Expected macro expression"),
298 }
299 }
300
301 #[test]
302 fn test_create_replacement_expr_custom() {
303 let mutation = NoOpArmToTodoMutation::new().with_replacement("unreachable");
304 let expr = mutation.create_replacement_expr();
305 match expr {
306 PureExpr::Macro { name, tokens, .. } => {
307 assert_eq!(name, "unreachable");
308 assert_eq!(tokens, "");
309 }
310 _ => panic!("Expected macro expression"),
311 }
312 }
313
314 #[test]
315 fn test_transform_match_arms() {
316 let mut arms = vec![
317 PureMatchArm {
318 pattern: PurePattern::Wild,
319 guard: None,
320 body: create_empty_block(),
321 },
322 PureMatchArm {
323 pattern: PurePattern::Wild,
324 guard: None,
325 body: PureExpr::Path("existing".to_string()),
326 },
327 ];
328
329 let mutation = NoOpArmToTodoMutation::new();
330 let changes = mutation.transform_arms(&mut arms);
331
332 assert_eq!(changes, 1);
333 match &arms[0].body {
335 PureExpr::Macro { name, .. } => assert_eq!(name, "todo"),
336 _ => panic!("Expected macro"),
337 }
338 match &arms[1].body {
340 PureExpr::Path(p) => assert_eq!(p, "existing"),
341 _ => panic!("Expected path"),
342 }
343 }
344}