use ryo_source::pure::{MacroDelimiter, PureBlock, PureExpr, PureMatchArm, PureStmt};
use ryo_symbol::SymbolId;
use crate::Mutation;
#[derive(Debug, Clone)]
pub struct NoOpArmToTodoMutation {
pub target_fn: Option<SymbolId>,
pub replacement: String,
}
impl Default for NoOpArmToTodoMutation {
fn default() -> Self {
Self {
target_fn: None,
replacement: "todo".to_string(),
}
}
}
impl NoOpArmToTodoMutation {
pub fn new() -> Self {
Self::default()
}
pub fn in_function(mut self, id: SymbolId) -> Self {
self.target_fn = Some(id);
self
}
pub fn with_replacement(mut self, replacement: impl Into<String>) -> Self {
self.replacement = replacement.into();
self
}
fn is_noop_body(expr: &PureExpr) -> bool {
match expr {
PureExpr::Block { block, .. } => block.stmts.is_empty(),
PureExpr::Tuple(items) => items.is_empty(),
_ => false,
}
}
fn create_replacement_expr(&self) -> PureExpr {
PureExpr::Macro {
name: self.replacement.clone(),
delimiter: MacroDelimiter::Paren,
tokens: "".to_string(),
}
}
fn transform_arms(&self, arms: &mut [PureMatchArm]) -> usize {
let mut changes = 0;
for arm in arms.iter_mut() {
changes += self.transform_expr(&mut arm.body);
if Self::is_noop_body(&arm.body) {
arm.body = self.create_replacement_expr();
changes += 1;
}
}
changes
}
fn transform_expr(&self, expr: &mut PureExpr) -> usize {
let mut changes = 0;
match expr {
PureExpr::Match { arms, expr: inner } => {
changes += self.transform_expr(inner);
changes += self.transform_arms(arms);
}
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::Block { block, .. } => {
changes += self.transform_block(block);
}
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::Binary { left, right, .. } => {
changes += self.transform_expr(left);
changes += self.transform_expr(right);
}
PureExpr::Unary { expr: inner, .. } => {
changes += self.transform_expr(inner);
}
PureExpr::Closure { body, .. } => {
changes += self.transform_expr(body);
}
PureExpr::While { cond, body, .. } => {
changes += self.transform_expr(cond);
changes += self.transform_block(body);
}
PureExpr::Loop { body, .. } => {
changes += self.transform_block(body);
}
PureExpr::For { expr, body, .. } => {
changes += self.transform_expr(expr);
changes += self.transform_block(body);
}
PureExpr::Tuple(items) | PureExpr::Array(items) => {
for item in items {
changes += self.transform_expr(item);
}
}
PureExpr::Struct { fields, .. } => {
for (_, value) in fields {
changes += self.transform_expr(value);
}
}
PureExpr::Index { expr, index } => {
changes += self.transform_expr(expr);
changes += self.transform_expr(index);
}
PureExpr::Field { expr, .. } => {
changes += self.transform_expr(expr);
}
PureExpr::Cast { expr, .. } => {
changes += self.transform_expr(expr);
}
PureExpr::Return(Some(inner))
| PureExpr::Break {
expr: Some(inner), ..
}
| PureExpr::Await(inner)
| PureExpr::Try(inner)
| PureExpr::Ref { expr: inner, .. }
| PureExpr::Let { expr: inner, .. } => {
changes += self.transform_expr(inner);
}
PureExpr::Range { start, end, .. } => {
if let Some(s) = start {
changes += self.transform_expr(s);
}
if let Some(e) = end {
changes += self.transform_expr(e);
}
}
PureExpr::Unsafe(block) | PureExpr::Async { body: block, .. } => {
changes += self.transform_block(block);
}
PureExpr::Repeat { expr, len } => {
changes += self.transform_expr(expr);
changes += self.transform_expr(len);
}
_ => {}
}
changes
}
pub fn transform_block(&self, block: &mut PureBlock) -> usize {
let mut changes = 0;
for stmt in &mut block.stmts {
match stmt {
PureStmt::Local {
init: Some(init_expr),
..
} => {
changes += self.transform_expr(init_expr);
}
PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
changes += self.transform_expr(expr);
}
_ => {}
}
}
changes
}
}
impl Mutation for NoOpArmToTodoMutation {
fn describe(&self) -> String {
format!(
"Replace empty match arms with {}!() (_ => {{}} → _ => {}!())",
self.replacement, self.replacement
)
}
fn mutation_type(&self) -> &'static str {
"NoOpArmToTodo"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_source::pure::PurePattern;
fn create_empty_block() -> PureExpr {
PureExpr::Block {
label: None,
block: PureBlock { stmts: vec![] },
}
}
fn create_unit_tuple() -> PureExpr {
PureExpr::Tuple(vec![])
}
#[test]
fn test_is_noop_body_empty_block() {
assert!(NoOpArmToTodoMutation::is_noop_body(&create_empty_block()));
}
#[test]
fn test_is_noop_body_unit_tuple() {
assert!(NoOpArmToTodoMutation::is_noop_body(&create_unit_tuple()));
}
#[test]
fn test_is_noop_body_non_empty() {
let non_empty = PureExpr::Path("something".to_string());
assert!(!NoOpArmToTodoMutation::is_noop_body(&non_empty));
}
#[test]
fn test_create_replacement_expr_default() {
let mutation = NoOpArmToTodoMutation::new();
let expr = mutation.create_replacement_expr();
match expr {
PureExpr::Macro { name, tokens, .. } => {
assert_eq!(name, "todo");
assert_eq!(tokens, "");
}
_ => panic!("Expected macro expression"),
}
}
#[test]
fn test_create_replacement_expr_custom() {
let mutation = NoOpArmToTodoMutation::new().with_replacement("unreachable");
let expr = mutation.create_replacement_expr();
match expr {
PureExpr::Macro { name, tokens, .. } => {
assert_eq!(name, "unreachable");
assert_eq!(tokens, "");
}
_ => panic!("Expected macro expression"),
}
}
#[test]
fn test_transform_match_arms() {
let mut arms = vec![
PureMatchArm {
pattern: PurePattern::Wild,
guard: None,
body: create_empty_block(),
},
PureMatchArm {
pattern: PurePattern::Wild,
guard: None,
body: PureExpr::Path("existing".to_string()),
},
];
let mutation = NoOpArmToTodoMutation::new();
let changes = mutation.transform_arms(&mut arms);
assert_eq!(changes, 1);
match &arms[0].body {
PureExpr::Macro { name, .. } => assert_eq!(name, "todo"),
_ => panic!("Expected macro"),
}
match &arms[1].body {
PureExpr::Path(p) => assert_eq!(p, "existing"),
_ => panic!("Expected path"),
}
}
}