use crate::{
diff::{diff, Diff, FieldDiff, FieldEditKind},
gc::GcPtr,
TypeDesc, TypeFields, TypeGroup, TypeMemory,
};
use std::{
collections::{HashMap, HashSet},
hash::Hash,
};
pub struct Mapping<T: Eq + Hash, U: TypeDesc + TypeMemory> {
pub deletions: HashSet<T>,
pub conversions: HashMap<T, Conversion<U>>,
pub identical: Vec<(T, T)>,
}
pub struct Conversion<T: TypeDesc + TypeMemory> {
pub field_mapping: Vec<FieldMapping<T>>,
pub new_ty: T,
}
pub struct FieldMapping<T: TypeDesc + TypeMemory> {
pub new_ty: T,
pub new_offset: usize,
pub action: Action<T>,
}
#[derive(Eq, PartialEq)]
pub enum Action<T: TypeDesc + TypeMemory> {
Cast { old_offset: usize, old_ty: T },
Copy { old_offset: usize },
Insert,
}
impl<T> Mapping<T, T>
where
T: TypeDesc + TypeFields<T> + TypeMemory + Copy + Eq + Hash,
{
pub fn new(old: &[T], new: &[T]) -> Self {
let diff = diff(old, new);
let mut conversions = HashMap::new();
let mut deletions = HashSet::new();
let mut insertions = HashSet::new();
let mut identical = Vec::new();
for diff in diff.iter() {
match diff {
Diff::Delete { index } => {
deletions.insert(unsafe { *old.get_unchecked(*index) });
}
Diff::Edit {
diff,
old_index,
new_index,
} => {
let old_ty = unsafe { *old.get_unchecked(*old_index) };
let new_ty = unsafe { *new.get_unchecked(*new_index) };
conversions.insert(old_ty, unsafe { field_mapping(old_ty, new_ty, diff) });
}
Diff::Insert { index } => {
insertions.insert(unsafe { *new.get_unchecked(*index) });
}
Diff::Move {
old_index,
new_index,
} => identical.push(unsafe {
(
*old.get_unchecked(*old_index),
*new.get_unchecked(*new_index),
)
}),
}
}
let mut new_candidates: HashSet<T> = new
.iter()
.filter(|ty| ty.group() == TypeGroup::Struct)
.filter(|ty| !insertions.contains(*ty))
.cloned()
.collect();
let mut old_candidates: HashSet<T> = old
.iter()
.filter(|ty| ty.group() == TypeGroup::Struct)
.filter(|ty| !deletions.contains(*ty))
.filter(|ty| {
if let Some(conversion) = conversions.get(*ty) {
new_candidates.remove(&conversion.new_ty);
false
} else {
true
}
})
.cloned()
.collect();
for (old_ty, new_ty) in identical.iter() {
old_candidates.remove(old_ty);
new_candidates.remove(new_ty);
}
for old_ty in old_candidates {
let new_ty = new_candidates.take(&old_ty).unwrap();
identical.push((old_ty, new_ty));
}
debug_assert!(new_candidates.is_empty());
Self {
deletions,
conversions,
identical,
}
}
}
pub unsafe fn field_mapping<T: Clone + TypeDesc + TypeFields<T> + TypeMemory>(
old_ty: T,
new_ty: T,
diff: &[FieldDiff],
) -> Conversion<T> {
let old_fields = old_ty.fields();
let deletions: HashSet<usize> = diff
.iter()
.filter_map(|diff| match diff {
FieldDiff::Delete { index } => Some(*index),
FieldDiff::Move { old_index, .. } => Some(*old_index),
FieldDiff::Edit { .. } | FieldDiff::Insert { .. } => None,
})
.collect();
struct FieldMappingDesc {
old_index: Option<usize>,
action: ActionDesc,
}
#[derive(PartialEq)]
enum ActionDesc {
Cast,
Copy,
Insert,
}
let mut mapping: Vec<FieldMappingDesc> = (0..old_fields.len())
.filter_map(|idx| {
if deletions.contains(&idx) {
None
} else {
Some(FieldMappingDesc {
old_index: Some(idx),
action: ActionDesc::Copy,
})
}
})
.collect();
let mut additions: Vec<(usize, FieldMappingDesc)> = diff
.iter()
.filter_map(|diff| match diff {
FieldDiff::Insert { index } => Some((
*index,
FieldMappingDesc {
old_index: None,
action: ActionDesc::Insert,
},
)),
FieldDiff::Move {
old_index,
new_index,
edit,
} => Some((
*new_index,
FieldMappingDesc {
old_index: Some(*old_index),
action: edit.as_ref().map_or(ActionDesc::Copy, |kind| {
if *kind == FieldEditKind::ConvertType {
ActionDesc::Cast
} else {
ActionDesc::Copy
}
}),
},
)),
FieldDiff::Delete { .. } | FieldDiff::Edit { .. } => None,
})
.collect();
additions.sort_by(|a, b| a.0.cmp(&b.0));
for (new_index, map) in additions {
mapping.insert(new_index, map);
}
for diff in diff.iter() {
if let FieldDiff::Edit { index, kind } = diff {
let map = mapping.get_mut(*index).unwrap();
map.action = if *kind == FieldEditKind::ConvertType {
ActionDesc::Cast
} else {
ActionDesc::Copy
};
}
}
let new_fields = new_ty.fields();
let old_offsets = old_ty.offsets();
let new_offsets = new_ty.offsets();
Conversion {
field_mapping: mapping
.into_iter()
.enumerate()
.map(|(new_index, desc)| {
let old_offset = desc
.old_index
.map(|idx| usize::from(*old_offsets.get_unchecked(idx)));
FieldMapping {
new_ty: new_fields.get_unchecked(new_index).1.clone(),
new_offset: usize::from(*new_offsets.get_unchecked(new_index)),
action: match desc.action {
ActionDesc::Cast => Action::Cast {
old_offset: old_offset.unwrap(),
old_ty: old_fields.get_unchecked(desc.old_index.unwrap()).1.clone(),
},
ActionDesc::Copy => Action::Copy {
old_offset: old_offset.unwrap(),
},
ActionDesc::Insert => Action::Insert,
},
}
})
.collect(),
new_ty,
}
}
pub trait MemoryMapper<T: Eq + Hash + TypeDesc + TypeMemory> {
fn map_memory(&self, mapping: Mapping<T, T>) -> Vec<GcPtr>;
}