use ryo_source::pure::{MacroDelimiter, PureBlock, PureExpr, PureFn, PureStmt};
use super::marker::DebugMarker;
use crate::Mutation;
#[derive(Debug, Clone)]
pub enum WrapTarget {
MethodChain { starts_with: String },
MethodCall { method: String },
Variable { name: String },
ReturnExpr { function: String },
}
#[derive(Debug, Clone)]
pub struct DbgWrapMutation {
pub target: WrapTarget,
pub marker: DebugMarker,
pub in_function: Option<String>,
}
impl DbgWrapMutation {
pub fn method_chain(starts_with: impl Into<String>, marker: DebugMarker) -> Self {
Self {
target: WrapTarget::MethodChain {
starts_with: starts_with.into(),
},
marker,
in_function: None,
}
}
pub fn method_call(method: impl Into<String>, marker: DebugMarker) -> Self {
Self {
target: WrapTarget::MethodCall {
method: method.into(),
},
marker,
in_function: None,
}
}
pub fn variable(name: impl Into<String>, marker: DebugMarker) -> Self {
Self {
target: WrapTarget::Variable { name: name.into() },
marker,
in_function: None,
}
}
pub fn return_expr(function: impl Into<String>, marker: DebugMarker) -> Self {
Self {
target: WrapTarget::ReturnExpr {
function: function.into(),
},
marker,
in_function: None,
}
}
pub fn in_function(mut self, name: impl Into<String>) -> Self {
self.in_function = Some(name.into());
self
}
fn matches_target(&self, expr: &PureExpr) -> bool {
match &self.target {
WrapTarget::MethodChain { starts_with } => {
self.is_chain_starting_with(expr, starts_with)
}
WrapTarget::MethodCall { method } => {
matches!(expr, PureExpr::MethodCall { method: m, .. } if m == method)
}
WrapTarget::Variable { name } => {
matches!(expr, PureExpr::Path(p) if p == name)
}
WrapTarget::ReturnExpr { .. } => false, }
}
fn is_chain_starting_with(&self, expr: &PureExpr, method: &str) -> bool {
match expr {
PureExpr::MethodCall {
receiver,
method: m,
..
} => {
if m == method {
!matches!(receiver.as_ref(), PureExpr::MethodCall { .. })
} else {
self.is_chain_starting_with(receiver, method)
}
}
_ => false,
}
}
fn wrap_with_dbg(&self, expr: PureExpr) -> PureExpr {
let marker_str = self.marker.to_marker_string();
PureExpr::Macro {
name: "dbg".to_string(),
delimiter: MacroDelimiter::Paren,
tokens: format!("\"{}\", {}", marker_str, expr_to_tokens(&expr)),
}
}
fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
if self.matches_target(expr) {
return (self.wrap_with_dbg(expr.clone()), 1);
}
match expr {
PureExpr::MethodCall {
receiver,
method,
args,
..
} => {
let (new_receiver, c1) = self.transform_expr(receiver);
let mut total = c1;
let new_args: Vec<_> = args
.iter()
.map(|a| {
let (new_a, c) = self.transform_expr(a);
total += c;
new_a
})
.collect();
(
PureExpr::MethodCall {
receiver: Box::new(new_receiver),
method: method.clone(),
turbofish: None,
args: new_args,
},
total,
)
}
PureExpr::Call { func, args } => {
let (new_func, mut total) = self.transform_expr(func);
let new_args: Vec<_> = args
.iter()
.map(|a| {
let (new_a, c) = self.transform_expr(a);
total += c;
new_a
})
.collect();
(
PureExpr::Call {
func: Box::new(new_func),
args: new_args,
},
total,
)
}
PureExpr::Binary { op, left, right } => {
let (new_left, c1) = self.transform_expr(left);
let (new_right, c2) = self.transform_expr(right);
(
PureExpr::Binary {
op: op.clone(),
left: Box::new(new_left),
right: Box::new(new_right),
},
c1 + c2,
)
}
PureExpr::Block { label, block } => {
let (new_block, count) = self.transform_block(block);
(
PureExpr::Block {
label: label.clone(),
block: new_block,
},
count,
)
}
PureExpr::If {
cond,
then_branch,
else_branch,
} => {
let (new_cond, c1) = self.transform_expr(cond);
let (new_then, c2) = self.transform_block(then_branch);
let (new_else, c3) = if let Some(e) = else_branch {
let (ne, c) = self.transform_expr(e);
(Some(Box::new(ne)), c)
} else {
(None, 0)
};
(
PureExpr::If {
cond: Box::new(new_cond),
then_branch: new_then,
else_branch: new_else,
},
c1 + c2 + c3,
)
}
PureExpr::Closure {
params, ret, body, ..
} => {
let (new_body, count) = self.transform_expr(body);
(
PureExpr::Closure {
is_async: false,
is_move: false,
params: params.clone(),
ret: ret.clone(),
body: Box::new(new_body),
},
count,
)
}
_ => (expr.clone(), 0),
}
}
fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
let mut count = 0;
let new_stmts: Vec<_> = block
.stmts
.iter()
.map(|stmt| {
let (new_stmt, c) = self.transform_stmt(stmt);
count += c;
new_stmt
})
.collect();
(PureBlock { stmts: new_stmts }, count)
}
fn transform_stmt(&self, stmt: &PureStmt) -> (PureStmt, usize) {
match stmt {
PureStmt::Local { pattern, ty, init } => {
if let Some(init_expr) = init {
let (new_init, count) = self.transform_expr(init_expr);
(
PureStmt::Local {
pattern: pattern.clone(),
ty: ty.clone(),
init: Some(new_init),
},
count,
)
} else {
(stmt.clone(), 0)
}
}
PureStmt::Expr(expr) => {
let (new_expr, count) = self.transform_expr(expr);
(PureStmt::Expr(new_expr), count)
}
PureStmt::Semi(expr) => {
let (new_expr, count) = self.transform_expr(expr);
(PureStmt::Semi(new_expr), count)
}
_ => (stmt.clone(), 0),
}
}
pub fn transform_fn(&self, func: &PureFn) -> (PureFn, usize) {
if let Some(ref target_fn) = self.in_function {
if &func.name != target_fn {
return (func.clone(), 0);
}
}
let mut new_func = func.clone();
let mut count = 0;
if let WrapTarget::ReturnExpr { function } = &self.target {
if &func.name == function {
if let Some(last_stmt) = new_func.body.stmts.last_mut() {
if let PureStmt::Expr(expr) = last_stmt {
*last_stmt = PureStmt::Expr(self.wrap_with_dbg(expr.clone()));
count += 1;
}
}
return (new_func, count);
}
}
let (new_body, block_count) = self.transform_block(&func.body);
new_func.body = new_body;
(new_func, count + block_count)
}
}
impl Mutation for DbgWrapMutation {
fn describe(&self) -> String {
let target_desc = match &self.target {
WrapTarget::MethodChain { starts_with } => {
format!("method chains starting with .{}()", starts_with)
}
WrapTarget::MethodCall { method } => {
format!(".{}() calls", method)
}
WrapTarget::Variable { name } => {
format!("variable '{}'", name)
}
WrapTarget::ReturnExpr { function } => {
format!("return expression in {}", function)
}
};
format!(
"Wrap {} with dbg!() [session: {}]",
target_desc, self.marker.session_id
)
}
fn mutation_type(&self) -> &'static str {
"DbgWrap"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
fn expr_to_tokens(expr: &PureExpr) -> String {
match expr {
PureExpr::Path(p) => p.clone(),
PureExpr::Lit(l) => l.clone(),
PureExpr::MethodCall {
receiver,
method,
args,
..
} => {
let receiver_str = expr_to_tokens(receiver);
let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
format!("{}.{}({})", receiver_str, method, args_str.join(", "))
}
PureExpr::Call { func, args } => {
let func_str = expr_to_tokens(func);
let args_str: Vec<_> = args.iter().map(expr_to_tokens).collect();
format!("{}({})", func_str, args_str.join(", "))
}
PureExpr::Binary { op, left, right } => {
format!("{} {} {}", expr_to_tokens(left), op, expr_to_tokens(right))
}
_ => "...".to_string(),
}
}