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    /// Flow edge: DataNode def-use or dataflow relationship.
32    Flow,
33}
34
35impl EdgeKind {
36    /// Returns true if this edge kind participates in SCC computation.
37    pub fn participates_in_scc(self) -> bool {
38        matches!(self, EdgeKind::Import | EdgeKind::Reference)
39    }
40
41    /// Returns true if this edge represents a cross-file dependency.
42    pub fn is_cross_file(self) -> bool {
43        matches!(self, EdgeKind::Import)
44    }
45
46    /// Returns the human-readable name of this edge kind.
47    pub const fn as_str(self) -> &'static str {
48        match self {
49            EdgeKind::Ownership => "ownership",
50            EdgeKind::Import => "import",
51            EdgeKind::Reference => "reference",
52            EdgeKind::Flow => "flow",
53        }
54    }
55}
56
57impl std::fmt::Display for EdgeKind {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "{}", self.as_str())
60    }
61}
62
63/// Data stored for each edge in the graph.
64#[derive(Debug, Clone, Copy, serde::Serialize)]
65pub struct EdgeData {
66    /// Semantic kind of the relationship.
67    pub kind: EdgeKind,
68
69    /// Confidence level for the edge resolution.
70    /// Used for cross-language and best-effort resolution.
71    pub confidence: f32,
72
73    /// Flow kind for dataflow edges (None for non-Flow edges).
74    pub flow_kind: Option<crate::model::FlowKind>,
75}
76
77impl EdgeData {
78    /// Creates a new edge with full confidence (1.0) and no flow kind.
79    pub fn new(kind: EdgeKind) -> Self {
80        Self {
81            kind,
82            confidence: 1.0,
83            flow_kind: None,
84        }
85    }
86
87    /// Creates a new edge with specified confidence and no flow kind.
88    pub fn with_confidence(kind: EdgeKind, confidence: f32) -> Self {
89        Self {
90            kind,
91            confidence: confidence.clamp(0.0, 1.0),
92            flow_kind: None,
93        }
94    }
95
96    /// Creates a Flow edge with a flow kind and confidence.
97    pub fn flow(flow_kind: crate::model::FlowKind, confidence: f32) -> Self {
98        Self {
99            kind: EdgeKind::Flow,
100            confidence: confidence.clamp(0.0, 1.0),
101            flow_kind: Some(flow_kind),
102        }
103    }
104
105    pub fn participates_in_scc(&self) -> bool {
106        self.kind.participates_in_scc()
107    }
108}
109
110impl Default for EdgeData {
111    fn default() -> Self {
112        Self::new(EdgeKind::Reference)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn edge_kind_scc_participation() {
122        assert!(!EdgeKind::Ownership.participates_in_scc());
123        assert!(EdgeKind::Import.participates_in_scc());
124        assert!(EdgeKind::Reference.participates_in_scc());
125    }
126
127    #[test]
128    fn edge_kind_as_str() {
129        assert_eq!(EdgeKind::Ownership.as_str(), "ownership");
130        assert_eq!(EdgeKind::Import.as_str(), "import");
131        assert_eq!(EdgeKind::Reference.as_str(), "reference");
132        assert_eq!(EdgeKind::Flow.as_str(), "flow");
133    }
134
135    #[test]
136    fn flow_edge_excluded_from_scc() {
137        assert!(!EdgeKind::Flow.participates_in_scc());
138    }
139
140    #[test]
141    fn flow_edge_not_cross_file() {
142        assert!(!EdgeKind::Flow.is_cross_file());
143    }
144
145    #[test]
146    fn edge_kind_display() {
147        assert_eq!(format!("{}", EdgeKind::Import), "import");
148    }
149
150    #[test]
151    fn edge_data_new_defaults_to_full_confidence() {
152        let edge = EdgeData::new(EdgeKind::Import);
153        assert_eq!(edge.kind, EdgeKind::Import);
154        assert_eq!(edge.confidence, 1.0);
155        assert!(edge.participates_in_scc());
156    }
157
158    #[test]
159    fn edge_data_with_confidence_clamps() {
160        let low = EdgeData::with_confidence(EdgeKind::Reference, -0.5);
161        assert_eq!(low.confidence, 0.0);
162
163        let high = EdgeData::with_confidence(EdgeKind::Reference, 1.5);
164        assert_eq!(high.confidence, 1.0);
165
166        let mid = EdgeData::with_confidence(EdgeKind::Reference, 0.75);
167        assert_eq!(mid.confidence, 0.75);
168    }
169
170    #[test]
171    fn edge_data_default() {
172        let edge: EdgeData = Default::default();
173        assert_eq!(edge.confidence, 1.0);
174        assert!(edge.participates_in_scc());
175    }
176}