ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! IdiomConverter: Converts idiom transformation MutationSpecs to Mutations
//!
//! Handles 16 idiom transformation variants:
//! - OrganizeImports: Sort, dedupe, merge imports
//! - LoopToIterator: Convert for loops to iterator chains
//! - UnwrapToQuestion: Convert unwrap/expect to ? operator
//! - AssignOp: Simplify `a = a + b` to `a += b`
//! - BoolSimplify: Simplify `x == true` to `x`
//! - CloneOnCopy: Remove redundant .clone() on Copy types
//! - CollapsibleIf: Merge nested if statements
//! - ComparisonToMethod: Convert `s == ""` to `s.is_empty()`
//! - RedundantClosure: Remove redundant closures `|x| f(x)` to `f`
//! - IntroduceVariable: Extract repeated expressions to variables
//! - ManualMap: Convert manual match to .map()
//! - MatchToIfLet: Convert simple match to if let
//! - FilterNext: Convert .filter().next() to .find()
//! - MapUnwrapOr: Convert .map().unwrap_or() to .map_or()
//! - NoOpArmToTodo: Replace `_ => {}` with `_ => todo!()`

use crate::engine::ASTRegApply;
use crate::executor::registry::converter::{ConvertError, MutationConverter};
use crate::executor::registry::converters::ResolveTargetSymbol;
use crate::executor::spec::MutationSpec;
use ryo_analysis::AnalysisContext;
use ryo_mutations::{
    AssignOpMutation, BoolSimplifyMutation, CloneOnCopyMutation, CollapsibleIfMutation,
    ComparisonToMethodMutation, FilterNextMutation, IntroduceVariableMutation,
    LoopToIteratorMutation, ManualMapMutation, MapUnwrapOrMutation, MatchToIfLetMutation,
    NoOpArmToTodoMutation, OrganizeImportsMutation, RedundantClosureMutation,
    UnwrapToQuestionMutation,
};
use ryo_source::pure::ToPure;

/// Converter for idiom transformation MutationSpecs
pub struct IdiomConverter;

impl IdiomConverter {
    pub fn new() -> Self {
        Self
    }
}

impl Default for IdiomConverter {
    fn default() -> Self {
        Self::new()
    }
}

// IdiomConverter uses the default implementation of ResolveTargetSymbol
impl ResolveTargetSymbol for IdiomConverter {}

impl MutationConverter for IdiomConverter {
    fn spec_kinds(&self) -> &'static [&'static str] {
        &[
            "OrganizeImports",
            "LoopToIterator",
            "UnwrapToQuestion",
            "AssignOp",
            "BoolSimplify",
            "CloneOnCopy",
            "CollapsibleIf",
            "ComparisonToMethod",
            "RedundantClosure",
            "IntroduceVariable",
            "ManualMap",
            "MatchToIfLet",
            "FilterNext",
            "MapUnwrapOr",
            "NoOpArmToTodo",
        ]
    }

    fn convert_v2(
        &self,
        spec: &MutationSpec,
        _ctx: &AnalysisContext,
    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
        match spec {
            MutationSpec::OrganizeImports {
                deduplicate,
                merge_groups,
                ..
            } => {
                let mutation = OrganizeImportsMutation::new()
                    .with_deduplicate(*deduplicate)
                    .with_merge_groups(*merge_groups);
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::LoopToIterator { target_var, .. } => {
                let mut mutation = LoopToIteratorMutation::new();
                if let Some(var) = target_var {
                    mutation = mutation.with_target(var);
                }
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::UnwrapToQuestion {
                target_fn,
                include_expect,
                ..
            } => {
                let mut mutation = UnwrapToQuestionMutation::new();
                if !include_expect {
                    mutation = mutation.unwrap_only();
                }
                if let Some(fn_id) = target_fn {
                    mutation = mutation.in_function(*fn_id);
                }
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::AssignOp { fn_id, .. } => {
                let mut mutation = AssignOpMutation::new();
                if let Some(id) = fn_id {
                    mutation = mutation.in_function(*id);
                }
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::BoolSimplify { .. } => Ok(vec![Box::new(BoolSimplifyMutation::new())]),

            MutationSpec::CloneOnCopy { .. } => Ok(vec![Box::new(CloneOnCopyMutation::new())]),

            MutationSpec::CollapsibleIf { .. } => Ok(vec![Box::new(CollapsibleIfMutation::new())]),

            MutationSpec::ComparisonToMethod { .. } => {
                Ok(vec![Box::new(ComparisonToMethodMutation::new())])
            }

            MutationSpec::RedundantClosure { .. } => {
                Ok(vec![Box::new(RedundantClosureMutation::new())])
            }

            MutationSpec::IntroduceVariable { expr, var_name, .. } => {
                let syn_expr: syn::Expr = syn::parse_str(expr).map_err(|e| {
                    ConvertError::Parse(format!("Failed to parse expression '{}': {}", expr, e))
                })?;
                let pure_expr = syn_expr.to_pure();
                Ok(vec![Box::new(IntroduceVariableMutation::new(
                    pure_expr, var_name,
                ))])
            }

            MutationSpec::ManualMap { .. } => Ok(vec![Box::new(ManualMapMutation::new())]),

            MutationSpec::MatchToIfLet { .. } => Ok(vec![Box::new(MatchToIfLetMutation::new())]),

            MutationSpec::FilterNext { fn_id, .. } => {
                let mut m = FilterNextMutation::new();
                if let Some(id) = fn_id {
                    m = m.in_function(*id);
                }
                Ok(vec![Box::new(m)])
            }

            MutationSpec::MapUnwrapOr { fn_id, .. } => {
                let mut m = MapUnwrapOrMutation::new();
                if let Some(id) = fn_id {
                    m = m.in_function(*id);
                }
                Ok(vec![Box::new(m)])
            }

            MutationSpec::NoOpArmToTodo {
                module_id,
                replacement,
            } => {
                let mut m = NoOpArmToTodoMutation::new().with_replacement(replacement);
                if let Some(id) = module_id {
                    m = m.in_function(*id);
                }
                Ok(vec![Box::new(m)])
            }

            _ => Err(ConvertError::TypeMismatch {
                expected: "Idiom transformation",
                actual: spec.kind_name().to_string(),
            }),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_idiom_converter_spec_kinds() {
        let converter = IdiomConverter::new();
        assert_eq!(converter.spec_kinds().len(), 15); // Was 16, now 15 (MergeImplBlocks removed)
        assert!(converter.spec_kinds().contains(&"OrganizeImports"));
        assert!(converter.spec_kinds().contains(&"LoopToIterator"));
        assert!(converter.spec_kinds().contains(&"MatchToIfLet"));
        assert!(converter.spec_kinds().contains(&"NoOpArmToTodo"));
    }
}