Skip to main content

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}