ryo-executor 0.1.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! FieldConverter: Converts MutationSpec::AddField and RemoveField
//!
//! Uses MutationTargetSymbol-based resolution to find the target struct and its file.

use crate::engine::ASTRegApply;
use crate::executor::registry::converters::ResolveTargetSymbol;
use crate::executor::registry::{ConvertError, MutationConverter};
use crate::executor::spec::{MutationSpec, Visibility};
use ryo_analysis::AnalysisContext;
use ryo_mutations::{AddFieldMutation, RemoveFieldMutation};

/// Converter for Field mutations (AddField, RemoveField)
#[derive(Debug, Clone, Default)]
pub struct FieldConverter;

impl FieldConverter {
    pub fn new() -> Self {
        Self
    }
}

// FieldConverter uses the default implementation of ResolveTargetSymbol
impl ResolveTargetSymbol for FieldConverter {}

impl MutationConverter for FieldConverter {
    fn spec_kinds(&self) -> &'static [&'static str] {
        &["AddField", "RemoveField"]
    }

    fn convert_v2(
        &self,
        spec: &MutationSpec,
        ctx: &AnalysisContext,
    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
        match spec {
            MutationSpec::AddField {
                target,
                field_name,
                field_type,
                visibility,
            } => {
                // Resolve MutationTargetSymbol to SymbolId using trait method
                let symbol_id = self.resolve_target_symbol(target, ctx)?;

                let mut mutation = AddFieldMutation::new(symbol_id, field_name, field_type);
                if *visibility == Visibility::Pub {
                    mutation = mutation.public();
                }
                Ok(vec![Box::new(mutation)])
            }
            MutationSpec::RemoveField { target, field_name } => {
                // Resolve MutationTargetSymbol to SymbolId using trait method
                let symbol_id = self.resolve_target_symbol(target, ctx)?;

                let mutation = RemoveFieldMutation::new(symbol_id, field_name);
                Ok(vec![Box::new(mutation)])
            }
            _ => Err(ConvertError::TypeMismatch {
                expected: "AddField or RemoveField",
                actual: spec.kind_name().to_string(),
            }),
        }
    }
}

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

    #[test]
    fn test_field_converter_spec_kinds() {
        let converter = FieldConverter::new();
        assert_eq!(converter.spec_kinds(), &["AddField", "RemoveField"]);
    }

    #[test]
    fn test_field_converter_can_handle_add() {
        use crate::executor::spec::MutationTargetSymbol;
        use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};

        let converter = FieldConverter::new();
        let mut registry = SymbolRegistry::new();
        let path = SymbolPath::parse("test_crate::Config").unwrap();
        let symbol_id = registry.register(path, SymbolKind::Struct).unwrap();

        let spec = MutationSpec::AddField {
            target: MutationTargetSymbol::by_id(symbol_id),
            field_name: "timeout".into(),
            field_type: "u64".into(),
            visibility: Visibility::Pub,
        };
        assert!(converter.can_handle(&spec));
    }

    #[test]
    fn test_field_converter_can_handle_remove() {
        use crate::executor::spec::MutationTargetSymbol;
        use ryo_symbol::{SymbolKind, SymbolPath, SymbolRegistry};

        let converter = FieldConverter::new();
        let mut registry = SymbolRegistry::new();
        let path = SymbolPath::parse("test_crate::Config").unwrap();
        let symbol_id = registry.register(path, SymbolKind::Struct).unwrap();

        let spec = MutationSpec::RemoveField {
            target: MutationTargetSymbol::by_id(symbol_id),
            field_name: "timeout".into(),
        };
        assert!(converter.can_handle(&spec));
    }
}