use crate::types::{Symbol, SymbolKind};
use std::collections::HashMap;
#[derive(Debug)]
pub(super) struct RelocationMatch<'a, M: Default + Clone + PartialEq = ()> {
pub old: &'a Symbol<M>,
pub new: &'a Symbol<M>,
pub relocation_type: RelocationType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum RelocationType {
MovedToDeprecated,
PromotedFromDeprecated,
PromotedFromNext,
MovedToNext,
Relocated,
}
pub(super) fn detect_relocations<'a, M, F, C>(
removed: &[&'a Symbol<M>],
added: &[&'a Symbol<M>],
canonical_fn: F,
classify_fn: C,
) -> (Vec<RelocationMatch<'a, M>>, Vec<usize>, Vec<usize>)
where
M: Default + Clone + PartialEq,
F: Fn(&str) -> String,
C: Fn(&str, &str) -> RelocationType,
{
if removed.is_empty() || added.is_empty() {
return (Vec::new(), Vec::new(), Vec::new());
}
#[allow(clippy::type_complexity)]
let mut added_by_canonical: HashMap<(String, SymbolKind), Vec<(usize, &'a Symbol<M>)>> =
HashMap::new();
for (ai, sym) in added.iter().enumerate() {
let canonical = canonical_fn(&sym.qualified_name);
added_by_canonical
.entry((canonical, sym.kind))
.or_default()
.push((ai, sym));
}
let mut matches = Vec::new();
let mut skip_removed = Vec::new();
let mut skip_added = Vec::new();
for (ri, rsym) in removed.iter().enumerate() {
let canonical = canonical_fn(&rsym.qualified_name);
let key = (canonical, rsym.kind);
if let Some(added_syms) = added_by_canonical.get_mut(&key) {
let best_idx = added_syms
.iter()
.position(|(_, asym)| asym.name == rsym.name)
.or({
if !added_syms.is_empty() {
Some(0)
} else {
None
}
});
if let Some(idx) = best_idx {
let (ai, asym) = added_syms.remove(idx);
let relocation_type = classify_fn(&rsym.qualified_name, &asym.qualified_name);
matches.push(RelocationMatch {
old: rsym,
new: asym,
relocation_type,
});
skip_removed.push(ri);
skip_added.push(ai);
}
}
}
(matches, skip_removed, skip_added)
}