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::registry::FileRegistry;
22
23#[derive(Clone)]
43pub struct CodeGraph {
44 nodes: Arc<NodeArena>,
46 edges: Arc<BidirectionalEdgeStore>,
48 strings: Arc<StringInterner>,
50 files: Arc<FileRegistry>,
52 indices: Arc<AuxiliaryIndices>,
54 epoch: u64,
56 confidence: HashMap<String, ConfidenceMetadata>,
59}
60
61impl CodeGraph {
62 #[must_use]
73 pub fn new() -> Self {
74 Self {
75 nodes: Arc::new(NodeArena::new()),
76 edges: Arc::new(BidirectionalEdgeStore::new()),
77 strings: Arc::new(StringInterner::new()),
78 files: Arc::new(FileRegistry::new()),
79 indices: Arc::new(AuxiliaryIndices::new()),
80 epoch: 0,
81 confidence: HashMap::new(),
82 }
83 }
84
85 #[must_use]
90 pub fn from_components(
91 nodes: NodeArena,
92 edges: BidirectionalEdgeStore,
93 strings: StringInterner,
94 files: FileRegistry,
95 indices: AuxiliaryIndices,
96 ) -> Self {
97 Self {
98 nodes: Arc::new(nodes),
99 edges: Arc::new(edges),
100 strings: Arc::new(strings),
101 files: Arc::new(files),
102 indices: Arc::new(indices),
103 epoch: 0,
104 confidence: HashMap::new(),
105 }
106 }
107
108 #[must_use]
123 pub fn snapshot(&self) -> GraphSnapshot {
124 GraphSnapshot {
125 nodes: Arc::clone(&self.nodes),
126 edges: Arc::clone(&self.edges),
127 strings: Arc::clone(&self.strings),
128 files: Arc::clone(&self.files),
129 indices: Arc::clone(&self.indices),
130 epoch: self.epoch,
131 }
132 }
133
134 #[inline]
136 #[must_use]
137 pub fn nodes(&self) -> &NodeArena {
138 &self.nodes
139 }
140
141 #[inline]
143 #[must_use]
144 pub fn edges(&self) -> &BidirectionalEdgeStore {
145 &self.edges
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn strings(&self) -> &StringInterner {
152 &self.strings
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn files(&self) -> &FileRegistry {
159 &self.files
160 }
161
162 #[inline]
164 #[must_use]
165 pub fn indices(&self) -> &AuxiliaryIndices {
166 &self.indices
167 }
168
169 #[inline]
171 #[must_use]
172 pub fn epoch(&self) -> u64 {
173 self.epoch
174 }
175
176 #[inline]
181 pub fn nodes_mut(&mut self) -> &mut NodeArena {
182 Arc::make_mut(&mut self.nodes)
183 }
184
185 #[inline]
189 pub fn edges_mut(&mut self) -> &mut BidirectionalEdgeStore {
190 Arc::make_mut(&mut self.edges)
191 }
192
193 #[inline]
197 pub fn strings_mut(&mut self) -> &mut StringInterner {
198 Arc::make_mut(&mut self.strings)
199 }
200
201 #[inline]
205 pub fn files_mut(&mut self) -> &mut FileRegistry {
206 Arc::make_mut(&mut self.files)
207 }
208
209 #[inline]
213 pub fn indices_mut(&mut self) -> &mut AuxiliaryIndices {
214 Arc::make_mut(&mut self.indices)
215 }
216
217 #[inline]
222 pub fn nodes_and_strings_mut(&mut self) -> (&mut NodeArena, &mut StringInterner) {
223 (
224 Arc::make_mut(&mut self.nodes),
225 Arc::make_mut(&mut self.strings),
226 )
227 }
228
229 pub fn rebuild_indices(&mut self) {
238 let nodes = &self.nodes;
239 Arc::make_mut(&mut self.indices).build_from_arena(nodes);
240 }
241
242 #[inline]
246 pub fn bump_epoch(&mut self) -> u64 {
247 self.epoch = self.epoch.wrapping_add(1);
248 self.epoch
249 }
250
251 #[inline]
255 pub fn set_epoch(&mut self, epoch: u64) {
256 self.epoch = epoch;
257 }
258
259 #[inline]
272 #[must_use]
273 pub fn node_count(&self) -> usize {
274 self.nodes.len()
275 }
276
277 #[inline]
290 #[must_use]
291 pub fn edge_count(&self) -> usize {
292 let stats = self.edges.stats();
293 stats.forward.csr_edge_count + stats.forward.delta_edge_count
294 }
295
296 #[inline]
309 #[must_use]
310 pub fn is_empty(&self) -> bool {
311 self.nodes.is_empty()
312 }
313
314 #[inline]
330 pub fn indexed_files(
331 &self,
332 ) -> impl Iterator<Item = (crate::graph::unified::file::FileId, &std::path::Path)> {
333 self.files
334 .iter()
335 .map(|(id, arc_path)| (id, arc_path.as_ref()))
336 }
337
338 #[inline]
343 #[must_use]
344 pub fn confidence(&self) -> &HashMap<String, ConfidenceMetadata> {
345 &self.confidence
346 }
347
348 pub fn merge_confidence(&mut self, language: &str, metadata: ConfidenceMetadata) {
354 use crate::confidence::ConfidenceLevel;
355
356 self.confidence
357 .entry(language.to_string())
358 .and_modify(|existing| {
359 let new_level = match (&existing.level, &metadata.level) {
361 (ConfidenceLevel::Verified, other) | (other, ConfidenceLevel::Verified) => {
362 *other
363 }
364 (ConfidenceLevel::Partial, ConfidenceLevel::AstOnly)
365 | (ConfidenceLevel::AstOnly, ConfidenceLevel::Partial) => {
366 ConfidenceLevel::AstOnly
367 }
368 (level, _) => *level,
369 };
370 existing.level = new_level;
371
372 for limitation in &metadata.limitations {
374 if !existing.limitations.contains(limitation) {
375 existing.limitations.push(limitation.clone());
376 }
377 }
378
379 for feature in &metadata.unavailable_features {
381 if !existing.unavailable_features.contains(feature) {
382 existing.unavailable_features.push(feature.clone());
383 }
384 }
385 })
386 .or_insert(metadata);
387 }
388
389 pub fn set_confidence(&mut self, confidence: HashMap<String, ConfidenceMetadata>) {
393 self.confidence = confidence;
394 }
395}
396
397impl Default for CodeGraph {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403impl fmt::Debug for CodeGraph {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 f.debug_struct("CodeGraph")
406 .field("nodes", &self.nodes.len())
407 .field("epoch", &self.epoch)
408 .finish_non_exhaustive()
409 }
410}
411
412pub struct ConcurrentCodeGraph {
449 inner: RwLock<CodeGraph>,
451 epoch: AtomicU64,
453}
454
455impl ConcurrentCodeGraph {
456 #[must_use]
458 pub fn new() -> Self {
459 Self {
460 inner: RwLock::new(CodeGraph::new()),
461 epoch: AtomicU64::new(0),
462 }
463 }
464
465 #[must_use]
467 pub fn from_graph(graph: CodeGraph) -> Self {
468 let epoch = graph.epoch();
469 Self {
470 inner: RwLock::new(graph),
471 epoch: AtomicU64::new(epoch),
472 }
473 }
474
475 #[inline]
480 pub fn read(&self) -> RwLockReadGuard<'_, CodeGraph> {
481 self.inner.read()
482 }
483
484 #[inline]
489 pub fn write(&self) -> RwLockWriteGuard<'_, CodeGraph> {
490 self.epoch.fetch_add(1, Ordering::SeqCst);
492 let mut guard = self.inner.write();
493 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
495 guard
496 }
497
498 #[inline]
503 #[must_use]
504 pub fn epoch(&self) -> u64 {
505 self.epoch.load(Ordering::SeqCst)
506 }
507
508 #[must_use]
513 pub fn snapshot(&self) -> GraphSnapshot {
514 self.inner.read().snapshot()
515 }
516
517 #[inline]
521 #[must_use]
522 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, CodeGraph>> {
523 self.inner.try_read()
524 }
525
526 #[inline]
531 pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, CodeGraph>> {
532 self.inner.try_write().map(|mut guard| {
533 self.epoch.fetch_add(1, Ordering::SeqCst);
534 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
535 guard
536 })
537 }
538}
539
540impl Default for ConcurrentCodeGraph {
541 fn default() -> Self {
542 Self::new()
543 }
544}
545
546impl fmt::Debug for ConcurrentCodeGraph {
547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548 f.debug_struct("ConcurrentCodeGraph")
549 .field("epoch", &self.epoch.load(Ordering::SeqCst))
550 .finish_non_exhaustive()
551 }
552}
553
554#[derive(Clone)]
583pub struct GraphSnapshot {
584 nodes: Arc<NodeArena>,
586 edges: Arc<BidirectionalEdgeStore>,
588 strings: Arc<StringInterner>,
590 files: Arc<FileRegistry>,
592 indices: Arc<AuxiliaryIndices>,
594 epoch: u64,
596}
597
598impl GraphSnapshot {
599 #[inline]
601 #[must_use]
602 pub fn nodes(&self) -> &NodeArena {
603 &self.nodes
604 }
605
606 #[inline]
608 #[must_use]
609 pub fn edges(&self) -> &BidirectionalEdgeStore {
610 &self.edges
611 }
612
613 #[inline]
615 #[must_use]
616 pub fn strings(&self) -> &StringInterner {
617 &self.strings
618 }
619
620 #[inline]
622 #[must_use]
623 pub fn files(&self) -> &FileRegistry {
624 &self.files
625 }
626
627 #[inline]
629 #[must_use]
630 pub fn indices(&self) -> &AuxiliaryIndices {
631 &self.indices
632 }
633
634 #[inline]
639 #[must_use]
640 pub fn epoch(&self) -> u64 {
641 self.epoch
642 }
643
644 #[inline]
648 #[must_use]
649 pub fn epoch_matches(&self, other_epoch: u64) -> bool {
650 self.epoch == other_epoch
651 }
652
653 #[must_use]
675 pub fn find_by_pattern(&self, pattern: &str) -> Vec<crate::graph::unified::node::NodeId> {
676 let mut matches = Vec::new();
677
678 for (str_id, s) in self.strings.iter() {
680 if s.contains(pattern) {
681 matches.extend_from_slice(self.indices.by_qualified_name(str_id));
684 matches.extend_from_slice(self.indices.by_name(str_id));
686 }
687 }
688
689 matches.sort_unstable();
691 matches.dedup();
692
693 matches
694 }
695
696 #[must_use]
708 pub fn get_callees(
709 &self,
710 node: crate::graph::unified::node::NodeId,
711 ) -> Vec<crate::graph::unified::node::NodeId> {
712 use crate::graph::unified::edge::EdgeKind;
713
714 self.edges
715 .edges_from(node)
716 .into_iter()
717 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
718 .map(|edge| edge.target)
719 .collect()
720 }
721
722 #[must_use]
734 pub fn get_callers(
735 &self,
736 node: crate::graph::unified::node::NodeId,
737 ) -> Vec<crate::graph::unified::node::NodeId> {
738 use crate::graph::unified::edge::EdgeKind;
739
740 self.edges
741 .edges_to(node)
742 .into_iter()
743 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
744 .map(|edge| edge.source)
745 .collect()
746 }
747
748 pub fn iter_nodes(
757 &self,
758 ) -> impl Iterator<
759 Item = (
760 crate::graph::unified::node::NodeId,
761 &crate::graph::unified::storage::arena::NodeEntry,
762 ),
763 > {
764 self.nodes.iter()
765 }
766
767 pub fn iter_edges(
776 &self,
777 ) -> impl Iterator<
778 Item = (
779 crate::graph::unified::node::NodeId,
780 crate::graph::unified::node::NodeId,
781 crate::graph::unified::edge::EdgeKind,
782 ),
783 > + '_ {
784 self.nodes.iter().flat_map(move |(node_id, _entry)| {
786 self.edges
788 .edges_from(node_id)
789 .into_iter()
790 .map(move |edge| (node_id, edge.target, edge.kind))
791 })
792 }
793
794 #[must_use]
807 pub fn get_node(
808 &self,
809 id: crate::graph::unified::node::NodeId,
810 ) -> Option<&crate::graph::unified::storage::arena::NodeEntry> {
811 self.nodes.get(id)
812 }
813}
814
815impl fmt::Debug for GraphSnapshot {
816 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
817 f.debug_struct("GraphSnapshot")
818 .field("nodes", &self.nodes.len())
819 .field("epoch", &self.epoch)
820 .finish_non_exhaustive()
821 }
822}
823
824#[cfg(test)]
825mod tests {
826 use super::*;
827 use crate::graph::unified::{
828 FileScope, NodeId, ResolutionMode, SymbolCandidateOutcome, SymbolQuery,
829 SymbolResolutionOutcome,
830 };
831
832 fn resolve_symbol_strict(snapshot: &GraphSnapshot, symbol: &str) -> Option<NodeId> {
833 match snapshot.resolve_symbol(&SymbolQuery {
834 symbol,
835 file_scope: FileScope::Any,
836 mode: ResolutionMode::Strict,
837 }) {
838 SymbolResolutionOutcome::Resolved(node_id) => Some(node_id),
839 SymbolResolutionOutcome::NotFound
840 | SymbolResolutionOutcome::FileNotIndexed
841 | SymbolResolutionOutcome::Ambiguous(_) => None,
842 }
843 }
844
845 fn candidate_nodes(snapshot: &GraphSnapshot, symbol: &str) -> Vec<NodeId> {
846 match snapshot.find_symbol_candidates(&SymbolQuery {
847 symbol,
848 file_scope: FileScope::Any,
849 mode: ResolutionMode::AllowSuffixCandidates,
850 }) {
851 SymbolCandidateOutcome::Candidates(candidates) => candidates,
852 SymbolCandidateOutcome::NotFound | SymbolCandidateOutcome::FileNotIndexed => Vec::new(),
853 }
854 }
855
856 #[test]
857 fn test_code_graph_new() {
858 let graph = CodeGraph::new();
859 assert_eq!(graph.epoch(), 0);
860 assert_eq!(graph.nodes().len(), 0);
861 }
862
863 #[test]
864 fn test_code_graph_default() {
865 let graph = CodeGraph::default();
866 assert_eq!(graph.epoch(), 0);
867 }
868
869 #[test]
870 fn test_code_graph_snapshot() {
871 let graph = CodeGraph::new();
872 let snapshot = graph.snapshot();
873 assert_eq!(snapshot.epoch(), 0);
874 assert_eq!(snapshot.nodes().len(), 0);
875 }
876
877 #[test]
878 fn test_code_graph_bump_epoch() {
879 let mut graph = CodeGraph::new();
880 assert_eq!(graph.epoch(), 0);
881 assert_eq!(graph.bump_epoch(), 1);
882 assert_eq!(graph.epoch(), 1);
883 assert_eq!(graph.bump_epoch(), 2);
884 assert_eq!(graph.epoch(), 2);
885 }
886
887 #[test]
888 fn test_code_graph_set_epoch() {
889 let mut graph = CodeGraph::new();
890 graph.set_epoch(42);
891 assert_eq!(graph.epoch(), 42);
892 }
893
894 #[test]
895 fn test_code_graph_from_components() {
896 let nodes = NodeArena::new();
897 let edges = BidirectionalEdgeStore::new();
898 let strings = StringInterner::new();
899 let files = FileRegistry::new();
900 let indices = AuxiliaryIndices::new();
901
902 let graph = CodeGraph::from_components(nodes, edges, strings, files, indices);
903 assert_eq!(graph.epoch(), 0);
904 }
905
906 #[test]
907 fn test_code_graph_mut_accessors() {
908 let mut graph = CodeGraph::new();
909
910 let _nodes = graph.nodes_mut();
912 let _edges = graph.edges_mut();
913 let _strings = graph.strings_mut();
914 let _files = graph.files_mut();
915 let _indices = graph.indices_mut();
916 }
917
918 #[test]
919 fn test_code_graph_snapshot_isolation() {
920 let mut graph = CodeGraph::new();
921 let snapshot1 = graph.snapshot();
922
923 graph.bump_epoch();
925
926 let snapshot2 = graph.snapshot();
927
928 assert_eq!(snapshot1.epoch(), 0);
930 assert_eq!(snapshot2.epoch(), 1);
931 }
932
933 #[test]
934 fn test_code_graph_debug() {
935 let graph = CodeGraph::new();
936 let debug_str = format!("{graph:?}");
937 assert!(debug_str.contains("CodeGraph"));
938 assert!(debug_str.contains("epoch"));
939 }
940
941 #[test]
942 fn test_concurrent_code_graph_new() {
943 let graph = ConcurrentCodeGraph::new();
944 assert_eq!(graph.epoch(), 0);
945 }
946
947 #[test]
948 fn test_concurrent_code_graph_default() {
949 let graph = ConcurrentCodeGraph::default();
950 assert_eq!(graph.epoch(), 0);
951 }
952
953 #[test]
954 fn test_concurrent_code_graph_from_graph() {
955 let mut inner = CodeGraph::new();
956 inner.set_epoch(10);
957 let graph = ConcurrentCodeGraph::from_graph(inner);
958 assert_eq!(graph.epoch(), 10);
959 }
960
961 #[test]
962 fn test_concurrent_code_graph_read() {
963 let graph = ConcurrentCodeGraph::new();
964 let guard = graph.read();
965 assert_eq!(guard.epoch(), 0);
966 assert_eq!(guard.nodes().len(), 0);
967 }
968
969 #[test]
970 fn test_concurrent_code_graph_write_increments_epoch() {
971 let graph = ConcurrentCodeGraph::new();
972 assert_eq!(graph.epoch(), 0);
973
974 {
975 let guard = graph.write();
976 assert_eq!(guard.epoch(), 1);
977 }
978
979 assert_eq!(graph.epoch(), 1);
980
981 {
982 let _guard = graph.write();
983 }
984
985 assert_eq!(graph.epoch(), 2);
986 }
987
988 #[test]
989 fn test_concurrent_code_graph_snapshot() {
990 let graph = ConcurrentCodeGraph::new();
991
992 {
993 let _guard = graph.write();
994 }
995
996 let snapshot = graph.snapshot();
997 assert_eq!(snapshot.epoch(), 1);
998 }
999
1000 #[test]
1001 fn test_concurrent_code_graph_try_read() {
1002 let graph = ConcurrentCodeGraph::new();
1003 let guard = graph.try_read();
1004 assert!(guard.is_some());
1005 }
1006
1007 #[test]
1008 fn test_concurrent_code_graph_try_write() {
1009 let graph = ConcurrentCodeGraph::new();
1010 let guard = graph.try_write();
1011 assert!(guard.is_some());
1012 assert_eq!(graph.epoch(), 1);
1013 }
1014
1015 #[test]
1016 fn test_concurrent_code_graph_debug() {
1017 let graph = ConcurrentCodeGraph::new();
1018 let debug_str = format!("{graph:?}");
1019 assert!(debug_str.contains("ConcurrentCodeGraph"));
1020 assert!(debug_str.contains("epoch"));
1021 }
1022
1023 #[test]
1024 fn test_graph_snapshot_accessors() {
1025 let graph = CodeGraph::new();
1026 let snapshot = graph.snapshot();
1027
1028 let _nodes = snapshot.nodes();
1030 let _edges = snapshot.edges();
1031 let _strings = snapshot.strings();
1032 let _files = snapshot.files();
1033 let _indices = snapshot.indices();
1034 let _epoch = snapshot.epoch();
1035 }
1036
1037 #[test]
1038 fn test_graph_snapshot_epoch_matches() {
1039 let graph = CodeGraph::new();
1040 let snapshot = graph.snapshot();
1041
1042 assert!(snapshot.epoch_matches(0));
1043 assert!(!snapshot.epoch_matches(1));
1044 }
1045
1046 #[test]
1047 fn test_graph_snapshot_clone() {
1048 let graph = CodeGraph::new();
1049 let snapshot1 = graph.snapshot();
1050 let snapshot2 = snapshot1.clone();
1051
1052 assert_eq!(snapshot1.epoch(), snapshot2.epoch());
1053 }
1054
1055 #[test]
1056 fn test_graph_snapshot_debug() {
1057 let graph = CodeGraph::new();
1058 let snapshot = graph.snapshot();
1059 let debug_str = format!("{snapshot:?}");
1060 assert!(debug_str.contains("GraphSnapshot"));
1061 assert!(debug_str.contains("epoch"));
1062 }
1063
1064 #[test]
1065 fn test_multiple_readers() {
1066 let graph = ConcurrentCodeGraph::new();
1067
1068 let guard1 = graph.read();
1070 let guard2 = graph.read();
1071 let guard3 = graph.read();
1072
1073 assert_eq!(guard1.epoch(), 0);
1074 assert_eq!(guard2.epoch(), 0);
1075 assert_eq!(guard3.epoch(), 0);
1076 }
1077
1078 #[test]
1079 fn test_code_graph_clone() {
1080 let mut graph = CodeGraph::new();
1081 graph.bump_epoch();
1082
1083 let cloned = graph.clone();
1084 assert_eq!(cloned.epoch(), 1);
1085 }
1086
1087 #[test]
1088 fn test_epoch_wrapping() {
1089 let mut graph = CodeGraph::new();
1090 graph.set_epoch(u64::MAX);
1091 let new_epoch = graph.bump_epoch();
1092 assert_eq!(new_epoch, 0); }
1094
1095 #[test]
1100 fn test_snapshot_resolve_symbol() {
1101 use crate::graph::unified::node::NodeKind;
1102 use crate::graph::unified::storage::arena::NodeEntry;
1103 use std::path::Path;
1104
1105 let mut graph = CodeGraph::new();
1106
1107 let name_id = graph.strings_mut().intern("test_func").unwrap();
1109 let qual_name_id = graph.strings_mut().intern("module::test_func").unwrap();
1110 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1111
1112 let entry =
1113 NodeEntry::new(NodeKind::Function, name_id, file_id).with_qualified_name(qual_name_id);
1114
1115 let node_id = graph.nodes_mut().alloc(entry).unwrap();
1116 graph.indices_mut().add(
1117 node_id,
1118 NodeKind::Function,
1119 name_id,
1120 Some(qual_name_id),
1121 file_id,
1122 );
1123
1124 let snapshot = graph.snapshot();
1125
1126 let found = resolve_symbol_strict(&snapshot, "module::test_func");
1128 assert_eq!(found, Some(node_id));
1129
1130 let found2 = resolve_symbol_strict(&snapshot, "test_func");
1132 assert_eq!(found2, Some(node_id));
1133
1134 assert!(resolve_symbol_strict(&snapshot, "nonexistent").is_none());
1136 }
1137
1138 #[test]
1139 fn test_snapshot_find_by_pattern() {
1140 use crate::graph::unified::node::NodeKind;
1141 use crate::graph::unified::storage::arena::NodeEntry;
1142 use std::path::Path;
1143
1144 let mut graph = CodeGraph::new();
1145
1146 let name1 = graph.strings_mut().intern("foo_bar").unwrap();
1148 let name2 = graph.strings_mut().intern("baz_bar").unwrap();
1149 let name3 = graph.strings_mut().intern("qux_test").unwrap();
1150 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1151
1152 let node1 = graph
1153 .nodes_mut()
1154 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1155 .unwrap();
1156 let node2 = graph
1157 .nodes_mut()
1158 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1159 .unwrap();
1160 let node3 = graph
1161 .nodes_mut()
1162 .alloc(NodeEntry::new(NodeKind::Function, name3, file_id))
1163 .unwrap();
1164
1165 graph
1166 .indices_mut()
1167 .add(node1, NodeKind::Function, name1, None, file_id);
1168 graph
1169 .indices_mut()
1170 .add(node2, NodeKind::Function, name2, None, file_id);
1171 graph
1172 .indices_mut()
1173 .add(node3, NodeKind::Function, name3, None, file_id);
1174
1175 let snapshot = graph.snapshot();
1176
1177 let matches = snapshot.find_by_pattern("bar");
1179 assert_eq!(matches.len(), 2);
1180 assert!(matches.contains(&node1));
1181 assert!(matches.contains(&node2));
1182
1183 let matches = snapshot.find_by_pattern("qux");
1185 assert_eq!(matches.len(), 1);
1186 assert_eq!(matches[0], node3);
1187
1188 let matches = snapshot.find_by_pattern("nonexistent");
1190 assert!(matches.is_empty());
1191 }
1192
1193 #[test]
1194 fn test_snapshot_get_callees() {
1195 use crate::graph::unified::edge::EdgeKind;
1196 use crate::graph::unified::node::NodeKind;
1197 use crate::graph::unified::storage::arena::NodeEntry;
1198 use std::path::Path;
1199
1200 let mut graph = CodeGraph::new();
1201
1202 let caller_name = graph.strings_mut().intern("caller").unwrap();
1204 let callee1_name = graph.strings_mut().intern("callee1").unwrap();
1205 let callee2_name = graph.strings_mut().intern("callee2").unwrap();
1206 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1207
1208 let caller_id = graph
1209 .nodes_mut()
1210 .alloc(NodeEntry::new(NodeKind::Function, caller_name, file_id))
1211 .unwrap();
1212 let callee1_id = graph
1213 .nodes_mut()
1214 .alloc(NodeEntry::new(NodeKind::Function, callee1_name, file_id))
1215 .unwrap();
1216 let callee2_id = graph
1217 .nodes_mut()
1218 .alloc(NodeEntry::new(NodeKind::Function, callee2_name, file_id))
1219 .unwrap();
1220
1221 graph.edges_mut().add_edge(
1223 caller_id,
1224 callee1_id,
1225 EdgeKind::Calls {
1226 argument_count: 0,
1227 is_async: false,
1228 },
1229 file_id,
1230 );
1231 graph.edges_mut().add_edge(
1232 caller_id,
1233 callee2_id,
1234 EdgeKind::Calls {
1235 argument_count: 0,
1236 is_async: false,
1237 },
1238 file_id,
1239 );
1240
1241 let snapshot = graph.snapshot();
1242
1243 let callees = snapshot.get_callees(caller_id);
1245 assert_eq!(callees.len(), 2);
1246 assert!(callees.contains(&callee1_id));
1247 assert!(callees.contains(&callee2_id));
1248
1249 let callees = snapshot.get_callees(callee1_id);
1251 assert!(callees.is_empty());
1252 }
1253
1254 #[test]
1255 fn test_snapshot_get_callers() {
1256 use crate::graph::unified::edge::EdgeKind;
1257 use crate::graph::unified::node::NodeKind;
1258 use crate::graph::unified::storage::arena::NodeEntry;
1259 use std::path::Path;
1260
1261 let mut graph = CodeGraph::new();
1262
1263 let caller1_name = graph.strings_mut().intern("caller1").unwrap();
1265 let caller2_name = graph.strings_mut().intern("caller2").unwrap();
1266 let callee_name = graph.strings_mut().intern("callee").unwrap();
1267 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1268
1269 let caller1_id = graph
1270 .nodes_mut()
1271 .alloc(NodeEntry::new(NodeKind::Function, caller1_name, file_id))
1272 .unwrap();
1273 let caller2_id = graph
1274 .nodes_mut()
1275 .alloc(NodeEntry::new(NodeKind::Function, caller2_name, file_id))
1276 .unwrap();
1277 let callee_id = graph
1278 .nodes_mut()
1279 .alloc(NodeEntry::new(NodeKind::Function, callee_name, file_id))
1280 .unwrap();
1281
1282 graph.edges_mut().add_edge(
1284 caller1_id,
1285 callee_id,
1286 EdgeKind::Calls {
1287 argument_count: 0,
1288 is_async: false,
1289 },
1290 file_id,
1291 );
1292 graph.edges_mut().add_edge(
1293 caller2_id,
1294 callee_id,
1295 EdgeKind::Calls {
1296 argument_count: 0,
1297 is_async: false,
1298 },
1299 file_id,
1300 );
1301
1302 let snapshot = graph.snapshot();
1303
1304 let callers = snapshot.get_callers(callee_id);
1306 assert_eq!(callers.len(), 2);
1307 assert!(callers.contains(&caller1_id));
1308 assert!(callers.contains(&caller2_id));
1309
1310 let callers = snapshot.get_callers(caller1_id);
1312 assert!(callers.is_empty());
1313 }
1314
1315 #[test]
1316 fn test_snapshot_find_symbol_candidates() {
1317 use crate::graph::unified::node::NodeKind;
1318 use crate::graph::unified::storage::arena::NodeEntry;
1319 use std::path::Path;
1320
1321 let mut graph = CodeGraph::new();
1322
1323 let symbol_name = graph.strings_mut().intern("test").unwrap();
1325 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1326
1327 let node1 = graph
1328 .nodes_mut()
1329 .alloc(NodeEntry::new(NodeKind::Function, symbol_name, file_id))
1330 .unwrap();
1331 let node2 = graph
1332 .nodes_mut()
1333 .alloc(NodeEntry::new(NodeKind::Method, symbol_name, file_id))
1334 .unwrap();
1335
1336 let other_name = graph.strings_mut().intern("other").unwrap();
1338 let node3 = graph
1339 .nodes_mut()
1340 .alloc(NodeEntry::new(NodeKind::Function, other_name, file_id))
1341 .unwrap();
1342
1343 graph
1344 .indices_mut()
1345 .add(node1, NodeKind::Function, symbol_name, None, file_id);
1346 graph
1347 .indices_mut()
1348 .add(node2, NodeKind::Method, symbol_name, None, file_id);
1349 graph
1350 .indices_mut()
1351 .add(node3, NodeKind::Function, other_name, None, file_id);
1352
1353 let snapshot = graph.snapshot();
1354
1355 let matches = candidate_nodes(&snapshot, "test");
1357 assert_eq!(matches.len(), 2);
1358 assert!(matches.contains(&node1));
1359 assert!(matches.contains(&node2));
1360
1361 let matches = candidate_nodes(&snapshot, "other");
1363 assert_eq!(matches.len(), 1);
1364 assert_eq!(matches[0], node3);
1365
1366 let matches = candidate_nodes(&snapshot, "nonexistent");
1368 assert!(matches.is_empty());
1369 }
1370
1371 #[test]
1372 fn test_snapshot_iter_nodes() {
1373 use crate::graph::unified::node::NodeKind;
1374 use crate::graph::unified::storage::arena::NodeEntry;
1375 use std::path::Path;
1376
1377 let mut graph = CodeGraph::new();
1378
1379 let name1 = graph.strings_mut().intern("func1").unwrap();
1381 let name2 = graph.strings_mut().intern("func2").unwrap();
1382 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1383
1384 let node1 = graph
1385 .nodes_mut()
1386 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1387 .unwrap();
1388 let node2 = graph
1389 .nodes_mut()
1390 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1391 .unwrap();
1392
1393 let snapshot = graph.snapshot();
1394
1395 let snapshot_nodes: Vec<_> = snapshot.iter_nodes().collect();
1397 assert_eq!(snapshot_nodes.len(), 2);
1398
1399 let node_ids: Vec<_> = snapshot_nodes.iter().map(|(id, _)| *id).collect();
1400 assert!(node_ids.contains(&node1));
1401 assert!(node_ids.contains(&node2));
1402 }
1403
1404 #[test]
1405 fn test_snapshot_iter_edges() {
1406 use crate::graph::unified::edge::EdgeKind;
1407 use crate::graph::unified::node::NodeKind;
1408 use crate::graph::unified::storage::arena::NodeEntry;
1409 use std::path::Path;
1410
1411 let mut graph = CodeGraph::new();
1412
1413 let name1 = graph.strings_mut().intern("func1").unwrap();
1415 let name2 = graph.strings_mut().intern("func2").unwrap();
1416 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1417
1418 let node1 = graph
1419 .nodes_mut()
1420 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1421 .unwrap();
1422 let node2 = graph
1423 .nodes_mut()
1424 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1425 .unwrap();
1426
1427 graph.edges_mut().add_edge(
1429 node1,
1430 node2,
1431 EdgeKind::Calls {
1432 argument_count: 0,
1433 is_async: false,
1434 },
1435 file_id,
1436 );
1437
1438 let snapshot = graph.snapshot();
1439
1440 let edges: Vec<_> = snapshot.iter_edges().collect();
1442 assert_eq!(edges.len(), 1);
1443
1444 let (src, tgt, kind) = &edges[0];
1445 assert_eq!(*src, node1);
1446 assert_eq!(*tgt, node2);
1447 assert!(matches!(
1448 kind,
1449 EdgeKind::Calls {
1450 argument_count: 0,
1451 is_async: false
1452 }
1453 ));
1454 }
1455
1456 #[test]
1457 fn test_snapshot_get_node() {
1458 use crate::graph::unified::node::NodeId;
1459 use crate::graph::unified::node::NodeKind;
1460 use crate::graph::unified::storage::arena::NodeEntry;
1461 use std::path::Path;
1462
1463 let mut graph = CodeGraph::new();
1464
1465 let name = graph.strings_mut().intern("test_func").unwrap();
1467 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1468
1469 let node_id = graph
1470 .nodes_mut()
1471 .alloc(NodeEntry::new(NodeKind::Function, name, file_id))
1472 .unwrap();
1473
1474 let snapshot = graph.snapshot();
1475
1476 let entry = snapshot.get_node(node_id);
1478 assert!(entry.is_some());
1479 assert_eq!(entry.unwrap().kind, NodeKind::Function);
1480
1481 let invalid_id = NodeId::INVALID;
1483 assert!(snapshot.get_node(invalid_id).is_none());
1484 }
1485
1486 #[test]
1487 fn test_snapshot_query_empty_graph() {
1488 use crate::graph::unified::node::NodeId;
1489
1490 let graph = CodeGraph::new();
1491 let snapshot = graph.snapshot();
1492
1493 assert!(resolve_symbol_strict(&snapshot, "test").is_none());
1495 assert!(snapshot.find_by_pattern("test").is_empty());
1496 assert!(candidate_nodes(&snapshot, "test").is_empty());
1497
1498 let dummy_id = NodeId::new(0, 1);
1499 assert!(snapshot.get_callees(dummy_id).is_empty());
1500 assert!(snapshot.get_callers(dummy_id).is_empty());
1501
1502 assert_eq!(snapshot.iter_nodes().count(), 0);
1503 assert_eq!(snapshot.iter_edges().count(), 0);
1504 }
1505
1506 #[test]
1507 fn test_snapshot_edge_filtering_by_kind() {
1508 use crate::graph::unified::edge::EdgeKind;
1509 use crate::graph::unified::node::NodeKind;
1510 use crate::graph::unified::storage::arena::NodeEntry;
1511 use std::path::Path;
1512
1513 let mut graph = CodeGraph::new();
1514
1515 let name1 = graph.strings_mut().intern("func1").unwrap();
1517 let name2 = graph.strings_mut().intern("func2").unwrap();
1518 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1519
1520 let node1 = graph
1521 .nodes_mut()
1522 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1523 .unwrap();
1524 let node2 = graph
1525 .nodes_mut()
1526 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1527 .unwrap();
1528
1529 graph.edges_mut().add_edge(
1531 node1,
1532 node2,
1533 EdgeKind::Calls {
1534 argument_count: 0,
1535 is_async: false,
1536 },
1537 file_id,
1538 );
1539 graph
1540 .edges_mut()
1541 .add_edge(node1, node2, EdgeKind::References, file_id);
1542
1543 let snapshot = graph.snapshot();
1544
1545 let callees = snapshot.get_callees(node1);
1547 assert_eq!(callees.len(), 1);
1548 assert_eq!(callees[0], node2);
1549
1550 let edges: Vec<_> = snapshot.iter_edges().collect();
1552 assert_eq!(edges.len(), 2);
1553 }
1554}