use super::{TypeFlowGraphV2, TypeImpactV2, TypeNodeId, UsageContext};
use crate::SymbolId;
#[derive(Debug, Clone)]
pub struct TypeImpactResult {
pub changed_symbol: SymbolId,
pub direct_usage_count: usize,
pub bound_usage_count: usize,
pub containing_types: Vec<SymbolId>,
pub issues: Vec<TypeImpactIssue>,
}
impl TypeImpactResult {
pub fn has_issues(&self) -> bool {
!self.issues.is_empty()
}
pub fn has_impact(&self) -> bool {
self.direct_usage_count > 0
|| self.bound_usage_count > 0
|| !self.containing_types.is_empty()
}
}
#[derive(Debug, Clone)]
pub enum TypeImpactIssue {
FieldUsageInType {
container_type: SymbolId,
},
TraitBoundUsage {
bound_count: usize,
},
ParameterUsage {
usage_count: usize,
},
ReturnTypeUsage {
usage_count: usize,
},
ImplUsage {
usage_count: usize,
},
}
pub struct TypeImpactChecker<'a> {
typeflow: &'a TypeFlowGraphV2,
}
impl<'a> TypeImpactChecker<'a> {
pub fn new(typeflow: &'a TypeFlowGraphV2) -> Self {
Self { typeflow }
}
pub fn analyze_impact(&self, symbol_id: SymbolId) -> TypeImpactResult {
let impact = self.typeflow.impact(symbol_id);
let mut issues = Vec::new();
let (param_count, return_count, impl_count) = self.categorize_usages(&impact);
if param_count > 0 {
issues.push(TypeImpactIssue::ParameterUsage {
usage_count: param_count,
});
}
if return_count > 0 {
issues.push(TypeImpactIssue::ReturnTypeUsage {
usage_count: return_count,
});
}
if impl_count > 0 {
issues.push(TypeImpactIssue::ImplUsage {
usage_count: impl_count,
});
}
if !impact.bound_usages.is_empty() {
issues.push(TypeImpactIssue::TraitBoundUsage {
bound_count: impact.bound_usages.len(),
});
}
for container in &impact.containing_types {
issues.push(TypeImpactIssue::FieldUsageInType {
container_type: *container,
});
}
TypeImpactResult {
changed_symbol: symbol_id,
direct_usage_count: impact.direct_usages.len(),
bound_usage_count: impact.bound_usages.len(),
containing_types: impact.containing_types,
issues,
}
}
pub fn affected_symbols(&self, symbol_id: SymbolId) -> Vec<SymbolId> {
let impact = self.typeflow.impact(symbol_id);
let mut affected = Vec::new();
affected.extend(impact.containing_types.iter().copied());
for usage_idx in &impact.direct_usages {
if let Some(usage) = self.typeflow.get_usage(TypeNodeId::usage(*usage_idx)) {
if let Some(resolved) = usage.resolved {
affected.push(resolved);
}
}
}
affected.sort();
affected.dedup();
affected
}
fn categorize_usages(&self, impact: &TypeImpactV2) -> (usize, usize, usize) {
let mut param_count = 0;
let mut return_count = 0;
let mut impl_count = 0;
for usage_idx in &impact.direct_usages {
if let Some(usage) = self.typeflow.get_usage(TypeNodeId::usage(*usage_idx)) {
match usage.context {
UsageContext::ParamType => param_count += 1,
UsageContext::ReturnType => return_count += 1,
UsageContext::ImplTarget | UsageContext::ImplTrait => impl_count += 1,
_ => {}
}
}
}
(param_count, return_count, impl_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol::{SymbolPath, SymbolRegistry};
use crate::SymbolKind;
use crate::TypeDefKind;
fn create_test_setup() -> (SymbolRegistry, TypeFlowGraphV2, 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 mut typeflow = TypeFlowGraphV2::new();
typeflow.add_definition(foo_id, TypeDefKind::Struct);
typeflow.add_definition(bar_id, TypeDefKind::Struct);
typeflow.add_contains(bar_id, foo_id);
(registry, typeflow, foo_id, bar_id)
}
#[test]
fn test_analyze_impact_no_usages() {
let (_, typeflow, _, bar_id) = create_test_setup();
let checker = TypeImpactChecker::new(&typeflow);
let result = checker.analyze_impact(bar_id);
assert_eq!(result.direct_usage_count, 0);
assert!(result.containing_types.is_empty());
}
#[test]
fn test_analyze_impact_with_containing_type() {
let (_, typeflow, foo_id, bar_id) = create_test_setup();
let checker = TypeImpactChecker::new(&typeflow);
let result = checker.analyze_impact(foo_id);
assert!(result.containing_types.contains(&bar_id));
assert!(result.has_impact());
}
#[test]
fn test_affected_symbols() {
let (_, typeflow, foo_id, bar_id) = create_test_setup();
let checker = TypeImpactChecker::new(&typeflow);
let affected = checker.affected_symbols(foo_id);
assert!(affected.contains(&bar_id));
}
}