use ryo_analysis::{
ASTRegistry, AnalysisContext, SymbolId, SymbolKind, SymbolPath, SymbolRegistry,
};
use ryo_mutations::MutationResult;
use ryo_source::pure::PureItem;
use super::ast_reg_apply::ASTRegApply;
use super::events::{ModificationType, MutationEvent};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveError {
NotFound,
Ambiguous,
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub result: MutationResult,
pub events: Vec<MutationEvent>,
}
impl ExecutionResult {
pub fn new(result: MutationResult, events: Vec<MutationEvent>) -> Self {
Self { result, events }
}
pub fn no_change() -> Self {
Self {
result: MutationResult {
mutation_type: String::new(),
changes: 0,
description: String::new(),
},
events: vec![],
}
}
pub fn has_changes(&self) -> bool {
self.result.changes > 0
}
}
pub struct ASTMutationEngine;
impl ASTMutationEngine {
pub fn execute_ast_reg<M: ASTRegApply>(
mutation: &M,
ctx: &mut AnalysisContext,
) -> ExecutionResult {
let mut mutation_ctx = ASTMutationContext::new(&mut ctx.ast_registry, &mut ctx.registry);
let result = mutation.apply_to_registry(&mut mutation_ctx);
let events = mutation_ctx.into_events();
ExecutionResult::new(result, events)
}
pub fn execute_ast_reg_batch<M: ASTRegApply>(
mutations: &[&M],
ctx: &mut AnalysisContext,
) -> ExecutionResult {
let mut total_changes = 0;
let mut all_events = Vec::new();
for mutation in mutations {
let result = Self::execute_ast_reg(*mutation, ctx);
total_changes += result.result.changes;
all_events.extend(result.events);
}
ExecutionResult::new(
MutationResult {
mutation_type: "ast_reg_batch".to_string(),
changes: total_changes,
description: format!("{} mutations executed", mutations.len()),
},
all_events,
)
}
pub fn execute_ast_reg_dyn(
mutation: &dyn ASTRegApply,
ctx: &mut AnalysisContext,
) -> ExecutionResult {
let mut mutation_ctx = ASTMutationContext::new(&mut ctx.ast_registry, &mut ctx.registry);
let result = mutation.apply_to_registry(&mut mutation_ctx);
let events = mutation_ctx.into_events();
ExecutionResult::new(result, events)
}
pub fn execute_ast_reg_batch_dyn(
mutations: Vec<Box<dyn ASTRegApply>>,
ctx: &mut AnalysisContext,
) -> ExecutionResult {
let mutation_count = mutations.len();
let mut total_changes = 0;
let mut all_events = Vec::new();
for mutation in mutations {
let result = Self::execute_ast_reg_dyn(mutation.as_ref(), ctx);
total_changes += result.result.changes;
all_events.extend(result.events);
}
ExecutionResult::new(
MutationResult {
mutation_type: "ast_reg_batch_v2".to_string(),
changes: total_changes,
description: format!("{} V2 mutations executed", mutation_count),
},
all_events,
)
}
}
pub struct ASTMutationContext<'a> {
pub ast_registry: &'a mut ASTRegistry,
pub symbol_registry: &'a mut SymbolRegistry,
events: Vec<MutationEvent>,
}
impl<'a> ASTMutationContext<'a> {
pub fn new(ast_registry: &'a mut ASTRegistry, symbol_registry: &'a mut SymbolRegistry) -> Self {
Self {
ast_registry,
symbol_registry,
events: Vec::new(),
}
}
pub fn into_events(self) -> Vec<MutationEvent> {
self.events
}
pub fn emit(&mut self, event: MutationEvent) {
self.events.push(event);
}
pub fn emit_added(&mut self, path: SymbolPath, kind: SymbolKind) {
self.emit(MutationEvent::SymbolAdded { path, kind });
}
pub fn emit_removed(&mut self, path: SymbolPath) {
self.emit(MutationEvent::SymbolRemoved { path });
}
pub fn emit_modified(&mut self, id: SymbolId, modification: ModificationType) {
let item_clone = self.ast_registry.get(id).cloned();
if let Some(item) = item_clone {
if matches!(item, ryo_source::pure::PureItem::Impl(_)) {
if let Some(path) = self.symbol_registry.path(id) {
if let Some(parent_path) = path.parent() {
if let Some(parent_id) = self.symbol_registry.lookup(&parent_path) {
if let Some(module_items) =
self.ast_registry.get_module_items_mut(parent_id)
{
if let Some(pos) = module_items.iter().position(|i| {
if let ryo_source::pure::PureItem::Impl(impl_block) = i {
if let ryo_source::pure::PureItem::Impl(ref modified_impl) =
item
{
impl_block.self_ty == modified_impl.self_ty
&& impl_block.trait_ == modified_impl.trait_
} else {
false
}
} else {
false
}
}) {
module_items[pos] = item;
}
}
}
}
}
}
}
self.emit(MutationEvent::SymbolModified { id, modification });
}
pub fn emit_renamed(&mut self, old_path: SymbolPath, new_path: SymbolPath) {
self.emit(MutationEvent::SymbolRenamed {
old_path,
new_path: Box::new(new_path),
});
}
pub fn get_ast(&self, id: SymbolId) -> Option<&PureItem> {
self.ast_registry.get(id)
}
pub fn get_ast_mut(&mut self, id: SymbolId) -> Option<&mut PureItem> {
self.ast_registry.get_mut(id)
}
pub fn set_ast(&mut self, id: SymbolId, item: PureItem) {
self.ast_registry.set(id, item);
}
pub fn has_ast(&self, id: SymbolId) -> bool {
self.ast_registry.contains(id)
}
pub fn lookup(&self, path: &SymbolPath) -> Option<SymbolId> {
self.symbol_registry.lookup(path)
}
pub fn path(&self, id: SymbolId) -> Option<&SymbolPath> {
self.symbol_registry.path(id)
}
pub fn kind(&self, id: SymbolId) -> Option<SymbolKind> {
self.symbol_registry.kind(id)
}
pub fn resolve_symbol_by_name(
&self,
name: &str,
kinds: &[SymbolKind],
) -> Result<SymbolId, ResolveError> {
let mut found: Option<SymbolId> = None;
for (id, path) in self.symbol_registry.iter() {
let kind_matches = self
.symbol_registry
.kind(id)
.map(|k| kinds.contains(&k))
.unwrap_or(false);
if !kind_matches {
continue;
}
let name_matches = if name.contains("::") {
path.to_string() == name
} else {
path.name() == name
};
if !name_matches {
continue;
}
if found.is_some() {
return Err(ResolveError::Ambiguous);
}
found = Some(id);
}
found.ok_or(ResolveError::NotFound)
}
pub fn register(&mut self, path: SymbolPath, kind: SymbolKind) -> Option<SymbolId> {
match self.symbol_registry.register(path.clone(), kind) {
Ok(id) => {
self.emit_added(path, kind);
Some(id)
}
Err(_) => None,
}
}
pub fn register_with_ast(
&mut self,
path: SymbolPath,
kind: SymbolKind,
ast: PureItem,
) -> Option<SymbolId> {
let id = self.register(path, kind)?;
self.set_ast(id, ast);
Some(id)
}
pub fn remove_symbol(&mut self, id: SymbolId) -> Option<PureItem> {
if let Some(path) = self.symbol_registry.path(id).cloned() {
let name = path.name().to_string();
if let Some(parent_path) = path.parent() {
if let Some(parent_id) = self.symbol_registry.lookup(&parent_path) {
if let Some(items) = self.ast_registry.get_module_items_mut(parent_id) {
items.retain(|item| !item_matches_name(item, &name));
}
}
}
self.symbol_registry.remove(id);
let ast = self.ast_registry.remove(id);
self.emit_removed(path);
ast
} else {
None
}
}
pub fn rename_symbol(&mut self, id: SymbolId, new_path: SymbolPath) -> Result<(), String> {
if let Some(old_path) = self.symbol_registry.path(id).cloned() {
self.symbol_registry
.rename(id, new_path.clone())
.map_err(|e| format!("{:?}", e))?;
self.emit_renamed(old_path, new_path);
Ok(())
} else {
Err("Symbol not found".to_string())
}
}
}
fn item_matches_name(item: &PureItem, name: &str) -> bool {
match item {
PureItem::Fn(f) => f.name == name,
PureItem::Struct(s) => s.name == name,
PureItem::Enum(e) => e.name == name,
PureItem::Const(c) => c.name == name,
PureItem::Static(s) => s.name == name,
PureItem::Type(t) => t.name == name,
PureItem::Trait(t) => t.name == name,
PureItem::Mod(m) => m.name == name,
PureItem::Impl(i) => {
let impl_name = if let Some(ref trait_name) = i.trait_ {
format!("<impl {} for {}>", trait_name, i.self_ty)
} else {
format!("<impl {}>", i.self_ty)
};
impl_name == name
}
PureItem::Use(_) | PureItem::Macro(_) | PureItem::Other(_) => false,
}
}
#[cfg(test)]
mod tests {
}