#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
#[non_exhaustive]
pub enum EdgeKind {
Ownership,
Import,
Reference,
Flow,
}
pub const CONFIDENCE_OWN_OR_DIRECT: f32 = 1.0;
pub const CONFIDENCE_TRANSITIVE: f32 = 0.8;
pub const CONFIDENCE_CROSS_LANGUAGE: f32 = 0.6;
pub const CONFIDENCE_CLIENT_UNIQUE_LOAD: f32 = 1.0;
pub const CONFIDENCE_CLIENT_MULTI_LOAD: f32 = 0.8;
pub const CONFIDENCE_CLIENT_UNIQUE_GLOBAL: f32 = 0.6;
pub const CONFIDENCE_CLIENT_MULTI_GLOBAL: f32 = 0.5;
pub const CONFIDENCE_COMPUTED: f32 = 0.4;
pub const CONFIDENCE_DEF_USE: f32 = 0.9;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ConfidenceTier {
OwnOrDirect,
DefUse,
Transitive,
CrossLanguage,
ClientMultiGlobal,
Computed,
Unknown,
}
pub fn confidence_tier(confidence: f32) -> ConfidenceTier {
if confidence == CONFIDENCE_OWN_OR_DIRECT || confidence == CONFIDENCE_CLIENT_UNIQUE_LOAD {
ConfidenceTier::OwnOrDirect
} else if confidence == CONFIDENCE_DEF_USE {
ConfidenceTier::DefUse
} else if confidence == CONFIDENCE_TRANSITIVE || confidence == CONFIDENCE_CLIENT_MULTI_LOAD {
ConfidenceTier::Transitive
} else if confidence == CONFIDENCE_CROSS_LANGUAGE
|| confidence == CONFIDENCE_CLIENT_UNIQUE_GLOBAL
{
ConfidenceTier::CrossLanguage
} else if confidence == CONFIDENCE_CLIENT_MULTI_GLOBAL {
ConfidenceTier::ClientMultiGlobal
} else if confidence == CONFIDENCE_COMPUTED {
ConfidenceTier::Computed
} else {
ConfidenceTier::Unknown
}
}
impl EdgeKind {
pub fn participates_in_scc(self) -> bool {
matches!(self, EdgeKind::Import | EdgeKind::Reference)
}
pub fn is_cross_file(self) -> bool {
matches!(self, EdgeKind::Import)
}
pub const fn as_str(self) -> &'static str {
match self {
EdgeKind::Ownership => "ownership",
EdgeKind::Import => "import",
EdgeKind::Reference => "reference",
EdgeKind::Flow => "flow",
}
}
}
impl std::fmt::Display for EdgeKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, serde::Serialize)]
pub struct EdgeData {
pub kind: EdgeKind,
pub confidence: f32,
pub flow_kind: Option<crate::model::FlowKind>,
}
impl EdgeData {
pub fn new(kind: EdgeKind) -> Self {
Self {
kind,
confidence: 1.0,
flow_kind: None,
}
}
pub fn with_confidence(kind: EdgeKind, confidence: f32) -> Self {
Self {
kind,
confidence: confidence.clamp(0.0, 1.0),
flow_kind: None,
}
}
pub fn flow(flow_kind: crate::model::FlowKind, confidence: f32) -> Self {
Self {
kind: EdgeKind::Flow,
confidence: confidence.clamp(0.0, 1.0),
flow_kind: Some(flow_kind),
}
}
pub fn participates_in_scc(&self) -> bool {
self.kind.participates_in_scc()
}
pub(crate) fn merge_repeated(
&mut self,
confidence: f32,
flow_kind: Option<crate::model::FlowKind>,
) {
self.confidence = self.confidence.max(confidence.clamp(0.0, 1.0));
if self.flow_kind.is_none() {
self.flow_kind = flow_kind;
}
}
}
impl Default for EdgeData {
fn default() -> Self {
Self::new(EdgeKind::Reference)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edge_kind_scc_participation() {
assert!(!EdgeKind::Ownership.participates_in_scc());
assert!(EdgeKind::Import.participates_in_scc());
assert!(EdgeKind::Reference.participates_in_scc());
}
#[test]
fn edge_kind_as_str() {
assert_eq!(EdgeKind::Ownership.as_str(), "ownership");
assert_eq!(EdgeKind::Import.as_str(), "import");
assert_eq!(EdgeKind::Reference.as_str(), "reference");
assert_eq!(EdgeKind::Flow.as_str(), "flow");
}
#[test]
fn flow_edge_excluded_from_scc() {
assert!(!EdgeKind::Flow.participates_in_scc());
}
#[test]
fn flow_edge_not_cross_file() {
assert!(!EdgeKind::Flow.is_cross_file());
}
#[test]
fn edge_kind_display() {
assert_eq!(format!("{}", EdgeKind::Import), "import");
}
#[test]
fn edge_data_new_defaults_to_full_confidence() {
let edge = EdgeData::new(EdgeKind::Import);
assert_eq!(edge.kind, EdgeKind::Import);
assert_eq!(edge.confidence, 1.0);
assert!(edge.participates_in_scc());
}
#[test]
fn edge_data_with_confidence_clamps() {
let low = EdgeData::with_confidence(EdgeKind::Reference, -0.5);
assert_eq!(low.confidence, 0.0);
let high = EdgeData::with_confidence(EdgeKind::Reference, 1.5);
assert_eq!(high.confidence, 1.0);
let mid = EdgeData::with_confidence(EdgeKind::Reference, 0.75);
assert_eq!(mid.confidence, 0.75);
}
#[test]
fn edge_data_default() {
let edge: EdgeData = Default::default();
assert_eq!(edge.confidence, 1.0);
assert!(edge.participates_in_scc());
}
#[test]
fn every_ladder_value_has_exactly_one_tier() {
for (confidence, tier) in [
(CONFIDENCE_OWN_OR_DIRECT, ConfidenceTier::OwnOrDirect),
(CONFIDENCE_CLIENT_UNIQUE_LOAD, ConfidenceTier::OwnOrDirect),
(CONFIDENCE_DEF_USE, ConfidenceTier::DefUse),
(CONFIDENCE_TRANSITIVE, ConfidenceTier::Transitive),
(CONFIDENCE_CLIENT_MULTI_LOAD, ConfidenceTier::Transitive),
(CONFIDENCE_CROSS_LANGUAGE, ConfidenceTier::CrossLanguage),
(
CONFIDENCE_CLIENT_UNIQUE_GLOBAL,
ConfidenceTier::CrossLanguage,
),
(
CONFIDENCE_CLIENT_MULTI_GLOBAL,
ConfidenceTier::ClientMultiGlobal,
),
(CONFIDENCE_COMPUTED, ConfidenceTier::Computed),
] {
assert_eq!(confidence_tier(confidence), tier, "at {confidence}");
}
}
#[test]
fn a_value_between_ladder_steps_is_unknown() {
for confidence in [0.7, 0.0, 1.5, -1.0] {
assert_eq!(
confidence_tier(confidence),
ConfidenceTier::Unknown,
"at {confidence}"
);
}
}
}