Skip to main content

ryo_executor/engine/impls/
collapsible_if.rs

1//! V2 ASTRegApply implementation for CollapsibleIfMutation
2//!
3//! Merges nested if statements into single if with &&:
4//! - `if a { if b { body } }` → `if a && b { body }`
5
6use ryo_analysis::SymbolKind;
7use ryo_mutations::idiom::CollapsibleIfMutation;
8use ryo_mutations::{Mutation, MutationResult};
9use ryo_source::pure::{PureImplItem, PureItem};
10
11use crate::engine::{ASTMutationContext, ASTRegApply};
12
13impl ASTRegApply for CollapsibleIfMutation {
14    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
15        let mut total_changes = 0;
16
17        if let Some(target_id) = self.target_fn {
18            // Target function specified: direct lookup
19            if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(target_id) {
20                total_changes += self.transform_block(&mut f.body);
21            }
22        } else {
23            // No target: process all functions
24            let fn_ids: Vec<_> = ctx
25                .symbol_registry
26                .iter()
27                .filter(|(id, _)| {
28                    matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
29                })
30                .map(|(id, _)| id)
31                .collect();
32
33            for id in fn_ids {
34                if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
35                    total_changes += self.transform_block(&mut f.body);
36                }
37            }
38
39            // Process impl blocks (methods)
40            let impl_ids: Vec<_> = ctx
41                .symbol_registry
42                .iter()
43                .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
44                .map(|(id, _)| id)
45                .collect();
46
47            for id in impl_ids {
48                if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
49                    for impl_item in &mut imp.items {
50                        if let PureImplItem::Fn(f) = impl_item {
51                            total_changes += self.transform_block(&mut f.body);
52                        }
53                    }
54                }
55            }
56        }
57
58        MutationResult {
59            mutation_type: self.mutation_type().to_string(),
60            changes: total_changes,
61            description: if total_changes > 0 {
62                format!("Collapsed {} nested if statement(s)", total_changes)
63            } else {
64                "No nested if statements collapsed".to_string()
65            },
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::engine::ASTMutationEngine;
74    use ryo_analysis::testing::ContextBuilder;
75
76    #[test]
77    fn test_v2_collapsible_if_basic() {
78        let mut ctx = ContextBuilder::new()
79            .with_file(
80                "src/lib.rs",
81                r#"
82fn check(a: bool, b: bool) {
83    if a {
84        if b {
85            println!("both");
86        }
87    }
88}
89"#,
90            )
91            .build();
92
93        let mutation = CollapsibleIfMutation::new();
94        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
95
96        assert_eq!(result.result.changes, 1);
97    }
98
99    #[test]
100    fn test_v2_collapsible_if_with_else() {
101        let mut ctx = ContextBuilder::new()
102            .with_file(
103                "src/lib.rs",
104                r#"
105fn check(a: bool, b: bool) {
106    if a {
107        if b {
108            println!("both");
109        } else {
110            println!("only a");
111        }
112    }
113}
114"#,
115            )
116            .build();
117
118        let mutation = CollapsibleIfMutation::new();
119        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
120
121        // Should not collapse because inner if has else
122        assert_eq!(result.result.changes, 0);
123    }
124
125    #[test]
126    fn test_v2_collapsible_if_no_changes() {
127        let mut ctx = ContextBuilder::new()
128            .with_file(
129                "src/lib.rs",
130                r#"
131fn check(a: bool, b: bool) {
132    if a && b {
133        println!("both");
134    }
135}
136"#,
137            )
138            .build();
139
140        let mutation = CollapsibleIfMutation::new();
141        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
142
143        assert_eq!(result.result.changes, 0);
144    }
145}