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 {
284 pattern,
285 ty,
286 init,
287 else_branch,
288 } => {
289 if let Some(init_expr) = init {
290 let (new_init, count) = self.transform_expr(init_expr);
291 (
292 PureStmt::Local {
293 pattern: pattern.clone(),
294 ty: ty.clone(),
295 init: Some(new_init),
296 else_branch: else_branch.clone(),
297 },
298 count,
299 )
300 } else {
301 (stmt.clone(), 0)
302 }
303 }
304 PureStmt::Expr(expr) => {
305 let (new_expr, count) = self.transform_expr(expr);
306 (PureStmt::Expr(new_expr), count)
307 }
308 PureStmt::Semi(expr) => {
309 let (new_expr, count) = self.transform_expr(expr);
310 (PureStmt::Semi(new_expr), count)
311 }
312 _ => (stmt.clone(), 0),
313 }
314 }
315
316 pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
318 if let Some(ref target_fn) = self.in_function {
320 if &func.name != target_fn {
321 return (func.clone(), 0);
322 }
323 }
324
325 let mut new_func = func.clone();
326 let mut count = 0;
327
328 if let WrapTarget::ReturnExpr { function } = &self.target {
330 if &func.name == function {
331 if let Some(last_stmt) = new_func.body.stmts.last_mut() {
333 if let PureStmt::Expr(expr) = last_stmt {
334 *last_stmt = PureStmt::Expr(self.wrap_with_dbg(expr.clone()));
335 count += 1;
336 }
337 }
338 return (new_func, count);
339 }
340 }
341
342 let (new_body, block_count) = self.transform_block(&func.body);
344 new_func.body = new_body;
345 (new_func, count + block_count)
346 }
347}
348
349impl Mutation for DbgWrapMutation {
350 fn describe(&self) -> String {
351 let target_desc = match &self.target {
352 WrapTarget::MethodChain { starts_with } => {
353 format!("method chains starting with .{}()", starts_with)
354 }
355 WrapTarget::MethodCall { method } => {
356 format!(".{}() calls", method)
357 }
358 WrapTarget::Variable { name } => {
359 format!("variable '{}'", name)
360 }
361 WrapTarget::ReturnExpr { function } => {
362 format!("return expression in {}", function)
363 }
364 };
365 format!(
366 "Wrap {} with dbg!() [session: {}]",
367 target_desc, self.marker.session_id
368 )
369 }
370
371 fn mutation_type(&self) -> &'static str {
372 "DbgWrap"
373 }
374
375 fn box_clone(&self) -> Box<dyn Mutation> {
376 Box::new(self.clone())
377 }
378}
379
380fn expr_to_tokens(expr: &PureExpr) -> String {
382 match expr {
384 PureExpr::Path(p) => p.clone(),
385 PureExpr::Lit(l) => l.clone(),
386 PureExpr::MethodCall {
387 receiver,
388 method,
389 args,
390 ..
391 } => {
392 let receiver_str = expr_to_tokens(receiver);
393 let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
394 format!("{}.{}({})", receiver_str, method, args_str.join(", "))
395 }
396 PureExpr::Call { func, args } => {
397 let func_str = expr_to_tokens(func);
398 let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
399 format!("{}({})", func_str, args_str.join(", "))
400 }
401 PureExpr::Binary { op, left, right } => {
402 format!("{} {} {}", expr_to_tokens(left), op, expr_to_tokens(right))
403 }
404 _ => "...".to_string(),
405 }
406}