pub mod myers;
use crate::{r#type::Field, r#type::Type};
use self::myers::Change;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FieldEditKind {
ChangedTyped,
RenamedField,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FieldDiff {
Insert {
index: usize,
new_type: Type,
},
Edit {
old_type: Type,
new_type: Type,
old_index: Option<usize>,
new_index: usize,
kind: FieldEditKind,
},
Move {
ty: Type,
old_index: usize,
new_index: usize,
},
Delete {
index: usize,
},
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StructDiff {
Insert { index: usize, ty: Type },
Edit {
diff: Vec<FieldDiff>,
old_index: usize,
new_index: usize,
old_ty: Type,
new_ty: Type,
},
Move {
old_index: usize,
new_index: usize,
old_ty: Type,
new_ty: Type,
},
Delete { index: usize, ty: Type },
}
impl Ord for StructDiff {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
fn get_index(diff: &StructDiff) -> usize {
match diff {
StructDiff::Insert { index, .. }
| StructDiff::Edit {
old_index: index, ..
}
| StructDiff::Move {
old_index: index, ..
}
| StructDiff::Delete { index, .. } => *index,
}
}
get_index(self).cmp(&get_index(other))
}
}
impl PartialOrd for StructDiff {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub fn compute_struct_diff(old: &[Type], new: &[Type]) -> Vec<StructDiff> {
let diff = myers::compute_diff(old, new);
let (deletions, insertions) = myers::split_diff(&diff);
let deleted_structs = deletions
.into_iter()
.filter(|Change { element, .. }| element.is_struct())
.collect();
let inserted_structs = insertions
.into_iter()
.filter(|Change { element, .. }| element.is_struct())
.collect();
let mut mapping: Vec<StructDiff> = Vec::with_capacity(diff.len());
append_struct_mapping(deleted_structs, inserted_structs, &mut mapping);
mapping.shrink_to_fit();
mapping.sort();
mapping
}
#[derive(Clone, Eq, PartialEq)]
struct UniqueFieldInfo<'a> {
name: &'a str,
ty: Type,
}
impl<'a> From<Field<'a>> for UniqueFieldInfo<'a> {
fn from(other: Field<'a>) -> Self {
Self {
name: other.name(),
ty: other.ty(),
}
}
}
fn append_struct_mapping(
deletions: Vec<Change<Type>>,
insertions: Vec<Change<Type>>,
mapping: &mut Vec<StructDiff>,
) {
let deletions: Vec<_> = deletions
.iter()
.enumerate()
.map(|(deletion_index, Change { index, element })| {
let fields = element
.as_struct()
.map(|s| s.fields().iter().map(UniqueFieldInfo::from).collect())
.unwrap_or_else(Vec::new);
(deletion_index, *index, element.clone(), fields)
})
.collect();
let insertions: Vec<_> = insertions
.iter()
.enumerate()
.map(|(insertion_index, Change { index, element })| {
let fields = element
.as_struct()
.map(|s| s.fields().iter().map(UniqueFieldInfo::from).collect())
.unwrap_or_else(Vec::new);
(insertion_index, *index, element.clone(), fields)
})
.collect();
struct LengthDescription<'f> {
deletion_idx: usize,
insertion_idx: usize,
old_index: usize,
new_index: usize,
old_ty: Type,
new_ty: Type,
old_fields: &'f Vec<UniqueFieldInfo<'f>>,
new_fields: &'f Vec<UniqueFieldInfo<'f>>,
length: usize,
}
let mut myers_lengths: Vec<_> = insertions
.iter()
.flat_map(|(insertion_idx, new_idx, new_ty, new_fields)| {
deletions
.iter()
.filter_map(|(deletion_idx, old_idx, old_ty, old_fields)| {
let length = myers::diff_length(old_fields, new_fields);
if old_ty.name() == new_ty.name() || length == 0 {
Some(LengthDescription {
deletion_idx: *deletion_idx,
insertion_idx: *insertion_idx,
old_index: *old_idx,
new_index: *new_idx,
old_ty: old_ty.clone(),
new_ty: new_ty.clone(),
old_fields,
new_fields,
length,
})
} else {
None
}
})
.collect::<Vec<LengthDescription>>()
})
.collect();
myers_lengths.sort_by(
|LengthDescription { length: lhs, .. }, LengthDescription { length: rhs, .. }| lhs.cmp(rhs),
);
let mut used_deletions = vec![false; deletions.len()];
let mut used_insertions = vec![false; insertions.len()];
for LengthDescription {
deletion_idx,
insertion_idx,
old_index,
new_index,
old_ty,
new_ty,
length,
old_fields,
new_fields,
} in myers_lengths
{
if used_deletions[deletion_idx] || used_insertions[insertion_idx] {
continue;
}
used_deletions[deletion_idx] = true;
used_insertions[insertion_idx] = true;
mapping.push(if length == 0 {
StructDiff::Move {
old_index,
new_index,
old_ty,
new_ty,
}
} else {
let diff = field_diff(old_fields, new_fields);
StructDiff::Edit {
diff,
old_index,
new_index,
old_ty,
new_ty,
}
});
}
used_deletions
.into_iter()
.zip(deletions.into_iter())
.for_each(|(used, (_, old_index, ty, _))| {
if !used {
mapping.push(StructDiff::Delete {
index: old_index,
ty,
});
}
});
used_insertions
.into_iter()
.zip(insertions.into_iter())
.for_each(|(used, (_, new_index, ty, _))| {
if !used {
mapping.push(StructDiff::Insert {
index: new_index,
ty,
});
}
});
}
fn field_diff<'a, 'b>(old: &[UniqueFieldInfo<'a>], new: &[UniqueFieldInfo<'b>]) -> Vec<FieldDiff> {
let diff = myers::compute_diff(old, new);
let (deletions, insertions) = myers::split_diff(&diff);
let mut insertions: Vec<Option<Change<UniqueFieldInfo>>> =
insertions.into_iter().map(Some).collect();
let mut mapping = Vec::with_capacity(diff.len());
#[allow(clippy::manual_flatten)]
'outer: for Change {
index: old_index,
element: old_field,
} in deletions
{
for insertion in insertions.iter_mut() {
if let Some(Change {
index: new_index,
element: new_field,
}) = insertion
{
if old_field == *new_field {
mapping.push(FieldDiff::Move {
ty: old_field.ty,
old_index,
new_index: *new_index,
});
*insertion = None;
continue 'outer;
}
}
}
for insertion in insertions.iter_mut() {
if let Some(Change {
index: new_index,
element: new_field,
}) = insertion
{
if old_field.name == new_field.name {
mapping.push({
FieldDiff::Edit {
old_type: old_field.ty,
new_type: new_field.ty.clone(),
old_index: if old_index != *new_index {
Some(old_index)
} else {
None
},
new_index: *new_index,
kind: FieldEditKind::ChangedTyped,
}
});
*insertion = None;
continue 'outer;
}
}
}
let mut closest = None;
for (insert_index, insertion) in insertions.iter_mut().enumerate() {
if let Some(Change {
index: new_index,
element: new_field,
}) = insertion
{
if old_field.ty == new_field.ty {
let diff = old_index.max(*new_index) - old_index.min(*new_index);
if let Some((closest_insert_index, closest_index, closest_ty, closest_diff)) =
&mut closest
{
if diff < *closest_diff {
*closest_insert_index = insert_index;
*closest_index = *new_index;
*closest_ty = new_field.ty.clone();
*closest_diff = diff;
}
} else {
closest = Some((insert_index, *new_index, new_field.ty.clone(), diff));
}
if diff == 0 {
break;
}
}
}
}
if let Some((closest_insert_index, closest_index, closest_type, _)) = closest {
insertions
.get_mut(closest_insert_index)
.expect("Closest index must be within insertions")
.take();
mapping.push({
FieldDiff::Edit {
old_type: old_field.ty.clone(),
new_type: closest_type,
old_index: if old_index != closest_index {
Some(old_index)
} else {
None
},
new_index: closest_index,
kind: FieldEditKind::RenamedField,
}
});
continue 'outer;
}
mapping.push(FieldDiff::Delete { index: old_index })
}
for Change {
index,
element: new_field,
} in insertions.into_iter().flatten()
{
mapping.push(FieldDiff::Insert {
index,
new_type: new_field.ty,
});
}
mapping.shrink_to_fit();
mapping
}