1use ryo_source::pure::{MacroDelimiter, PureBlock, PureExpr, PureFn, PureStmt};
7
8use super::marker::DebugMarker;
9use crate::Mutation;
10
11#[derive(Debug, Clone)]
13pub enum WrapTarget {
14 MethodChain { starts_with: String },
16 MethodCall { method: String },
18 Variable { name: String },
20 ReturnExpr { function: String },
22}
23
24#[derive(Debug, Clone)]
38pub struct DbgWrapMutation {
39 pub target: WrapTarget,
41 pub marker: DebugMarker,
43 pub in_function: Option<String>,
45}
46
47impl DbgWrapMutation {
48 pub fn method_chain(starts_with: impl Into<String>, marker: DebugMarker) -> Self {
50 Self {
51 target: WrapTarget::MethodChain {
52 starts_with: starts_with.into(),
53 },
54 marker,
55 in_function: None,
56 }
57 }
58
59 pub fn method_call(method: impl Into<String>, marker: DebugMarker) -> Self {
61 Self {
62 target: WrapTarget::MethodCall {
63 method: method.into(),
64 },
65 marker,
66 in_function: None,
67 }
68 }
69
70 pub fn variable(name: impl Into<String>, marker: DebugMarker) -> Self {
72 Self {
73 target: WrapTarget::Variable { name: name.into() },
74 marker,
75 in_function: None,
76 }
77 }
78
79 pub fn return_expr(function: impl Into<String>, marker: DebugMarker) -> Self {
81 Self {
82 target: WrapTarget::ReturnExpr {
83 function: function.into(),
84 },
85 marker,
86 in_function: None,
87 }
88 }
89
90 pub fn in_function(mut self, name: impl Into<String>) -> Self {
92 self.in_function = Some(name.into());
93 self
94 }
95
96 fn matches_target(&self, expr: &PureExpr) -> bool {
98 match &self.target {
99 WrapTarget::MethodChain { starts_with } => {
100 self.is_chain_starting_with(expr, starts_with)
101 }
102 WrapTarget::MethodCall { method } => {
103 matches!(expr, PureExpr::MethodCall { method: m, .. } if m == method)
104 }
105 WrapTarget::Variable { name } => {
106 matches!(expr, PureExpr::Path(p) if p == name)
107 }
108 WrapTarget::ReturnExpr { .. } => false, }
110 }
111
112 fn is_chain_starting_with(&self, expr: &PureExpr, method: &str) -> bool {
114 match expr {
115 PureExpr::MethodCall {
116 receiver,
117 method: m,
118 ..
119 } => {
120 if m == method {
121 !matches!(receiver.as_ref(), PureExpr::MethodCall { .. })
123 } else {
124 self.is_chain_starting_with(receiver, method)
126 }
127 }
128 _ => false,
129 }
130 }
131
132 fn wrap_with_dbg(&self, expr: PureExpr) -> PureExpr {
134 let marker_str = self.marker.to_marker_string();
135
136 PureExpr::Macro {
137 name: "dbg".to_string(),
138 delimiter: MacroDelimiter::Paren,
139 tokens: format!("\"{}\", {}", marker_str, expr_to_tokens(&expr)),
140 }
141 }
142
143 fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
145 if self.matches_target(expr) {
147 return (self.wrap_with_dbg(expr.clone()), 1);
148 }
149
150 match expr {
152 PureExpr::MethodCall {
153 receiver,
154 method,
155 args,
156 ..
157 } => {
158 let (new_receiver, c1) = self.transform_expr(receiver);
159 let mut total = c1;
160 let new_args: Vec<_> = args
161 .iter()
162 .map(|a| {
163 let (new_a, c) = self.transform_expr(a);
164 total += c;
165 new_a
166 })
167 .collect();
168 (
169 PureExpr::MethodCall {
170 receiver: Box::new(new_receiver),
171 method: method.clone(),
172 turbofish: None,
173 args: new_args,
174 },
175 total,
176 )
177 }
178
179 PureExpr::Call { func, args } => {
180 let (new_func, mut total) = self.transform_expr(func);
181 let new_args: Vec<_> = args
182 .iter()
183 .map(|a| {
184 let (new_a, c) = self.transform_expr(a);
185 total += c;
186 new_a
187 })
188 .collect();
189 (
190 PureExpr::Call {
191 func: Box::new(new_func),
192 args: new_args,
193 },
194 total,
195 )
196 }
197
198 PureExpr::Binary { op, left, right } => {
199 let (new_left, c1) = self.transform_expr(left);
200 let (new_right, c2) = self.transform_expr(right);
201 (
202 PureExpr::Binary {
203 op: op.clone(),
204 left: Box::new(new_left),
205 right: Box::new(new_right),
206 },
207 c1 + c2,
208 )
209 }
210
211 PureExpr::Block { label, block } => {
212 let (new_block, count) = self.transform_block(block);
213 (
214 PureExpr::Block {
215 label: label.clone(),
216 block: new_block,
217 },
218 count,
219 )
220 }
221
222 PureExpr::If {
223 cond,
224 then_branch,
225 else_branch,
226 } => {
227 let (new_cond, c1) = self.transform_expr(cond);
228 let (new_then, c2) = self.transform_block(then_branch);
229 let (new_else, c3) = if let Some(e) = else_branch {
230 let (ne, c) = self.transform_expr(e);
231 (Some(Box::new(ne)), c)
232 } else {
233 (None, 0)
234 };
235 (
236 PureExpr::If {
237 cond: Box::new(new_cond),
238 then_branch: new_then,
239 else_branch: new_else,
240 },
241 c1 + c2 + c3,
242 )
243 }
244
245 PureExpr::Closure {
246 params, ret, body, ..
247 } => {
248 let (new_body, count) = self.transform_expr(body);
249 (
250 PureExpr::Closure {
251 is_async: false,
252 is_move: false,
253 params: params.clone(),
254 ret: ret.clone(),
255 body: Box::new(new_body),
256 },
257 count,
258 )
259 }
260
261 _ => (expr.clone(), 0),
262 }
263 }
264
265 fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
267 let mut count = 0;
268 let new_stmts: Vec<_> = block
269 .stmts
270 .iter()
271 .map(|stmt| {
272 let (new_stmt, c) = self.transform_stmt(stmt);
273 count += c;
274 new_stmt
275 })
276 .collect();
277 (PureBlock { stmts: new_stmts }, count)
278 }
279
280 fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
282 match stmt {
283 PureStmt::Local { pattern, ty, init } => {
284 if let Some(init_expr) = init {
285 let (new_init, count) = self.transform_expr(init_expr);
286 (
287 PureStmt::Local {
288 pattern: pattern.clone(),
289 ty: ty.clone(),
290 init: Some(new_init),
291 },
292 count,
293 )
294 } else {
295 (stmt.clone(), 0)
296 }
297 }
298 PureStmt::Expr(expr) => {
299 let (new_expr, count) = self.transform_expr(expr);
300 (PureStmt::Expr(new_expr), count)
301 }
302 PureStmt::Semi(expr) => {
303 let (new_expr, count) = self.transform_expr(expr);
304 (PureStmt::Semi(new_expr), count)
305 }
306 _ => (stmt.clone(), 0),
307 }
308 }
309
310 pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
312 if let Some(ref target_fn) = self.in_function {
314 if &func.name != target_fn {
315 return (func.clone(), 0);
316 }
317 }
318
319 let mut new_func = func.clone();
320 let mut count = 0;
321
322 if let WrapTarget::ReturnExpr { function } = &self.target {
324 if &func.name == function {
325 if let Some(last_stmt) = new_func.body.stmts.last_mut() {
327 if let PureStmt::Expr(expr) = last_stmt {
328 *last_stmt = PureStmt::Expr(self.wrap_with_dbg(expr.clone()));
329 count += 1;
330 }
331 }
332 return (new_func, count);
333 }
334 }
335
336 let (new_body, block_count) = self.transform_block(&func.body);
338 new_func.body = new_body;
339 (new_func, count + block_count)
340 }
341}
342
343impl Mutation for DbgWrapMutation {
344 fn describe(&self) -> String {
345 let target_desc = match &self.target {
346 WrapTarget::MethodChain { starts_with } => {
347 format!("method chains starting with .{}()", starts_with)
348 }
349 WrapTarget::MethodCall { method } => {
350 format!(".{}() calls", method)
351 }
352 WrapTarget::Variable { name } => {
353 format!("variable '{}'", name)
354 }
355 WrapTarget::ReturnExpr { function } => {
356 format!("return expression in {}", function)
357 }
358 };
359 format!(
360 "Wrap {} with dbg!() [session: {}]",
361 target_desc, self.marker.session_id
362 )
363 }
364
365 fn mutation_type(&self) -> &'static str {
366 "DbgWrap"
367 }
368
369 fn box_clone(&self) -> Box<dyn Mutation> {
370 Box::new(self.clone())
371 }
372}
373
374fn expr_to_tokens(expr: &PureExpr) -> String {
376 match expr {
378 PureExpr::Path(p) => p.clone(),
379 PureExpr::Lit(l) => l.clone(),
380 PureExpr::MethodCall {
381 receiver,
382 method,
383 args,
384 ..
385 } => {
386 let receiver_str = expr_to_tokens(receiver);
387 let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
388 format!("{}.{}({})", receiver_str, method, args_str.join(", "))
389 }
390 PureExpr::Call { func, args } => {
391 let func_str = expr_to_tokens(func);
392 let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
393 format!("{}({})", func_str, args_str.join(", "))
394 }
395 PureExpr::Binary { op, left, right } => {
396 format!("{} {} {}", expr_to_tokens(left), op, expr_to_tokens(right))
397 }
398 _ => "...".to_string(),
399 }
400}