nichlink/registry_core/mir/
merge.rs1use crate::registry_core::declaration::{CallEdge, EvidenceKind};
5
6use super::model::{CallRelation, MirCall};
7
8pub fn merge_call_relations(
18 static_calls: &[MirCall],
19 runtime_calls: &[CallEdge],
20) -> Vec<CallRelation> {
21 let mut relations = Vec::new();
22 for edge in runtime_calls {
23 relations.push(CallRelation {
24 caller: edge.caller.function.to_owned(),
25 callee: edge.callee.function.to_owned(),
26 evidence: EvidenceKind::Live,
27 source: edge.callee.source,
28 mir_line: None,
29 caller_frame: Some(edge.caller.frame_id),
30 callee_frame: Some(edge.callee.frame_id),
31 });
32 }
33 for edge in static_calls {
34 if relations.iter().any(|relation| {
35 same_symbol(&relation.caller, &edge.caller)
36 && same_symbol(&relation.callee, &edge.callee)
37 }) {
38 continue;
39 }
40 relations.push(CallRelation {
41 caller: edge.caller.clone(),
42 callee: edge.callee.clone(),
43 evidence: EvidenceKind::Mir,
44 source: None,
45 mir_line: Some(edge.mir_line),
46 caller_frame: None,
47 callee_frame: None,
48 });
49 }
50 relations
51}
52
53pub fn same_symbol(left: &str, right: &str) -> bool {
74 left == right
75 || left
76 .strip_suffix(right)
77 .is_some_and(|prefix| prefix.ends_with("::"))
78 || right
79 .strip_suffix(left)
80 .is_some_and(|prefix| prefix.ends_with("::"))
81}
82
83#[cfg(test)]
84mod tests {
85 use super::super::model::{CallEvidence, MirCall};
86 use super::{merge_call_relations, same_symbol};
87 use crate::registry_core::declaration::{CallEdge, CallSite, EvidenceKind, SourceLocation};
88 use crate::registry_core::identity::NodeId;
89
90 fn live_edge(caller: &'static str, callee: &'static str) -> CallEdge {
91 fn site(function: &'static str, line: usize) -> CallSite {
92 CallSite {
93 node: NodeId::from_path("a.rs", "A"),
94 function,
95 frame_id: 1,
96 source: Some(SourceLocation {
97 file: "a.rs",
98 line: line.try_into().unwrap(),
99 column: 1,
100 function,
101 }),
102 }
103 }
104 CallEdge {
105 caller: site(caller, 1),
106 callee: site(callee, 2),
107 }
108 }
109
110 #[test]
111 fn live_edges_win_over_same_symbol_mir_candidates() {
112 let static_calls = vec![MirCall {
113 caller: "crate::a".to_owned(),
114 callee: "crate::b".to_owned(),
115 mir_line: 1,
116 }];
117 let relations = merge_call_relations(&static_calls, &[live_edge("a", "b")]);
118 assert_eq!(relations.len(), 1);
119 assert_eq!(relations[0].evidence, CallEvidence::Live);
120 }
121
122 #[test]
123 fn unobserved_mir_candidate_stays_visible_as_mir() {
124 let static_calls = vec![MirCall {
125 caller: "crate::a".to_owned(),
126 callee: "crate::c".to_owned(),
127 mir_line: 7,
128 }];
129 let relations = merge_call_relations(&static_calls, &[]);
130 assert_eq!(relations.len(), 1);
131 assert_eq!(relations[0].evidence, EvidenceKind::Mir);
132 assert_eq!(relations[0].mir_line, Some(7));
133 assert!(!relations[0].evidence.confirmed());
134 }
135
136 #[test]
140 fn same_symbol_requires_a_path_boundary_before_the_suffix() {
141 assert!(same_symbol("crate::ui::button", "button"));
142 assert!(same_symbol("button", "crate::ui::button"));
143 assert!(same_symbol("crate::ui::button", "crate::ui::button"));
144 assert!(!same_symbol("crate::fastbutton", "button"));
145 assert!(!same_symbol("fastbutton", "button"));
146 assert!(!same_symbol("crate::ui::button", "crate::other::button"));
147 assert!(!same_symbol("crate::ui", ""));
151 assert!(!same_symbol("", "crate::ui"));
152 assert!(same_symbol("crate::ui::", ""));
153 }
154}