use crate::engine::ASTRegApply;
use crate::executor::registry::converters::ResolveTargetSymbol;
use crate::executor::registry::{ConvertError, MutationConverter};
use crate::executor::spec::{MutationSpec, StmtInsertPosition};
use ryo_analysis::AnalysisContext;
use ryo_analysis::SymbolPath;
use ryo_mutations::basic::stmt::{
InsertPosition, InsertStatementMutation, RemoveStatementMutation, ReplaceExprAtMutation,
ReplaceExprMutation, ReplaceStatementMutation,
};
use ryo_source::pure::{PureExpr, PureStmt, ToPure};
#[derive(Debug, Clone, Default)]
pub struct StmtConverter;
impl StmtConverter {
pub fn new() -> Self {
Self
}
pub fn parse_expr(expr_str: &str) -> Result<PureExpr, ConvertError> {
let expr: syn::Expr = syn::parse_str(expr_str).map_err(|e| {
ConvertError::Parse(format!("Failed to parse expression '{}': {}", expr_str, e))
})?;
Ok(expr.to_pure())
}
pub fn parse_stmt(stmt_str: &str) -> Result<PureStmt, ConvertError> {
let stmt_with_semi = if stmt_str.ends_with(';') {
stmt_str.to_string()
} else {
format!("{};", stmt_str)
};
let stmt: syn::Stmt = syn::parse_str(&stmt_with_semi).map_err(|e| {
ConvertError::Parse(format!("Failed to parse statement '{}': {}", stmt_str, e))
})?;
Ok(stmt.to_pure())
}
fn convert_position(pos: &StmtInsertPosition) -> InsertPosition {
match pos {
StmtInsertPosition::Start => InsertPosition::Start,
StmtInsertPosition::End => InsertPosition::End,
StmtInsertPosition::BeforePattern => InsertPosition::BeforePattern,
StmtInsertPosition::AfterPattern => InsertPosition::AfterPattern,
}
}
}
impl ResolveTargetSymbol for StmtConverter {}
impl MutationConverter for StmtConverter {
fn spec_kinds(&self) -> &'static [&'static str] {
&[
"ReplaceExpr",
"RemoveStatement",
"InsertStatement",
"ReplaceStatement",
]
}
fn convert_v2(
&self,
spec: &MutationSpec,
ctx: &AnalysisContext,
) -> Result<Vec<Box<dyn ASTRegApply>>, ConvertError> {
match spec {
MutationSpec::ReplaceExpr {
fn_id,
old_expr,
new_expr,
replace_all,
symbol_path,
..
} => {
let new = Self::parse_expr(new_expr)?;
if let Some(path_str) = symbol_path {
let path = SymbolPath::parse(path_str).map_err(|e| {
ConvertError::Parse(format!("Invalid symbol_path '{}': {}", path_str, e))
})?;
if !path.has_body_segment() {
return Err(ConvertError::Parse(format!(
"symbol_path '{}' must contain $body segment for position-based replacement",
path_str
)));
}
let (fn_path, indices) = path.split_at_body().ok_or_else(|| {
ConvertError::Parse(format!(
"Failed to extract function path from symbol_path '{}'",
path_str
))
})?;
let fn_id = ctx.registry.lookup(&fn_path).ok_or_else(|| {
ConvertError::TargetNotFound(format!(
"Function '{}' not found in registry",
fn_path
))
})?;
let mutation = ReplaceExprAtMutation::new(fn_id, indices, new);
return Ok(vec![Box::new(mutation)]);
}
let old = Self::parse_expr(old_expr)?;
let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
let mut mutation = ReplaceExprMutation::new(old.clone(), new.clone(), *id);
mutation.replace_all = *replace_all;
vec![Box::new(mutation)]
} else {
use ryo_analysis::SymbolKind;
ctx.registry
.iter()
.filter(|(id, _)| {
matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
})
.map(|(id, _)| {
let mut mutation =
ReplaceExprMutation::new(old.clone(), new.clone(), id);
mutation.replace_all = *replace_all;
Box::new(mutation) as Box<dyn ASTRegApply>
})
.collect()
};
Ok(mutations)
}
MutationSpec::RemoveStatement {
fn_id,
pattern,
remove_all,
..
} => {
let target_stmt = Self::parse_stmt(pattern)?;
let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
let mut mutation =
RemoveStatementMutation::new(target_stmt.clone(), pattern.clone(), *id);
mutation.remove_all = *remove_all;
vec![Box::new(mutation)]
} else {
use ryo_analysis::SymbolKind;
ctx.registry
.iter()
.filter(|(id, _)| {
matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
})
.map(|(id, _)| {
let mut mutation = RemoveStatementMutation::new(
target_stmt.clone(),
pattern.clone(),
id,
);
mutation.remove_all = *remove_all;
Box::new(mutation) as Box<dyn ASTRegApply>
})
.collect()
};
Ok(mutations)
}
MutationSpec::InsertStatement {
fn_id,
stmt,
position,
reference_pattern,
..
} => {
let pure_stmt = Self::parse_stmt(stmt)?;
let reference_stmt = reference_pattern
.as_ref()
.map(|p| Self::parse_stmt(p))
.transpose()?;
let mut mutation = InsertStatementMutation::new(pure_stmt, *fn_id);
mutation.position = Self::convert_position(position);
mutation.reference_stmt = reference_stmt;
Ok(vec![Box::new(mutation)])
}
MutationSpec::ReplaceStatement {
fn_id,
old_stmt,
new_stmt,
..
} => {
let old = Self::parse_stmt(old_stmt)?;
let new = Self::parse_stmt(new_stmt)?;
let mutations: Vec<Box<dyn ASTRegApply>> = if let Some(id) = fn_id {
vec![Box::new(ReplaceStatementMutation::new(
old.clone(),
new.clone(),
*id,
))]
} else {
use ryo_analysis::SymbolKind;
ctx.registry
.iter()
.filter(|(id, _)| {
matches!(ctx.registry.kind(*id), Some(SymbolKind::Function))
})
.map(|(id, _)| {
Box::new(ReplaceStatementMutation::new(old.clone(), new.clone(), id))
as Box<dyn ASTRegApply>
})
.collect()
};
Ok(mutations)
}
_ => Err(ConvertError::TypeMismatch {
expected: "ReplaceExpr|RemoveStatement|InsertStatement|ReplaceStatement",
actual: spec.kind_name().to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_symbol::SymbolId;
#[test]
fn test_stmt_converter_spec_kinds() {
let converter = StmtConverter::new();
let kinds = converter.spec_kinds();
assert!(kinds.contains(&"ReplaceExpr"));
assert!(kinds.contains(&"RemoveStatement"));
assert!(kinds.contains(&"InsertStatement"));
assert!(kinds.contains(&"ReplaceStatement"));
}
#[test]
fn test_stmt_converter_can_handle() {
let converter = StmtConverter::new();
let replace_expr_spec = MutationSpec::ReplaceExpr {
module_id: None,
fn_id: None,
old_expr: "a + b".into(),
new_expr: "c + d".into(),
replace_all: true,
symbol_path: None,
};
assert!(converter.can_handle(&replace_expr_spec));
let remove_stmt_spec = MutationSpec::RemoveStatement {
module_id: None,
fn_id: None,
pattern: "println!".into(),
remove_all: true,
symbol_path: None,
};
assert!(converter.can_handle(&remove_stmt_spec));
let insert_stmt_spec = MutationSpec::InsertStatement {
module_id: None,
fn_id: SymbolId::default(),
stmt: "let x = 1".into(),
position: StmtInsertPosition::Start,
reference_pattern: None,
symbol_path: None,
};
assert!(converter.can_handle(&insert_stmt_spec));
let replace_stmt_spec = MutationSpec::ReplaceStatement {
module_id: None,
fn_id: None,
old_stmt: "let x = 1".into(),
new_stmt: "let x = 2".into(),
symbol_path: None,
};
assert!(converter.can_handle(&replace_stmt_spec));
}
}