ryo-source 0.1.0

High-speed Rust AST manipulation engine
Documentation
//! Parallel processing for PureFile.
//!
//! PureFile is `Send + Sync`, enabling true parallel processing:
//!
//! - Share with `Arc<PureFile>` across threads
//! - Use rayon parallel iterators
//! - Parallel analysis of multiple files
//! - COW mutations across threads
//!
//! # Examples
//!
//! ```ignore
//! use std::sync::Arc;
//! use ryo_source::pure::{PureFile, PureParallel};
//!
//! // Parse multiple files
//! let sources = vec!["fn a() {}", "fn b() {}", "fn c() {}"];
//! let files: Vec<PureFile> = PureParallel::parse_all(&sources);
//!
//! // Analyze all in parallel
//! let fn_counts: Vec<usize> = PureParallel::analyze_all(&files, |f| f.functions().len());
//! ```

use super::analysis::{PureDefRefs, PureSymbolTable};
use super::ast::PureFile;
use super::rename::{PureRename, PureRenameResult};

/// Parallel processing utilities for PureFile.
///
/// All operations leverage PureFile's thread-safety for true parallelism.
pub struct PureParallel;

impl PureParallel {
    /// Parse multiple sources in parallel.
    ///
    /// Uses rayon to parse sources concurrently.
    /// Returns successfully parsed files (errors are filtered out).
    #[cfg(feature = "parallel")]
    pub fn parse_all(sources: &[&str]) -> Vec<PureFile> {
        use rayon::prelude::*;

        sources
            .par_iter()
            .filter_map(|src| PureFile::from_source(src).ok())
            .collect()
    }

    /// Parse multiple sources, returning results for all.
    #[cfg(feature = "parallel")]
    pub fn try_parse_all(sources: &[&str]) -> Vec<Result<PureFile, crate::SourceError>> {
        use rayon::prelude::*;

        sources
            .par_iter()
            .map(|src| PureFile::from_source(src))
            .collect()
    }

    /// Analyze multiple files in parallel.
    ///
    /// Applies the analyzer function to each file concurrently.
    #[cfg(feature = "parallel")]
    pub fn analyze_all<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
    where
        F: Fn(&PureFile) -> T + Sync,
        T: Send,
    {
        use rayon::prelude::*;

        files.par_iter().map(|f| analyzer(f)).collect()
    }

    /// Analyze shared files in parallel.
    ///
    /// Uses Arc for zero-copy sharing across threads.
    #[cfg(feature = "parallel")]
    pub fn analyze_shared<F, T>(files: &[std::sync::Arc<PureFile>], analyzer: F) -> Vec<T>
    where
        F: Fn(&PureFile) -> T + Sync,
        T: Send,
    {
        use rayon::prelude::*;

        files.par_iter().map(|f| analyzer(f)).collect()
    }

    /// Build symbol tables for all files in parallel.
    #[cfg(feature = "parallel")]
    pub fn build_symbol_tables(files: &[PureFile]) -> Vec<PureSymbolTable> {
        use rayon::prelude::*;

        files.par_iter().map(|f| PureDefRefs::analyze(f)).collect()
    }

    /// Apply rename to multiple files in parallel (COW pattern).
    ///
    /// Each file is cloned and renamed independently.
    #[cfg(feature = "parallel")]
    pub fn rename_all(
        files: &[PureFile],
        old_name: &str,
        new_name: &str,
    ) -> Vec<(PureFile, PureRenameResult)> {
        use rayon::prelude::*;

        files
            .par_iter()
            .map(|f| PureRename::apply_cow(f, old_name, new_name))
            .collect()
    }

    /// Apply different renames to each file in parallel.
    ///
    /// Each (file, old, new) tuple is processed independently.
    #[cfg(feature = "parallel")]
    pub fn rename_each<'a>(
        tasks: &[(&'a PureFile, &'a str, &'a str)],
    ) -> Vec<(PureFile, PureRenameResult)> {
        use rayon::prelude::*;

        tasks
            .par_iter()
            .map(|(f, old, new)| PureRename::apply_cow(f, old, new))
            .collect()
    }

    /// Transform files in parallel with custom transformer.
    ///
    /// The transformer receives a mutable clone of each file.
    #[cfg(feature = "parallel")]
    pub fn transform_all<F, T>(files: &[PureFile], transformer: F) -> Vec<(PureFile, T)>
    where
        F: Fn(&mut PureFile) -> T + Sync,
        T: Send,
    {
        use rayon::prelude::*;

        files
            .par_iter()
            .map(|f| {
                let mut cloned = f.clone();
                let result = transformer(&mut cloned);
                (cloned, result)
            })
            .collect()
    }

    // Non-rayon versions (always available)

    /// Parse multiple sources sequentially.
    pub fn parse_all_seq(sources: &[&str]) -> Vec<PureFile> {
        sources
            .iter()
            .filter_map(|src| PureFile::from_source(src).ok())
            .collect()
    }

    /// Analyze multiple files sequentially.
    pub fn analyze_all_seq<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
    where
        F: Fn(&PureFile) -> T,
    {
        files.iter().map(analyzer).collect()
    }

    /// Build symbol tables sequentially.
    pub fn build_symbol_tables_seq(files: &[PureFile]) -> Vec<PureSymbolTable> {
        files.iter().map(PureDefRefs::analyze).collect()
    }

    /// Rename in all files sequentially (COW pattern).
    pub fn rename_all_seq(
        files: &[PureFile],
        old_name: &str,
        new_name: &str,
    ) -> Vec<(PureFile, PureRenameResult)> {
        files
            .iter()
            .map(|f| PureRename::apply_cow(f, old_name, new_name))
            .collect()
    }
}

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

    #[test]
    fn test_parse_all_seq() {
        let sources = vec!["fn a() {}", "fn b() {}", "fn c() {}"];
        let files = PureParallel::parse_all_seq(&sources);

        assert_eq!(files.len(), 3);
        assert_eq!(files[0].functions().len(), 1);
        assert_eq!(files[0].functions()[0].name, "a");
    }

    #[test]
    fn test_analyze_all_seq() {
        let sources = vec![
            "fn a() {} fn a2() {}",
            "fn b() {}",
            "fn c() {} fn c2() {} fn c3() {}",
        ];
        let files = PureParallel::parse_all_seq(&sources);

        let counts = PureParallel::analyze_all_seq(&files, |f| f.functions().len());

        assert_eq!(counts, vec![2, 1, 3]);
    }

    #[test]
    fn test_build_symbol_tables_seq() {
        let sources = vec!["fn foo() { let x = 1; }"];
        let files = PureParallel::parse_all_seq(&sources);

        let tables = PureParallel::build_symbol_tables_seq(&files);

        assert_eq!(tables.len(), 1);
        assert!(tables[0].get("foo").is_some());
    }

    #[test]
    fn test_rename_all_seq() {
        let sources = vec!["fn foo() {}", "fn foo() { foo(); }"];
        let files = PureParallel::parse_all_seq(&sources);

        let results = PureParallel::rename_all_seq(&files, "foo", "bar");

        assert_eq!(results.len(), 2);
        assert!(results[0].0.functions()[0].name == "bar");
        assert!(results[1].0.functions()[0].name == "bar");
        assert_eq!(results[0].1.count, 1);
        assert_eq!(results[1].1.count, 2);
    }

    #[test]
    fn test_thread_safety_with_arc() {
        use std::thread;

        let file = PureFile::from_source("fn test() { let x = 1; let y = x + 1; }").unwrap();
        let shared = Arc::new(file);

        let handles: Vec<_> = (0..4)
            .map(|_| {
                let f = Arc::clone(&shared);
                thread::spawn(move || {
                    let table = PureDefRefs::analyze(&f);
                    table.functions().len()
                })
            })
            .collect();

        for handle in handles {
            assert_eq!(handle.join().unwrap(), 1);
        }
    }

    #[test]
    fn test_parallel_cow_mutations() {
        use std::thread;

        let file = PureFile::from_source("fn alpha() {} fn beta() {} fn gamma() {}").unwrap();
        let shared = Arc::new(file);

        let renames = vec![("alpha", "first"), ("beta", "second"), ("gamma", "third")];

        let handles: Vec<_> = renames
            .into_iter()
            .map(|(old, new)| {
                let f = Arc::clone(&shared);
                thread::spawn(move || {
                    let (renamed, result) = PureRename::apply_cow(&f, old, new);
                    (renamed.functions().len(), result.count)
                })
            })
            .collect();

        for handle in handles {
            let (fn_count, rename_count) = handle.join().unwrap();
            assert_eq!(fn_count, 3);
            assert_eq!(rename_count, 1);
        }

        // Original unchanged
        assert!(shared.functions().iter().any(|f| f.name == "alpha"));
    }

    // Rayon-specific tests
    #[cfg(feature = "parallel")]
    mod rayon_tests {
        use super::*;

        #[test]
        fn test_parse_all_parallel() {
            let sources: Vec<&str> = (0..100)
                .map(|i| {
                    // Create unique source for each
                    Box::leak(format!("fn func_{i}() {{}}").into_boxed_str()) as &str
                })
                .collect();

            let files = PureParallel::parse_all(&sources);
            assert_eq!(files.len(), 100);
        }

        #[test]
        fn test_analyze_all_parallel() {
            let sources: Vec<&str> = (0..50)
                .map(|i| Box::leak(format!("fn f{i}() {{}}").into_boxed_str()) as &str)
                .collect();

            let files = PureParallel::parse_all(&sources);
            let counts = PureParallel::analyze_all(&files, |f| f.functions().len());

            assert!(counts.iter().all(|&c| c == 1));
        }

        #[test]
        fn test_build_symbol_tables_parallel() {
            let sources: Vec<&str> = (0..20)
                .map(|i| {
                    Box::leak(format!("fn func{i}() {{ let x = 1; }}").into_boxed_str()) as &str
                })
                .collect();

            let files = PureParallel::parse_all(&sources);
            let tables = PureParallel::build_symbol_tables(&files);

            assert_eq!(tables.len(), 20);
            for (i, table) in tables.iter().enumerate() {
                assert!(table.get(&format!("func{i}")).is_some());
            }
        }

        #[test]
        fn test_rename_all_parallel() {
            let sources = vec![
                "fn foo() {}",
                "fn foo() { foo(); }",
                "fn foo() { foo(); foo(); }",
            ];
            let files = PureParallel::parse_all(&sources);

            let results = PureParallel::rename_all(&files, "foo", "bar");

            assert_eq!(results.len(), 3);
            assert_eq!(results[0].1.count, 1);
            assert_eq!(results[1].1.count, 2);
            assert_eq!(results[2].1.count, 3);
        }

        #[test]
        fn test_transform_all_parallel() {
            let sources = vec!["fn alpha() {}", "fn beta() {}", "fn gamma() {}"];
            let files = PureParallel::parse_all(&sources);

            let results = PureParallel::transform_all(&files, |f| {
                let fn_count = f.functions().len();
                // Mutate the file
                if let Some(func) = f.items.iter_mut().find_map(|item| {
                    if let crate::pure::PureItem::Fn(f) = item {
                        Some(f)
                    } else {
                        None
                    }
                }) {
                    func.name = format!("{}_transformed", func.name);
                }
                fn_count
            });

            assert_eq!(results.len(), 3);
            assert!(results[0].0.functions()[0].name.ends_with("_transformed"));
        }
    }
}