use crate::symbol::SymbolId;
use serde::{Deserialize, Serialize};
use slotmap::SecondaryMap;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet, VecDeque};
use super::graph_v2::{ChainDirection, ChainNode, ChainResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum RefKind {
#[default]
Owned,
Ref,
RefMut,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TypeDefKind {
Struct,
Enum,
Trait,
TypeAlias,
GenericParam,
AssociatedType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum UsageContext {
FieldType,
ParamType,
ReturnType,
LocalVar,
WhereBound,
ImplTarget,
ImplTrait,
UseStatement,
Cast,
Turbofish,
VariantField,
ConstType,
MatchScrutinee,
ConstructorCall,
StructLiteral,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
#[repr(transparent)]
pub struct TypeNodeId(u32);
impl TypeNodeId {
const KIND_SHIFT: u32 = 30;
const KIND_MASK: u32 = 0xC000_0000;
const INDEX_MASK: u32 = 0x3FFF_FFFF;
const KIND_DEF: u32 = 0;
const KIND_USAGE: u32 = 1;
const KIND_ARG: u32 = 2;
const KIND_BOUND: u32 = 3;
#[inline]
pub const fn def(idx: u32) -> Self {
Self((Self::KIND_DEF << Self::KIND_SHIFT) | idx)
}
#[inline]
pub const fn usage(idx: u32) -> Self {
Self((Self::KIND_USAGE << Self::KIND_SHIFT) | idx)
}
#[inline]
pub const fn arg(idx: u32) -> Self {
Self((Self::KIND_ARG << Self::KIND_SHIFT) | idx)
}
#[inline]
pub const fn bound(idx: u32) -> Self {
Self((Self::KIND_BOUND << Self::KIND_SHIFT) | idx)
}
#[inline]
pub const fn kind(self) -> NodeKind {
match (self.0 & Self::KIND_MASK) >> Self::KIND_SHIFT {
Self::KIND_DEF => NodeKind::Definition,
Self::KIND_USAGE => NodeKind::Usage,
Self::KIND_ARG => NodeKind::GenericArg,
Self::KIND_BOUND => NodeKind::TraitBound,
_ => unreachable!(),
}
}
#[inline]
pub const fn index(self) -> u32 {
self.0 & Self::INDEX_MASK
}
#[inline]
pub const fn as_u32(self) -> u32 {
self.0
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum NodeKind {
Definition,
Usage,
GenericArg,
TraitBound,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct DefinitionData {
pub symbol_id: SymbolId,
pub kind: TypeDefKind,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct UsageData {
pub resolved: Option<SymbolId>,
pub context: UsageContext,
pub ref_kind: RefKind,
pub container: Option<SymbolId>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct GenericArgData {
pub parent_usage: u32,
pub arg_index: u8,
pub resolved: Option<SymbolId>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct TraitBoundData {
pub target: TypeNodeId,
pub trait_id: Option<SymbolId>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct LookupTable {
pub symbol_to_def: SecondaryMap<SymbolId, u32>,
pub symbol_to_usages: HashMap<SymbolId, Vec<u32>>,
pub container_to_usages: HashMap<SymbolId, Vec<u32>>,
pub trait_to_bounds: HashMap<SymbolId, Vec<u32>>,
pub container_fields: HashMap<SymbolId, Vec<SymbolId>>,
}
#[derive(Clone, Default, Debug, Serialize)]
pub struct TypeFlowGraphV2 {
pub definitions: Vec<DefinitionData>,
pub usages: Vec<UsageData>,
pub generic_args: Vec<GenericArgData>,
pub trait_bounds: Vec<TraitBoundData>,
pub usage_to_args: Vec<SmallVec<[u32; 4]>>,
pub node_to_bounds: HashMap<TypeNodeId, SmallVec<[u32; 2]>>,
pub lookup: LookupTable,
}
#[derive(Debug, Clone, Default)]
pub struct TypeImpactV2 {
pub direct_usages: Vec<u32>,
pub bound_usages: Vec<u32>,
pub containing_types: Vec<SymbolId>,
pub generic_usages: Vec<u32>,
}
impl TypeFlowGraphV2 {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(defs: usize, usages: usize) -> Self {
Self {
definitions: Vec::with_capacity(defs),
usages: Vec::with_capacity(usages),
generic_args: Vec::with_capacity(usages), trait_bounds: Vec::new(),
usage_to_args: Vec::with_capacity(usages),
node_to_bounds: HashMap::new(),
lookup: LookupTable::default(),
}
}
pub fn add_definition(&mut self, symbol_id: SymbolId, kind: TypeDefKind) -> TypeNodeId {
if let Some(&idx) = self.lookup.symbol_to_def.get(symbol_id) {
return TypeNodeId::def(idx);
}
let idx = self.definitions.len() as u32;
self.definitions.push(DefinitionData { symbol_id, kind });
self.lookup.symbol_to_def.insert(symbol_id, idx);
TypeNodeId::def(idx)
}
pub fn add_usage(
&mut self,
context: UsageContext,
ref_kind: RefKind,
resolved: Option<SymbolId>,
container: Option<SymbolId>,
) -> TypeNodeId {
let idx = self.usages.len() as u32;
self.usages.push(UsageData {
resolved,
context,
ref_kind,
container,
});
self.usage_to_args.push(SmallVec::new());
if let Some(symbol_id) = resolved {
self.lookup
.symbol_to_usages
.entry(symbol_id)
.or_default()
.push(idx);
}
if let Some(cid) = container {
self.lookup
.container_to_usages
.entry(cid)
.or_default()
.push(idx);
}
TypeNodeId::usage(idx)
}
pub fn add_generic_arg(
&mut self,
parent: TypeNodeId,
arg_index: u8,
resolved: Option<SymbolId>,
) -> TypeNodeId {
let idx = self.generic_args.len() as u32;
self.generic_args.push(GenericArgData {
parent_usage: parent.index(),
arg_index,
resolved,
});
if parent.kind() == NodeKind::Usage {
if let Some(args) = self.usage_to_args.get_mut(parent.index() as usize) {
args.push(idx);
}
if let Some(symbol_id) = resolved {
self.lookup
.symbol_to_usages
.entry(symbol_id)
.or_default()
.push(parent.index());
}
}
TypeNodeId::arg(idx)
}
pub fn add_trait_bound(
&mut self,
target: TypeNodeId,
trait_id: Option<SymbolId>,
) -> TypeNodeId {
let idx = self.trait_bounds.len() as u32;
self.trait_bounds.push(TraitBoundData { target, trait_id });
self.node_to_bounds.entry(target).or_default().push(idx);
if let Some(tid) = trait_id {
self.lookup
.trait_to_bounds
.entry(tid)
.or_default()
.push(idx);
}
TypeNodeId::bound(idx)
}
pub fn add_contains(&mut self, container: SymbolId, field_type: SymbolId) {
self.lookup
.container_fields
.entry(container)
.or_default()
.push(field_type);
}
#[inline]
pub fn definition(&self, id: SymbolId) -> Option<TypeNodeId> {
self.lookup
.symbol_to_def
.get(id)
.map(|&idx| TypeNodeId::def(idx))
}
#[inline]
pub fn usages(&self, id: SymbolId) -> impl Iterator<Item = TypeNodeId> + '_ {
self.lookup
.symbol_to_usages
.get(&id)
.into_iter()
.flat_map(|v| v.iter().map(|&idx| TypeNodeId::usage(idx)))
}
#[inline]
pub fn usage_count(&self, id: SymbolId) -> usize {
self.lookup.symbol_to_usages.get(&id).map_or(0, |v| v.len())
}
pub fn types_used_by(&self, container: SymbolId) -> impl Iterator<Item = SymbolId> + '_ {
self.lookup
.container_to_usages
.get(&container)
.into_iter()
.flat_map(|indices| {
indices.iter().flat_map(|&idx| {
let mut types = SmallVec::<[SymbolId; 4]>::new();
if let Some(u) = self.usages.get(idx as usize) {
if let Some(resolved) = u.resolved {
types.push(resolved);
}
}
if let Some(args) = self.usage_to_args.get(idx as usize) {
for &arg_idx in args.iter() {
if let Some(arg) = self.generic_args.get(arg_idx as usize) {
if let Some(resolved) = arg.resolved {
types.push(resolved);
}
}
}
}
types.into_iter()
})
})
}
pub fn type_users(&self, type_id: SymbolId) -> impl Iterator<Item = SymbolId> + '_ {
self.lookup
.symbol_to_usages
.get(&type_id)
.into_iter()
.flat_map(|indices| {
indices
.iter()
.filter_map(|&idx| self.usages.get(idx as usize).and_then(|u| u.container))
})
}
pub fn type_users_chain(&self, start: SymbolId, max_depth: usize) -> Vec<ChainNode> {
self.traverse_type_chain(start, max_depth, ChainDirection::TypeUsers)
}
pub fn types_used_by_chain(&self, start: SymbolId, max_depth: usize) -> Vec<ChainNode> {
self.traverse_type_chain(start, max_depth, ChainDirection::TypeDeps)
}
pub fn analyze_type_chain(
&self,
start: SymbolId,
max_depth: usize,
direction: ChainDirection,
) -> ChainResult {
let nodes = self.traverse_type_chain(start, max_depth, direction);
let mut by_depth: HashMap<usize, usize> = HashMap::new();
for node in &nodes {
*by_depth.entry(node.depth).or_default() += 1;
}
let max_actual_depth = nodes.iter().map(|n| n.depth).max().unwrap_or(0);
ChainResult {
start,
direction,
max_depth,
nodes,
max_actual_depth,
by_depth,
}
}
fn traverse_type_chain(
&self,
start: SymbolId,
max_depth: usize,
direction: ChainDirection,
) -> Vec<ChainNode> {
let mut result = Vec::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
visited.insert(start);
queue.push_back((start, 0usize));
while let Some((current, depth)) = queue.pop_front() {
if depth > 0 {
result.push(ChainNode {
symbol: current,
depth,
});
}
if depth >= max_depth {
continue;
}
let neighbors: Vec<SymbolId> = match direction {
ChainDirection::TypeUsers => self.type_users(current).collect(),
ChainDirection::TypeDeps => self.types_used_by(current).collect(),
ChainDirection::Callers | ChainDirection::Callees => {
unreachable!("Callers/Callees must use CodeGraphV2")
}
};
for neighbor in neighbors {
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back((neighbor, depth + 1));
}
}
}
result
}
#[inline]
pub fn generic_args(&self, node: TypeNodeId) -> impl Iterator<Item = TypeNodeId> + '_ {
let args = if node.kind() == NodeKind::Usage {
self.usage_to_args.get(node.index() as usize)
} else {
None
};
args.into_iter()
.flat_map(|v| v.iter().map(|&idx| TypeNodeId::arg(idx)))
}
#[inline]
pub fn trait_bounds(&self, node: TypeNodeId) -> impl Iterator<Item = TypeNodeId> + '_ {
self.node_to_bounds
.get(&node)
.into_iter()
.flat_map(|v| v.iter().map(|&idx| TypeNodeId::bound(idx)))
}
#[inline]
pub fn get_definition(&self, id: TypeNodeId) -> Option<&DefinitionData> {
if id.kind() == NodeKind::Definition {
self.definitions.get(id.index() as usize)
} else {
None
}
}
#[inline]
pub fn get_usage(&self, id: TypeNodeId) -> Option<&UsageData> {
if id.kind() == NodeKind::Usage {
self.usages.get(id.index() as usize)
} else {
None
}
}
#[inline]
pub fn get_generic_arg(&self, id: TypeNodeId) -> Option<&GenericArgData> {
if id.kind() == NodeKind::GenericArg {
self.generic_args.get(id.index() as usize)
} else {
None
}
}
#[inline]
pub fn get_trait_bound(&self, id: TypeNodeId) -> Option<&TraitBoundData> {
if id.kind() == NodeKind::TraitBound {
self.trait_bounds.get(id.index() as usize)
} else {
None
}
}
pub fn impact(&self, id: SymbolId) -> TypeImpactV2 {
TypeImpactV2 {
direct_usages: self
.lookup
.symbol_to_usages
.get(&id)
.cloned()
.unwrap_or_default(),
bound_usages: self
.lookup
.trait_to_bounds
.get(&id)
.cloned()
.unwrap_or_default(),
containing_types: self
.lookup
.container_fields
.iter()
.filter(|(_, fields)| fields.contains(&id))
.map(|(&container, _)| container)
.collect(),
generic_usages: vec![],
}
}
pub fn node_count(&self) -> usize {
self.definitions.len()
+ self.usages.len()
+ self.generic_args.len()
+ self.trait_bounds.len()
}
pub fn definition_count(&self) -> usize {
self.definitions.len()
}
pub fn usages_count(&self) -> usize {
self.usages.len()
}
pub fn is_empty(&self) -> bool {
self.definitions.is_empty() && self.usages.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol::{SymbolPath, SymbolRegistry};
use crate::SymbolKind;
fn create_test_registry() -> SymbolRegistry {
let mut registry = SymbolRegistry::new();
let _ = registry.register(SymbolPath::parse("test::Foo").unwrap(), SymbolKind::Struct);
let _ = registry.register(SymbolPath::parse("test::Bar").unwrap(), SymbolKind::Struct);
let _ = registry.register(SymbolPath::parse("test::Clone").unwrap(), SymbolKind::Trait);
registry
}
fn lookup(registry: &SymbolRegistry, path: &str) -> SymbolId {
let symbol_path = SymbolPath::parse(path).unwrap();
registry.lookup(&symbol_path).unwrap()
}
#[test]
fn test_new_graph() {
let graph = TypeFlowGraphV2::new();
assert!(graph.is_empty());
assert_eq!(graph.node_count(), 0);
}
#[test]
fn test_add_definition() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let node = graph.add_definition(foo_id, TypeDefKind::Struct);
assert_eq!(graph.definition_count(), 1);
assert_eq!(graph.definition(foo_id), Some(node));
assert_eq!(node.kind(), NodeKind::Definition);
let node2 = graph.add_definition(foo_id, TypeDefKind::Struct);
assert_eq!(node, node2);
assert_eq!(graph.definition_count(), 1);
}
#[test]
fn test_add_usage() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
graph.add_definition(foo_id, TypeDefKind::Struct);
let usage = graph.add_usage(UsageContext::FieldType, RefKind::Owned, Some(foo_id), None);
assert_eq!(graph.usage_count(foo_id), 1);
assert_eq!(usage.kind(), NodeKind::Usage);
let usages: Vec<_> = graph.usages(foo_id).collect();
assert_eq!(usages.len(), 1);
assert_eq!(usages[0], usage);
}
#[test]
fn test_add_usage_with_container() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
let usage = graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(bar_id),
);
let data = graph.get_usage(usage).unwrap();
assert_eq!(data.container, Some(bar_id));
assert_eq!(data.resolved, Some(foo_id));
}
#[test]
fn test_container_to_usages_index() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
let clone_id = lookup(®istry, "test::Clone");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
graph.add_definition(clone_id, TypeDefKind::Trait);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(bar_id),
);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(clone_id),
Some(bar_id),
);
let indices = graph.lookup.container_to_usages.get(&bar_id).unwrap();
assert_eq!(indices.len(), 2);
}
#[test]
fn test_types_used_by() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
let clone_id = lookup(®istry, "test::Clone");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
graph.add_definition(clone_id, TypeDefKind::Trait);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(bar_id),
);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(clone_id),
Some(bar_id),
);
let used: Vec<_> = graph.types_used_by(bar_id).collect();
assert_eq!(used.len(), 2);
assert!(used.contains(&foo_id));
assert!(used.contains(&clone_id));
let used_by_foo: Vec<_> = graph.types_used_by(foo_id).collect();
assert!(used_by_foo.is_empty());
}
#[test]
fn test_types_used_by_includes_generic_arg_types() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
let vec_usage = graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
None, Some(bar_id),
);
graph.add_generic_arg(vec_usage, 0, Some(foo_id));
let used: Vec<_> = graph.types_used_by(bar_id).collect();
assert!(
used.contains(&foo_id),
"types_used_by should include Foo referenced via Vec<Foo>, got: {:?}",
used
);
}
#[test]
fn test_type_users() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
let clone_id = lookup(®istry, "test::Clone");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
graph.add_definition(clone_id, TypeDefKind::Trait);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(bar_id),
);
graph.add_usage(
UsageContext::ParamType,
RefKind::Ref,
Some(foo_id),
Some(clone_id),
);
let users: Vec<_> = graph.type_users(foo_id).collect();
assert_eq!(users.len(), 2);
assert!(users.contains(&bar_id));
assert!(users.contains(&clone_id));
let users_of_clone: Vec<_> = graph.type_users(clone_id).collect();
assert!(users_of_clone.is_empty());
}
#[test]
fn test_type_users_includes_generic_arg_references() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
let vec_usage =
graph.add_usage(UsageContext::FieldType, RefKind::Owned, None, Some(bar_id));
graph.add_generic_arg(vec_usage, 0, Some(foo_id));
let users: Vec<_> = graph.type_users(foo_id).collect();
assert!(
users.contains(&bar_id),
"type_users should include Bar which references Foo via Vec<Foo>, got: {:?}",
users
);
}
#[test]
fn test_type_users_without_container() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_usage(UsageContext::FieldType, RefKind::Owned, Some(foo_id), None);
let users: Vec<_> = graph.type_users(foo_id).collect();
assert!(users.is_empty());
assert_eq!(graph.usage_count(foo_id), 1);
}
#[test]
fn test_generic_args() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
graph.add_definition(foo_id, TypeDefKind::Struct);
let vec_usage = graph.add_usage(UsageContext::FieldType, RefKind::Owned, None, None);
let arg = graph.add_generic_arg(vec_usage, 0, Some(foo_id));
let args: Vec<_> = graph.generic_args(vec_usage).collect();
assert_eq!(args.len(), 1);
assert_eq!(args[0], arg);
assert_eq!(arg.kind(), NodeKind::GenericArg);
}
#[test]
fn test_trait_bounds() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let clone_id = lookup(®istry, "test::Clone");
graph.add_definition(clone_id, TypeDefKind::Trait);
let param = graph.add_usage(UsageContext::WhereBound, RefKind::Owned, None, None);
let bound = graph.add_trait_bound(param, Some(clone_id));
let bounds: Vec<_> = graph.trait_bounds(param).collect();
assert_eq!(bounds.len(), 1);
assert_eq!(bounds[0], bound);
assert_eq!(bound.kind(), NodeKind::TraitBound);
}
#[test]
fn test_impact_analysis() {
let mut graph = TypeFlowGraphV2::new();
let registry = create_test_registry();
let foo_id = lookup(®istry, "test::Foo");
let bar_id = lookup(®istry, "test::Bar");
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(bar_id),
);
graph.add_contains(bar_id, foo_id);
let impact = graph.impact(foo_id);
assert_eq!(impact.direct_usages.len(), 1);
assert!(impact.containing_types.contains(&bar_id));
}
#[test]
fn test_type_node_id_encoding() {
let def = TypeNodeId::def(100);
let usage = TypeNodeId::usage(100);
let arg = TypeNodeId::arg(100);
let bound = TypeNodeId::bound(100);
assert_ne!(def, usage);
assert_ne!(usage, arg);
assert_ne!(arg, bound);
assert_eq!(def.kind(), NodeKind::Definition);
assert_eq!(def.index(), 100);
assert_eq!(usage.kind(), NodeKind::Usage);
assert_eq!(usage.index(), 100);
assert_eq!(arg.kind(), NodeKind::GenericArg);
assert_eq!(arg.index(), 100);
assert_eq!(bound.kind(), NodeKind::TraitBound);
assert_eq!(bound.index(), 100);
}
fn build_chain_test_graph() -> (
TypeFlowGraphV2,
SymbolId,
SymbolId,
SymbolId,
SymbolId,
SymbolId,
) {
let mut registry = SymbolRegistry::new();
let foo_id = registry
.register(SymbolPath::parse("test::Foo").unwrap(), SymbolKind::Struct)
.unwrap();
let bar_id = registry
.register(SymbolPath::parse("test::Bar").unwrap(), SymbolKind::Struct)
.unwrap();
let baz_id = registry
.register(SymbolPath::parse("test::Baz").unwrap(), SymbolKind::Struct)
.unwrap();
let process_id = registry
.register(
SymbolPath::parse("test::process").unwrap(),
SymbolKind::Function,
)
.unwrap();
let render_id = registry
.register(
SymbolPath::parse("test::render").unwrap(),
SymbolKind::Function,
)
.unwrap();
let mut graph = TypeFlowGraphV2::new();
graph.add_definition(foo_id, TypeDefKind::Struct);
graph.add_definition(bar_id, TypeDefKind::Struct);
graph.add_definition(baz_id, TypeDefKind::Struct);
graph.add_usage(
UsageContext::ParamType,
RefKind::Owned,
Some(foo_id),
Some(process_id),
);
graph.add_usage(
UsageContext::ReturnType,
RefKind::Owned,
Some(bar_id),
Some(process_id),
);
graph.add_usage(
UsageContext::ParamType,
RefKind::Owned,
Some(bar_id),
Some(render_id),
);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(foo_id),
Some(baz_id),
);
(graph, foo_id, bar_id, baz_id, process_id, render_id)
}
#[test]
fn test_type_users_chain_depth1() {
let (graph, foo_id, _, baz_id, process_id, _) = build_chain_test_graph();
let chain = graph.type_users_chain(foo_id, 1);
let symbols: Vec<_> = chain.iter().map(|n| n.symbol).collect();
assert_eq!(chain.len(), 2);
assert!(symbols.contains(&process_id));
assert!(symbols.contains(&baz_id));
assert!(chain.iter().all(|n| n.depth == 1));
}
#[test]
fn test_types_used_by_chain_depth1() {
let (graph, foo_id, bar_id, _, process_id, _) = build_chain_test_graph();
let chain = graph.types_used_by_chain(process_id, 1);
let symbols: Vec<_> = chain.iter().map(|n| n.symbol).collect();
assert_eq!(chain.len(), 2);
assert!(symbols.contains(&foo_id));
assert!(symbols.contains(&bar_id));
}
#[test]
fn test_type_users_chain_transitive() {
let mut registry = SymbolRegistry::new();
let a_id = registry
.register(SymbolPath::parse("test::A").unwrap(), SymbolKind::Struct)
.unwrap();
let b_id = registry
.register(SymbolPath::parse("test::B").unwrap(), SymbolKind::Struct)
.unwrap();
let c_id = registry
.register(SymbolPath::parse("test::c").unwrap(), SymbolKind::Function)
.unwrap();
let mut graph = TypeFlowGraphV2::new();
graph.add_definition(a_id, TypeDefKind::Struct);
graph.add_definition(b_id, TypeDefKind::Struct);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(a_id),
Some(b_id),
);
graph.add_usage(
UsageContext::ParamType,
RefKind::Owned,
Some(b_id),
Some(c_id),
);
let chain = graph.type_users_chain(a_id, 2);
assert_eq!(chain.len(), 2);
assert_eq!(chain[0].symbol, b_id);
assert_eq!(chain[0].depth, 1);
assert_eq!(chain[1].symbol, c_id);
assert_eq!(chain[1].depth, 2);
}
#[test]
fn test_type_users_chain_respects_max_depth() {
let mut registry = SymbolRegistry::new();
let a_id = registry
.register(SymbolPath::parse("test::A").unwrap(), SymbolKind::Struct)
.unwrap();
let b_id = registry
.register(SymbolPath::parse("test::B").unwrap(), SymbolKind::Struct)
.unwrap();
let c_id = registry
.register(SymbolPath::parse("test::c").unwrap(), SymbolKind::Function)
.unwrap();
let mut graph = TypeFlowGraphV2::new();
graph.add_definition(a_id, TypeDefKind::Struct);
graph.add_definition(b_id, TypeDefKind::Struct);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(a_id),
Some(b_id),
);
graph.add_usage(
UsageContext::ParamType,
RefKind::Owned,
Some(b_id),
Some(c_id),
);
let chain = graph.type_users_chain(a_id, 1);
assert_eq!(chain.len(), 1);
assert_eq!(chain[0].symbol, b_id);
assert_eq!(chain[0].depth, 1);
}
#[test]
fn test_analyze_type_chain_statistics() {
let (graph, foo_id, _, _, _, _) = build_chain_test_graph();
let result = graph.analyze_type_chain(foo_id, 5, ChainDirection::TypeUsers);
assert_eq!(result.start, foo_id);
assert_eq!(result.direction, ChainDirection::TypeUsers);
assert_eq!(result.max_depth, 5);
assert_eq!(result.nodes.len(), 2); assert_eq!(result.max_actual_depth, 1);
assert_eq!(*result.by_depth.get(&1).unwrap_or(&0), 2);
}
#[test]
fn test_chain_no_cycles() {
let mut registry = SymbolRegistry::new();
let a_id = registry
.register(SymbolPath::parse("test::A").unwrap(), SymbolKind::Struct)
.unwrap();
let b_id = registry
.register(SymbolPath::parse("test::B").unwrap(), SymbolKind::Struct)
.unwrap();
let mut graph = TypeFlowGraphV2::new();
graph.add_definition(a_id, TypeDefKind::Struct);
graph.add_definition(b_id, TypeDefKind::Struct);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(b_id),
Some(a_id),
);
graph.add_usage(
UsageContext::FieldType,
RefKind::Owned,
Some(a_id),
Some(b_id),
);
let chain = graph.type_users_chain(a_id, 10);
assert_eq!(chain.len(), 1); assert_eq!(chain[0].symbol, b_id);
}
}