ryo_executor/executor/registry/converters/
stmt.rs1use crate::engine::ASTRegApply;
10use crate::executor::registry::converters::ResolveTargetSymbol;
11use crate::executor::registry::{ConvertError, MutationConverter};
12use crate::executor::spec::{MutationSpec, StmtInsertPosition};
13use ryo_analysis::AnalysisContext;
14use ryo_analysis::SymbolPath;
15use ryo_mutations::basic::stmt::{
16 InsertPosition, InsertStatementMutation, RemoveStatementMutation, ReplaceExprAtMutation,
17 ReplaceExprMutation, ReplaceStatementMutation,
18};
19use ryo_source::pure::{PureExpr, PureStmt, ToPure};
20
21#[derive(Debug, Clone, Default)]
23pub struct StmtConverter;
24
25impl StmtConverter {
26 pub fn new() -> Self {
27 Self
28 }
29
30 pub fn parse_expr(expr_str: &str) -> Result<PureExpr, ConvertError> {
32 let expr: syn::Expr = syn::parse_str(expr_str).map_err(|e| {
33 ConvertError::Parse(format!("Failed to parse expression '{}': {}", expr_str, e))
34 })?;
35 Ok(expr.to_pure())
36 }
37
38 pub fn parse_stmt(stmt_str: &str) -> Result<PureStmt, ConvertError> {
40 let stmt_with_semi = if stmt_str.ends_with(';') {
42 stmt_str.to_string()
43 } else {
44 format!("{};", stmt_str)
45 };
46
47 let stmt: syn::Stmt = syn::parse_str(&stmt_with_semi).map_err(|e| {
48 ConvertError::Parse(format!("Failed to parse statement '{}': {}", stmt_str, e))
49 })?;
50
51 Ok(stmt.to_pure())
53 }
54
55 fn convert_position(pos: &StmtInsertPosition) -> InsertPosition {
57 match pos {
58 StmtInsertPosition::Start => InsertPosition::Start,
59 StmtInsertPosition::End => InsertPosition::End,
60 StmtInsertPosition::BeforePattern => InsertPosition::BeforePattern,
61 StmtInsertPosition::AfterPattern => InsertPosition::AfterPattern,
62 }
63 }
64}
65
66impl ResolveTargetSymbol for StmtConverter {}
68
69impl MutationConverter for StmtConverter {
70 fn spec_kinds(&self) -> &'static [&'static str] {
71 &[
72 "ReplaceExpr",
73 "RemoveStatement",
74 "InsertStatement",
75 "ReplaceStatement",
76 ]
77 }
78
79 fn convert_v2(
80 &self,
81 spec: &MutationSpec,
82 ctx: &AnalysisContext,
83 ) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
84 match spec {
85 MutationSpec::ReplaceExpr {
86 fn_id,
87 old_expr,
88 new_expr,
89 replace_all,
90 symbol_path,
91 ..
92 } => {
93 let new = Self::parse_expr(new_expr)?;
94
95 if let Some(path_str) = symbol_path {
97 let path = SymbolPath::parse(path_str).map_err(|e| {
98 ConvertError::Parse(format!("Invalid symbol_path '{}': {}", path_str, e))
99 })?;
100
101 if !path.has_body_segment() {
102 return Err(ConvertError::Parse(format!(
103 "symbol_path '{}' must contain $body segment for position-based replacement",
104 path_str
105 )));
106 }
107
108 let (fn_path, indices) = path.split_at_body().ok_or_else(|| {
109 ConvertError::Parse(format!(
110 "Failed to extract function path from symbol_path '{}'",
111 path_str
112 ))
113 })?;
114
115 let fn_id = ctx.registry.lookup(&fn_path).ok_or_else(|| {
117 ConvertError::TargetNotFound(format!(
118 "Function '{}' not found in registry",
119 fn_path
120 ))
121 })?;
122
123 let mutation = ReplaceExprAtMutation::new(fn_id, indices, new);
124 return Ok(vec![Box::new(mutation)]);
125 }
126
127 let old = Self::parse_expr(old_expr)?;
128
129 let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
131 let mut mutation = ReplaceExprMutation::new(old.clone(), new.clone(), *id);
132 mutation.replace_all = *replace_all;
133 vec![Box::new(mutation)]
134 } else {
135 use ryo_analysis::SymbolKind;
136 ctx.registry
137 .iter()
138 .filter(|(id, _)| {
139 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
140 })
141 .map(|(id, _)| {
142 let mut mutation =
143 ReplaceExprMutation::new(old.clone(), new.clone(), id);
144 mutation.replace_all = *replace_all;
145 Box::new(mutation) as Box<dyn ASTRegApply>
146 })
147 .collect()
148 };
149
150 Ok(mutations)
151 }
152
153 MutationSpec::RemoveStatement {
154 fn_id,
155 pattern,
156 remove_all,
157 ..
158 } => {
159 let target_stmt = Self::parse_stmt(pattern)?;
160
161 let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
163 let mut mutation =
164 RemoveStatementMutation::new(target_stmt.clone(), pattern.clone(), *id);
165 mutation.remove_all = *remove_all;
166 vec![Box::new(mutation)]
167 } else {
168 use ryo_analysis::SymbolKind;
169 ctx.registry
170 .iter()
171 .filter(|(id, _)| {
172 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
173 })
174 .map(|(id, _)| {
175 let mut mutation = RemoveStatementMutation::new(
176 target_stmt.clone(),
177 pattern.clone(),
178 id,
179 );
180 mutation.remove_all = *remove_all;
181 Box::new(mutation) as Box<dyn ASTRegApply>
182 })
183 .collect()
184 };
185
186 Ok(mutations)
187 }
188
189 MutationSpec::InsertStatement {
190 fn_id,
191 stmt,
192 position,
193 reference_pattern,
194 ..
195 } => {
196 let pure_stmt = Self::parse_stmt(stmt)?;
197 let reference_stmt = reference_pattern
198 .as_ref()
199 .map(|p| Self::parse_stmt(p))
200 .transpose()?;
201
202 let mut mutation = InsertStatementMutation::new(pure_stmt, *fn_id);
203 mutation.position = Self::convert_position(position);
204 mutation.reference_stmt = reference_stmt;
205
206 Ok(vec![Box::new(mutation)])
207 }
208
209 MutationSpec::ReplaceStatement {
210 fn_id,
211 old_stmt,
212 new_stmt,
213 ..
214 } => {
215 let old = Self::parse_stmt(old_stmt)?;
216 let new = Self::parse_stmt(new_stmt)?;
217
218 let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
220 vec![Box::new(ReplaceStatementMutation::new(
221 old.clone(),
222 new.clone(),
223 *id,
224 ))]
225 } else {
226 use ryo_analysis::SymbolKind;
227 ctx.registry
228 .iter()
229 .filter(|(id, _)| {
230 matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
231 })
232 .map(|(id, _)| {
233 Box::new(ReplaceStatementMutation::new(old.clone(), new.clone(), id))
234 as Box<dyn ASTRegApply>
235 })
236 .collect()
237 };
238
239 Ok(mutations)
240 }
241
242 _ => Err(ConvertError::TypeMismatch {
243 expected: "ReplaceExpr|RemoveStatement|InsertStatement|ReplaceStatement",
244 actual: spec.kind_name().to_string(),
245 }),
246 }
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use ryo_symbol::SymbolId;
254
255 #[test]
256 fn test_stmt_converter_spec_kinds() {
257 let converter = StmtConverter::new();
258 let kinds = converter.spec_kinds();
259 assert!(kinds.contains(&"ReplaceExpr"));
260 assert!(kinds.contains(&"RemoveStatement"));
261 assert!(kinds.contains(&"InsertStatement"));
262 assert!(kinds.contains(&"ReplaceStatement"));
263 }
264
265 #[test]
266 fn test_stmt_converter_can_handle() {
267 let converter = StmtConverter::new();
268
269 let replace_expr_spec = MutationSpec::ReplaceExpr {
270 module_id: None,
271 fn_id: None,
272 old_expr: "a + b".into(),
273 new_expr: "c + d".into(),
274 replace_all: true,
275 symbol_path: None,
276 };
277 assert!(converter.can_handle(&replace_expr_spec));
278
279 let remove_stmt_spec = MutationSpec::RemoveStatement {
280 module_id: None,
281 fn_id: None,
282 pattern: "println!".into(),
283 remove_all: true,
284 symbol_path: None,
285 };
286 assert!(converter.can_handle(&remove_stmt_spec));
287
288 let insert_stmt_spec = MutationSpec::InsertStatement {
289 module_id: None,
290 fn_id: SymbolId::default(),
291 stmt: "let x = 1".into(),
292 position: StmtInsertPosition::Start,
293 reference_pattern: None,
294 symbol_path: None,
295 };
296 assert!(converter.can_handle(&insert_stmt_spec));
297
298 let replace_stmt_spec = MutationSpec::ReplaceStatement {
299 module_id: None,
300 fn_id: None,
301 old_stmt: "let x = 1".into(),
302 new_stmt: "let x = 2".into(),
303 symbol_path: None,
304 };
305 assert!(converter.can_handle(&replace_stmt_spec));
306 }
307}