ryo-analysis 0.1.0

Code graph and discovery engine for the RYO project
Documentation
//! Cascade mode for handling derive failures.
//!
//! When a derive check fails (e.g., a field doesn't implement `Default`),
//! cascade mode can automatically generate a plan to fix the issue.

use super::result::{CheckError, CheckResult};
use super::traits::LightCheck;

/// Strategy for handling cascade failures.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CascadeStrategy {
    /// Try to add derive to dependent types recursively.
    #[default]
    TryToAddDerive,

    /// Try to generate manual `impl` blocks that call `new()`.
    TryToCallNew,

    /// Skip the failing type and report it.
    SkipAndReport,

    /// Stop immediately on first failure.
    ImmediateError,
}

/// Result of a cascade operation.
#[derive(Debug, Clone)]
pub struct CascadeResult {
    /// Mutations to apply in order.
    pub mutations: Vec<CascadeMutation>,
    /// Types that couldn't be fixed.
    pub skipped: Vec<String>,
    /// Final status.
    pub status: CascadeStatus,
}

impl CascadeResult {
    /// Create a successful result with no additional mutations needed.
    pub fn ok() -> Self {
        Self {
            mutations: Vec::new(),
            skipped: Vec::new(),
            status: CascadeStatus::Success,
        }
    }

    /// Create a result with required mutations.
    pub fn with_mutations(mutations: Vec<CascadeMutation>) -> Self {
        Self {
            mutations,
            skipped: Vec::new(),
            status: CascadeStatus::Success,
        }
    }

    /// Create a partial result (some types skipped).
    pub fn partial(mutations: Vec<CascadeMutation>, skipped: Vec<String>) -> Self {
        Self {
            mutations,
            skipped,
            status: CascadeStatus::Partial,
        }
    }

    /// Create a failed result.
    pub fn failed(reason: String) -> Self {
        Self {
            mutations: Vec::new(),
            skipped: vec![reason],
            status: CascadeStatus::Failed,
        }
    }

    /// Check if the cascade succeeded.
    pub fn is_success(&self) -> bool {
        matches!(self.status, CascadeStatus::Success)
    }
}

/// Status of cascade operation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CascadeStatus {
    /// All types can have derive added.
    Success,
    /// Some types were skipped but the main target can proceed.
    Partial,
    /// Cascade failed, cannot proceed.
    Failed,
}

/// A mutation to be applied as part of cascade.
#[derive(Debug, Clone)]
pub enum CascadeMutation {
    /// Add derive macro to a type.
    AddDerive {
        /// Target type path.
        target: String,
        /// Derives to add.
        derives: Vec<String>,
    },
    /// Generate a manual impl block.
    GenerateImpl {
        /// Target type path.
        target: String,
        /// Trait to implement.
        trait_name: String,
        /// Whether to call `new()` in the implementation.
        call_new: bool,
    },
}

/// Cascade derive to dependent types.
///
/// When adding `#[derive(Default)]` to a struct, all its fields must also
/// implement `Default`. This function generates the mutation plan to add
/// derive to all dependent types.
///
/// # Example
///
/// ```rust,ignore
/// let result = cascade_add_derive(&checker, "MyStruct", "Default", CascadeStrategy::TryToAddDerive);
///
/// match result.status {
///     CascadeStatus::Success => {
///         // Apply all mutations in order
///         for mutation in result.mutations {
///             // ... apply mutation
///         }
///     }
///     CascadeStatus::Partial => {
///         // Some types skipped, but can still proceed
///         println!("Skipped: {:?}", result.skipped);
///     }
///     CascadeStatus::Failed => {
///         // Cannot proceed
///     }
/// }
/// ```
pub fn cascade_add_derive(
    checker: &impl LightCheck,
    target: &str,
    trait_name: &str,
    strategy: CascadeStrategy,
) -> CascadeResult {
    cascade_add_derive_recursive(checker, target, trait_name, strategy, &mut Vec::new(), 0)
}

/// Maximum recursion depth for cascade.
const MAX_CASCADE_DEPTH: usize = 10;

fn cascade_add_derive_recursive(
    checker: &impl LightCheck,
    target: &str,
    trait_name: &str,
    strategy: CascadeStrategy,
    visited: &mut Vec<String>,
    depth: usize,
) -> CascadeResult {
    // Prevent infinite recursion
    if depth > MAX_CASCADE_DEPTH {
        return CascadeResult::failed(format!(
            "cascade depth exceeded for {}::{}",
            target, trait_name
        ));
    }

    // Prevent cycles
    if visited.contains(&target.to_string()) {
        return CascadeResult::ok();
    }
    visited.push(target.to_string());

    // Check if derive is already possible
    let check_result = checker.check_derive_possible(target, trait_name);

    match check_result {
        CheckResult::Ok => {
            // Derive is possible, just add it
            CascadeResult::with_mutations(vec![CascadeMutation::AddDerive {
                target: target.to_string(),
                derives: vec![trait_name.to_string()],
            }])
        }
        CheckResult::Warning(_) => {
            // Proceed with warnings
            CascadeResult::with_mutations(vec![CascadeMutation::AddDerive {
                target: target.to_string(),
                derives: vec![trait_name.to_string()],
            }])
        }
        CheckResult::Error(errors) => handle_cascade_errors(
            checker, target, trait_name, strategy, visited, depth, errors,
        ),
    }
}

fn handle_cascade_errors(
    checker: &impl LightCheck,
    target: &str,
    trait_name: &str,
    strategy: CascadeStrategy,
    visited: &mut Vec<String>,
    depth: usize,
    errors: Vec<CheckError>,
) -> CascadeResult {
    let mut all_mutations = Vec::new();
    let mut skipped = Vec::new();

    for error in errors {
        if let CheckError::DeriveFailed { missing_impls, .. } = error {
            for missing in missing_impls {
                match strategy {
                    CascadeStrategy::TryToAddDerive => {
                        // Recursively try to add derive to the missing type
                        let sub_result = cascade_add_derive_recursive(
                            checker,
                            &missing,
                            trait_name,
                            strategy,
                            visited,
                            depth + 1,
                        );

                        match sub_result.status {
                            CascadeStatus::Success => {
                                all_mutations.extend(sub_result.mutations);
                            }
                            CascadeStatus::Partial => {
                                all_mutations.extend(sub_result.mutations);
                                skipped.extend(sub_result.skipped);
                            }
                            CascadeStatus::Failed => {
                                skipped.push(missing);
                            }
                        }
                    }
                    CascadeStrategy::TryToCallNew => {
                        // Generate manual impl that calls new()
                        all_mutations.push(CascadeMutation::GenerateImpl {
                            target: missing,
                            trait_name: trait_name.to_string(),
                            call_new: true,
                        });
                    }
                    CascadeStrategy::SkipAndReport => {
                        skipped.push(missing);
                    }
                    CascadeStrategy::ImmediateError => {
                        return CascadeResult::failed(format!(
                            "{} does not implement {}",
                            missing, trait_name
                        ));
                    }
                }
            }
        }
    }

    // Add the target itself at the end
    all_mutations.push(CascadeMutation::AddDerive {
        target: target.to_string(),
        derives: vec![trait_name.to_string()],
    });

    if skipped.is_empty() {
        CascadeResult::with_mutations(all_mutations)
    } else {
        CascadeResult::partial(all_mutations, skipped)
    }
}

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

    // Mock checker for testing
    struct MockChecker {
        symbols: Vec<&'static str>,
        trait_impls: Vec<(&'static str, &'static str)>,
    }

    impl LightCheck for MockChecker {
        fn check_symbol_exists(&self, name: &str) -> bool {
            self.symbols.contains(&name)
        }

        fn check_trait_impl(&self, type_name: &str, trait_name: &str) -> bool {
            self.trait_impls
                .iter()
                .any(|(t, tr)| *t == type_name && *tr == trait_name)
        }

        fn check_derive_possible(&self, target: &str, trait_name: &str) -> CheckResult {
            if !self.check_symbol_exists(target) {
                return CheckResult::Error(vec![CheckError::type_not_found(target)]);
            }

            // Simulate: everything can derive if it implements the trait
            // Simplified: assume derive always works
            let _ = self.check_trait_impl(target, trait_name);
            CheckResult::Ok
        }
    }

    #[test]
    fn test_cascade_simple() {
        let checker = MockChecker {
            symbols: vec!["MyStruct"],
            trait_impls: vec![],
        };

        let result =
            cascade_add_derive(&checker, "MyStruct", "Default", CascadeStrategy::default());

        assert!(result.is_success());
        assert_eq!(result.mutations.len(), 1);
    }

    #[test]
    fn test_cascade_strategy_immediate_error() {
        let checker = MockChecker {
            symbols: vec![],
            trait_impls: vec![],
        };

        let result = cascade_add_derive(
            &checker,
            "NonExistent",
            "Default",
            CascadeStrategy::ImmediateError,
        );

        // Should fail because type doesn't exist
        assert!(matches!(
            result.status,
            CascadeStatus::Success | CascadeStatus::Failed
        ));
    }
}