pedant_graph/analysis/selection.rs
1//! Which edges one analysis admits.
2//!
3//! The selection is stated by the caller and applied once, so every query and
4//! metric reads the same admitted set. Each kind and certainty is assigned its
5//! bit by an explicit match, which keeps the declaration order of the graph's
6//! own enums out of the algorithm.
7
8use crate::edge::{GraphCertainty, GraphEdge, GraphEdgeKind};
9
10/// Every edge kind the graph vocabulary states.
11const EVERY_KIND: &[GraphEdgeKind] = &[
12 GraphEdgeKind::Call,
13 GraphEdgeKind::Import,
14 GraphEdgeKind::Implementation,
15 GraphEdgeKind::Reference,
16 GraphEdgeKind::DependsOn,
17];
18
19/// Every certainty the graph vocabulary states.
20const EVERY_CERTAINTY: &[GraphCertainty] = &[GraphCertainty::Resolved, GraphCertainty::Possible];
21
22/// The edge kinds and certainties one analysis admits.
23///
24/// A selection admits exactly the Cartesian intersection of the kinds and the
25/// certainties it was stated with: naming a kind twice changes nothing, and
26/// either empty side admits no edge at all. There is no default, because which
27/// relations answer a question is the caller's to state rather than this
28/// crate's to assume.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct GraphEdgeSelection {
31 kinds: u8,
32 certainties: u8,
33}
34
35impl GraphEdgeSelection {
36 /// Exactly the edges whose kind is one of `kinds` and whose certainty is
37 /// one of `certainties`.
38 pub fn new(kinds: &[GraphEdgeKind], certainties: &[GraphCertainty]) -> Self {
39 Self {
40 kinds: kinds.iter().fold(0, |mask, kind| mask | kind_bit(*kind)),
41 certainties: certainties
42 .iter()
43 .fold(0, |mask, certainty| mask | certainty_bit(*certainty)),
44 }
45 }
46
47 /// Every kind at every certainty.
48 pub fn all() -> Self {
49 Self::new(EVERY_KIND, EVERY_CERTAINTY)
50 }
51
52 /// Every source relation at every certainty, and no build-unit dependency.
53 pub fn code_relations() -> Self {
54 Self {
55 kinds: EVERY_KIND
56 .iter()
57 .filter(|kind| is_code_relation(**kind))
58 .fold(0, |mask, kind| mask | kind_bit(*kind)),
59 certainties: Self::all().certainties,
60 }
61 }
62
63 /// Whether this selection admits one edge.
64 ///
65 /// The exact predicate analysis construction applies, so a caller can ask
66 /// the same question of a raw edge that the indexes were built with.
67 pub fn allows(self, edge: &GraphEdge) -> bool {
68 self.kinds & kind_bit(edge.kind()) != 0
69 && self.certainties & certainty_bit(edge.certainty()) != 0
70 }
71}
72
73/// Whether one kind states a relation between source declarations rather than
74/// between build units.
75///
76/// An explicit match rather than a second written-down list: a kind added to the
77/// vocabulary has to be placed on one side of this split here, instead of
78/// falling silently out of the selection that names itself the source relations.
79fn is_code_relation(kind: GraphEdgeKind) -> bool {
80 match kind {
81 GraphEdgeKind::Call
82 | GraphEdgeKind::Import
83 | GraphEdgeKind::Implementation
84 | GraphEdgeKind::Reference => true,
85 GraphEdgeKind::DependsOn => false,
86 }
87}
88
89/// The bit one edge kind occupies.
90fn kind_bit(kind: GraphEdgeKind) -> u8 {
91 match kind {
92 GraphEdgeKind::Call => 1,
93 GraphEdgeKind::Import => 1 << 1,
94 GraphEdgeKind::Implementation => 1 << 2,
95 GraphEdgeKind::Reference => 1 << 3,
96 GraphEdgeKind::DependsOn => 1 << 4,
97 }
98}
99
100/// The bit one certainty occupies.
101fn certainty_bit(certainty: GraphCertainty) -> u8 {
102 match certainty {
103 GraphCertainty::Resolved => 1,
104 GraphCertainty::Possible => 1 << 1,
105 }
106}