use super::bind::{Binder, From, Step};
use super::scope::{Ns, Target};
use crate::extract::{RsFragment, RsRole, RsSite};
use crate::graph::{Candidate, Edge, EdgesByKind};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
UsesMember,
UsesType,
Implements,
}
impl Kind {
fn label(self) -> &'static str {
match self {
Kind::UsesMember => "uses-member",
Kind::UsesType => "uses-type",
Kind::Implements => "implements",
}
}
}
pub(super) fn edges(
binder: &Binder,
fragments: &[(String, RsFragment)],
) -> (Vec<Edge>, EdgesByKind) {
let mut out = Vec::new();
let mut counts = EdgesByKind::default();
for (file, (rel, fragment)) in fragments.iter().enumerate() {
for site in &fragment.sites {
site_edges(binder, file, rel, site, &mut out, &mut counts);
}
}
(out, counts)
}
fn last_ns(role: RsRole) -> Ns {
match role {
RsRole::Call | RsRole::Value => Ns::Value,
RsRole::Type | RsRole::Lit | RsRole::Impl | RsRole::Pat => Ns::Type,
}
}
fn self_def(binder: &Binder, file: usize, site: &RsSite) -> Option<usize> {
if let Some(k) = site.self_impl {
return binder.t.impl_self.get(&(file, k)).copied();
}
let path = site.self_trait.as_ref()?;
binder.t.def_by_path.get(&(file, path.clone())).copied()
}
fn site_edges(
binder: &Binder,
file: usize,
rel: &str,
site: &RsSite,
out: &mut Vec<Edge>,
counts: &mut EdgesByKind,
) {
let Some(module) = binder.tree.node_of(file, &site.scope) else {
return;
};
let from = From {
module,
file,
block: site.block,
self_def: self_def(binder, file, site),
import: false,
};
let segs: Vec<&str> = site.path.split("::").collect();
let lines = site.segment_lines();
let steps = binder.walk(&from, &segs, last_ns(site.role), &mut Vec::new());
for (i, step) in steps.iter().enumerate() {
let last = i + 1 == segs.len();
let line = lines.get(i).copied().unwrap_or(site.line);
match step {
Step::Bound(Target::Def(d)) if segs[i] != "Self" => {
if let Some(kind) = kind_of(binder, *d, site.role, last) {
out.push(edge(binder, kind, rel, line, *d));
count(counts, kind);
}
}
Step::Ambiguous(targets) => {
if let Some(e) = ambiguous(binder, targets, site.role, last, rel, line, &segs[..=i])
{
out.push(e);
}
}
_ => {}
}
}
}
fn kind_of(binder: &Binder, def: usize, role: RsRole, last: bool) -> Option<Kind> {
let d = &binder.t.defs[def];
if !last {
return d.is_type().then_some(Kind::UsesType);
}
match role {
RsRole::Call if d.kind == "fn" => Some(Kind::UsesMember),
RsRole::Call | RsRole::Value => (d.kind == "struct").then_some(Kind::UsesType),
RsRole::Type | RsRole::Lit => d.is_type().then_some(Kind::UsesType),
RsRole::Impl => (d.kind == "trait").then_some(Kind::Implements),
RsRole::Pat => None,
}
}
fn edge(binder: &Binder, kind: Kind, rel: &str, line: usize, def: usize) -> Edge {
let d = &binder.t.defs[def];
let to_file = d.rel.clone();
match kind {
Kind::UsesMember => Edge::uses_member(
rel.to_string(),
line,
d.id.clone(),
to_file,
Some(d.name.clone()),
None,
),
Kind::UsesType => Edge::UsesType {
from_file: rel.to_string(),
from_line: line,
to: d.id.clone(),
to_file,
heuristic: false,
},
Kind::Implements => Edge::Implements {
from_file: rel.to_string(),
from_line: line,
to: d.id.clone(),
to_file,
member: None,
},
}
}
fn count(counts: &mut EdgesByKind, kind: Kind) {
match kind {
Kind::UsesMember => counts.uses_member += 1,
Kind::UsesType => counts.uses_type += 1,
Kind::Implements => counts.implements += 1,
}
}
fn ambiguous(
binder: &Binder,
targets: &[Target],
role: RsRole,
last: bool,
rel: &str,
line: usize,
written: &[&str],
) -> Option<Edge> {
let mut defs: Vec<usize> = targets
.iter()
.filter_map(|t| match t {
Target::Def(d) => Some(*d),
Target::Module(_) => None,
})
.collect();
defs.sort_by(|a, b| binder.t.defs[*a].id.cmp(&binder.t.defs[*b].id));
defs.dedup_by(|a, b| binder.t.defs[*a].id == binder.t.defs[*b].id);
let origin = defs.iter().find_map(|&d| kind_of(binder, d, role, last))?;
let candidates: Vec<Candidate> = defs
.iter()
.take(crate::resolve::AMBIGUOUS_CAP)
.map(|&d| Candidate {
id: binder.t.defs[d].id.clone(),
file: binder.t.defs[d].rel.clone(),
})
.collect();
Some(Edge::Ambiguous {
origin: origin.label().to_string(),
from_file: rel.to_string(),
from_line: line,
raw: written.join("::"),
candidates,
candidate_count: defs.len(),
})
}