use crate::core::{CallKind, JSON_SCHEMA_GRAPH_INDEX};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::SystemTime;
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Qn(pub String);
impl Qn {
pub fn new<S: Into<String>>(s: S) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn file(&self) -> &str {
match self.0.find("::") {
Some(i) => &self.0[..i],
None => &self.0,
}
}
pub fn name(&self) -> &str {
match self.0.rfind("::") {
Some(i) => &self.0[i + 2..],
None => &self.0,
}
}
}
impl std::fmt::Display for Qn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Confidence {
Exact,
Inferred,
Ambiguous,
}
impl Confidence {
pub fn as_str(&self) -> &'static str {
match self {
Self::Exact => "Exact",
Self::Inferred => "Inferred",
Self::Ambiguous => "Ambiguous",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CallTarget {
Resolved(Qn),
External(String),
Bare(String),
}
impl CallTarget {
pub fn display(&self) -> String {
match self {
Self::Resolved(qn) => qn.to_string(),
Self::External(s) => format!("[external] {}", s),
Self::Bare(s) => format!("[unresolved] {}", s),
}
}
pub fn name_or_raw(&self) -> String {
match self {
Self::Resolved(qn) => qn.name().to_string(),
Self::External(s) | Self::Bare(s) => s.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallEdge {
pub source: Qn,
pub target: CallTarget,
pub kind: CallKindCompat,
pub line: u32,
pub file: PathBuf,
pub confidence: Confidence,
#[serde(default)]
pub receiver: Option<String>,
#[serde(default)]
pub candidates: Vec<Qn>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CallKindCompat {
Call,
Construct,
Macro,
Super,
}
impl From<CallKind> for CallKindCompat {
fn from(k: CallKind) -> Self {
match k {
CallKind::Call => Self::Call,
CallKind::Construct => Self::Construct,
CallKind::Macro => Self::Macro,
CallKind::Super => Self::Super,
}
}
}
impl CallKindCompat {
pub fn as_str(&self) -> &'static str {
match self {
Self::Call => "call",
Self::Construct => "construct",
Self::Macro => "macro",
Self::Super => "super",
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GraphStats {
pub function_count: usize,
pub edge_count: usize,
pub resolved_edge_count: usize,
pub external_edge_count: usize,
pub ambiguous_edge_count: usize,
pub type_count: usize,
pub build_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallableMeta {
pub file: PathBuf,
pub line: u32,
pub kind: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeMeta {
pub kind: String,
pub file: PathBuf,
pub line: u32,
pub bases: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallGraph {
pub schema: String,
pub forward: HashMap<Qn, Vec<CallEdge>>,
pub reverse: HashMap<Qn, Vec<CallEdge>>,
pub symbol_table: HashMap<String, Vec<Qn>>,
#[serde(default)]
pub callable_meta: HashMap<Qn, CallableMeta>,
#[serde(default)]
pub types: HashMap<Qn, TypeMeta>,
#[serde(default)]
pub type_by_name: HashMap<String, Vec<Qn>>,
#[serde(default)]
pub implementors: HashMap<String, Vec<Qn>>,
pub root: PathBuf,
pub built_at: SystemTime,
pub stats: GraphStats,
}
impl CallGraph {
pub fn empty(root: PathBuf) -> Self {
Self {
schema: JSON_SCHEMA_GRAPH_INDEX.to_string(),
forward: HashMap::new(),
reverse: HashMap::new(),
symbol_table: HashMap::new(),
callable_meta: HashMap::new(),
types: HashMap::new(),
type_by_name: HashMap::new(),
implementors: HashMap::new(),
root,
built_at: SystemTime::now(),
stats: GraphStats::default(),
}
}
pub fn rebuild_reverse(&mut self) {
let mut rev: HashMap<Qn, Vec<CallEdge>> = HashMap::new();
for edges in self.forward.values() {
for e in edges {
if let CallTarget::Resolved(qn) = &e.target {
rev.entry(qn.clone()).or_default().push(e.clone());
}
}
}
for v in rev.values_mut() {
v.sort_by(|a, b| a.source.0.cmp(&b.source.0).then(a.line.cmp(&b.line)));
}
self.reverse = rev;
}
}