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, ModificationType};
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                let changes = self.transform_block(&mut f.body);
23                if changes > 0 {
24                    ctx.emit_modified(target_id, ModificationType::BodyModified);
25                    total_changes += changes;
26                }
27            }
28        } else {
29            // No target: process all functions
30            let fn_ids: Vec<_> = ctx
31                .symbol_registry
32                .iter()
33                .filter(|(id, _)| {
34                    matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Function))
35                })
36                .map(|(id, _)| id)
37                .collect();
38
39            for id in fn_ids {
40                if let Some(PureItem::Fn(f)) = ctx.ast_registry.get_mut(id) {
41                    let changes = self.transform_block(&mut f.body);
42                    if changes > 0 {
43                        ctx.emit_modified(id, ModificationType::BodyModified);
44                        total_changes += changes;
45                    }
46                }
47            }
48
49            // Process impl blocks (methods)
50            let impl_ids: Vec<_> = ctx
51                .symbol_registry
52                .iter()
53                .filter(|(id, _)| matches!(ctx.symbol_registry.kind(*id), Some(SymbolKind::Impl)))
54                .map(|(id, _)| id)
55                .collect();
56
57            for id in impl_ids {
58                let mut impl_changes = 0;
59                if let Some(PureItem::Impl(imp)) = ctx.ast_registry.get_mut(id) {
60                    for impl_item in &mut imp.items {
61                        if let PureImplItem::Fn(f) = impl_item {
62                            impl_changes += self.transform_block(&mut f.body);
63                        }
64                    }
65                }
66                if impl_changes > 0 {
67                    ctx.emit_modified(id, ModificationType::BodyModified);
68                    total_changes += impl_changes;
69                }
70            }
71        }
72
73        MutationResult {
74            mutation_type: self.mutation_type().to_string(),
75            changes: total_changes,
76            description: if total_changes > 0 {
77                format!(
78                    "Converted {} .map().unwrap_or() chain(s) to .map_or()",
79                    total_changes
80                )
81            } else {
82                "No map().unwrap_or() chains found".to_string()
83            },
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::engine::ASTMutationEngine;
92    use ryo_analysis::testing::ContextBuilder;
93
94    #[test]
95    fn test_v2_map_unwrap_or_basic() {
96        let mut ctx = ContextBuilder::new()
97            .with_file(
98                "src/lib.rs",
99                r#"
100fn get_length(opt: Option<String>) -> usize {
101    opt.map(|s| s.len()).unwrap_or(0)
102}
103"#,
104            )
105            .build();
106
107        let mutation = MapUnwrapOrMutation::new();
108        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
109
110        assert_eq!(result.result.changes, 1);
111    }
112
113    #[test]
114    fn test_v2_map_unwrap_or_else() {
115        let mut ctx = ContextBuilder::new()
116            .with_file(
117                "src/lib.rs",
118                r#"
119fn get_length(opt: Option<String>) -> usize {
120    opt.map(|s| s.len()).unwrap_or_else(|| compute_default())
121}
122
123fn compute_default() -> usize { 0 }
124"#,
125            )
126            .build();
127
128        let mutation = MapUnwrapOrMutation::new();
129        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
130
131        assert_eq!(result.result.changes, 1);
132    }
133
134    #[test]
135    fn test_v2_map_unwrap_or_no_changes() {
136        let mut ctx = ContextBuilder::new()
137            .with_file(
138                "src/lib.rs",
139                r#"
140fn get_length(opt: Option<String>) -> usize {
141    opt.map_or(0, |s| s.len())
142}
143"#,
144            )
145            .build();
146
147        let mutation = MapUnwrapOrMutation::new();
148        let result = ASTMutationEngine::execute_ast_reg(&mutation, &mut ctx);
149
150        assert_eq!(result.result.changes, 0);
151    }
152}