ryo-mutations 0.1.0

[experimental] Code transformation primitives for Rust source code
Documentation
//! IntroduceVariableMutation: Extract complex expressions into named variables
//!
//! Converts:
//! ```ignore
//! foo(a + b * c, a + b * c);
//! ```
//! Into:
//! ```ignore
//! let product = a + b * c;
//! foo(product, product);
//! ```
//!
//! Use cases:
//! - Extract repeated sub-expressions
//! - Improve readability of complex expressions
//! - Enable debugging by naming intermediate values

use ryo_source::pure::PureExpr;
use ryo_symbol::SymbolId;

use crate::Mutation;

/// Extract an expression into a named variable
#[derive(Debug, Clone)]
pub struct IntroduceVariableMutation {
    /// The expression to extract (matched by structure)
    pub target_expr: PureExpr,
    /// Name for the new variable
    pub var_name: String,
    /// Target function SymbolId. If None, applies to all functions.
    pub target_fn: Option<SymbolId>,
    /// Make the variable mutable
    pub is_mut: bool,
}

impl IntroduceVariableMutation {
    pub fn new(target_expr: PureExpr, var_name: impl Into<String>) -> Self {
        Self {
            target_expr,
            var_name: var_name.into(),
            target_fn: None,
            is_mut: false,
        }
    }

    /// Only apply in a specific function
    pub fn in_function(mut self, id: SymbolId) -> Self {
        self.target_fn = Some(id);
        self
    }

    /// Make the introduced variable mutable
    pub fn mutable(mut self) -> Self {
        self.is_mut = true;
        self
    }
}

impl Mutation for IntroduceVariableMutation {
    fn describe(&self) -> String {
        format!("Introduce variable '{}'", self.var_name)
    }

    fn mutation_type(&self) -> &'static str {
        "IntroduceVariable"
    }

    fn box_clone(&self) -> Box<dyn Mutation> {
        Box::new(self.clone())
    }
}

/// Find duplicate expressions in code that could be extracted
#[derive(Debug, Clone, Default)]
pub struct FindDuplicateExpressions {
    /// Minimum complexity to consider (number of nodes)
    pub min_complexity: usize,
    /// Minimum occurrences to report
    pub min_occurrences: usize,
}

impl FindDuplicateExpressions {
    pub fn new() -> Self {
        Self {
            min_complexity: 3,
            min_occurrences: 2,
        }
    }

    /// Calculate expression complexity (number of nodes)
    pub fn complexity(expr: &PureExpr) -> usize {
        let mut count = 1;
        match expr {
            PureExpr::Binary { left, right, .. } => {
                count += Self::complexity(left) + Self::complexity(right);
            }
            PureExpr::Unary { expr: inner, .. } => {
                count += Self::complexity(inner);
            }
            PureExpr::Call { func, args } => {
                count += Self::complexity(func);
                for arg in args {
                    count += Self::complexity(arg);
                }
            }
            PureExpr::MethodCall { receiver, args, .. } => {
                count += Self::complexity(receiver);
                for arg in args {
                    count += Self::complexity(arg);
                }
            }
            PureExpr::Field { expr: inner, .. } => {
                count += Self::complexity(inner);
            }
            PureExpr::Index { expr: inner, index } => {
                count += Self::complexity(inner) + Self::complexity(index);
            }
            _ => {}
        }
        count
    }
}

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

    fn make_binary_expr() -> PureExpr {
        // a + b * c
        PureExpr::Binary {
            op: "+".to_string(),
            left: Box::new(PureExpr::Path("a".to_string())),
            right: Box::new(PureExpr::Binary {
                op: "*".to_string(),
                left: Box::new(PureExpr::Path("b".to_string())),
                right: Box::new(PureExpr::Path("c".to_string())),
            }),
        }
    }

    #[test]
    fn test_complexity() {
        let simple = PureExpr::Path("x".to_string());
        assert_eq!(FindDuplicateExpressions::complexity(&simple), 1);

        let complex = make_binary_expr();
        assert_eq!(FindDuplicateExpressions::complexity(&complex), 5); // +, a, *, b, c
    }
}