use super::{
BTreeMap, BTreeSet, BuildVariant, CloneClass, FileFeatures, FragmentFingerprint, GroupingSet,
SimilarityEdge, SyntaxIrFile, Unit, VerifiedPair, candidate, control_flow,
dominant_boilerplate_members, near_match, stable_id, verify, written_once_per_width_members,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NotAPair {
Nested,
Alternatives,
DivergentShapes,
}
pub(super) struct LiftedPairs {
pub(super) pairs: BTreeSet<(usize, usize)>,
pub(super) nested: usize,
pub(super) alternatives: usize,
pub(super) divergent: usize,
}
pub(super) fn lift_to_unit_pairs(
candidate: &candidate::CandidateSet,
near: &near_match::NearMatchSet,
skeleton: &control_flow::ControlFlowSet,
units: &[Unit],
offsets: &[usize],
feature_files: &[FileFeatures],
max_shape_divergence: f64,
) -> LiftedPairs {
let mut pairs: BTreeSet<(usize, usize)> = BTreeSet::new();
let mut nested = 0usize;
let mut alternatives = 0usize;
let mut divergent = 0usize;
let places = candidate
.pairs
.iter()
.map(|pair| (pair.a.file, pair.a.unit, pair.b.file, pair.b.unit))
.chain(
near.pairs
.iter()
.map(|pair| (pair.a.file, pair.a.unit, pair.b.file, pair.b.unit)),
)
.chain(
skeleton
.pairs
.iter()
.map(|pair| (pair.a.file, pair.a.unit, pair.b.file, pair.b.unit)),
);
for (file_a, unit_a, file_b, unit_b) in places {
let proposal = Proposal {
units,
offsets,
feature_files,
max_shape_divergence,
};
match proposal.insert(&mut pairs, file_a, unit_a, file_b, unit_b) {
Some(NotAPair::Nested) => nested += 1,
Some(NotAPair::Alternatives) => alternatives += 1,
Some(NotAPair::DivergentShapes) => divergent += 1,
None => {}
}
}
LiftedPairs {
pairs,
nested,
alternatives,
divergent,
}
}
struct Proposal<'a> {
units: &'a [Unit],
offsets: &'a [usize],
feature_files: &'a [FileFeatures],
max_shape_divergence: f64,
}
impl Proposal<'_> {
fn insert(
&self,
pairs: &mut BTreeSet<(usize, usize)>,
file_a: usize,
unit_a: usize,
file_b: usize,
unit_b: usize,
) -> Option<NotAPair> {
let a = self.offsets[file_a] + unit_a;
let b = self.offsets[file_b] + unit_b;
if a == b {
return None;
}
if encloses(&self.units[a], &self.units[b]) {
return Some(NotAPair::Nested);
}
if self.units[a].arms.excludes(&self.units[b].arms) {
return Some(NotAPair::Alternatives);
}
let (vector_a, vector_b) = (
&self.feature_files[file_a].units[unit_a].vector,
&self.feature_files[file_b].units[unit_b].vector,
);
if vector_a.shape_divergence(vector_b) > self.max_shape_divergence {
return Some(NotAPair::DivergentShapes);
}
pairs.insert(if a <= b { (a, b) } else { (b, a) });
None
}
}
pub(super) fn unrepresented_pairs(
edges: &[SimilarityEdge],
groups: &GroupingSet,
units: &[Unit],
files: &[SyntaxIrFile],
variant: &BuildVariant,
) -> (Vec<VerifiedPair>, usize, usize) {
let mut group_of: BTreeMap<usize, usize> = BTreeMap::new();
for (index, group) in groups.groups.iter().enumerate() {
for &member in &group.members {
group_of.insert(member, index);
}
}
let severed = edges
.iter()
.filter(|edge| groups.severed_by_the_ceiling(edge.a, edge.b))
.count();
let mut folded: BTreeMap<(FragmentFingerprint, FragmentFingerprint, CloneClass), Folded> =
BTreeMap::new();
for edge in edges.iter().filter(|edge| {
!groups.severed_by_the_ceiling(edge.a, edge.b)
&& match (group_of.get(&edge.a), group_of.get(&edge.b)) {
(Some(a), Some(b)) => a != b,
_ => true,
}
}) {
let content = |member: usize| units[member].group_content(edge.class);
let (low, high) = if content(edge.a) <= content(edge.b) {
(content(edge.a), content(edge.b))
} else {
(content(edge.b), content(edge.a))
};
let entry = folded
.entry((low, high, edge.class))
.or_insert_with(|| Folded {
members: BTreeSet::new(),
crossings: 0,
similarity: edge.similarity,
breakdown: edge.breakdown,
confidence: edge.confidence,
described: true,
});
entry.members.insert(edge.a);
entry.members.insert(edge.b);
entry.crossings += 1;
entry.described &= already_described(edge, &group_of, groups, units);
if edge.similarity < entry.similarity {
entry.similarity = edge.similarity;
entry.breakdown = edge.breakdown;
entry.confidence = edge.confidence;
}
}
let described: usize = folded
.values()
.filter(|entry| entry.described)
.map(|entry| entry.crossings)
.sum();
let mut pairs: Vec<VerifiedPair> = folded
.into_iter()
.filter(|(_, entry)| !entry.described)
.map(|((_low, _high, class), entry)| {
let members: Vec<usize> = entry.members.into_iter().collect();
let canonical = members
.iter()
.copied()
.min_by_key(|&member| units[member].content)
.unwrap_or(members[0]);
let boilerplate = dominant_boilerplate_members(&members, units);
let width_family = written_once_per_width_members(canonical, &members, units, files);
let identity_contents = members
.iter()
.map(|&member| units[member].group_content(class))
.collect::<Vec<_>>();
VerifiedPair {
members,
canonical,
fingerprint: stable_id::structural_clone_group_fingerprint(
variant,
class,
&units[canonical].group_content(class),
&identity_contents,
),
similarity: entry.similarity,
breakdown: entry.breakdown,
class,
confidence: entry.confidence,
boilerplate,
width_family,
}
})
.collect();
pairs.sort_by(|left, right| {
right
.similarity
.total_cmp(&left.similarity)
.then_with(|| left.members.cmp(&right.members))
});
(pairs, described, severed)
}
struct Folded {
members: BTreeSet<usize>,
crossings: usize,
similarity: f64,
breakdown: Option<verify::SimilarityBreakdown>,
confidence: verify::Confidence,
described: bool,
}
fn already_described(
edge: &SimilarityEdge,
group_of: &BTreeMap<usize, usize>,
groups: &GroupingSet,
units: &[Unit],
) -> bool {
let nested_peer = |side: usize, other: usize| {
group_of
.get(&side)
.map(|&index| groups.groups[index].members.as_slice())
.unwrap_or_default()
.iter()
.any(|&peer| peer != other && encloses(&units[peer], &units[other]))
};
nested_peer(edge.a, edge.b) || nested_peer(edge.b, edge.a)
}
pub(super) const fn encloses(a: &Unit, b: &Unit) -> bool {
a.file == b.file
&& ((a.tokens.0 <= b.tokens.0 && b.tokens.1 <= a.tokens.1)
|| (b.tokens.0 <= a.tokens.0 && a.tokens.1 <= b.tokens.1))
}