ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! CascadeSpec → MutationSpec 変換
//!
//! # 責務
//!
//! ryo-analysisで定義されたCascadeSpec(Policy)を
//! 実行可能なMutationSpec(実装)に変換する。
//!
//! # 設計意図
//!
//! - ryo-analysisはPolicy(方針)を決定する
//! - ryo-executorは実装(変換・実行)を担当する
//! - この分離により、分析ロジックと実行ロジックが疎結合になる
//!
//! # Orphan Rule
//!
//! ryo-executorがryo-analysisに依存しており、MutationSpecはryo-executorで
//! 定義されているため、`From<CascadeSpec> for MutationSpec`をここで実装可能。

use ryo_analysis::cascade::{CascadeSpec, Visibility as CascadeVisibility};

use super::spec::{InsertPosition, MutationSpec, MutationTargetSymbol, Visibility};

impl From<CascadeSpec> for MutationSpec {
    fn from(cascade: CascadeSpec) -> Self {
        match cascade {
            CascadeSpec::AddDerive { symbol_id, derives } => MutationSpec::AddDerive {
                target: MutationTargetSymbol::ById(symbol_id),
                derives,
            },

            CascadeSpec::GenerateImpl {
                target,
                trait_name,
                call_new,
            } => {
                let body = if call_new {
                    format!("Self {{ inner: {}::new() }}", target.name())
                } else {
                    "todo!()".to_string()
                };

                let parent = target.parent().unwrap_or_else(|| {
                    ryo_analysis::SymbolPath::parse("test_crate")
                        .expect("literal 'test_crate' is a valid SymbolPath")
                });

                MutationSpec::AddItem {
                    target: MutationTargetSymbol::ByPath(Box::new(parent)),
                    content: format!(
                        "impl {} for {} {{\n    fn default() -> Self {{\n        {}\n    }}\n}}",
                        trait_name,
                        target.name(),
                        body
                    ),
                    position: InsertPosition::Bottom,
                }
            }

            CascadeSpec::ChangeVisibility {
                symbol_id,
                visibility,
            } => MutationSpec::ChangeVisibility {
                target: MutationTargetSymbol::ById(symbol_id),
                visibility: convert_visibility(visibility),
            },

            CascadeSpec::AddUse {
                target_module,
                path,
            } => MutationSpec::AddItem {
                target: MutationTargetSymbol::ByPath(Box::new(target_module)),
                content: format!("use {};", path),
                position: InsertPosition::Top,
            },

            CascadeSpec::AddMatchArm {
                target,
                function_name,
                enum_name,
                pattern,
                body,
            } => {
                // Construct function path from module path + function name
                let fn_path = target
                    .child(&function_name)
                    .unwrap_or_else(|_| target.clone());
                MutationSpec::AddMatchArm {
                    target: MutationTargetSymbol::ByPath(Box::new(fn_path)),
                    enum_name,
                    pattern,
                    body,
                }
            }

            CascadeSpec::RemoveMatchArm {
                target,
                function_name,
                enum_name,
                pattern,
            } => {
                let fn_path = target
                    .child(&function_name)
                    .unwrap_or_else(|_| target.clone());
                MutationSpec::RemoveMatchArm {
                    target: MutationTargetSymbol::ByPath(Box::new(fn_path)),
                    enum_name,
                    pattern,
                }
            }
        }
    }
}

/// CascadeVisibility → Visibility 変換
fn convert_visibility(vis: CascadeVisibility) -> Visibility {
    match vis {
        CascadeVisibility::Private => Visibility::Private,
        CascadeVisibility::Crate => Visibility::PubCrate,
        CascadeVisibility::Super => Visibility::PubSuper,
        CascadeVisibility::Public => Visibility::Pub,
    }
}

/// CascadeSpecのリストをMutationSpecのリストに変換
pub fn convert_cascade_specs(specs: Vec<CascadeSpec>) -> Vec<MutationSpec> {
    specs.into_iter().map(Into::into).collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use ryo_analysis::SymbolPath;
    use ryo_symbol::SymbolId;

    /// Create a dummy SymbolId for testing
    fn dummy_id(index: u32) -> SymbolId {
        SymbolId::parse(&format!("{}v1", index)).expect("valid dummy id")
    }

    #[test]
    fn test_add_derive_conversion() {
        let _path = SymbolPath::parse("test_crate::Config").unwrap();
        let symbol_id = dummy_id(1);
        let cascade = CascadeSpec::AddDerive {
            symbol_id,
            derives: vec!["Default".to_string(), "Debug".to_string()],
        };

        let mutation: MutationSpec = cascade.into();

        match mutation {
            MutationSpec::AddDerive {
                target, derives, ..
            } => {
                assert_eq!(target, MutationTargetSymbol::ById(symbol_id));
                assert_eq!(derives, vec!["Default", "Debug"]);
            }
            _ => panic!("Expected AddDerive"),
        }
    }

    #[test]
    fn test_change_visibility_conversion() {
        let _path = SymbolPath::parse("test_crate::internal::Helper").unwrap();
        let symbol_id = dummy_id(2);
        let cascade = CascadeSpec::ChangeVisibility {
            symbol_id,
            visibility: CascadeVisibility::Public,
        };

        let mutation: MutationSpec = cascade.into();

        match mutation {
            MutationSpec::ChangeVisibility {
                target, visibility, ..
            } => {
                assert_eq!(target, MutationTargetSymbol::ById(symbol_id));
                assert!(matches!(visibility, Visibility::Pub));
            }
            _ => panic!("Expected ChangeVisibility"),
        }
    }

    #[test]
    fn test_add_use_conversion() {
        let path = SymbolPath::parse("test_crate::module").unwrap();
        let cascade = CascadeSpec::AddUse {
            target_module: path.clone(),
            path: "std::collections::HashMap".to_string(),
        };

        let mutation: MutationSpec = cascade.into();

        match mutation {
            MutationSpec::AddItem {
                target,
                content,
                position,
            } => {
                if let MutationTargetSymbol::ByPath(target_path) = target {
                    assert_eq!(*target_path, path);
                } else {
                    panic!("Expected ByPath target");
                }
                assert_eq!(content, "use std::collections::HashMap;");
                assert_eq!(position, InsertPosition::Top);
            }
            _ => panic!("Expected AddItem"),
        }
    }
}