Skip to main content

ryo_executor/executor/registry/converters/
spec_alias.rs

1//! SpecAliasConverter: Converts AddSpec / RemoveSpec / ValidateSpec
2//!
3//! Composes existing primitives rather than introducing a dedicated
4//! ASTRegApply impl:
5//! - `AddSpec`     → render `pub type {alias} = Spec<G, T>;` / `SpecWith<G, R, T>;`
6//!   text and delegate to [`AddItemMutation`]
7//! - `RemoveSpec`  → delegate to [`RemoveItemMutation`] with `ItemKind::TypeAlias`
8//! - `ValidateSpec` → read-only, emits no mutations
9//!
10//! This wiring closes the gap surfaced by PoC-1 follow-up: Spec-aware
11//! Suggests (RS001-RS008) detect issues and produce these MutationSpecs,
12//! but `convert_v2` previously bailed with "Unknown spec kind: RemoveSpec".
13
14use crate::engine::ASTRegApply;
15use crate::executor::registry::converters::ResolveTargetSymbol;
16use crate::executor::registry::{ConvertError, MutationConverter};
17use crate::executor::spec::MutationSpec;
18use ryo_analysis::AnalysisContext;
19use ryo_mutations::{AddItemMutation, RemoveItemMutation};
20use ryo_source::ItemKind;
21
22#[derive(Debug, Clone, Default)]
23pub struct SpecAliasConverter;
24
25impl SpecAliasConverter {
26    pub fn new() -> Self {
27        Self
28    }
29}
30
31impl ResolveTargetSymbol for SpecAliasConverter {}
32
33impl MutationConverter for SpecAliasConverter {
34    fn spec_kinds(&self) -> &'static [&'static str] {
35        &["AddSpec", "RemoveSpec", "ValidateSpec"]
36    }
37
38    fn convert_v2(
39        &self,
40        spec: &MutationSpec,
41        ctx: &AnalysisContext,
42    ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
43        match spec {
44            MutationSpec::AddSpec {
45                type_id,
46                module_id,
47                group,
48                alias_name,
49                relations,
50            } => {
51                let wrapped_name = ctx
52                    .registry
53                    .resolve(*type_id)
54                    .map(|p| p.name().to_string())
55                    .ok_or_else(|| {
56                        ConvertError::TargetNotFound(format!(
57                            "AddSpec wrapped type SymbolId {:?} not in registry",
58                            type_id
59                        ))
60                    })?;
61
62                let alias = alias_name
63                    .clone()
64                    .unwrap_or_else(|| format!("{}Spec", wrapped_name));
65
66                let content = if relations.is_empty() {
67                    format!("pub type {} = Spec<{}, {}>;", alias, group, wrapped_name)
68                } else {
69                    let rel_terms: Vec<String> = relations
70                        .iter()
71                        .map(|r| format!("{}<{}>", r.kind.as_type_name(), r.target))
72                        .collect();
73                    let rel_block = if rel_terms.len() == 1 {
74                        rel_terms.into_iter().next().expect("len 1 above")
75                    } else {
76                        format!("({})", rel_terms.join(", "))
77                    };
78                    format!(
79                        "pub type {} = SpecWith<{}, {}, {}>;",
80                        alias, group, rel_block, wrapped_name
81                    )
82                };
83
84                let mutation = AddItemMutation::new(*module_id, content);
85                Ok(vec![Box::new(mutation)])
86            }
87
88            MutationSpec::RemoveSpec { type_id, .. } => {
89                let mutation = RemoveItemMutation::new(*type_id, ItemKind::TypeAlias);
90                Ok(vec![Box::new(mutation)])
91            }
92
93            MutationSpec::ValidateSpec { .. } => {
94                // Read-only check; the actual validation result is surfaced
95                // by Spec-aware Suggests (RS003 InvalidSpecRelation,
96                // RS004 SpecGroupInconsistency, RS005 SpecRelationCycle).
97                Ok(vec![])
98            }
99
100            _ => Err(ConvertError::TypeMismatch {
101                expected: "AddSpec | RemoveSpec | ValidateSpec",
102                actual: spec.kind_name().to_string(),
103            }),
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_spec_alias_converter_spec_kinds() {
114        let converter = SpecAliasConverter::new();
115        assert_eq!(
116            converter.spec_kinds(),
117            &["AddSpec", "RemoveSpec", "ValidateSpec"]
118        );
119    }
120}