use crate::graph;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Why {
Inherits,
UsesType,
UsesMemberPrecise,
UsesMemberExt,
UsesMemberGuess,
CtorDi,
Imports,
Declaration,
TestAttribute,
TestProject,
Implements,
Overrides,
ImportedEdge,
BusHop,
}
impl Why {
pub const ALL: [Why; 14] = [
Why::Inherits,
Why::UsesType,
Why::UsesMemberPrecise,
Why::UsesMemberExt,
Why::UsesMemberGuess,
Why::CtorDi,
Why::Imports,
Why::Declaration,
Why::TestAttribute,
Why::TestProject,
Why::Implements,
Why::Overrides,
Why::ImportedEdge,
Why::BusHop,
];
pub fn as_str(self) -> &'static str {
match self {
Why::Inherits => "inherits",
Why::UsesType => "uses-type",
Why::UsesMemberPrecise => "uses-member-precise",
Why::UsesMemberExt => "uses-member-ext",
Why::UsesMemberGuess => "uses-member-guess",
Why::CtorDi => "ctor-di",
Why::Imports => "imports",
Why::Declaration => "declaration",
Why::TestAttribute => "test-attribute",
Why::TestProject => "test-project",
Why::Implements => "implements",
Why::Overrides => "overrides",
Why::ImportedEdge => "imported-edge",
Why::BusHop => "bus-hop",
}
}
}
pub(crate) fn why_for_uses_member(heuristic: bool, tier: Option<graph::HeuristicTier>) -> Why {
if !heuristic {
return Why::UsesMemberPrecise;
}
match tier {
Some(graph::HeuristicTier::Ext) => Why::UsesMemberExt,
_ => Why::UsesMemberGuess,
}
}
pub(crate) fn why_for_edge(edge: &graph::Edge) -> Why {
match edge {
graph::Edge::Inherits { .. } => Why::Inherits,
graph::Edge::UsesType { .. } => Why::UsesType,
graph::Edge::UsesMember { heuristic, tier, .. } => why_for_uses_member(*heuristic, *tier),
graph::Edge::Imports { .. } => Why::Imports,
graph::Edge::Implements { .. } => Why::Implements,
graph::Edge::Overrides { .. } => Why::Overrides,
graph::Edge::BusHop { .. } => Why::BusHop,
graph::Edge::Ambiguous { origin, .. } => match origin.as_str() {
"inherits" => Why::Inherits,
"uses-type" => Why::UsesType,
_ => Why::UsesMemberPrecise,
},
_ => unreachable!(
"why_for_edge is only called with inherits/uses-type/uses-member/imports/implements/overrides/bus-hop/ambiguous edges, the only kinds that ever populate a refs/read/impact row"
),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_why_word_is_one_of_the_closed_vocabulary() {
const EXPECTED: &[&str] = &[
"inherits",
"uses-type",
"uses-member-precise",
"uses-member-ext",
"uses-member-guess",
"ctor-di",
"imports",
"declaration",
"test-attribute",
"test-project",
"implements",
"overrides",
"imported-edge",
"bus-hop",
];
let words: Vec<&str> = Why::ALL.iter().map(|w| w.as_str()).collect();
for word in &words {
assert!(
EXPECTED.contains(word),
"{word} is not in the closed why vocabulary"
);
}
assert_eq!(
words.len(),
EXPECTED.len(),
"ALL must name every vocabulary word exactly once"
);
}
#[test]
fn why_for_edge_reads_uses_member_tier_the_same_way_row_tier_folds_it() {
let precise = graph::Edge::uses_member(
"a.cs".to_string(),
1,
"T".to_string(),
"b.cs".to_string(),
None,
None,
);
assert_eq!(why_for_edge(&precise), Why::UsesMemberPrecise);
let ext = graph::Edge::uses_member(
"a.cs".to_string(),
1,
"T".to_string(),
"b.cs".to_string(),
None,
Some(graph::HeuristicTier::Ext),
);
assert_eq!(why_for_edge(&ext), Why::UsesMemberExt);
let guess = graph::Edge::uses_member(
"a.cs".to_string(),
1,
"T".to_string(),
"b.cs".to_string(),
None,
Some(graph::HeuristicTier::Guess),
);
assert_eq!(why_for_edge(&guess), Why::UsesMemberGuess);
}
#[test]
fn why_for_edge_reads_ambiguous_origin_as_its_ref_kind() {
let inherits = graph::Edge::Ambiguous {
origin: "inherits".to_string(),
from_file: "a.cs".to_string(),
from_line: 1,
raw: "Base".to_string(),
candidates: Vec::new(),
candidate_count: 2,
};
assert_eq!(why_for_edge(&inherits), Why::Inherits);
let uses_type = graph::Edge::Ambiguous {
origin: "uses-type".to_string(),
from_file: "a.cs".to_string(),
from_line: 1,
raw: "T".to_string(),
candidates: Vec::new(),
candidate_count: 2,
};
assert_eq!(why_for_edge(&uses_type), Why::UsesType);
let uses_member = graph::Edge::Ambiguous {
origin: "uses-member".to_string(),
from_file: "a.cs".to_string(),
from_line: 1,
raw: "M".to_string(),
candidates: Vec::new(),
candidate_count: 2,
};
assert_eq!(why_for_edge(&uses_member), Why::UsesMemberPrecise);
}
#[test]
fn why_for_edge_reads_bus_hop_as_its_own_word() {
let hop = graph::Edge::BusHop {
from_file: "Publisher.cs".to_string(),
from_line: 1,
message: "App.Msg".to_string(),
to: "App.Handler".to_string(),
to_file: "Handler.cs".to_string(),
evidence: "base-arg".to_string(),
};
assert_eq!(why_for_edge(&hop), Why::BusHop);
assert_eq!(Why::BusHop.as_str(), "bus-hop");
}
}