use std::collections::{HashMap, HashSet};
use crate::types::{Edge, EdgeKind, Node, NodeKind};
fn is_callable(kind: &NodeKind) -> bool {
matches!(
kind,
NodeKind::Function
| NodeKind::Method
| NodeKind::StructMethod
| NodeKind::Constructor
| NodeKind::AbstractMethod
)
}
fn parent_dir(path: &str) -> &str {
path.rfind('/').map_or("", |i| &path[..i])
}
pub fn propagate_variant_edges(nodes: &[Node], edges: &[Edge]) -> Vec<Edge> {
let node_by_id: HashMap<&str, &Node> = nodes.iter().map(|n| (n.id.as_str(), n)).collect();
let mut cfg_gated: HashSet<&str> = HashSet::new();
for e in edges {
if e.kind == EdgeKind::Annotates {
if let Some(src) = node_by_id.get(e.source.as_str()) {
if src.kind == NodeKind::AnnotationUsage
&& (src.name == "cfg" || src.name == "cfg_attr")
{
cfg_gated.insert(e.target.as_str());
}
}
}
}
let mut groups: HashMap<String, Vec<&str>> = HashMap::new();
for n in nodes {
if !is_callable(&n.kind) {
continue;
}
if n.file_path.ends_with(".rs") {
if cfg_gated.contains(n.id.as_str()) {
groups
.entry(format!("rs\u{1}{}", n.qualified_name))
.or_default()
.push(n.id.as_str());
}
} else if n.file_path.ends_with(".go") && n.kind == NodeKind::Function {
groups
.entry(format!(
"go\u{1}{}\u{1}{}",
parent_dir(&n.file_path),
n.name
))
.or_default()
.push(n.id.as_str());
}
}
let mut incoming: HashMap<&str, Vec<&Edge>> = HashMap::new();
let mut existing: HashSet<(&str, &str)> = HashSet::new();
for e in edges {
if e.kind == EdgeKind::Calls {
incoming.entry(e.target.as_str()).or_default().push(e);
existing.insert((e.source.as_str(), e.target.as_str()));
}
}
let mut out = Vec::new();
let mut emitted: HashSet<(String, String)> = HashSet::new();
for members in groups.values() {
if members.len() < 2 {
continue;
}
for &m in members {
let Some(in_edges) = incoming.get(m) else {
continue;
};
for e in in_edges {
for &sibling in members {
if sibling == m || e.source == sibling {
continue; }
if existing.contains(&(e.source.as_str(), sibling)) {
continue;
}
if emitted.insert((e.source.clone(), sibling.to_string())) {
out.push(Edge {
source: e.source.clone(),
target: sibling.to_string(),
kind: EdgeKind::Calls,
line: e.line,
});
}
}
}
}
}
out
}