use scip::types::{self as scip_types};
use super::ScipBackend;
use super::parse;
use crate::call_graph::{CalleeEntry, CallerEntry, is_noise_callee};
impl ScipBackend {
pub fn find_callees(&self, function_name: &str, file_path: &str) -> Vec<CalleeEntry> {
let Some(doc) = self.documents.get(file_path) else {
return Vec::new();
};
let mut sorted_defs: Vec<&scip_types::Occurrence> = doc
.occurrences
.iter()
.filter(|occ| parse::is_definition(occ))
.collect();
sorted_defs.sort_by_key(|occ| parse::parse_range(occ).0);
let Some(fn_def_idx) = sorted_defs
.iter()
.position(|occ| parse::short_name(&occ.symbol) == function_name)
else {
return Vec::new();
};
let fn_def = sorted_defs[fn_def_idx];
let body_start = parse::parse_range(fn_def).0;
let body_end = sorted_defs
.get(fn_def_idx + 1)
.map(|next_def| parse::parse_range(next_def).0)
.unwrap_or(usize::MAX);
let mut callees: Vec<CalleeEntry> = Vec::new();
let mut seen: std::collections::HashSet<(String, usize)> = std::collections::HashSet::new();
for occ in &doc.occurrences {
if parse::is_definition(occ) {
continue;
}
let (line, _, _, _) = parse::parse_range(occ);
if line < body_start || line >= body_end {
continue;
}
if parse::short_name(&occ.symbol) == function_name {
continue;
}
let name = parse::short_name(&occ.symbol).to_owned();
if name.is_empty() || is_noise_callee(&name) {
continue;
}
if !seen.insert((name.clone(), line)) {
continue;
}
let resolved_file = self.documents.iter().find_map(|(other_path, other_doc)| {
other_doc
.occurrences
.iter()
.find(|o| o.symbol == occ.symbol && parse::is_definition(o))
.map(|_| other_path.clone())
});
callees.push(CalleeEntry {
name,
line,
resolved_file,
confidence: 0.95,
resolution: Some("scip"),
});
}
callees
}
pub fn find_callers(&self, function_name: &str) -> Vec<CallerEntry> {
let target_symbols: std::collections::HashSet<String> = self
.symbol_info
.keys()
.filter(|s| parse::short_name(s) == function_name && parse::is_function_like_symbol(s))
.cloned()
.chain(self.documents.values().flat_map(|doc| {
doc.occurrences
.iter()
.filter(|occ| parse::is_definition(occ))
.filter(|occ| {
parse::short_name(&occ.symbol) == function_name
&& parse::is_function_like_symbol(&occ.symbol)
})
.map(|occ| occ.symbol.clone())
}))
.collect();
if target_symbols.is_empty() {
return Vec::new();
}
let mut callers: Vec<CallerEntry> = Vec::new();
let mut seen: std::collections::HashSet<(String, String, usize)> =
std::collections::HashSet::new();
for (path, doc) in &self.documents {
let mut fn_defs: Vec<(usize, &str)> = doc
.occurrences
.iter()
.filter(|occ| {
parse::is_definition(occ) && parse::is_function_like_symbol(&occ.symbol)
})
.map(|occ| (parse::parse_range(occ).0, occ.symbol.as_str()))
.collect();
fn_defs.sort_by_key(|(line, _)| *line);
for occ in &doc.occurrences {
if parse::is_definition(occ) {
continue;
}
if !target_symbols.contains(&occ.symbol) {
continue;
}
let (line, _, _, _) = parse::parse_range(occ);
let Some(enc_idx) = fn_defs.iter().rposition(|(def_line, _)| *def_line <= line)
else {
continue;
};
let next_def_line = fn_defs
.get(enc_idx + 1)
.map(|(l, _)| *l)
.unwrap_or(usize::MAX);
if line >= next_def_line {
continue;
}
let enc_symbol = fn_defs[enc_idx].1;
let caller_name = parse::short_name(enc_symbol).to_owned();
if caller_name == function_name {
continue;
}
if !seen.insert((path.clone(), caller_name.clone(), line)) {
continue;
}
callers.push(CallerEntry {
file: path.clone(),
function: caller_name,
line,
confidence: 0.95,
resolution: Some("scip"),
});
}
}
callers
}
}