use ryo_source::pure::{PureBlock, PureExpr, PureStmt};
use ryo_symbol::SymbolId;
use crate::Mutation;
#[derive(Debug, Clone, Default)]
pub struct CollapsibleIfMutation {
pub target_fn: Option<SymbolId>,
}
impl CollapsibleIfMutation {
pub fn new() -> Self {
Self::default()
}
pub fn in_function(mut self, id: SymbolId) -> Self {
self.target_fn = Some(id);
self
}
fn is_single_if_block(block: &PureBlock) -> Option<(&PureExpr, &PureBlock)> {
if block.stmts.len() != 1 {
return None;
}
let expr = match &block.stmts[0] {
PureStmt::Expr(e) | PureStmt::Semi(e) => e,
_ => return None,
};
match expr {
PureExpr::If {
cond,
then_branch,
else_branch: None,
} => Some((cond.as_ref(), then_branch)),
_ => None,
}
}
fn transform_expr(&self, expr: &mut PureExpr) -> usize {
let mut changes = 0;
if let PureExpr::If {
cond,
then_branch,
else_branch: None,
} = expr
{
changes += self.transform_expr(cond);
changes += self.transform_block(then_branch);
if let Some((inner_cond, inner_body)) = Self::is_single_if_block(then_branch) {
let outer_cond =
std::mem::replace(cond.as_mut(), PureExpr::Path("__placeholder".to_string()));
let inner_cond = inner_cond.clone();
let inner_body = inner_body.clone();
let new_cond = PureExpr::Binary {
op: "&&".to_string(),
left: Box::new(outer_cond),
right: Box::new(inner_cond),
};
*expr = PureExpr::If {
cond: Box::new(new_cond),
then_branch: inner_body,
else_branch: None,
};
return changes + 1;
}
}
match expr {
PureExpr::Binary { left, right, .. } => {
changes += self.transform_expr(left);
changes += self.transform_expr(right);
}
PureExpr::Unary { expr: inner, .. } => {
changes += self.transform_expr(inner);
}
PureExpr::Call { func, args } => {
changes += self.transform_expr(func);
for arg in args {
changes += self.transform_expr(arg);
}
}
PureExpr::MethodCall { receiver, args, .. } => {
changes += self.transform_expr(receiver);
for arg in args {
changes += self.transform_expr(arg);
}
}
PureExpr::Block { block, .. } => {
changes += self.transform_block(block);
}
PureExpr::If {
cond,
then_branch,
else_branch,
} => {
changes += self.transform_expr(cond);
changes += self.transform_block(then_branch);
if let Some(else_expr) = else_branch {
changes += self.transform_expr(else_expr);
}
}
PureExpr::Match { expr: e, arms } => {
changes += self.transform_expr(e);
for arm in arms {
changes += self.transform_expr(&mut arm.body);
}
}
PureExpr::Loop { body: block, .. } | PureExpr::While { body: block, .. } => {
changes += self.transform_block(block);
}
PureExpr::For {
expr: iter_expr,
body,
..
} => {
changes += self.transform_expr(iter_expr);
changes += self.transform_block(body);
}
PureExpr::Closure { body, .. } => {
changes += self.transform_expr(body);
}
_ => {}
}
changes
}
pub fn transform_block(&self, block: &mut PureBlock) -> usize {
let mut changes = 0;
for stmt in &mut block.stmts {
changes += self.transform_stmt(stmt);
}
changes
}
fn transform_stmt(&self, stmt: &mut PureStmt) -> usize {
match stmt {
PureStmt::Local { init: Some(e), .. } => self.transform_expr(e),
PureStmt::Semi(e) | PureStmt::Expr(e) => self.transform_expr(e),
_ => 0,
}
}
}
impl Mutation for CollapsibleIfMutation {
fn describe(&self) -> String {
"Collapse nested if statements (if a { if b { } } → if a && b { })".to_string()
}
fn mutation_type(&self) -> &'static str {
"CollapsibleIf"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_single_if_block() {
let block = PureBlock {
stmts: vec![PureStmt::Expr(PureExpr::If {
cond: Box::new(PureExpr::Path("b".to_string())),
then_branch: PureBlock { stmts: vec![] },
else_branch: None,
})],
};
assert!(CollapsibleIfMutation::is_single_if_block(&block).is_some());
let block2 = PureBlock {
stmts: vec![PureStmt::Expr(PureExpr::If {
cond: Box::new(PureExpr::Path("b".to_string())),
then_branch: PureBlock { stmts: vec![] },
else_branch: Some(Box::new(PureExpr::Block {
label: None,
block: PureBlock { stmts: vec![] },
})),
})],
};
assert!(CollapsibleIfMutation::is_single_if_block(&block2).is_none());
let block3 = PureBlock {
stmts: vec![
PureStmt::Expr(PureExpr::Path("x".to_string())),
PureStmt::Expr(PureExpr::Path("y".to_string())),
],
};
assert!(CollapsibleIfMutation::is_single_if_block(&block3).is_none());
}
}