Skip to main content

ryo_executor/engine/impls/
map_unwrap_or.rs

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