ryo_mutations/idiom/
filter_next.rs1use ryo_source::pure::{PureBlock, PureExpr, PureStmt};
10use ryo_symbol::SymbolId;
11
12use crate::Mutation;
13
14#[derive(Debug, Clone, Default)]
26pub struct FilterNextMutation {
27 pub target_fn: Option<SymbolId>,
29}
30
31impl FilterNextMutation {
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn in_function(mut self, id: SymbolId) -> Self {
38 self.target_fn = Some(id);
39 self
40 }
41
42 fn is_filter_next_pattern(expr: &PureExpr) -> Option<(PureExpr, PureExpr)> {
45 if let PureExpr::MethodCall {
47 receiver,
48 method,
49 args,
50 ..
51 } = expr
52 {
53 if method == "next" && args.is_empty() {
55 if let PureExpr::MethodCall {
57 receiver: filter_receiver,
58 method: filter_method,
59 args: filter_args,
60 ..
61 } = receiver.as_ref()
62 {
63 if filter_method == "filter" && filter_args.len() == 1 {
64 return Some((filter_receiver.as_ref().clone(), filter_args[0].clone()));
65 }
66 }
67 }
68 }
69 None
70 }
71
72 fn transform_expr(&self, expr: &mut PureExpr) -> usize {
74 let mut changes = 0;
75
76 if let Some((receiver, predicate)) = Self::is_filter_next_pattern(expr) {
78 *expr = PureExpr::MethodCall {
79 receiver: Box::new(receiver),
80 method: "find".to_string(),
81 turbofish: None,
82 args: vec![predicate],
83 };
84 return 1;
85 }
86
87 match expr {
89 PureExpr::Binary { left, right, .. } => {
90 changes += self.transform_expr(left);
91 changes += self.transform_expr(right);
92 }
93 PureExpr::Unary { expr: inner, .. } => {
94 changes += self.transform_expr(inner);
95 }
96 PureExpr::Call { func, args } => {
97 changes += self.transform_expr(func);
98 for arg in args {
99 changes += self.transform_expr(arg);
100 }
101 }
102 PureExpr::MethodCall { receiver, args, .. } => {
103 changes += self.transform_expr(receiver);
104 for arg in args {
105 changes += self.transform_expr(arg);
106 }
107 }
108 PureExpr::Field { expr: inner, .. } => {
109 changes += self.transform_expr(inner);
110 }
111 PureExpr::Index { expr: inner, index } => {
112 changes += self.transform_expr(inner);
113 changes += self.transform_expr(index);
114 }
115 PureExpr::Block { block, .. } => {
116 changes += self.transform_block(block);
117 }
118 PureExpr::If {
119 cond,
120 then_branch,
121 else_branch,
122 } => {
123 changes += self.transform_expr(cond);
124 changes += self.transform_block(then_branch);
125 if let Some(else_expr) = else_branch {
126 changes += self.transform_expr(else_expr);
127 }
128 }
129 PureExpr::Match { expr: e, arms } => {
130 changes += self.transform_expr(e);
131 for arm in arms {
132 changes += self.transform_expr(&mut arm.body);
133 }
134 }
135 PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
136 changes += self.transform_block(block);
137 }
138 PureExpr::For {
139 expr: iter_expr,
140 body,
141 ..
142 } => {
143 changes += self.transform_expr(iter_expr);
144 changes += self.transform_block(body);
145 }
146 PureExpr::Closure { body, .. } => {
147 changes += self.transform_expr(body);
148 }
149 PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
150 for e in exprs {
151 changes += self.transform_expr(e);
152 }
153 }
154 PureExpr::Struct { fields, .. } => {
155 for (_, e) in fields {
156 changes += self.transform_expr(e);
157 }
158 }
159 PureExpr::Ref { expr: inner, .. } => {
160 changes += self.transform_expr(inner);
161 }
162 PureExpr::Return(Some(inner)) => {
163 changes += self.transform_expr(inner);
164 }
165 PureExpr::Try(inner) | PureExpr::Await(inner) => {
166 changes += self.transform_expr(inner);
167 }
168 _ => {}
169 }
170
171 changes
172 }
173
174 pub fn transform_block(&self, block: &mut PureBlock) -> usize {
175 let mut changes = 0;
176 for stmt in &mut block.stmts {
177 changes += self.transform_stmt(stmt);
178 }
179 changes
180 }
181
182 fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
183 match stmt {
184 PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
185 PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
186 _ => 0,
187 }
188 }
189}
190
191impl Mutation for FilterNextMutation {
192 fn describe(&self) -> String {
193 "Convert .filter().next() to .find()".to_string()
194 }
195
196 fn mutation_type(&self) -> &'static str {
197 "FilterNext"
198 }
199
200 fn box_clone(&self) -> Box<dyn Mutation> {
201 Box::new(self.clone())
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_is_filter_next_pattern_basic() {
211 let expr = PureExpr::MethodCall {
213 receiver: Box::new(PureExpr::MethodCall {
214 receiver: Box::new(PureExpr::Path("iter".to_string())),
215 method: "filter".to_string(),
216 turbofish: None,
217 args: vec![PureExpr::Closure {
218 params: vec![],
219 ret: None,
220 body: Box::new(PureExpr::Path("predicate".to_string())),
221 is_async: false,
222 is_move: false,
223 }],
224 }),
225 method: "next".to_string(),
226 turbofish: None,
227 args: vec![],
228 };
229
230 assert!(FilterNextMutation::is_filter_next_pattern(&expr).is_some());
231 }
232
233 #[test]
234 fn test_is_filter_next_pattern_not_matching() {
235 let expr = PureExpr::MethodCall {
237 receiver: Box::new(PureExpr::MethodCall {
238 receiver: Box::new(PureExpr::Path("iter".to_string())),
239 method: "map".to_string(),
240 turbofish: None,
241 args: vec![PureExpr::Path("f".to_string())],
242 }),
243 method: "next".to_string(),
244 turbofish: None,
245 args: vec![],
246 };
247
248 assert!(FilterNextMutation::is_filter_next_pattern(&expr).is_none());
249 }
250
251 #[test]
252 fn test_is_filter_next_pattern_filter_without_next() {
253 let expr = PureExpr::MethodCall {
255 receiver: Box::new(PureExpr::Path("iter".to_string())),
256 method: "filter".to_string(),
257 turbofish: None,
258 args: vec![PureExpr::Path("predicate".to_string())],
259 };
260
261 assert!(FilterNextMutation::is_filter_next_pattern(&expr).is_none());
262 }
263}