ryo_executor/engine/impls/
map_unwrap_or.rs1use 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 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 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 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}