ryo-source 0.1.0

High-speed Rust AST manipulation engine
Documentation
//! Parallel access patterns for RustAST and PureFile.
//!
//! # Thread Safety Notes
//!
//! ## RustAST (syn-based)
//!
//! `syn::File` is NOT `Send`/`Sync` due to `proc_macro2::Span` containing
//! raw pointers. This means:
//!
//! - Cannot share `RustAST` across threads with `Arc`
//! - Cannot use `thread::spawn` with `RustAST`
//!
//! ## PureFile (span-free)
//!
//! `PureFile` is `Send + Sync` - fully thread-safe! This means:
//!
//! - Can share with `Arc<PureFile>` across threads
//! - Can use rayon parallel iterators directly
//! - Ideal for parallel code analysis
//!
//! ## Practical Patterns for Murmuration
//!
//! 1. **PureFile + Arc**: Share read-only AST across threads
//! 2. **PureFile + COW**: Clone-and-modify for parallel mutations
//! 3. **Parse-per-thread**: Each thread parses source independently
//! 4. **Rayon for CPU-bound**: Use rayon for parallel iteration on collections

use crate::ast::RustAST;
use crate::ops::{Rename, RenameResult};

/// Result of a mutation operation with Copy-on-Write semantics.
#[derive(Debug)]
pub struct MutResult<T> {
    /// The mutated AST (new copy).
    pub ast: RustAST,
    /// The operation result.
    pub result: T,
}

/// Copy-on-Write mutation patterns for safe AST manipulation.
///
/// These patterns ensure the original AST is never modified,
/// making it safe to work with multiple "versions" of the code.
pub struct CowMut;

impl CowMut {
    /// Apply a rename with Copy-on-Write semantics.
    ///
    /// Returns a new AST, leaving the original unchanged.
    pub fn rename(ast: &RustAST, old: &str, new: &str) -> MutResult<RenameResult> {
        let mut cloned = ast.clone();
        let result = Rename::apply(&mut cloned, old, new);
        MutResult {
            ast: cloned,
            result,
        }
    }

    /// Apply multiple independent renames, each producing a new AST.
    ///
    /// Returns one result per rename operation.
    pub fn multi_rename(ast: &RustAST, renames: &[(&str, &str)]) -> Vec<MutResult<RenameResult>> {
        renames
            .iter()
            .map(|(old, new)| Self::rename(ast, old, new))
            .collect()
    }

    /// Chain multiple renames on the same AST.
    ///
    /// Each rename is applied sequentially to the result of the previous.
    pub fn chain_renames(ast: &RustAST, renames: &[(&str, &str)]) -> MutResult<Vec<RenameResult>> {
        let mut current = ast.clone();
        let mut results = Vec::with_capacity(renames.len());

        for (old, new) in renames {
            let result = Rename::apply(&mut current, old, new);
            results.push(result);
        }

        MutResult {
            ast: current,
            result: results,
        }
    }
}

/// Parallel patterns using source strings (thread-safe).
///
/// Work with source code strings across threads, parse in each thread.
pub struct SourceParallel;

impl SourceParallel {
    /// Parse and analyze source in parallel using rayon.
    ///
    /// Each source is parsed independently in the thread pool.
    /// Note: RustAST is not Send, so parsing and analysis happen
    /// together within each thread.
    #[cfg(feature = "parallel")]
    pub fn analyze_sources<F, T>(sources: &[&str], analyzer: F) -> Vec<T>
    where
        F: Fn(&RustAST) -> T + Sync,
        T: Send,
    {
        use rayon::prelude::*;

        // Parse and analyze in one step to avoid sending RustAST across threads
        sources
            .par_iter()
            .filter_map(|src| {
                let ast = RustAST::parse(src).ok()?;
                Some(analyzer(&ast))
            })
            .collect()
    }

    /// Transform multiple sources in parallel.
    ///
    /// Returns (transformed_source, result) for each input.
    /// Note: RustAST is not Send, so parsing and transformation happen
    /// together within each thread.
    #[cfg(feature = "parallel")]
    pub fn transform_sources<F, T>(sources: &[&str], transformer: F) -> Vec<(String, T)>
    where
        F: Fn(&mut RustAST) -> T + Sync,
        T: Send,
    {
        use rayon::prelude::*;

        sources
            .par_iter()
            .filter_map(|src| {
                let mut ast = RustAST::parse(src).ok()?;
                let result = transformer(&mut ast);
                Some((ast.to_string(), result))
            })
            .collect()
    }
}

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

    #[test]
    fn test_cow_rename() {
        let original = RustAST::parse("fn foo() {}").unwrap();
        let original_str = original.to_string();

        // COW mutation
        let result = CowMut::rename(&original, "foo", "bar");

        // Original unchanged
        assert_eq!(original.to_string(), original_str);

        // New AST has the change
        assert!(result.ast.to_string().contains("fn bar"));
        assert_eq!(result.result.count, 1);
    }

    #[test]
    fn test_multi_rename() {
        let ast = RustAST::parse(
            r#"
            fn alpha() {}
            fn beta() {}
            fn gamma() {}
            "#,
        )
        .unwrap();

        let renames = vec![("alpha", "first"), ("beta", "second"), ("gamma", "third")];
        let results = CowMut::multi_rename(&ast, &renames);

        assert_eq!(results.len(), 3);

        // Each result has only its rename applied (independent copies)
        assert!(results[0].ast.to_string().contains("fn first"));
        assert!(results[0].ast.to_string().contains("fn beta")); // others unchanged

        assert!(results[1].ast.to_string().contains("fn second"));
        assert!(results[1].ast.to_string().contains("fn alpha")); // others unchanged

        assert!(results[2].ast.to_string().contains("fn third"));
        assert!(results[2].ast.to_string().contains("fn alpha")); // others unchanged
    }

    #[test]
    fn test_chain_renames() {
        let ast = RustAST::parse(
            r#"
            fn alpha() {}
            fn beta() {}
            "#,
        )
        .unwrap();

        let renames = vec![("alpha", "first"), ("beta", "second")];
        let result = CowMut::chain_renames(&ast, &renames);

        // Both renames applied to the final AST
        assert!(result.ast.to_string().contains("fn first"));
        assert!(result.ast.to_string().contains("fn second"));
        assert!(!result.ast.to_string().contains("alpha"));
        assert!(!result.ast.to_string().contains("beta"));

        // Got results for each step
        assert_eq!(result.result.len(), 2);
    }
}