1use crate::graph;
7
8use super::impact::VisitedEntry;
9use super::seq::SeqSet;
10use super::why::why_for_edge;
13pub(super) use super::why::Why;
14
15#[derive(Debug, Clone, Default, PartialEq)]
24pub struct KindLines {
25 pub direct: usize,
27 pub direct_amb: usize,
29 pub ctor_di: usize,
31 pub heuristic: usize,
33 pub iface: usize,
35 pub bus: usize,
37}
38
39pub(super) fn note_line(slot: &mut usize, line: usize) {
42 if line > 0 && (*slot == 0 || line < *slot) {
43 *slot = line;
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
57pub struct BusOrigin {
58 pub file: String,
60 pub line: usize,
62 pub message: String,
64 pub to: String,
66 pub to_file: String,
68}
69
70#[derive(Debug, Clone, Default)]
73pub(super) struct Hit {
74 pub(super) via_count: u32,
75 pub(super) ambiguous_count: u32,
76 pub(super) heuristic_count: u32,
77 pub(super) ext_count: u32,
81 pub(super) symbols: SeqSet<String>,
82 pub(super) iface_via: SeqSet<String>,
85 pub(super) lines: KindLines,
87 pub(super) why_track: WhyTrack,
89 pub(super) has_non_bus_path: bool,
91 pub(super) bus_origin: Option<BusOrigin>,
98}
99
100#[derive(Debug, Clone, Default)]
106pub(super) struct WhyTrack {
107 pub(super) direct: Option<Why>,
108 pub(super) direct_amb: Option<Why>,
109 pub(super) heuristic: Option<Why>,
110 pub(super) iface: Option<Why>,
111 pub(super) bus: Option<Why>,
116}
117
118fn note_line_why(slot: &mut usize, why_slot: &mut Option<Why>, line: usize, why: Why) {
123 if line > 0 && (*slot == 0 || line < *slot) {
124 *slot = line;
125 *why_slot = Some(why);
126 }
127}
128
129impl Hit {
130 pub(super) fn note_direct(
137 &mut self,
138 line: usize,
139 edge: &graph::Edge,
140 origin: Option<&BusOrigin>,
141 ) {
142 let why = why_for_edge(edge);
143 note_line_why(
144 &mut self.lines.direct,
145 &mut self.why_track.direct,
146 line,
147 why,
148 );
149 self.note_taint(origin);
150 }
151 pub(super) fn note_direct_amb(
153 &mut self,
154 line: usize,
155 edge: &graph::Edge,
156 origin: Option<&BusOrigin>,
157 ) {
158 let why = why_for_edge(edge);
159 note_line_why(
160 &mut self.lines.direct_amb,
161 &mut self.why_track.direct_amb,
162 line,
163 why,
164 );
165 self.note_taint(origin);
166 }
167 pub(super) fn note_iface(
169 &mut self,
170 line: usize,
171 edge: &graph::Edge,
172 origin: Option<&BusOrigin>,
173 ) {
174 let why = why_for_edge(edge);
175 note_line_why(&mut self.lines.iface, &mut self.why_track.iface, line, why);
176 self.note_taint(origin);
177 }
178 pub(super) fn note_heuristic(
180 &mut self,
181 line: usize,
182 edge: &graph::Edge,
183 origin: Option<&BusOrigin>,
184 ) {
185 let why = why_for_edge(edge);
186 note_line_why(
187 &mut self.lines.heuristic,
188 &mut self.why_track.heuristic,
189 line,
190 why,
191 );
192 self.note_taint(origin);
193 }
194 pub(super) fn note_bus(&mut self, line: usize, edge: &graph::Edge) {
202 let why = why_for_edge(edge);
203 note_line_why(&mut self.lines.bus, &mut self.why_track.bus, line, why);
204 if let graph::Edge::BusHop {
205 from_file,
206 from_line,
207 message,
208 to,
209 to_file,
210 ..
211 } = edge
212 {
213 self.note_bus_origin(BusOrigin {
214 file: from_file.clone(),
215 line: *from_line,
216 message: message.clone(),
217 to: to.clone(),
218 to_file: to_file.clone(),
219 });
220 }
221 }
222 fn note_bus_origin(&mut self, origin: BusOrigin) {
224 if self
225 .bus_origin
226 .as_ref()
227 .is_none_or(|existing| origin < *existing)
228 {
229 self.bus_origin = Some(origin);
230 }
231 }
232 pub(super) fn note_taint(&mut self, origin: Option<&BusOrigin>) {
239 match origin {
240 Some(o) => self.note_bus_origin(o.clone()),
241 None => self.has_non_bus_path = true,
242 }
243 }
244}
245
246pub(super) fn from_lines_of(lines: &KindLines) -> Vec<(&'static str, usize)> {
250 let mut out: Vec<(&'static str, usize)> = Vec::new();
251 let direct = if lines.direct != 0 {
252 lines.direct
253 } else {
254 lines.direct_amb
255 };
256 if direct != 0 {
257 out.push(("direct", direct));
258 }
259 if lines.ctor_di != 0 {
260 out.push(("ctor-di", lines.ctor_di));
261 }
262 if lines.heuristic != 0 {
263 out.push(("heuristic", lines.heuristic));
264 }
265 if lines.iface != 0 {
266 out.push(("iface", lines.iface));
267 }
268 if lines.bus != 0 {
269 out.push(("bus", lines.bus));
270 }
271 out
272}
273
274pub(super) fn note_kind_edge(
280 h: &mut Hit,
281 line: usize,
282 edge: &graph::Edge,
283 origin: Option<&BusOrigin>,
284) {
285 if matches!(edge, graph::Edge::BusHop { .. }) {
286 h.note_bus(line, edge);
287 } else {
288 h.note_direct(line, edge, origin);
289 }
290}
291
292fn primary_why(lines: &KindLines, track: &WhyTrack) -> Why {
298 if lines.ctor_di != 0 {
299 return Why::CtorDi;
300 }
301 if lines.direct != 0 {
302 return track.direct.unwrap_or(Why::UsesMemberPrecise);
303 }
304 if lines.direct_amb != 0 {
305 return track.direct_amb.unwrap_or(Why::UsesMemberPrecise);
306 }
307 if lines.iface != 0 {
308 return track.iface.unwrap_or(Why::UsesMemberPrecise);
309 }
310 if lines.heuristic != 0 || track.heuristic.is_some() {
311 return track.heuristic.unwrap_or(Why::UsesMemberGuess);
312 }
313 track.bus.unwrap_or(Why::BusHop)
314}
315
316pub(super) fn build_visited_entry(hop: u32, infra: bool, h: &Hit) -> VisitedEntry {
320 VisitedEntry {
321 hop,
322 via_count: h.via_count,
323 ambiguous_count: h.ambiguous_count,
324 heuristic_count: h.heuristic_count,
325 ext_count: h.ext_count,
326 symbols: h.symbols.clone().into_vec(),
327 iface_via: h.iface_via.clone().into_vec(),
328 lines: h.lines.clone(),
329 infra,
330 why: primary_why(&h.lines, &h.why_track),
331 bus_only: !h.has_non_bus_path,
332 bus_origin: if h.has_non_bus_path {
335 None
336 } else {
337 h.bus_origin.clone()
338 },
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 fn lines(ctor_di: usize, direct: usize, direct_amb: usize, iface: usize) -> KindLines {
347 KindLines {
348 ctor_di,
349 direct,
350 direct_amb,
351 iface,
352 heuristic: 0,
353 bus: 0,
354 }
355 }
356
357 #[test]
358 fn ctor_di_alone_wins() {
359 let l = lines(1, 0, 0, 0);
360 let track = WhyTrack::default();
361 assert_eq!(primary_why(&l, &track), Why::CtorDi);
362 }
363
364 #[test]
365 fn direct_alone_reports_its_tracked_why() {
366 let l = lines(0, 1, 0, 0);
367 let track = WhyTrack {
368 direct: Some(Why::UsesType),
369 ..WhyTrack::default()
370 };
371 assert_eq!(primary_why(&l, &track), Why::UsesType);
372 }
373
374 #[test]
375 fn direct_amb_alone_reports_its_tracked_why() {
376 let l = lines(0, 0, 1, 0);
377 let track = WhyTrack {
378 direct_amb: Some(Why::Inherits),
379 ..WhyTrack::default()
380 };
381 assert_eq!(primary_why(&l, &track), Why::Inherits);
382 }
383
384 #[test]
385 fn iface_alone_reports_its_tracked_why() {
386 let l = lines(0, 0, 0, 1);
387 let track = WhyTrack {
388 iface: Some(Why::UsesMemberExt),
389 ..WhyTrack::default()
390 };
391 assert_eq!(primary_why(&l, &track), Why::UsesMemberExt);
392 }
393
394 #[test]
395 fn heuristic_alone_reports_its_tracked_why() {
396 let l = lines(0, 0, 0, 0);
397 let track = WhyTrack {
398 heuristic: Some(Why::UsesMemberGuess),
399 ..WhyTrack::default()
400 };
401 assert_eq!(primary_why(&l, &track), Why::UsesMemberGuess);
402 }
403
404 #[test]
405 fn ctor_di_outranks_direct() {
406 let l = lines(1, 1, 0, 0);
407 let track = WhyTrack {
408 direct: Some(Why::UsesType),
409 ..WhyTrack::default()
410 };
411 assert_eq!(primary_why(&l, &track), Why::CtorDi);
412 }
413
414 #[test]
415 fn direct_outranks_direct_amb() {
416 let l = lines(0, 1, 1, 0);
417 let track = WhyTrack {
418 direct: Some(Why::UsesType),
419 direct_amb: Some(Why::Inherits),
420 ..WhyTrack::default()
421 };
422 assert_eq!(primary_why(&l, &track), Why::UsesType);
423 }
424
425 #[test]
426 fn direct_amb_outranks_iface() {
427 let l = lines(0, 0, 1, 1);
428 let track = WhyTrack {
429 direct_amb: Some(Why::Inherits),
430 iface: Some(Why::UsesMemberExt),
431 ..WhyTrack::default()
432 };
433 assert_eq!(primary_why(&l, &track), Why::Inherits);
434 }
435
436 #[test]
437 fn iface_outranks_heuristic() {
438 let l = lines(0, 0, 0, 1);
439 let track = WhyTrack {
440 iface: Some(Why::UsesMemberExt),
441 heuristic: Some(Why::UsesMemberGuess),
442 ..WhyTrack::default()
443 };
444 assert_eq!(primary_why(&l, &track), Why::UsesMemberExt);
445 }
446
447 #[test]
448 fn bus_alone_reports_its_tracked_why() {
449 let l = KindLines {
450 bus: 1,
451 ..lines(0, 0, 0, 0)
452 };
453 let track = WhyTrack {
454 bus: Some(Why::BusHop),
455 ..WhyTrack::default()
456 };
457 assert_eq!(primary_why(&l, &track), Why::BusHop);
458 }
459
460 #[test]
461 fn heuristic_outranks_bus() {
462 let l = KindLines {
463 heuristic: 1,
464 bus: 1,
465 ..lines(0, 0, 0, 0)
466 };
467 let track = WhyTrack {
468 heuristic: Some(Why::UsesMemberGuess),
469 bus: Some(Why::BusHop),
470 ..WhyTrack::default()
471 };
472 assert_eq!(primary_why(&l, &track), Why::UsesMemberGuess);
473 }
474
475 #[test]
476 fn all_six_slots_set_still_picks_ctor_di() {
477 let l = KindLines {
478 ctor_di: 1,
479 direct: 1,
480 direct_amb: 1,
481 iface: 1,
482 heuristic: 1,
483 bus: 1,
484 };
485 let track = WhyTrack {
486 direct: Some(Why::UsesType),
487 direct_amb: Some(Why::Inherits),
488 iface: Some(Why::UsesMemberExt),
489 heuristic: Some(Why::UsesMemberGuess),
490 bus: Some(Why::BusHop),
491 };
492 assert_eq!(primary_why(&l, &track), Why::CtorDi);
493 }
494}