use super::interner::{SymbolCandidate, SymbolIndex, SymbolIx};
use crate::db::{Edge, Node, RawCall, UnresolvedRef};
pub fn resolve_calls_global(
calls: &[RawCall],
index: &SymbolIndex,
) -> (Vec<Edge>, Vec<UnresolvedRef>) {
let mut edges = Vec::new();
let mut unresolved = Vec::new();
for call in calls {
let (call_namespace, simple_name) = call_namespace_and_simple_name(&call.callee_name);
let candidates = match index.by_name.get(simple_name) {
Some(indices) if !indices.is_empty() => indices,
_ => {
unresolved.push(UnresolvedRef {
id: None,
source_id: call.caller_id.clone(),
specifier: call.callee_name.clone(),
kind: "calls".to_string(),
line: call.line,
column: call.column,
});
continue;
}
};
let caller = index
.by_id
.get(&call.caller_id)
.and_then(|ix| index.symbols.get(*ix as usize));
let same_file = caller.and_then(|caller| {
index
.by_name_file
.get(simple_name)
.and_then(|by_file| by_file.get(&caller.file_path))
});
let working_set = if let Some(indices) = same_file.filter(|indices| !indices.is_empty()) {
indices.as_slice()
} else {
candidates.as_slice()
};
let target_id = if working_set.len() == 1 {
index.strings[index.symbols[working_set[0] as usize].id as usize].clone()
} else {
let caller_namespace = caller.and_then(|symbol| {
symbol
.namespace
.map(|namespace| index.strings[namespace as usize].as_str())
});
let target_ix = disambiguate(
working_set,
&index.symbols,
&index.strings,
call_namespace,
caller_namespace,
)
.unwrap_or(working_set[0]);
index.strings[index.symbols[target_ix as usize].id as usize].clone()
};
edges.push(Edge {
source_id: call.caller_id.clone(),
target_id,
kind: "calls".to_string(),
});
}
edges.sort_by(|a, b| {
(&a.source_id, &a.target_id, &a.kind).cmp(&(&b.source_id, &b.target_id, &b.kind))
});
edges.dedup_by(|a, b| {
a.source_id == b.source_id && a.target_id == b.target_id && a.kind == b.kind
});
(edges, unresolved)
}
fn disambiguate(
candidates: &[SymbolIx],
symbols: &[SymbolCandidate],
interned_strings: &[String],
call_namespace: Option<&str>,
caller_namespace: Option<&str>,
) -> Option<SymbolIx> {
if let Some(namespace) = call_namespace {
if let Some(candidate) = candidates.iter().copied().find(|ix| {
symbols
.get(*ix as usize)
.and_then(|symbol| symbol.namespace)
.is_some_and(|candidate_namespace| {
interned_strings[candidate_namespace as usize] == namespace
})
}) {
return Some(candidate);
}
}
if let Some(namespace) = caller_namespace {
if let Some(candidate) = candidates.iter().copied().find(|ix| {
symbols
.get(*ix as usize)
.and_then(|symbol| symbol.namespace)
.is_some_and(|candidate_namespace| {
interned_strings[candidate_namespace as usize] == namespace
})
}) {
return Some(candidate);
}
}
None
}
fn call_namespace_and_simple_name(callee_name: &str) -> (Option<&str>, &str) {
if let Some((namespace, simple_name)) = callee_name.rsplit_once("::") {
(namespace.rsplit("::").next(), simple_name)
} else {
(None, callee_name)
}
}
pub fn resolve_calls_local(nodes: &[Node], calls: &[RawCall]) -> Vec<Edge> {
let index = SymbolIndex::from_nodes(nodes);
resolve_calls_global(calls, &index).0
}