Skip to main content

sinter_core/
edge.rs

1use serde::{Deserialize, Serialize};
2
3use crate::node::{NodeId, Span};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6pub enum Relation {
7    Calls,
8    Uses,
9    Imports,
10    Contains,
11    Implements,
12    Extends,
13    /// The source reads rows from the destination relation.
14    Reads,
15    /// The source can insert, update, or delete rows in the destination table.
16    Writes,
17    /// The source declares creation of the destination database object.
18    Creates,
19    /// The source changes the destination database object.
20    Alters,
21    /// The source declares removal of the destination database object.
22    Drops,
23}
24
25impl Relation {
26    pub fn as_str(self) -> &'static str {
27        match self {
28            Self::Calls => "calls",
29            Self::Uses => "uses",
30            Self::Imports => "imports",
31            Self::Contains => "contains",
32            Self::Implements => "implements",
33            Self::Extends => "extends",
34            Self::Reads => "reads",
35            Self::Writes => "writes",
36            Self::Creates => "creates",
37            Self::Alters => "alters",
38            Self::Drops => "drops",
39        }
40    }
41}
42
43/// What binds this edge to its target (R2: evidence or nothing).
44/// Global name uniqueness is not evidence and has no variant here.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
46pub enum Evidence {
47    /// Syntactic containment seen directly in the parse tree.
48    Structural,
49    /// Name visible in the reference's own file scope.
50    Scope,
51    /// An import statement binds the reference's path to the target.
52    Import,
53    /// A compiler-produced SCIP index binds reference to definition.
54    Scip,
55    /// An operator-declared binding from a workspace manifest (runtime
56    /// coupling like queue topics/HTTP routes that no static analysis can
57    /// see). Auditable in the manifest; never inferred.
58    Declared,
59    /// Dynamic-dispatch fan-out: a trait/interface method is assumed to
60    /// reach every implementation in the corpus. Conservative
61    /// over-approximation, deliberately excludable (`--certain`,
62    /// `--evidence` without "dynamic").
63    Dynamic,
64}
65
66impl Evidence {
67    pub fn as_str(self) -> &'static str {
68        match self {
69            Self::Structural => "structural",
70            Self::Scope => "scope",
71            Self::Import => "import",
72            Self::Scip => "scip",
73            Self::Declared => "declared",
74            Self::Dynamic => "dynamic",
75        }
76    }
77
78    /// Compiler-grade evidence is certain; heuristic-free but indirect
79    /// evidence (scope/import matching) is inferred.
80    pub fn confidence(self) -> Confidence {
81        match self {
82            Self::Structural | Self::Scip | Self::Declared => Confidence::Certain,
83            Self::Scope | Self::Import | Self::Dynamic => Confidence::Inferred,
84        }
85    }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
89pub enum Confidence {
90    Certain,
91    Inferred,
92}
93
94/// Directed edge `src -> dst`. The graph is a multigraph: parallel edges
95/// that differ in relation, evidence, or confidence coexist.
96#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
97pub struct Edge {
98    pub src: NodeId,
99    pub dst: NodeId,
100    pub relation: Relation,
101    pub evidence: Evidence,
102    pub confidence: Confidence,
103    /// Byte span of the binding reference in the src node's file (the file
104    /// is derivable from `src`). None when no single site exists:
105    /// containment, dynamic fan-out, implements/extends pairing, declared
106    /// links. When several sites bind the same (src, dst, relation,
107    /// evidence), this is the representative (smallest) one — the field is
108    /// after identity so identity orders first.
109    pub site: Option<Span>,
110    /// Further sites binding the same identity, ascending, capped so that
111    /// `1 + extra_sites.len() <= MAX_SITES`. Empty for a single-site edge.
112    pub extra_sites: Vec<Span>,
113    /// Distinct sites observed for this identity, including the ones the
114    /// cap dropped. 0 when the edge has no site at all.
115    pub sites_total: u32,
116}
117
118/// Sites kept per edge. A hub edge can be called dozens of times; the
119/// answer stays bounded ("3 of 12 shown") instead of growing with fan-in.
120pub const MAX_SITES: usize = 8;
121
122impl Edge {
123    /// Identity without the site: two edges equal here are the same
124    /// dependency fact observed at (possibly) different call sites.
125    /// One edge with a single site: the shape extraction and resolution
126    /// produce, before storage merges same-identity sites together.
127    pub fn single(
128        src: NodeId,
129        dst: NodeId,
130        relation: Relation,
131        evidence: Evidence,
132        confidence: Confidence,
133        site: Option<Span>,
134    ) -> Self {
135        Self {
136            src,
137            dst,
138            relation,
139            evidence,
140            confidence,
141            site,
142            extra_sites: Vec::new(),
143            sites_total: u32::from(site.is_some()),
144        }
145    }
146
147    /// Every kept site, ascending (representative first). Empty when the
148    /// edge has none.
149    pub fn sites(&self) -> impl Iterator<Item = Span> + '_ {
150        self.site
151            .into_iter()
152            .chain(self.extra_sites.iter().copied())
153    }
154
155    /// Sites this edge has beyond the ones it kept.
156    pub fn sites_omitted(&self) -> u32 {
157        self.sites_total.saturating_sub(self.sites().count() as u32)
158    }
159
160    pub fn identity(&self) -> (&NodeId, &NodeId, Relation, Evidence, Confidence) {
161        (
162            &self.src,
163            &self.dst,
164            self.relation,
165            self.evidence,
166            self.confidence,
167        )
168    }
169}