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, 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 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 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 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}