1use std::collections::HashMap;
10use std::fmt;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicU64, Ordering};
13
14use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
15
16use crate::confidence::ConfidenceMetadata;
17use crate::graph::unified::edge::bidirectional::BidirectionalEdgeStore;
18use crate::graph::unified::storage::arena::NodeArena;
19use crate::graph::unified::storage::indices::AuxiliaryIndices;
20use crate::graph::unified::storage::interner::StringInterner;
21use crate::graph::unified::storage::metadata::NodeMetadataStore;
22use crate::graph::unified::storage::registry::FileRegistry;
23
24#[derive(Clone)]
44pub struct CodeGraph {
45 nodes: Arc<NodeArena>,
47 edges: Arc<BidirectionalEdgeStore>,
49 strings: Arc<StringInterner>,
51 files: Arc<FileRegistry>,
53 indices: Arc<AuxiliaryIndices>,
55 macro_metadata: Arc<NodeMetadataStore>,
57 epoch: u64,
59 confidence: HashMap<String, ConfidenceMetadata>,
62}
63
64impl CodeGraph {
65 #[must_use]
76 pub fn new() -> Self {
77 Self {
78 nodes: Arc::new(NodeArena::new()),
79 edges: Arc::new(BidirectionalEdgeStore::new()),
80 strings: Arc::new(StringInterner::new()),
81 files: Arc::new(FileRegistry::new()),
82 indices: Arc::new(AuxiliaryIndices::new()),
83 macro_metadata: Arc::new(NodeMetadataStore::new()),
84 epoch: 0,
85 confidence: HashMap::new(),
86 }
87 }
88
89 #[must_use]
94 pub fn from_components(
95 nodes: NodeArena,
96 edges: BidirectionalEdgeStore,
97 strings: StringInterner,
98 files: FileRegistry,
99 indices: AuxiliaryIndices,
100 macro_metadata: NodeMetadataStore,
101 ) -> Self {
102 Self {
103 nodes: Arc::new(nodes),
104 edges: Arc::new(edges),
105 strings: Arc::new(strings),
106 files: Arc::new(files),
107 indices: Arc::new(indices),
108 macro_metadata: Arc::new(macro_metadata),
109 epoch: 0,
110 confidence: HashMap::new(),
111 }
112 }
113
114 #[must_use]
129 pub fn snapshot(&self) -> GraphSnapshot {
130 GraphSnapshot {
131 nodes: Arc::clone(&self.nodes),
132 edges: Arc::clone(&self.edges),
133 strings: Arc::clone(&self.strings),
134 files: Arc::clone(&self.files),
135 indices: Arc::clone(&self.indices),
136 macro_metadata: Arc::clone(&self.macro_metadata),
137 epoch: self.epoch,
138 }
139 }
140
141 #[inline]
143 #[must_use]
144 pub fn nodes(&self) -> &NodeArena {
145 &self.nodes
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn edges(&self) -> &BidirectionalEdgeStore {
152 &self.edges
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn strings(&self) -> &StringInterner {
159 &self.strings
160 }
161
162 #[inline]
164 #[must_use]
165 pub fn files(&self) -> &FileRegistry {
166 &self.files
167 }
168
169 #[inline]
171 #[must_use]
172 pub fn indices(&self) -> &AuxiliaryIndices {
173 &self.indices
174 }
175
176 #[inline]
178 #[must_use]
179 pub fn macro_metadata(&self) -> &NodeMetadataStore {
180 &self.macro_metadata
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn epoch(&self) -> u64 {
187 self.epoch
188 }
189
190 #[inline]
195 pub fn nodes_mut(&mut self) -> &mut NodeArena {
196 Arc::make_mut(&mut self.nodes)
197 }
198
199 #[inline]
203 pub fn edges_mut(&mut self) -> &mut BidirectionalEdgeStore {
204 Arc::make_mut(&mut self.edges)
205 }
206
207 #[inline]
211 pub fn strings_mut(&mut self) -> &mut StringInterner {
212 Arc::make_mut(&mut self.strings)
213 }
214
215 #[inline]
219 pub fn files_mut(&mut self) -> &mut FileRegistry {
220 Arc::make_mut(&mut self.files)
221 }
222
223 #[inline]
227 pub fn indices_mut(&mut self) -> &mut AuxiliaryIndices {
228 Arc::make_mut(&mut self.indices)
229 }
230
231 #[inline]
235 pub fn macro_metadata_mut(&mut self) -> &mut NodeMetadataStore {
236 Arc::make_mut(&mut self.macro_metadata)
237 }
238
239 #[inline]
244 pub fn nodes_and_strings_mut(&mut self) -> (&mut NodeArena, &mut StringInterner) {
245 (
246 Arc::make_mut(&mut self.nodes),
247 Arc::make_mut(&mut self.strings),
248 )
249 }
250
251 pub fn rebuild_indices(&mut self) {
260 let nodes = &self.nodes;
261 Arc::make_mut(&mut self.indices).build_from_arena(nodes);
262 }
263
264 #[inline]
268 pub fn bump_epoch(&mut self) -> u64 {
269 self.epoch = self.epoch.wrapping_add(1);
270 self.epoch
271 }
272
273 #[inline]
277 pub fn set_epoch(&mut self, epoch: u64) {
278 self.epoch = epoch;
279 }
280
281 #[inline]
294 #[must_use]
295 pub fn node_count(&self) -> usize {
296 self.nodes.len()
297 }
298
299 #[inline]
312 #[must_use]
313 pub fn edge_count(&self) -> usize {
314 let stats = self.edges.stats();
315 stats.forward.csr_edge_count + stats.forward.delta_edge_count
316 }
317
318 #[inline]
331 #[must_use]
332 pub fn is_empty(&self) -> bool {
333 self.nodes.is_empty()
334 }
335
336 #[inline]
352 pub fn indexed_files(
353 &self,
354 ) -> impl Iterator<Item = (crate::graph::unified::file::FileId, &std::path::Path)> {
355 self.files
356 .iter()
357 .map(|(id, arc_path)| (id, arc_path.as_ref()))
358 }
359
360 #[inline]
365 #[must_use]
366 pub fn confidence(&self) -> &HashMap<String, ConfidenceMetadata> {
367 &self.confidence
368 }
369
370 pub fn merge_confidence(&mut self, language: &str, metadata: ConfidenceMetadata) {
376 use crate::confidence::ConfidenceLevel;
377
378 self.confidence
379 .entry(language.to_string())
380 .and_modify(|existing| {
381 let new_level = match (&existing.level, &metadata.level) {
383 (ConfidenceLevel::Verified, other) | (other, ConfidenceLevel::Verified) => {
384 *other
385 }
386 (ConfidenceLevel::Partial, ConfidenceLevel::AstOnly)
387 | (ConfidenceLevel::AstOnly, ConfidenceLevel::Partial) => {
388 ConfidenceLevel::AstOnly
389 }
390 (level, _) => *level,
391 };
392 existing.level = new_level;
393
394 for limitation in &metadata.limitations {
396 if !existing.limitations.contains(limitation) {
397 existing.limitations.push(limitation.clone());
398 }
399 }
400
401 for feature in &metadata.unavailable_features {
403 if !existing.unavailable_features.contains(feature) {
404 existing.unavailable_features.push(feature.clone());
405 }
406 }
407 })
408 .or_insert(metadata);
409 }
410
411 pub fn set_confidence(&mut self, confidence: HashMap<String, ConfidenceMetadata>) {
415 self.confidence = confidence;
416 }
417}
418
419impl Default for CodeGraph {
420 fn default() -> Self {
421 Self::new()
422 }
423}
424
425impl fmt::Debug for CodeGraph {
426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427 f.debug_struct("CodeGraph")
428 .field("nodes", &self.nodes.len())
429 .field("epoch", &self.epoch)
430 .finish_non_exhaustive()
431 }
432}
433
434pub struct ConcurrentCodeGraph {
471 inner: RwLock<CodeGraph>,
473 epoch: AtomicU64,
475}
476
477impl ConcurrentCodeGraph {
478 #[must_use]
480 pub fn new() -> Self {
481 Self {
482 inner: RwLock::new(CodeGraph::new()),
483 epoch: AtomicU64::new(0),
484 }
485 }
486
487 #[must_use]
489 pub fn from_graph(graph: CodeGraph) -> Self {
490 let epoch = graph.epoch();
491 Self {
492 inner: RwLock::new(graph),
493 epoch: AtomicU64::new(epoch),
494 }
495 }
496
497 #[inline]
502 pub fn read(&self) -> RwLockReadGuard<'_, CodeGraph> {
503 self.inner.read()
504 }
505
506 #[inline]
511 pub fn write(&self) -> RwLockWriteGuard<'_, CodeGraph> {
512 self.epoch.fetch_add(1, Ordering::SeqCst);
514 let mut guard = self.inner.write();
515 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
517 guard
518 }
519
520 #[inline]
525 #[must_use]
526 pub fn epoch(&self) -> u64 {
527 self.epoch.load(Ordering::SeqCst)
528 }
529
530 #[must_use]
535 pub fn snapshot(&self) -> GraphSnapshot {
536 self.inner.read().snapshot()
537 }
538
539 #[inline]
543 #[must_use]
544 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, CodeGraph>> {
545 self.inner.try_read()
546 }
547
548 #[inline]
553 pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, CodeGraph>> {
554 self.inner.try_write().map(|mut guard| {
555 self.epoch.fetch_add(1, Ordering::SeqCst);
556 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
557 guard
558 })
559 }
560}
561
562impl Default for ConcurrentCodeGraph {
563 fn default() -> Self {
564 Self::new()
565 }
566}
567
568impl fmt::Debug for ConcurrentCodeGraph {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 f.debug_struct("ConcurrentCodeGraph")
571 .field("epoch", &self.epoch.load(Ordering::SeqCst))
572 .finish_non_exhaustive()
573 }
574}
575
576#[derive(Clone)]
605pub struct GraphSnapshot {
606 nodes: Arc<NodeArena>,
608 edges: Arc<BidirectionalEdgeStore>,
610 strings: Arc<StringInterner>,
612 files: Arc<FileRegistry>,
614 indices: Arc<AuxiliaryIndices>,
616 macro_metadata: Arc<NodeMetadataStore>,
618 epoch: u64,
620}
621
622impl GraphSnapshot {
623 #[inline]
625 #[must_use]
626 pub fn nodes(&self) -> &NodeArena {
627 &self.nodes
628 }
629
630 #[inline]
632 #[must_use]
633 pub fn edges(&self) -> &BidirectionalEdgeStore {
634 &self.edges
635 }
636
637 #[inline]
639 #[must_use]
640 pub fn strings(&self) -> &StringInterner {
641 &self.strings
642 }
643
644 #[inline]
646 #[must_use]
647 pub fn files(&self) -> &FileRegistry {
648 &self.files
649 }
650
651 #[inline]
653 #[must_use]
654 pub fn indices(&self) -> &AuxiliaryIndices {
655 &self.indices
656 }
657
658 #[inline]
660 #[must_use]
661 pub fn macro_metadata(&self) -> &NodeMetadataStore {
662 &self.macro_metadata
663 }
664
665 #[inline]
670 #[must_use]
671 pub fn epoch(&self) -> u64 {
672 self.epoch
673 }
674
675 #[inline]
679 #[must_use]
680 pub fn epoch_matches(&self, other_epoch: u64) -> bool {
681 self.epoch == other_epoch
682 }
683
684 #[must_use]
706 pub fn find_by_pattern(&self, pattern: &str) -> Vec<crate::graph::unified::node::NodeId> {
707 let mut matches = Vec::new();
708
709 for (str_id, s) in self.strings.iter() {
711 if s.contains(pattern) {
712 matches.extend_from_slice(self.indices.by_qualified_name(str_id));
715 matches.extend_from_slice(self.indices.by_name(str_id));
717 }
718 }
719
720 matches.sort_unstable();
722 matches.dedup();
723
724 matches
725 }
726
727 #[must_use]
739 pub fn get_callees(
740 &self,
741 node: crate::graph::unified::node::NodeId,
742 ) -> Vec<crate::graph::unified::node::NodeId> {
743 use crate::graph::unified::edge::EdgeKind;
744
745 self.edges
746 .edges_from(node)
747 .into_iter()
748 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
749 .map(|edge| edge.target)
750 .collect()
751 }
752
753 #[must_use]
765 pub fn get_callers(
766 &self,
767 node: crate::graph::unified::node::NodeId,
768 ) -> Vec<crate::graph::unified::node::NodeId> {
769 use crate::graph::unified::edge::EdgeKind;
770
771 self.edges
772 .edges_to(node)
773 .into_iter()
774 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
775 .map(|edge| edge.source)
776 .collect()
777 }
778
779 pub fn iter_nodes(
788 &self,
789 ) -> impl Iterator<
790 Item = (
791 crate::graph::unified::node::NodeId,
792 &crate::graph::unified::storage::arena::NodeEntry,
793 ),
794 > {
795 self.nodes.iter()
796 }
797
798 pub fn iter_edges(
807 &self,
808 ) -> impl Iterator<
809 Item = (
810 crate::graph::unified::node::NodeId,
811 crate::graph::unified::node::NodeId,
812 crate::graph::unified::edge::EdgeKind,
813 ),
814 > + '_ {
815 self.nodes.iter().flat_map(move |(node_id, _entry)| {
817 self.edges
819 .edges_from(node_id)
820 .into_iter()
821 .map(move |edge| (node_id, edge.target, edge.kind))
822 })
823 }
824
825 #[must_use]
838 pub fn get_node(
839 &self,
840 id: crate::graph::unified::node::NodeId,
841 ) -> Option<&crate::graph::unified::storage::arena::NodeEntry> {
842 self.nodes.get(id)
843 }
844}
845
846impl fmt::Debug for GraphSnapshot {
847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848 f.debug_struct("GraphSnapshot")
849 .field("nodes", &self.nodes.len())
850 .field("epoch", &self.epoch)
851 .finish_non_exhaustive()
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use crate::graph::unified::{
859 FileScope, NodeId, ResolutionMode, SymbolCandidateOutcome, SymbolQuery,
860 SymbolResolutionOutcome,
861 };
862
863 fn resolve_symbol_strict(snapshot: &GraphSnapshot, symbol: &str) -> Option<NodeId> {
864 match snapshot.resolve_symbol(&SymbolQuery {
865 symbol,
866 file_scope: FileScope::Any,
867 mode: ResolutionMode::Strict,
868 }) {
869 SymbolResolutionOutcome::Resolved(node_id) => Some(node_id),
870 SymbolResolutionOutcome::NotFound
871 | SymbolResolutionOutcome::FileNotIndexed
872 | SymbolResolutionOutcome::Ambiguous(_) => None,
873 }
874 }
875
876 fn candidate_nodes(snapshot: &GraphSnapshot, symbol: &str) -> Vec<NodeId> {
877 match snapshot.find_symbol_candidates(&SymbolQuery {
878 symbol,
879 file_scope: FileScope::Any,
880 mode: ResolutionMode::AllowSuffixCandidates,
881 }) {
882 SymbolCandidateOutcome::Candidates(candidates) => candidates,
883 SymbolCandidateOutcome::NotFound | SymbolCandidateOutcome::FileNotIndexed => Vec::new(),
884 }
885 }
886
887 #[test]
888 fn test_code_graph_new() {
889 let graph = CodeGraph::new();
890 assert_eq!(graph.epoch(), 0);
891 assert_eq!(graph.nodes().len(), 0);
892 }
893
894 #[test]
895 fn test_code_graph_default() {
896 let graph = CodeGraph::default();
897 assert_eq!(graph.epoch(), 0);
898 }
899
900 #[test]
901 fn test_code_graph_snapshot() {
902 let graph = CodeGraph::new();
903 let snapshot = graph.snapshot();
904 assert_eq!(snapshot.epoch(), 0);
905 assert_eq!(snapshot.nodes().len(), 0);
906 }
907
908 #[test]
909 fn test_code_graph_bump_epoch() {
910 let mut graph = CodeGraph::new();
911 assert_eq!(graph.epoch(), 0);
912 assert_eq!(graph.bump_epoch(), 1);
913 assert_eq!(graph.epoch(), 1);
914 assert_eq!(graph.bump_epoch(), 2);
915 assert_eq!(graph.epoch(), 2);
916 }
917
918 #[test]
919 fn test_code_graph_set_epoch() {
920 let mut graph = CodeGraph::new();
921 graph.set_epoch(42);
922 assert_eq!(graph.epoch(), 42);
923 }
924
925 #[test]
926 fn test_code_graph_from_components() {
927 let nodes = NodeArena::new();
928 let edges = BidirectionalEdgeStore::new();
929 let strings = StringInterner::new();
930 let files = FileRegistry::new();
931 let indices = AuxiliaryIndices::new();
932 let macro_metadata = NodeMetadataStore::new();
933
934 let graph =
935 CodeGraph::from_components(nodes, edges, strings, files, indices, macro_metadata);
936 assert_eq!(graph.epoch(), 0);
937 }
938
939 #[test]
940 fn test_code_graph_mut_accessors() {
941 let mut graph = CodeGraph::new();
942
943 let _nodes = graph.nodes_mut();
945 let _edges = graph.edges_mut();
946 let _strings = graph.strings_mut();
947 let _files = graph.files_mut();
948 let _indices = graph.indices_mut();
949 }
950
951 #[test]
952 fn test_code_graph_snapshot_isolation() {
953 let mut graph = CodeGraph::new();
954 let snapshot1 = graph.snapshot();
955
956 graph.bump_epoch();
958
959 let snapshot2 = graph.snapshot();
960
961 assert_eq!(snapshot1.epoch(), 0);
963 assert_eq!(snapshot2.epoch(), 1);
964 }
965
966 #[test]
967 fn test_code_graph_debug() {
968 let graph = CodeGraph::new();
969 let debug_str = format!("{graph:?}");
970 assert!(debug_str.contains("CodeGraph"));
971 assert!(debug_str.contains("epoch"));
972 }
973
974 #[test]
975 fn test_concurrent_code_graph_new() {
976 let graph = ConcurrentCodeGraph::new();
977 assert_eq!(graph.epoch(), 0);
978 }
979
980 #[test]
981 fn test_concurrent_code_graph_default() {
982 let graph = ConcurrentCodeGraph::default();
983 assert_eq!(graph.epoch(), 0);
984 }
985
986 #[test]
987 fn test_concurrent_code_graph_from_graph() {
988 let mut inner = CodeGraph::new();
989 inner.set_epoch(10);
990 let graph = ConcurrentCodeGraph::from_graph(inner);
991 assert_eq!(graph.epoch(), 10);
992 }
993
994 #[test]
995 fn test_concurrent_code_graph_read() {
996 let graph = ConcurrentCodeGraph::new();
997 let guard = graph.read();
998 assert_eq!(guard.epoch(), 0);
999 assert_eq!(guard.nodes().len(), 0);
1000 }
1001
1002 #[test]
1003 fn test_concurrent_code_graph_write_increments_epoch() {
1004 let graph = ConcurrentCodeGraph::new();
1005 assert_eq!(graph.epoch(), 0);
1006
1007 {
1008 let guard = graph.write();
1009 assert_eq!(guard.epoch(), 1);
1010 }
1011
1012 assert_eq!(graph.epoch(), 1);
1013
1014 {
1015 let _guard = graph.write();
1016 }
1017
1018 assert_eq!(graph.epoch(), 2);
1019 }
1020
1021 #[test]
1022 fn test_concurrent_code_graph_snapshot() {
1023 let graph = ConcurrentCodeGraph::new();
1024
1025 {
1026 let _guard = graph.write();
1027 }
1028
1029 let snapshot = graph.snapshot();
1030 assert_eq!(snapshot.epoch(), 1);
1031 }
1032
1033 #[test]
1034 fn test_concurrent_code_graph_try_read() {
1035 let graph = ConcurrentCodeGraph::new();
1036 let guard = graph.try_read();
1037 assert!(guard.is_some());
1038 }
1039
1040 #[test]
1041 fn test_concurrent_code_graph_try_write() {
1042 let graph = ConcurrentCodeGraph::new();
1043 let guard = graph.try_write();
1044 assert!(guard.is_some());
1045 assert_eq!(graph.epoch(), 1);
1046 }
1047
1048 #[test]
1049 fn test_concurrent_code_graph_debug() {
1050 let graph = ConcurrentCodeGraph::new();
1051 let debug_str = format!("{graph:?}");
1052 assert!(debug_str.contains("ConcurrentCodeGraph"));
1053 assert!(debug_str.contains("epoch"));
1054 }
1055
1056 #[test]
1057 fn test_graph_snapshot_accessors() {
1058 let graph = CodeGraph::new();
1059 let snapshot = graph.snapshot();
1060
1061 let _nodes = snapshot.nodes();
1063 let _edges = snapshot.edges();
1064 let _strings = snapshot.strings();
1065 let _files = snapshot.files();
1066 let _indices = snapshot.indices();
1067 let _epoch = snapshot.epoch();
1068 }
1069
1070 #[test]
1071 fn test_graph_snapshot_epoch_matches() {
1072 let graph = CodeGraph::new();
1073 let snapshot = graph.snapshot();
1074
1075 assert!(snapshot.epoch_matches(0));
1076 assert!(!snapshot.epoch_matches(1));
1077 }
1078
1079 #[test]
1080 fn test_graph_snapshot_clone() {
1081 let graph = CodeGraph::new();
1082 let snapshot1 = graph.snapshot();
1083 let snapshot2 = snapshot1.clone();
1084
1085 assert_eq!(snapshot1.epoch(), snapshot2.epoch());
1086 }
1087
1088 #[test]
1089 fn test_graph_snapshot_debug() {
1090 let graph = CodeGraph::new();
1091 let snapshot = graph.snapshot();
1092 let debug_str = format!("{snapshot:?}");
1093 assert!(debug_str.contains("GraphSnapshot"));
1094 assert!(debug_str.contains("epoch"));
1095 }
1096
1097 #[test]
1098 fn test_multiple_readers() {
1099 let graph = ConcurrentCodeGraph::new();
1100
1101 let guard1 = graph.read();
1103 let guard2 = graph.read();
1104 let guard3 = graph.read();
1105
1106 assert_eq!(guard1.epoch(), 0);
1107 assert_eq!(guard2.epoch(), 0);
1108 assert_eq!(guard3.epoch(), 0);
1109 }
1110
1111 #[test]
1112 fn test_code_graph_clone() {
1113 let mut graph = CodeGraph::new();
1114 graph.bump_epoch();
1115
1116 let cloned = graph.clone();
1117 assert_eq!(cloned.epoch(), 1);
1118 }
1119
1120 #[test]
1121 fn test_epoch_wrapping() {
1122 let mut graph = CodeGraph::new();
1123 graph.set_epoch(u64::MAX);
1124 let new_epoch = graph.bump_epoch();
1125 assert_eq!(new_epoch, 0); }
1127
1128 #[test]
1133 fn test_snapshot_resolve_symbol() {
1134 use crate::graph::unified::node::NodeKind;
1135 use crate::graph::unified::storage::arena::NodeEntry;
1136 use std::path::Path;
1137
1138 let mut graph = CodeGraph::new();
1139
1140 let name_id = graph.strings_mut().intern("test_func").unwrap();
1142 let qual_name_id = graph.strings_mut().intern("module::test_func").unwrap();
1143 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1144
1145 let entry =
1146 NodeEntry::new(NodeKind::Function, name_id, file_id).with_qualified_name(qual_name_id);
1147
1148 let node_id = graph.nodes_mut().alloc(entry).unwrap();
1149 graph.indices_mut().add(
1150 node_id,
1151 NodeKind::Function,
1152 name_id,
1153 Some(qual_name_id),
1154 file_id,
1155 );
1156
1157 let snapshot = graph.snapshot();
1158
1159 let found = resolve_symbol_strict(&snapshot, "module::test_func");
1161 assert_eq!(found, Some(node_id));
1162
1163 let found2 = resolve_symbol_strict(&snapshot, "test_func");
1165 assert_eq!(found2, Some(node_id));
1166
1167 assert!(resolve_symbol_strict(&snapshot, "nonexistent").is_none());
1169 }
1170
1171 #[test]
1172 fn test_snapshot_find_by_pattern() {
1173 use crate::graph::unified::node::NodeKind;
1174 use crate::graph::unified::storage::arena::NodeEntry;
1175 use std::path::Path;
1176
1177 let mut graph = CodeGraph::new();
1178
1179 let name1 = graph.strings_mut().intern("foo_bar").unwrap();
1181 let name2 = graph.strings_mut().intern("baz_bar").unwrap();
1182 let name3 = graph.strings_mut().intern("qux_test").unwrap();
1183 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1184
1185 let node1 = graph
1186 .nodes_mut()
1187 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1188 .unwrap();
1189 let node2 = graph
1190 .nodes_mut()
1191 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1192 .unwrap();
1193 let node3 = graph
1194 .nodes_mut()
1195 .alloc(NodeEntry::new(NodeKind::Function, name3, file_id))
1196 .unwrap();
1197
1198 graph
1199 .indices_mut()
1200 .add(node1, NodeKind::Function, name1, None, file_id);
1201 graph
1202 .indices_mut()
1203 .add(node2, NodeKind::Function, name2, None, file_id);
1204 graph
1205 .indices_mut()
1206 .add(node3, NodeKind::Function, name3, None, file_id);
1207
1208 let snapshot = graph.snapshot();
1209
1210 let matches = snapshot.find_by_pattern("bar");
1212 assert_eq!(matches.len(), 2);
1213 assert!(matches.contains(&node1));
1214 assert!(matches.contains(&node2));
1215
1216 let matches = snapshot.find_by_pattern("qux");
1218 assert_eq!(matches.len(), 1);
1219 assert_eq!(matches[0], node3);
1220
1221 let matches = snapshot.find_by_pattern("nonexistent");
1223 assert!(matches.is_empty());
1224 }
1225
1226 #[test]
1227 fn test_snapshot_get_callees() {
1228 use crate::graph::unified::edge::EdgeKind;
1229 use crate::graph::unified::node::NodeKind;
1230 use crate::graph::unified::storage::arena::NodeEntry;
1231 use std::path::Path;
1232
1233 let mut graph = CodeGraph::new();
1234
1235 let caller_name = graph.strings_mut().intern("caller").unwrap();
1237 let callee1_name = graph.strings_mut().intern("callee1").unwrap();
1238 let callee2_name = graph.strings_mut().intern("callee2").unwrap();
1239 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1240
1241 let caller_id = graph
1242 .nodes_mut()
1243 .alloc(NodeEntry::new(NodeKind::Function, caller_name, file_id))
1244 .unwrap();
1245 let callee1_id = graph
1246 .nodes_mut()
1247 .alloc(NodeEntry::new(NodeKind::Function, callee1_name, file_id))
1248 .unwrap();
1249 let callee2_id = graph
1250 .nodes_mut()
1251 .alloc(NodeEntry::new(NodeKind::Function, callee2_name, file_id))
1252 .unwrap();
1253
1254 graph.edges_mut().add_edge(
1256 caller_id,
1257 callee1_id,
1258 EdgeKind::Calls {
1259 argument_count: 0,
1260 is_async: false,
1261 },
1262 file_id,
1263 );
1264 graph.edges_mut().add_edge(
1265 caller_id,
1266 callee2_id,
1267 EdgeKind::Calls {
1268 argument_count: 0,
1269 is_async: false,
1270 },
1271 file_id,
1272 );
1273
1274 let snapshot = graph.snapshot();
1275
1276 let callees = snapshot.get_callees(caller_id);
1278 assert_eq!(callees.len(), 2);
1279 assert!(callees.contains(&callee1_id));
1280 assert!(callees.contains(&callee2_id));
1281
1282 let callees = snapshot.get_callees(callee1_id);
1284 assert!(callees.is_empty());
1285 }
1286
1287 #[test]
1288 fn test_snapshot_get_callers() {
1289 use crate::graph::unified::edge::EdgeKind;
1290 use crate::graph::unified::node::NodeKind;
1291 use crate::graph::unified::storage::arena::NodeEntry;
1292 use std::path::Path;
1293
1294 let mut graph = CodeGraph::new();
1295
1296 let caller1_name = graph.strings_mut().intern("caller1").unwrap();
1298 let caller2_name = graph.strings_mut().intern("caller2").unwrap();
1299 let callee_name = graph.strings_mut().intern("callee").unwrap();
1300 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1301
1302 let caller1_id = graph
1303 .nodes_mut()
1304 .alloc(NodeEntry::new(NodeKind::Function, caller1_name, file_id))
1305 .unwrap();
1306 let caller2_id = graph
1307 .nodes_mut()
1308 .alloc(NodeEntry::new(NodeKind::Function, caller2_name, file_id))
1309 .unwrap();
1310 let callee_id = graph
1311 .nodes_mut()
1312 .alloc(NodeEntry::new(NodeKind::Function, callee_name, file_id))
1313 .unwrap();
1314
1315 graph.edges_mut().add_edge(
1317 caller1_id,
1318 callee_id,
1319 EdgeKind::Calls {
1320 argument_count: 0,
1321 is_async: false,
1322 },
1323 file_id,
1324 );
1325 graph.edges_mut().add_edge(
1326 caller2_id,
1327 callee_id,
1328 EdgeKind::Calls {
1329 argument_count: 0,
1330 is_async: false,
1331 },
1332 file_id,
1333 );
1334
1335 let snapshot = graph.snapshot();
1336
1337 let callers = snapshot.get_callers(callee_id);
1339 assert_eq!(callers.len(), 2);
1340 assert!(callers.contains(&caller1_id));
1341 assert!(callers.contains(&caller2_id));
1342
1343 let callers = snapshot.get_callers(caller1_id);
1345 assert!(callers.is_empty());
1346 }
1347
1348 #[test]
1349 fn test_snapshot_find_symbol_candidates() {
1350 use crate::graph::unified::node::NodeKind;
1351 use crate::graph::unified::storage::arena::NodeEntry;
1352 use std::path::Path;
1353
1354 let mut graph = CodeGraph::new();
1355
1356 let symbol_name = graph.strings_mut().intern("test").unwrap();
1358 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1359
1360 let node1 = graph
1361 .nodes_mut()
1362 .alloc(NodeEntry::new(NodeKind::Function, symbol_name, file_id))
1363 .unwrap();
1364 let node2 = graph
1365 .nodes_mut()
1366 .alloc(NodeEntry::new(NodeKind::Method, symbol_name, file_id))
1367 .unwrap();
1368
1369 let other_name = graph.strings_mut().intern("other").unwrap();
1371 let node3 = graph
1372 .nodes_mut()
1373 .alloc(NodeEntry::new(NodeKind::Function, other_name, file_id))
1374 .unwrap();
1375
1376 graph
1377 .indices_mut()
1378 .add(node1, NodeKind::Function, symbol_name, None, file_id);
1379 graph
1380 .indices_mut()
1381 .add(node2, NodeKind::Method, symbol_name, None, file_id);
1382 graph
1383 .indices_mut()
1384 .add(node3, NodeKind::Function, other_name, None, file_id);
1385
1386 let snapshot = graph.snapshot();
1387
1388 let matches = candidate_nodes(&snapshot, "test");
1390 assert_eq!(matches.len(), 2);
1391 assert!(matches.contains(&node1));
1392 assert!(matches.contains(&node2));
1393
1394 let matches = candidate_nodes(&snapshot, "other");
1396 assert_eq!(matches.len(), 1);
1397 assert_eq!(matches[0], node3);
1398
1399 let matches = candidate_nodes(&snapshot, "nonexistent");
1401 assert!(matches.is_empty());
1402 }
1403
1404 #[test]
1405 fn test_snapshot_iter_nodes() {
1406 use crate::graph::unified::node::NodeKind;
1407 use crate::graph::unified::storage::arena::NodeEntry;
1408 use std::path::Path;
1409
1410 let mut graph = CodeGraph::new();
1411
1412 let name1 = graph.strings_mut().intern("func1").unwrap();
1414 let name2 = graph.strings_mut().intern("func2").unwrap();
1415 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1416
1417 let node1 = graph
1418 .nodes_mut()
1419 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1420 .unwrap();
1421 let node2 = graph
1422 .nodes_mut()
1423 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1424 .unwrap();
1425
1426 let snapshot = graph.snapshot();
1427
1428 let snapshot_nodes: Vec<_> = snapshot.iter_nodes().collect();
1430 assert_eq!(snapshot_nodes.len(), 2);
1431
1432 let node_ids: Vec<_> = snapshot_nodes.iter().map(|(id, _)| *id).collect();
1433 assert!(node_ids.contains(&node1));
1434 assert!(node_ids.contains(&node2));
1435 }
1436
1437 #[test]
1438 fn test_snapshot_iter_edges() {
1439 use crate::graph::unified::edge::EdgeKind;
1440 use crate::graph::unified::node::NodeKind;
1441 use crate::graph::unified::storage::arena::NodeEntry;
1442 use std::path::Path;
1443
1444 let mut graph = CodeGraph::new();
1445
1446 let name1 = graph.strings_mut().intern("func1").unwrap();
1448 let name2 = graph.strings_mut().intern("func2").unwrap();
1449 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1450
1451 let node1 = graph
1452 .nodes_mut()
1453 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1454 .unwrap();
1455 let node2 = graph
1456 .nodes_mut()
1457 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1458 .unwrap();
1459
1460 graph.edges_mut().add_edge(
1462 node1,
1463 node2,
1464 EdgeKind::Calls {
1465 argument_count: 0,
1466 is_async: false,
1467 },
1468 file_id,
1469 );
1470
1471 let snapshot = graph.snapshot();
1472
1473 let edges: Vec<_> = snapshot.iter_edges().collect();
1475 assert_eq!(edges.len(), 1);
1476
1477 let (src, tgt, kind) = &edges[0];
1478 assert_eq!(*src, node1);
1479 assert_eq!(*tgt, node2);
1480 assert!(matches!(
1481 kind,
1482 EdgeKind::Calls {
1483 argument_count: 0,
1484 is_async: false
1485 }
1486 ));
1487 }
1488
1489 #[test]
1490 fn test_snapshot_get_node() {
1491 use crate::graph::unified::node::NodeId;
1492 use crate::graph::unified::node::NodeKind;
1493 use crate::graph::unified::storage::arena::NodeEntry;
1494 use std::path::Path;
1495
1496 let mut graph = CodeGraph::new();
1497
1498 let name = graph.strings_mut().intern("test_func").unwrap();
1500 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1501
1502 let node_id = graph
1503 .nodes_mut()
1504 .alloc(NodeEntry::new(NodeKind::Function, name, file_id))
1505 .unwrap();
1506
1507 let snapshot = graph.snapshot();
1508
1509 let entry = snapshot.get_node(node_id);
1511 assert!(entry.is_some());
1512 assert_eq!(entry.unwrap().kind, NodeKind::Function);
1513
1514 let invalid_id = NodeId::INVALID;
1516 assert!(snapshot.get_node(invalid_id).is_none());
1517 }
1518
1519 #[test]
1520 fn test_snapshot_query_empty_graph() {
1521 use crate::graph::unified::node::NodeId;
1522
1523 let graph = CodeGraph::new();
1524 let snapshot = graph.snapshot();
1525
1526 assert!(resolve_symbol_strict(&snapshot, "test").is_none());
1528 assert!(snapshot.find_by_pattern("test").is_empty());
1529 assert!(candidate_nodes(&snapshot, "test").is_empty());
1530
1531 let dummy_id = NodeId::new(0, 1);
1532 assert!(snapshot.get_callees(dummy_id).is_empty());
1533 assert!(snapshot.get_callers(dummy_id).is_empty());
1534
1535 assert_eq!(snapshot.iter_nodes().count(), 0);
1536 assert_eq!(snapshot.iter_edges().count(), 0);
1537 }
1538
1539 #[test]
1540 fn test_snapshot_edge_filtering_by_kind() {
1541 use crate::graph::unified::edge::EdgeKind;
1542 use crate::graph::unified::node::NodeKind;
1543 use crate::graph::unified::storage::arena::NodeEntry;
1544 use std::path::Path;
1545
1546 let mut graph = CodeGraph::new();
1547
1548 let name1 = graph.strings_mut().intern("func1").unwrap();
1550 let name2 = graph.strings_mut().intern("func2").unwrap();
1551 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1552
1553 let node1 = graph
1554 .nodes_mut()
1555 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1556 .unwrap();
1557 let node2 = graph
1558 .nodes_mut()
1559 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1560 .unwrap();
1561
1562 graph.edges_mut().add_edge(
1564 node1,
1565 node2,
1566 EdgeKind::Calls {
1567 argument_count: 0,
1568 is_async: false,
1569 },
1570 file_id,
1571 );
1572 graph
1573 .edges_mut()
1574 .add_edge(node1, node2, EdgeKind::References, file_id);
1575
1576 let snapshot = graph.snapshot();
1577
1578 let callees = snapshot.get_callees(node1);
1580 assert_eq!(callees.len(), 1);
1581 assert_eq!(callees[0], node2);
1582
1583 let edges: Vec<_> = snapshot.iter_edges().collect();
1585 assert_eq!(edges.len(), 2);
1586 }
1587}