ryo-executor 0.2.0

[experimental] Mutation execution engine for RYO - parallel execution, conflict detection, workspace management
Documentation
//! SpecAliasConverter: Converts AddSpec / RemoveSpec / ValidateSpec
//!
//! Composes existing primitives rather than introducing a dedicated
//! ASTRegApply impl:
//! - `AddSpec`     → render `pub type {alias} = Spec<G, T>;` / `SpecWith<G, R, T>;`
//!   text and delegate to [`AddItemMutation`]
//! - `RemoveSpec`  → delegate to [`RemoveItemMutation`] with `ItemKind::TypeAlias`
//! - `ValidateSpec` → read-only, emits no mutations
//!
//! This wiring closes the gap surfaced by PoC-1 follow-up: Spec-aware
//! Suggests (RS001-RS008) detect issues and produce these MutationSpecs,
//! but `convert_v2` previously bailed with "Unknown spec kind: RemoveSpec".

use crate::engine::ASTRegApply;
use crate::executor::registry::converters::ResolveTargetSymbol;
use crate::executor::registry::{ConvertError, MutationConverter};
use crate::executor::spec::MutationSpec;
use ryo_analysis::AnalysisContext;
use ryo_mutations::{AddItemMutation, RemoveItemMutation};
use ryo_source::ItemKind;

#[derive(Debug, Clone, Default)]
pub struct SpecAliasConverter;

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

impl ResolveTargetSymbol for SpecAliasConverter {}

impl MutationConverter for SpecAliasConverter {
    fn spec_kinds(&self) -> &'static [&'static str] {
        &["AddSpec", "RemoveSpec", "ValidateSpec"]
    }

    fn convert_v2(
        &self,
        spec: &MutationSpec,
        ctx: &AnalysisContext,
    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
        match spec {
            MutationSpec::AddSpec {
                type_id,
                module_id,
                group,
                alias_name,
                relations,
            } => {
                let wrapped_name = ctx
                    .registry
                    .resolve(*type_id)
                    .map(|p| p.name().to_string())
                    .ok_or_else(|| {
                        ConvertError::TargetNotFound(format!(
                            "AddSpec wrapped type SymbolId {:?} not in registry",
                            type_id
                        ))
                    })?;

                let alias = alias_name
                    .clone()
                    .unwrap_or_else(|| format!("{}Spec", wrapped_name));

                let content = if relations.is_empty() {
                    format!("pub type {} = Spec<{}, {}>;", alias, group, wrapped_name)
                } else {
                    let rel_terms: Vec<String> = relations
                        .iter()
                        .map(|r| format!("{}<{}>", r.kind.as_type_name(), r.target))
                        .collect();
                    let rel_block = if rel_terms.len() == 1 {
                        rel_terms.into_iter().next().expect("len 1 above")
                    } else {
                        format!("({})", rel_terms.join(", "))
                    };
                    format!(
                        "pub type {} = SpecWith<{}, {}, {}>;",
                        alias, group, rel_block, wrapped_name
                    )
                };

                let mutation = AddItemMutation::new(*module_id, content);
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::RemoveSpec { type_id, .. } => {
                let mutation = RemoveItemMutation::new(*type_id, ItemKind::TypeAlias);
                Ok(vec![Box::new(mutation)])
            }

            MutationSpec::ValidateSpec { .. } => {
                // Read-only check; the actual validation result is surfaced
                // by Spec-aware Suggests (RS003 InvalidSpecRelation,
                // RS004 SpecGroupInconsistency, RS005 SpecRelationCycle).
                Ok(vec![])
            }

            _ => Err(ConvertError::TypeMismatch {
                expected: "AddSpec | RemoveSpec | ValidateSpec",
                actual: spec.kind_name().to_string(),
            }),
        }
    }
}

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

    #[test]
    fn test_spec_alias_converter_spec_kinds() {
        let converter = SpecAliasConverter::new();
        assert_eq!(
            converter.spec_kinds(),
            &["AddSpec", "RemoveSpec", "ValidateSpec"]
        );
    }
}