Skip to main content

meta_ast/graph/
edge.rs

1//! Edge types for the dependency graph.
2//!
3//! Defines the semantic relationships between nodes in the code graph.
4//! Edges are directed and carry metadata about the relationship type
5//! and confidence level.
6//!
7//! ## Design: uni-directional edges
8//!
9//! All edges are directed. There is no bidirectional or monitor/link
10//! pattern - an edge from A to B means A depends on B, not that B will
11//! be notified of A's failure. Shared-fate semantics (failure propagation)
12//! are a separate, optional annotation that the deploy layer may add
13//! during cut-edge RPC conversion. This follows the principle from
14//! *A Unified Semantics for Future Erlang* §2.2/§6.2: bidirectional links
15//! are replaced by uni-directional links plus monitors, and supervision
16//! trees can be built from uni-directional links alone.
17
18/// Semantic kind of a directed edge in the code graph.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
20#[non_exhaustive]
21pub enum EdgeKind {
22    /// Ownership edge: File owns/contains a symbol.
23    Ownership,
24
25    /// Import edge: File imports/depends on another file.
26    Import,
27
28    /// Reference edge: Symbol references/uses another symbol.
29    Reference,
30}
31
32impl EdgeKind {
33    /// Returns true if this edge kind participates in SCC computation.
34    pub fn participates_in_scc(self) -> bool {
35        matches!(self, EdgeKind::Import | EdgeKind::Reference)
36    }
37
38    /// Returns true if this edge represents a cross-file dependency.
39    pub fn is_cross_file(self) -> bool {
40        matches!(self, EdgeKind::Import)
41    }
42
43    /// Returns the human-readable name of this edge kind.
44    pub const fn as_str(self) -> &'static str {
45        match self {
46            EdgeKind::Ownership => "ownership",
47            EdgeKind::Import => "import",
48            EdgeKind::Reference => "reference",
49        }
50    }
51}
52
53impl std::fmt::Display for EdgeKind {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "{}", self.as_str())
56    }
57}
58
59/// Data stored for each edge in the graph.
60#[derive(Debug, Clone, Copy, serde::Serialize)]
61pub struct EdgeData {
62    /// Semantic kind of the relationship.
63    pub kind: EdgeKind,
64
65    /// Confidence level for the edge resolution.
66    /// Used for cross-language and best-effort resolution.
67    pub confidence: f32,
68}
69
70impl EdgeData {
71    /// Creates a new edge with full confidence (1.0).
72    pub fn new(kind: EdgeKind) -> Self {
73        Self {
74            kind,
75            confidence: 1.0,
76        }
77    }
78
79    /// Creates a new edge with specified confidence.
80    pub fn with_confidence(kind: EdgeKind, confidence: f32) -> Self {
81        Self {
82            kind,
83            confidence: confidence.clamp(0.0, 1.0),
84        }
85    }
86    pub fn participates_in_scc(&self) -> bool {
87        self.kind.participates_in_scc()
88    }
89}
90
91impl Default for EdgeData {
92    fn default() -> Self {
93        Self::new(EdgeKind::Reference)
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn edge_kind_scc_participation() {
103        assert!(!EdgeKind::Ownership.participates_in_scc());
104        assert!(EdgeKind::Import.participates_in_scc());
105        assert!(EdgeKind::Reference.participates_in_scc());
106    }
107
108    #[test]
109    fn edge_kind_as_str() {
110        assert_eq!(EdgeKind::Ownership.as_str(), "ownership");
111        assert_eq!(EdgeKind::Import.as_str(), "import");
112        assert_eq!(EdgeKind::Reference.as_str(), "reference");
113    }
114
115    #[test]
116    fn edge_kind_display() {
117        assert_eq!(format!("{}", EdgeKind::Import), "import");
118    }
119
120    #[test]
121    fn edge_data_new_defaults_to_full_confidence() {
122        let edge = EdgeData::new(EdgeKind::Import);
123        assert_eq!(edge.kind, EdgeKind::Import);
124        assert_eq!(edge.confidence, 1.0);
125        assert!(edge.participates_in_scc());
126    }
127
128    #[test]
129    fn edge_data_with_confidence_clamps() {
130        let low = EdgeData::with_confidence(EdgeKind::Reference, -0.5);
131        assert_eq!(low.confidence, 0.0);
132
133        let high = EdgeData::with_confidence(EdgeKind::Reference, 1.5);
134        assert_eq!(high.confidence, 1.0);
135
136        let mid = EdgeData::with_confidence(EdgeKind::Reference, 0.75);
137        assert_eq!(mid.confidence, 0.75);
138    }
139
140    #[test]
141    fn edge_data_default() {
142        let edge: EdgeData = Default::default();
143        assert_eq!(edge.confidence, 1.0);
144        assert!(edge.participates_in_scc());
145    }
146}