Skip to main content

ryo_executor/engine/impls/
noop_arm.rs

1//! V2 ASTRegApply implementation for NoOpArmToTodoMutation
2//!
3//! Replaces empty/noop match arms with todo!/unimplemented!/unreachable!:
4//! - `_ => {}` → `_ => todo!()`
5//! - `_ => ()` → `_ => todo!()`
6
7use ryo_analysis::SymbolKind;
8use ryo_mutations::idiom::NoOpArmToTodoMutation;
9use ryo_mutations::{Mutation, MutationResult};
10use ryo_source::pure::{PureImplItem, PureItem};
11
12use crate::engine::{ASTMutationContext, ASTRegApply};
13
14impl ASTRegApply for NoOpArmToTodoMutation {
15    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
16        let mut total_changes = 0;
17
18        if let Some(target_id) = self.target_fn {
19            // Target function specified: direct lookup
20            if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(target_id) {
21                total_changes += self.transform_block(&mut f.body);
22            }
23        } else {
24            // No target: process all functions
25            let fn_ids: Vec<_> = ctx
26                .symbol_registry
27                .iter()
28                .filter(|(id, _)| {
29                    matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
30                })
31                .map(|(id, _)| id)
32                .collect();
33
34            for id in fn_ids {
35                if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
36                    total_changes += self.transform_block(&mut f.body);
37                }
38            }
39
40            // Process impl blocks (methods)
41            let impl_ids: Vec<_> = ctx
42                .symbol_registry
43                .iter()
44                .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
45                .map(|(id, _)| id)
46                .collect();
47
48            for id in impl_ids {
49                if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
50                    for impl_item in &mut imp.items {
51                        if let PureImplItem::Fn(f) = impl_item {
52                            total_changes += self.transform_block(&mut f.body);
53                        }
54                    }
55                }
56            }
57        }
58
59        MutationResult {
60            mutation_type: self.mutation_type().to_string(),
61            changes: total_changes,
62            description: if total_changes > 0 {
63                format!(
64                    "Replaced {} empty match arm(s) with {}!()",
65                    total_changes, self.replacement
66                )
67            } else {
68                "No empty match arms found".to_string()
69            },
70        }
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use crate::engine::ASTMutationEngine;
78    use ryo_analysis::testing::ContextBuilder;
79
80    #[test]
81    fn test_v2_noop_arm_empty_block() {
82        let mut ctx = ContextBuilder::new()
83            .with_file(
84                "src/lib.rs",
85                r#"
86fn process(x: Option<i32>) {
87    match x {
88        Some(v) => println!("{}", v),
89        _ => {}
90    }
91}
92"#,
93            )
94            .build();
95
96        let mutation = NoOpArmToTodoMutation::new();
97        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
98
99        assert_eq!(result.result.changes, 1);
100    }
101
102    #[test]
103    fn test_v2_noop_arm_unit_tuple() {
104        let mut ctx = ContextBuilder::new()
105            .with_file(
106                "src/lib.rs",
107                r#"
108fn process(x: Option<i32>) {
109    match x {
110        Some(v) => println!("{}", v),
111        None => ()
112    }
113}
114"#,
115            )
116            .build();
117
118        let mutation = NoOpArmToTodoMutation::new();
119        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
120
121        assert_eq!(result.result.changes, 1);
122    }
123
124    #[test]
125    fn test_v2_noop_arm_with_replacement() {
126        let mut ctx = ContextBuilder::new()
127            .with_file(
128                "src/lib.rs",
129                r#"
130fn process(x: Option<i32>) {
131    match x {
132        Some(v) => println!("{}", v),
133        _ => {}
134    }
135}
136"#,
137            )
138            .build();
139
140        let mutation = NoOpArmToTodoMutation::new().with_replacement("unreachable");
141        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
142
143        assert_eq!(result.result.changes, 1);
144        assert!(result.result.description.contains("unreachable!()"));
145    }
146
147    #[test]
148    fn test_v2_noop_arm_no_changes() {
149        let mut ctx = ContextBuilder::new()
150            .with_file(
151                "src/lib.rs",
152                r#"
153fn process(x: Option<i32>) {
154    match x {
155        Some(v) => println!("{}", v),
156        None => todo!()
157    }
158}
159"#,
160            )
161            .build();
162
163        let mutation = NoOpArmToTodoMutation::new();
164        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
165
166        assert_eq!(result.result.changes, 0);
167    }
168}