ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! V2 ASTRegApply implementations for lock-related idiom mutations
//!
//! # Classification: Primitive
//!
//! These detect lock optimization opportunities:
//! - UseAtomic: Detect Mutex<primitive> that could use atomic types
//! - UseRwLock: Detect Mutex that could use RwLock for read-heavy patterns
//! - LockScope: Detect locks held across await points
//!
//! Note: These mutations currently only DETECT opportunities.
//! Actual refactoring is not yet implemented (returns changes: 0).

use ryo_mutations::idiom::{LockScopeMutation, UseAtomicMutation, UseRwLockMutation};
use ryo_mutations::MutationResult;
use ryo_source::pure::{PureFields, PureItem, PureType};
use ryo_symbol::SymbolKind;

use crate::engine::{ASTMutationContext, ASTRegApply};

impl ASTRegApply for UseAtomicMutation {
    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
        let mut opportunities = Vec::new();

        // Find structs and check for atomic opportunities
        let struct_ids: Vec<_> = ctx
            .symbol_registry
            .iter()
            .filter(|(id, path)| {
                if ctx.symbol_registry.kind(*id) != Some(SymbolKind::Struct) {
                    return false;
                }
                // Apply target filter
                if let Some(ref target) = self.target_struct {
                    return path.name() == target;
                }
                true
            })
            .map(|(id, path)| (id, path.name().to_string()))
            .collect();

        for (struct_id, struct_name) in struct_ids {
            if let Some(PureItem::Struct(s)) = ctx.ast_registry.get(struct_id) {
                if let PureFields::Named(fields) = &s.fields {
                    for field in fields {
                        let type_str = match &field.ty {
                            PureType::Path(p) => p.as_str(),
                            _ => continue,
                        };

                        if type_str.contains("Mutex<") {
                            if let Some(atomic_type) = is_atomic_candidate(&field.name) {
                                opportunities.push(format!(
                                    "{}.{}{}",
                                    struct_name, field.name, atomic_type
                                ));
                            }
                        }
                    }
                }
            }
        }

        MutationResult {
            mutation_type: "UseAtomic".to_string(),
            changes: 0, // Detection only
            description: if opportunities.is_empty() {
                "No atomic optimization opportunities found".to_string()
            } else {
                format!(
                    "Found {} atomic opportunities: {}",
                    opportunities.len(),
                    opportunities.join(", ")
                )
            },
        }
    }
}

impl ASTRegApply for UseRwLockMutation {
    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
        let mut opportunities = Vec::new();

        // Find structs and check for RwLock opportunities
        let struct_ids: Vec<_> = ctx
            .symbol_registry
            .iter()
            .filter(|(id, path)| {
                if ctx.symbol_registry.kind(*id) != Some(SymbolKind::Struct) {
                    return false;
                }
                if let Some(ref target) = self.target_struct {
                    return path.name() == target;
                }
                true
            })
            .map(|(id, path)| (id, path.name().to_string()))
            .collect();

        for (struct_id, struct_name) in struct_ids {
            if let Some(PureItem::Struct(s)) = ctx.ast_registry.get(struct_id) {
                if let PureFields::Named(fields) = &s.fields {
                    for field in fields {
                        let type_str = match &field.ty {
                            PureType::Path(p) => p.as_str(),
                            _ => continue,
                        };

                        // Check for Mutex<Collection> patterns
                        if type_str.contains("Mutex<")
                            && (type_str.contains("HashMap")
                                || type_str.contains("BTreeMap")
                                || type_str.contains("Vec<")
                                || type_str.contains("HashSet")
                                || type_str.contains("BTreeSet")
                                || type_str.contains("VecDeque"))
                        {
                            opportunities
                                .push(format!("{}.{}: Mutex → RwLock", struct_name, field.name));
                        }
                    }
                }
            }
        }

        MutationResult {
            mutation_type: "UseRwLock".to_string(),
            changes: 0, // Detection only
            description: if opportunities.is_empty() {
                "No RwLock optimization opportunities found".to_string()
            } else {
                format!(
                    "Found {} RwLock opportunities: {}",
                    opportunities.len(),
                    opportunities.join(", ")
                )
            },
        }
    }
}

impl ASTRegApply for LockScopeMutation {
    fn apply_to_registry(&self, ctx: &mut ASTMutationContext) -> MutationResult {
        let mut opportunities = Vec::new();

        // Find async functions and check for lock scope issues
        let fn_ids: Vec<_> = if let Some(target_id) = self.target_fn {
            // Target function specified: direct lookup
            if let Some(path) = ctx.symbol_registry.resolve(target_id) {
                vec![(target_id, path.name().to_string())]
            } else {
                vec![]
            }
        } else {
            // No target: collect all functions
            ctx.symbol_registry
                .iter()
                .filter(|(id, _)| ctx.symbol_registry.kind(*id) == Some(SymbolKind::Function))
                .map(|(id, path)| (id, path.name().to_string()))
                .collect()
        };

        for (fn_id, fn_name) in fn_ids {
            if let Some(PureItem::Fn(func)) = ctx.ast_registry.get(fn_id) {
                if func.is_async {
                    // Check for lock patterns in async functions
                    // This is a simplified detection - real impl would trace lock guards
                    let body_str = format!("{:?}", func.body);
                    if (body_str.contains("lock()")
                        || body_str.contains("read()")
                        || body_str.contains("write()"))
                        && body_str.contains("await")
                    {
                        opportunities.push(format!("{}: potential lock across await", fn_name));
                    }
                }
            }
        }

        MutationResult {
            mutation_type: "LockScope".to_string(),
            changes: 0, // Detection only
            description: if opportunities.is_empty() {
                "No lock scope issues detected".to_string()
            } else {
                format!(
                    "Found {} potential lock scope issues: {}",
                    opportunities.len(),
                    opportunities.join(", ")
                )
            },
        }
    }
}

/// Check if field name suggests atomic usage
fn is_atomic_candidate(field_name: &str) -> Option<&'static str> {
    let lower = field_name.to_lowercase();

    // Counter-like
    if lower.contains("count")
        || lower.contains("counter")
        || lower.contains("num")
        || lower.contains("total")
        || lower.contains("size")
        || lower.contains("len")
    {
        return Some("AtomicUsize");
    }

    // Flag-like
    if lower.contains("flag")
        || lower.contains("enabled")
        || lower.contains("active")
        || lower.contains("ready")
        || lower.contains("done")
        || lower.starts_with("is_")
    {
        return Some("AtomicBool");
    }

    // ID-like
    if lower.contains("id") || lower.contains("index") || lower.contains("seq") {
        return Some("AtomicU64");
    }

    None
}