Skip to main content

ryo_analysis/cascade/
spec.rs

1//! CascadeSpec: Cascade分析結果の仕様定義
2//!
3//! # 責務
4//!
5//! CascadeSpecは「何をすべきか」を表現するPolicy層の型。
6//! 実際の実行方法(MutationSpecへの変換)はryo-executorで実装される。
7//!
8//! # 設計意図
9//!
10//! - ryo-analysisはPolicy(方針)を決定する
11//! - ryo-executorは実装(変換・実行)を担当する
12//! - この分離により、分析ロジックと実行ロジックが疎結合になる
13//!
14//! # 例
15//!
16//! ```rust,ignore
17//! use ryo_analysis::cascade::{CascadeAnalyzer, CascadeSpec, CascadeStrategy};
18//!
19//! let analyzer = CascadeAnalyzer::new(&graph, &registry, &derive_index);
20//! let specs: Vec<CascadeSpec> = analyzer.cascade_add_derive(
21//!     &target_path,
22//!     &["Default".to_string()],
23//!     &CascadeStrategy::eager(),
24//! );
25//!
26//! // ryo-executor側でMutationSpecに変換
27//! let mutation_specs: Vec<MutationSpec> = specs.into_iter()
28//!     .map(Into::into)
29//!     .collect();
30//! ```
31
32use crate::SymbolPath;
33use ryo_symbol::SymbolId;
34
35/// Cascade分析結果として生成される変更仕様
36///
37/// MutationSpecへの変換はryo-executorの`From<CascadeSpec>`で実装される。
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum CascadeSpec {
40    /// derive macroを追加
41    ///
42    /// 例: `#[derive(Default)]` を追加し、フィールド型にも伝播
43    ///
44    /// **Note**: `symbol_id` is required. `request_*` fields are for diagnostics only.
45    AddDerive {
46        /// SymbolId - required for O(1) lookup
47        symbol_id: SymbolId,
48        /// 追加するderive macro名
49        derives: Vec<String>,
50    },
51
52    /// manual impl blockを生成
53    ///
54    /// derive不可能な場合(フィールド型がderive未対応)に使用
55    GenerateImpl {
56        /// 対象の型へのパス
57        target: SymbolPath,
58        /// 実装するtrait名
59        trait_name: String,
60        /// new()を自動生成呼び出しするか
61        call_new: bool,
62    },
63
64    /// visibilityを変更
65    ///
66    /// cross-module accessエラーの修正に使用
67    ChangeVisibility {
68        /// SymbolId - required for O(1) lookup
69        symbol_id: SymbolId,
70        /// 新しいvisibility
71        visibility: Visibility,
72    },
73
74    /// use文を追加
75    ///
76    /// 型参照の解決に使用
77    AddUse {
78        /// use文を追加するモジュール
79        target_module: SymbolPath,
80        /// 追加するパス
81        path: String,
82    },
83
84    /// match式に新しいarmを追加
85    ///
86    /// enum variantを追加した際の網羅性エラー修正に使用
87    AddMatchArm {
88        /// 対象モジュールへのSymbolPath
89        target: SymbolPath,
90        /// match式を含む関数名
91        function_name: String,
92        /// 対象のenum型名
93        enum_name: String,
94        /// 追加するパターン (e.g., "Status::Cancelled")
95        pattern: String,
96        /// armのbody (e.g., "todo!()")
97        body: String,
98    },
99
100    /// match式からarmを削除
101    ///
102    /// enum variantを削除した際の不要arm除去に使用
103    RemoveMatchArm {
104        /// 対象モジュールへのSymbolPath
105        target: SymbolPath,
106        /// match式を含む関数名
107        function_name: String,
108        /// 対象のenum型名
109        enum_name: String,
110        /// 削除するパターン (e.g., "Status::Cancelled")
111        pattern: String,
112    },
113}
114
115/// Visibility レベル
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum Visibility {
118    /// Private (default)
119    Private,
120    /// pub(crate)
121    Crate,
122    /// pub(super)
123    Super,
124    /// pub
125    Public,
126}
127
128impl CascadeSpec {
129    /// AddDeriveを作成
130    pub fn add_derive(symbol_id: SymbolId, derives: Vec<String>) -> Self {
131        Self::AddDerive { symbol_id, derives }
132    }
133
134    /// GenerateImplを作成
135    pub fn generate_impl(
136        target: SymbolPath,
137        trait_name: impl Into<String>,
138        call_new: bool,
139    ) -> Self {
140        Self::GenerateImpl {
141            target,
142            trait_name: trait_name.into(),
143            call_new,
144        }
145    }
146
147    /// ChangeVisibilityを作成
148    pub fn change_visibility(symbol_id: SymbolId, visibility: Visibility) -> Self {
149        Self::ChangeVisibility {
150            symbol_id,
151            visibility,
152        }
153    }
154
155    /// AddUseを作成
156    pub fn add_use(target_module: SymbolPath, path: impl Into<String>) -> Self {
157        Self::AddUse {
158            target_module,
159            path: path.into(),
160        }
161    }
162
163    /// AddMatchArmを作成
164    pub fn add_match_arm(
165        target: SymbolPath,
166        function_name: impl Into<String>,
167        enum_name: impl Into<String>,
168        pattern: impl Into<String>,
169        body: impl Into<String>,
170    ) -> Self {
171        Self::AddMatchArm {
172            target,
173            function_name: function_name.into(),
174            enum_name: enum_name.into(),
175            pattern: pattern.into(),
176            body: body.into(),
177        }
178    }
179
180    /// RemoveMatchArmを作成
181    pub fn remove_match_arm(
182        target: SymbolPath,
183        function_name: impl Into<String>,
184        enum_name: impl Into<String>,
185        pattern: impl Into<String>,
186    ) -> Self {
187        Self::RemoveMatchArm {
188            target,
189            function_name: function_name.into(),
190            enum_name: enum_name.into(),
191            pattern: pattern.into(),
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use ryo_symbol::{SymbolKind, SymbolRegistry};
200
201    /// Create a dummy SymbolId for testing purposes
202    fn dummy_symbol_id(name: &str) -> SymbolId {
203        let mut registry = SymbolRegistry::new();
204        let path = SymbolPath::parse(&format!("test_crate::test_{}", name)).unwrap();
205        registry.register(path, SymbolKind::Struct).unwrap()
206    }
207
208    #[test]
209    fn test_cascade_spec_add_derive() {
210        let symbol_id = dummy_symbol_id("config");
211        let spec = CascadeSpec::add_derive(symbol_id, vec!["Default".to_string()]);
212
213        match spec {
214            CascadeSpec::AddDerive {
215                symbol_id: sid,
216                derives,
217            } => {
218                assert_eq!(sid, symbol_id);
219                assert_eq!(derives, vec!["Default".to_string()]);
220            }
221            _ => panic!("Expected AddDerive"),
222        }
223    }
224
225    #[test]
226    fn test_cascade_spec_add_match_arm() {
227        let target_path = SymbolPath::parse("test_crate::handlers").unwrap();
228        let spec = CascadeSpec::add_match_arm(
229            target_path.clone(),
230            "process_status",
231            "Status",
232            "Status::Cancelled",
233            "todo!()",
234        );
235
236        match &spec {
237            CascadeSpec::AddMatchArm {
238                target,
239                function_name,
240                enum_name,
241                pattern,
242                body,
243            } => {
244                assert_eq!(target, &target_path);
245                assert_eq!(function_name, "process_status");
246                assert_eq!(enum_name, "Status");
247                assert_eq!(pattern, "Status::Cancelled");
248                assert_eq!(body, "todo!()");
249            }
250            _ => panic!("Expected AddMatchArm"),
251        }
252    }
253}