use ryo_source::pure::{
MacroDelimiter, PureBlock, PureClosureParam, PureExpr, PureFn, PurePattern, PureStmt,
};
use super::marker::DebugMarker;
use crate::Mutation;
#[derive(Debug, Clone)]
pub struct InsertInspectMutation {
pub after_method: String,
pub marker: DebugMarker,
pub var_name: String,
pub in_function: Option<String>,
}
impl InsertInspectMutation {
pub fn new(after_method: impl Into<String>, marker: DebugMarker) -> Self {
Self {
after_method: after_method.into(),
marker,
var_name: "_x".to_string(),
in_function: None,
}
}
pub fn with_var_name(mut self, name: impl Into<String>) -> Self {
self.var_name = name.into();
self
}
pub fn in_function(mut self, name: impl Into<String>) -> Self {
self.in_function = Some(name.into());
self
}
fn transform_expr(&self, expr: &PureExpr) -> (PureExpr, usize) {
match expr {
PureExpr::MethodCall {
receiver,
method,
args,
..
} => {
let (new_receiver, mut count) = self.transform_expr(receiver);
if method == &self.after_method {
let method_call = PureExpr::MethodCall {
receiver: Box::new(new_receiver),
method: method.clone(),
turbofish: None,
args: args.clone(),
};
let inspect_call = self.build_inspect_call(method_call);
count += 1;
(inspect_call, count)
} else {
let new_expr = PureExpr::MethodCall {
receiver: Box::new(new_receiver),
method: method.clone(),
turbofish: None,
args: args.clone(),
};
(new_expr, count)
}
}
PureExpr::Call { func, args } => {
let (new_func, mut count) = self.transform_expr(func);
let mut new_args = Vec::new();
for arg in args {
let (new_arg, c) = self.transform_expr(arg);
new_args.push(new_arg);
count += c;
}
(
PureExpr::Call {
func: Box::new(new_func),
args: new_args,
},
count,
)
}
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::Unary { op, expr: inner } => {
let (new_inner, count) = self.transform_expr(inner);
(
PureExpr::Unary {
op: op.clone(),
expr: Box::new(new_inner),
},
count,
)
}
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,
)
}
PureExpr::Match { expr: e, arms } => {
let (new_expr, mut count) = self.transform_expr(e);
let new_arms = arms
.iter()
.map(|arm| {
let (new_body, c) = self.transform_expr(&arm.body);
count += c;
ryo_source::pure::PureMatchArm {
pattern: arm.pattern.clone(),
guard: arm.guard.clone(),
body: new_body,
}
})
.collect();
(
PureExpr::Match {
expr: Box::new(new_expr),
arms: new_arms,
},
count,
)
}
_ => (expr.clone(), 0),
}
}
fn build_inspect_call(&self, receiver: PureExpr) -> PureExpr {
let marker_str = self.marker.to_marker_string();
let closure = PureExpr::Closure {
is_async: false,
is_move: false,
params: vec![PureClosureParam::untyped(PurePattern::Ident {
name: self.var_name.clone(),
is_mut: false,
})],
ret: None,
body: Box::new(PureExpr::Block {
label: None,
block: PureBlock {
stmts: vec![PureStmt::Semi(PureExpr::Macro {
name: "dbg".to_string(),
delimiter: MacroDelimiter::Paren,
tokens: format!("\"{}\", &{}", marker_str, self.var_name),
})],
},
}),
};
PureExpr::MethodCall {
receiver: Box::new(receiver),
method: "inspect".to_string(),
turbofish: None,
args: vec![closure],
}
}
fn transform_block(&self, block: &PureBlock) -> (PureBlock, usize) {
let mut count = 0;
let new_stmts = 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 (new_body, count) = self.transform_block(&func.body);
let mut new_func = func.clone();
new_func.body = new_body;
(new_func, count)
}
}
impl Mutation for InsertInspectMutation {
fn describe(&self) -> String {
format!(
"Insert .inspect() after .{}() [session: {}]",
self.after_method, self.marker.session_id
)
}
fn mutation_type(&self) -> &'static str {
"InsertInspect"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}