use crate::graph;
use super::impact::VisitedEntry;
use super::seq::SeqSet;
use super::why::why_for_edge;
pub(super) use super::why::Why;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct KindLines {
pub direct: usize,
pub direct_amb: usize,
pub ctor_di: usize,
pub heuristic: usize,
pub iface: usize,
pub bus: usize,
}
pub(super) fn note_line(slot: &mut usize, line: usize) {
if line > 0 && (*slot == 0 || line < *slot) {
*slot = line;
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct BusOrigin {
pub file: String,
pub line: usize,
pub message: String,
pub to: String,
pub to_file: String,
}
#[derive(Debug, Clone, Default)]
pub(super) struct Hit {
pub(super) via_count: u32,
pub(super) ambiguous_count: u32,
pub(super) heuristic_count: u32,
pub(super) ext_count: u32,
pub(super) symbols: SeqSet<String>,
pub(super) iface_via: SeqSet<String>,
pub(super) lines: KindLines,
pub(super) why_track: WhyTrack,
pub(super) has_non_bus_path: bool,
pub(super) bus_origin: Option<BusOrigin>,
}
#[derive(Debug, Clone, Default)]
pub(super) struct WhyTrack {
pub(super) direct: Option<Why>,
pub(super) direct_amb: Option<Why>,
pub(super) heuristic: Option<Why>,
pub(super) iface: Option<Why>,
pub(super) bus: Option<Why>,
}
fn note_line_why(slot: &mut usize, why_slot: &mut Option<Why>, line: usize, why: Why) {
if line > 0 && (*slot == 0 || line < *slot) {
*slot = line;
*why_slot = Some(why);
}
}
impl Hit {
pub(super) fn note_direct(
&mut self,
line: usize,
edge: &graph::Edge,
origin: Option<&BusOrigin>,
) {
let why = why_for_edge(edge);
note_line_why(
&mut self.lines.direct,
&mut self.why_track.direct,
line,
why,
);
self.note_taint(origin);
}
pub(super) fn note_direct_amb(
&mut self,
line: usize,
edge: &graph::Edge,
origin: Option<&BusOrigin>,
) {
let why = why_for_edge(edge);
note_line_why(
&mut self.lines.direct_amb,
&mut self.why_track.direct_amb,
line,
why,
);
self.note_taint(origin);
}
pub(super) fn note_iface(
&mut self,
line: usize,
edge: &graph::Edge,
origin: Option<&BusOrigin>,
) {
let why = why_for_edge(edge);
note_line_why(&mut self.lines.iface, &mut self.why_track.iface, line, why);
self.note_taint(origin);
}
pub(super) fn note_heuristic(
&mut self,
line: usize,
edge: &graph::Edge,
origin: Option<&BusOrigin>,
) {
let why = why_for_edge(edge);
note_line_why(
&mut self.lines.heuristic,
&mut self.why_track.heuristic,
line,
why,
);
self.note_taint(origin);
}
pub(super) fn note_bus(&mut self, line: usize, edge: &graph::Edge) {
let why = why_for_edge(edge);
note_line_why(&mut self.lines.bus, &mut self.why_track.bus, line, why);
if let graph::Edge::BusHop {
from_file,
from_line,
message,
to,
to_file,
..
} = edge
{
self.note_bus_origin(BusOrigin {
file: from_file.clone(),
line: *from_line,
message: message.clone(),
to: to.clone(),
to_file: to_file.clone(),
});
}
}
fn note_bus_origin(&mut self, origin: BusOrigin) {
if self
.bus_origin
.as_ref()
.is_none_or(|existing| origin < *existing)
{
self.bus_origin = Some(origin);
}
}
pub(super) fn note_taint(&mut self, origin: Option<&BusOrigin>) {
match origin {
Some(o) => self.note_bus_origin(o.clone()),
None => self.has_non_bus_path = true,
}
}
}
pub(super) fn from_lines_of(lines: &KindLines) -> Vec<(&'static str, usize)> {
let mut out: Vec<(&'static str, usize)> = Vec::new();
let direct = if lines.direct != 0 {
lines.direct
} else {
lines.direct_amb
};
if direct != 0 {
out.push(("direct", direct));
}
if lines.ctor_di != 0 {
out.push(("ctor-di", lines.ctor_di));
}
if lines.heuristic != 0 {
out.push(("heuristic", lines.heuristic));
}
if lines.iface != 0 {
out.push(("iface", lines.iface));
}
if lines.bus != 0 {
out.push(("bus", lines.bus));
}
out
}
pub(super) fn note_kind_edge(
h: &mut Hit,
line: usize,
edge: &graph::Edge,
origin: Option<&BusOrigin>,
) {
if matches!(edge, graph::Edge::BusHop { .. }) {
h.note_bus(line, edge);
} else {
h.note_direct(line, edge, origin);
}
}
fn primary_why(lines: &KindLines, track: &WhyTrack) -> Why {
if lines.ctor_di != 0 {
return Why::CtorDi;
}
if lines.direct != 0 {
return track.direct.unwrap_or(Why::UsesMemberPrecise);
}
if lines.direct_amb != 0 {
return track.direct_amb.unwrap_or(Why::UsesMemberPrecise);
}
if lines.iface != 0 {
return track.iface.unwrap_or(Why::UsesMemberPrecise);
}
if lines.heuristic != 0 || track.heuristic.is_some() {
return track.heuristic.unwrap_or(Why::UsesMemberGuess);
}
track.bus.unwrap_or(Why::BusHop)
}
pub(super) fn build_visited_entry(hop: u32, infra: bool, h: &Hit) -> VisitedEntry {
VisitedEntry {
hop,
via_count: h.via_count,
ambiguous_count: h.ambiguous_count,
heuristic_count: h.heuristic_count,
ext_count: h.ext_count,
symbols: h.symbols.clone().into_vec(),
iface_via: h.iface_via.clone().into_vec(),
lines: h.lines.clone(),
infra,
why: primary_why(&h.lines, &h.why_track),
bus_only: !h.has_non_bus_path,
bus_origin: if h.has_non_bus_path {
None
} else {
h.bus_origin.clone()
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lines(ctor_di: usize, direct: usize, direct_amb: usize, iface: usize) -> KindLines {
KindLines {
ctor_di,
direct,
direct_amb,
iface,
heuristic: 0,
bus: 0,
}
}
#[test]
fn ctor_di_alone_wins() {
let l = lines(1, 0, 0, 0);
let track = WhyTrack::default();
assert_eq!(primary_why(&l, &track), Why::CtorDi);
}
#[test]
fn direct_alone_reports_its_tracked_why() {
let l = lines(0, 1, 0, 0);
let track = WhyTrack {
direct: Some(Why::UsesType),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesType);
}
#[test]
fn direct_amb_alone_reports_its_tracked_why() {
let l = lines(0, 0, 1, 0);
let track = WhyTrack {
direct_amb: Some(Why::Inherits),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::Inherits);
}
#[test]
fn iface_alone_reports_its_tracked_why() {
let l = lines(0, 0, 0, 1);
let track = WhyTrack {
iface: Some(Why::UsesMemberExt),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesMemberExt);
}
#[test]
fn heuristic_alone_reports_its_tracked_why() {
let l = lines(0, 0, 0, 0);
let track = WhyTrack {
heuristic: Some(Why::UsesMemberGuess),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesMemberGuess);
}
#[test]
fn ctor_di_outranks_direct() {
let l = lines(1, 1, 0, 0);
let track = WhyTrack {
direct: Some(Why::UsesType),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::CtorDi);
}
#[test]
fn direct_outranks_direct_amb() {
let l = lines(0, 1, 1, 0);
let track = WhyTrack {
direct: Some(Why::UsesType),
direct_amb: Some(Why::Inherits),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesType);
}
#[test]
fn direct_amb_outranks_iface() {
let l = lines(0, 0, 1, 1);
let track = WhyTrack {
direct_amb: Some(Why::Inherits),
iface: Some(Why::UsesMemberExt),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::Inherits);
}
#[test]
fn iface_outranks_heuristic() {
let l = lines(0, 0, 0, 1);
let track = WhyTrack {
iface: Some(Why::UsesMemberExt),
heuristic: Some(Why::UsesMemberGuess),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesMemberExt);
}
#[test]
fn bus_alone_reports_its_tracked_why() {
let l = KindLines {
bus: 1,
..lines(0, 0, 0, 0)
};
let track = WhyTrack {
bus: Some(Why::BusHop),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::BusHop);
}
#[test]
fn heuristic_outranks_bus() {
let l = KindLines {
heuristic: 1,
bus: 1,
..lines(0, 0, 0, 0)
};
let track = WhyTrack {
heuristic: Some(Why::UsesMemberGuess),
bus: Some(Why::BusHop),
..WhyTrack::default()
};
assert_eq!(primary_why(&l, &track), Why::UsesMemberGuess);
}
#[test]
fn all_six_slots_set_still_picks_ctor_di() {
let l = KindLines {
ctor_di: 1,
direct: 1,
direct_amb: 1,
iface: 1,
heuristic: 1,
bus: 1,
};
let track = WhyTrack {
direct: Some(Why::UsesType),
direct_amb: Some(Why::Inherits),
iface: Some(Why::UsesMemberExt),
heuristic: Some(Why::UsesMemberGuess),
bus: Some(Why::BusHop),
};
assert_eq!(primary_why(&l, &track), Why::CtorDi);
}
}