use crate::registry_core::declaration::{CallEdge, EvidenceKind};
use super::model::{CallRelation, MirCall};
pub fn merge_call_relations(
static_calls: &[MirCall],
runtime_calls: &[CallEdge],
) -> Vec<CallRelation> {
let mut relations = Vec::new();
for edge in runtime_calls {
relations.push(CallRelation {
caller: edge.caller.function.to_owned(),
callee: edge.callee.function.to_owned(),
evidence: EvidenceKind::Live,
source: edge.callee.source,
mir_line: None,
caller_frame: Some(edge.caller.frame_id),
callee_frame: Some(edge.callee.frame_id),
});
}
for edge in static_calls {
if relations.iter().any(|relation| {
same_symbol(&relation.caller, &edge.caller)
&& same_symbol(&relation.callee, &edge.callee)
}) {
continue;
}
relations.push(CallRelation {
caller: edge.caller.clone(),
callee: edge.callee.clone(),
evidence: EvidenceKind::Mir,
source: None,
mir_line: Some(edge.mir_line),
caller_frame: None,
callee_frame: None,
});
}
relations
}
pub fn same_symbol(left: &str, right: &str) -> bool {
left == right
|| left
.strip_suffix(right)
.is_some_and(|prefix| prefix.ends_with("::"))
|| right
.strip_suffix(left)
.is_some_and(|prefix| prefix.ends_with("::"))
}
#[cfg(test)]
mod tests {
use super::super::model::{CallEvidence, MirCall};
use super::{merge_call_relations, same_symbol};
use crate::registry_core::declaration::{CallEdge, CallSite, EvidenceKind, SourceLocation};
use crate::registry_core::identity::NodeId;
fn live_edge(caller: &'static str, callee: &'static str) -> CallEdge {
fn site(function: &'static str, line: usize) -> CallSite {
CallSite {
node: NodeId::from_path("a.rs", "A"),
function,
frame_id: 1,
source: Some(SourceLocation {
file: "a.rs",
line: line.try_into().unwrap(),
column: 1,
function,
}),
}
}
CallEdge {
caller: site(caller, 1),
callee: site(callee, 2),
}
}
#[test]
fn live_edges_win_over_same_symbol_mir_candidates() {
let static_calls = vec![MirCall {
caller: "crate::a".to_owned(),
callee: "crate::b".to_owned(),
mir_line: 1,
}];
let relations = merge_call_relations(&static_calls, &[live_edge("a", "b")]);
assert_eq!(relations.len(), 1);
assert_eq!(relations[0].evidence, CallEvidence::Live);
}
#[test]
fn unobserved_mir_candidate_stays_visible_as_mir() {
let static_calls = vec![MirCall {
caller: "crate::a".to_owned(),
callee: "crate::c".to_owned(),
mir_line: 7,
}];
let relations = merge_call_relations(&static_calls, &[]);
assert_eq!(relations.len(), 1);
assert_eq!(relations[0].evidence, EvidenceKind::Mir);
assert_eq!(relations[0].mir_line, Some(7));
assert!(!relations[0].evidence.confirmed());
}
#[test]
fn same_symbol_requires_a_path_boundary_before_the_suffix() {
assert!(same_symbol("crate::ui::button", "button"));
assert!(same_symbol("button", "crate::ui::button"));
assert!(same_symbol("crate::ui::button", "crate::ui::button"));
assert!(!same_symbol("crate::fastbutton", "button"));
assert!(!same_symbol("fastbutton", "button"));
assert!(!same_symbol("crate::ui::button", "crate::other::button"));
assert!(!same_symbol("crate::ui", ""));
assert!(!same_symbol("", "crate::ui"));
assert!(same_symbol("crate::ui::", ""));
}
}