1use ryo_source::pure::{
7 MacroDelimiter, PureBlock, PureClosureParam, PureExpr, PureFn, PurePattern, PureStmt,
8};
9
10use super::marker::DebugMarker;
11use crate::Mutation;
12
13#[derive(Debug, Clone)]
27pub struct InsertInspectMutation {
28 pub after_method: String,
30 pub marker: DebugMarker,
32 pub var_name: String,
34 pub in_function: Option<String>,
36}
37
38impl InsertInspectMutation {
39 pub fn new(after_method: impl Into<String>, marker: DebugMarker) -> Self {
41 Self {
42 after_method: after_method.into(),
43 marker,
44 var_name: "_x".to_string(),
45 in_function: None,
46 }
47 }
48
49 pub fn with_var_name(mut self, name: impl Into<String>) -> Self {
51 self.var_name = name.into();
52 self
53 }
54
55 pub fn in_function(mut self, name: impl Into<String>) -> Self {
57 self.in_function = Some(name.into());
58 self
59 }
60
61 fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
63 match expr {
64 PureExpr::MethodCall {
65 receiver,
66 method,
67 args,
68 ..
69 } => {
70 let (new_receiver, mut count) = self.transform_expr(receiver);
72
73 if method == &self.after_method {
75 let method_call = PureExpr::MethodCall {
77 receiver: Box::new(new_receiver),
78 method: method.clone(),
79 turbofish: None,
80 args: args.clone(),
81 };
82
83 let inspect_call = self.build_inspect_call(method_call);
84 count += 1;
85 (inspect_call, count)
86 } else {
87 let new_expr = PureExpr::MethodCall {
89 receiver: Box::new(new_receiver),
90 method: method.clone(),
91 turbofish: None,
92 args: args.clone(),
93 };
94 (new_expr, count)
95 }
96 }
97
98 PureExpr::Call { func, args } => {
100 let (new_func, mut count) = self.transform_expr(func);
101 let mut new_args = Vec::new();
102 for arg in args {
103 let (new_arg, c) = self.transform_expr(arg);
104 new_args.push(new_arg);
105 count += c;
106 }
107 (
108 PureExpr::Call {
109 func: Box::new(new_func),
110 args: new_args,
111 },
112 count,
113 )
114 }
115
116 PureExpr::Binary { op, left, right } => {
117 let (new_left, c1) = self.transform_expr(left);
118 let (new_right, c2) = self.transform_expr(right);
119 (
120 PureExpr::Binary {
121 op: op.clone(),
122 left: Box::new(new_left),
123 right: Box::new(new_right),
124 },
125 c1 + c2,
126 )
127 }
128
129 PureExpr::Unary { op, expr: inner } => {
130 let (new_inner, count) = self.transform_expr(inner);
131 (
132 PureExpr::Unary {
133 op: op.clone(),
134 expr: Box::new(new_inner),
135 },
136 count,
137 )
138 }
139
140 PureExpr::Block { label, block } => {
141 let (new_block, count) = self.transform_block(block);
142 (
143 PureExpr::Block {
144 label: label.clone(),
145 block: new_block,
146 },
147 count,
148 )
149 }
150
151 PureExpr::If {
152 cond,
153 then_branch,
154 else_branch,
155 } => {
156 let (new_cond, c1) = self.transform_expr(cond);
157 let (new_then, c2) = self.transform_block(then_branch);
158 let (new_else, c3) = if let Some(e) = else_branch {
159 let (ne, c) = self.transform_expr(e);
160 (Some(Box::new(ne)), c)
161 } else {
162 (None, 0)
163 };
164 (
165 PureExpr::If {
166 cond: Box::new(new_cond),
167 then_branch: new_then,
168 else_branch: new_else,
169 },
170 c1 + c2 + c3,
171 )
172 }
173
174 PureExpr::Closure {
175 params, ret, body, ..
176 } => {
177 let (new_body, count) = self.transform_expr(body);
178 (
179 PureExpr::Closure {
180 is_async: false,
181 is_move: false,
182 params: params.clone(),
183 ret: ret.clone(),
184 body: Box::new(new_body),
185 },
186 count,
187 )
188 }
189
190 PureExpr::Match { expr: e, arms } => {
191 let (new_expr, mut count) = self.transform_expr(e);
192 let new_arms = arms
193 .iter()
194 .map(|arm| {
195 let (new_body, c) = self.transform_expr(&arm.body);
196 count += c;
197 ryo_source::pure::PureMatchArm {
198 pattern: arm.pattern.clone(),
199 guard: arm.guard.clone(),
200 body: new_body,
201 }
202 })
203 .collect();
204 (
205 PureExpr::Match {
206 expr: Box::new(new_expr),
207 arms: new_arms,
208 },
209 count,
210 )
211 }
212
213 _ => (expr.clone(), 0),
215 }
216 }
217
218 fn build_inspect_call(&self, receiver: PureExpr) -> PureExpr {
220 let marker_str = self.marker.to_marker_string();
223
224 let closure = PureExpr::Closure {
225 is_async: false,
226 is_move: false,
227 params: vec![PureClosureParam::untyped(PurePattern::Ident {
228 name: self.var_name.clone(),
229 is_mut: false,
230 })],
231 ret: None,
232 body: Box::new(PureExpr::Block {
233 label: None,
234 block: PureBlock {
235 stmts: vec![PureStmt::Semi(PureExpr::Macro {
236 name: "dbg".to_string(),
237 delimiter: MacroDelimiter::Paren,
238 tokens: format!("\"{}\", &{}", marker_str, self.var_name),
239 })],
240 },
241 }),
242 };
243
244 PureExpr::MethodCall {
245 receiver: Box::new(receiver),
246 method: "inspect".to_string(),
247 turbofish: None,
248 args: vec![closure],
249 }
250 }
251
252 fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
254 let mut count = 0;
255 let new_stmts = block
256 .stmts
257 .iter()
258 .map(|stmt| {
259 let (new_stmt, c) = self.transform_stmt(stmt);
260 count += c;
261 new_stmt
262 })
263 .collect();
264 (PureBlock { stmts: new_stmts }, count)
265 }
266
267 fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
269 match stmt {
270 PureStmt::Local { pattern, ty, init } => {
271 if let Some(init_expr) = init {
272 let (new_init, count) = self.transform_expr(init_expr);
273 (
274 PureStmt::Local {
275 pattern: pattern.clone(),
276 ty: ty.clone(),
277 init: Some(new_init),
278 },
279 count,
280 )
281 } else {
282 (stmt.clone(), 0)
283 }
284 }
285 PureStmt::Expr(expr) => {
286 let (new_expr, count) = self.transform_expr(expr);
287 (PureStmt::Expr(new_expr), count)
288 }
289 PureStmt::Semi(expr) => {
290 let (new_expr, count) = self.transform_expr(expr);
291 (PureStmt::Semi(new_expr), count)
292 }
293 _ => (stmt.clone(), 0),
294 }
295 }
296
297 pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
299 if let Some(ref target_fn) = self.in_function {
301 if &func.name != target_fn {
302 return (func.clone(), 0);
303 }
304 }
305
306 let (new_body, count) = self.transform_block(&func.body);
307 let mut new_func = func.clone();
308 new_func.body = new_body;
309 (new_func, count)
310 }
311}
312
313impl Mutation for InsertInspectMutation {
314 fn describe(&self) -> String {
315 format!(
316 "Insert .inspect() after .{}() [session: {}]",
317 self.after_method, self.marker.session_id
318 )
319 }
320
321 fn mutation_type(&self) -> &'static str {
322 "InsertInspect"
323 }
324
325 fn box_clone(&self) -> Box<dyn Mutation> {
326 Box::new(self.clone())
327 }
328}