use crate::calls::graph::{CallGraph, Qn};
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
Callable,
Type,
}
#[derive(Debug, Clone)]
pub struct ResolvedTarget {
pub qn: Qn,
pub kind: SymbolKind,
}
pub fn resolve_target_qns(calls: &CallGraph, target: &str) -> Vec<Qn> {
resolve_target_full(calls, target)
.into_iter()
.filter(|r| r.kind == SymbolKind::Callable)
.map(|r| r.qn)
.collect()
}
pub fn resolve_target_full(calls: &CallGraph, target: &str) -> Vec<ResolvedTarget> {
let (file_filter, symbol) = split_file_filter(target);
let parts: Vec<&str> = symbol.split('.').collect();
let mut out: Vec<ResolvedTarget> = Vec::new();
for qn in collect_callable_qns(calls) {
if let Some(filter) = file_filter {
if !file_matches(&qn, filter) {
continue;
}
}
if qn_matches(&qn, symbol, &parts) {
out.push(ResolvedTarget { qn, kind: SymbolKind::Callable });
}
}
for qn in calls.types.keys() {
if let Some(filter) = file_filter {
if !file_matches(qn, filter) {
continue;
}
}
if qn_matches(qn, symbol, &parts) {
out.push(ResolvedTarget {
qn: qn.clone(),
kind: SymbolKind::Type,
});
}
}
out.sort_by(|a, b| a.qn.0.cmp(&b.qn.0));
out.dedup_by(|a, b| a.qn == b.qn && a.kind == b.kind);
out
}
fn split_file_filter(target: &str) -> (Option<&str>, &str) {
let mut i = 0;
let bytes = target.as_bytes();
while i < bytes.len() {
if bytes[i] == b':' {
if bytes.get(i + 1) == Some(&b':') {
i += 2;
continue;
}
let (file, rest) = (&target[..i], &target[i + 1..]);
if file.is_empty() || rest.is_empty() {
return (None, target);
}
return (Some(file), rest);
}
i += 1;
}
(None, target)
}
fn file_matches(qn: &Qn, filter: &str) -> bool {
let f = qn.file();
if f == filter {
return true;
}
f.ends_with(filter) && f.as_bytes().get(f.len().saturating_sub(filter.len()).saturating_sub(1)) == Some(&b'/')
}
fn collect_callable_qns(calls: &CallGraph) -> Vec<Qn> {
let mut s: BTreeSet<Qn> = BTreeSet::new();
for k in calls.forward.keys() {
s.insert(k.clone());
}
for k in calls.reverse.keys() {
s.insert(k.clone());
}
s.into_iter().collect()
}
fn qn_matches(qn: &Qn, raw: &str, parts: &[&str]) -> bool {
if qn.0 == raw {
return true;
}
let segments: Vec<&str> = qn.0.split("::").collect();
if parts.len() > segments.len() {
return false;
}
let start = segments.len() - parts.len();
parts.iter().enumerate().all(|(i, p)| segments[start + i] == *p)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_file_filter_plain_symbol() {
assert_eq!(split_file_filter("greet"), (None, "greet"));
}
#[test]
fn split_file_filter_with_file() {
assert_eq!(
split_file_filter("src/main.rs:parse_file"),
(Some("src/main.rs"), "parse_file")
);
}
#[test]
fn split_file_filter_double_colon_stays_intact() {
assert_eq!(
split_file_filter("src/main.rs::parse_file"),
(None, "src/main.rs::parse_file")
);
}
#[test]
fn split_file_filter_with_dotted_symbol() {
assert_eq!(
split_file_filter("src/foo.rs:Foo.bar"),
(Some("src/foo.rs"), "Foo.bar")
);
}
}