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::memory::{GraphMemorySize, HASHMAP_ENTRY_OVERHEAD};
19use crate::graph::unified::storage::arena::NodeArena;
20use crate::graph::unified::storage::edge_provenance::{EdgeProvenance, EdgeProvenanceStore};
21use crate::graph::unified::storage::indices::AuxiliaryIndices;
22use crate::graph::unified::storage::interner::StringInterner;
23use crate::graph::unified::storage::metadata::NodeMetadataStore;
24use crate::graph::unified::storage::node_provenance::{NodeProvenance, NodeProvenanceStore};
25use crate::graph::unified::storage::registry::{FileProvenanceView, FileRegistry};
26use crate::graph::unified::string::id::StringId;
27
28#[derive(Clone)]
48pub struct CodeGraph {
49 nodes: Arc<NodeArena>,
51 edges: Arc<BidirectionalEdgeStore>,
53 strings: Arc<StringInterner>,
55 files: Arc<FileRegistry>,
57 indices: Arc<AuxiliaryIndices>,
59 macro_metadata: Arc<NodeMetadataStore>,
61 node_provenance: Arc<NodeProvenanceStore>,
63 edge_provenance: Arc<EdgeProvenanceStore>,
65 fact_epoch: u64,
67 epoch: u64,
69 confidence: HashMap<String, ConfidenceMetadata>,
72}
73
74impl CodeGraph {
75 #[must_use]
86 pub fn new() -> Self {
87 Self {
88 nodes: Arc::new(NodeArena::new()),
89 edges: Arc::new(BidirectionalEdgeStore::new()),
90 strings: Arc::new(StringInterner::new()),
91 files: Arc::new(FileRegistry::new()),
92 indices: Arc::new(AuxiliaryIndices::new()),
93 macro_metadata: Arc::new(NodeMetadataStore::new()),
94 node_provenance: Arc::new(NodeProvenanceStore::new()),
95 edge_provenance: Arc::new(EdgeProvenanceStore::new()),
96 fact_epoch: 0,
97 epoch: 0,
98 confidence: HashMap::new(),
99 }
100 }
101
102 #[must_use]
107 pub fn from_components(
108 nodes: NodeArena,
109 edges: BidirectionalEdgeStore,
110 strings: StringInterner,
111 files: FileRegistry,
112 indices: AuxiliaryIndices,
113 macro_metadata: NodeMetadataStore,
114 ) -> Self {
115 Self {
116 nodes: Arc::new(nodes),
117 edges: Arc::new(edges),
118 strings: Arc::new(strings),
119 files: Arc::new(files),
120 indices: Arc::new(indices),
121 macro_metadata: Arc::new(macro_metadata),
122 node_provenance: Arc::new(NodeProvenanceStore::new()),
123 edge_provenance: Arc::new(EdgeProvenanceStore::new()),
124 fact_epoch: 0,
125 epoch: 0,
126 confidence: HashMap::new(),
127 }
128 }
129
130 #[must_use]
145 pub fn snapshot(&self) -> GraphSnapshot {
146 GraphSnapshot {
147 nodes: Arc::clone(&self.nodes),
148 edges: Arc::clone(&self.edges),
149 strings: Arc::clone(&self.strings),
150 files: Arc::clone(&self.files),
151 indices: Arc::clone(&self.indices),
152 macro_metadata: Arc::clone(&self.macro_metadata),
153 node_provenance: Arc::clone(&self.node_provenance),
154 edge_provenance: Arc::clone(&self.edge_provenance),
155 fact_epoch: self.fact_epoch,
156 epoch: self.epoch,
157 }
158 }
159
160 #[inline]
162 #[must_use]
163 pub fn nodes(&self) -> &NodeArena {
164 &self.nodes
165 }
166
167 #[inline]
169 #[must_use]
170 pub fn edges(&self) -> &BidirectionalEdgeStore {
171 &self.edges
172 }
173
174 #[inline]
176 #[must_use]
177 pub fn strings(&self) -> &StringInterner {
178 &self.strings
179 }
180
181 #[inline]
183 #[must_use]
184 pub fn files(&self) -> &FileRegistry {
185 &self.files
186 }
187
188 #[inline]
190 #[must_use]
191 pub fn indices(&self) -> &AuxiliaryIndices {
192 &self.indices
193 }
194
195 #[inline]
197 #[must_use]
198 pub fn macro_metadata(&self) -> &NodeMetadataStore {
199 &self.macro_metadata
200 }
201
202 #[inline]
210 #[must_use]
211 pub fn fact_epoch(&self) -> u64 {
212 self.fact_epoch
213 }
214
215 #[inline]
220 #[must_use]
221 pub fn node_provenance(
222 &self,
223 id: crate::graph::unified::node::id::NodeId,
224 ) -> Option<&NodeProvenance> {
225 self.node_provenance.lookup(id)
226 }
227
228 #[inline]
233 #[must_use]
234 pub fn edge_provenance(
235 &self,
236 id: crate::graph::unified::edge::id::EdgeId,
237 ) -> Option<&EdgeProvenance> {
238 self.edge_provenance.lookup(id)
239 }
240
241 #[inline]
245 #[must_use]
246 pub fn file_provenance(
247 &self,
248 id: crate::graph::unified::file::id::FileId,
249 ) -> Option<FileProvenanceView<'_>> {
250 self.files.file_provenance(id)
251 }
252
253 pub(crate) fn set_provenance(
256 &mut self,
257 node_provenance: NodeProvenanceStore,
258 edge_provenance: EdgeProvenanceStore,
259 fact_epoch: u64,
260 ) {
261 self.node_provenance = Arc::new(node_provenance);
262 self.edge_provenance = Arc::new(edge_provenance);
263 self.fact_epoch = fact_epoch;
264 }
265
266 #[inline]
268 #[must_use]
269 pub fn epoch(&self) -> u64 {
270 self.epoch
271 }
272
273 #[inline]
278 pub fn nodes_mut(&mut self) -> &mut NodeArena {
279 Arc::make_mut(&mut self.nodes)
280 }
281
282 #[inline]
286 pub fn edges_mut(&mut self) -> &mut BidirectionalEdgeStore {
287 Arc::make_mut(&mut self.edges)
288 }
289
290 #[inline]
294 pub fn strings_mut(&mut self) -> &mut StringInterner {
295 Arc::make_mut(&mut self.strings)
296 }
297
298 #[inline]
302 pub fn files_mut(&mut self) -> &mut FileRegistry {
303 Arc::make_mut(&mut self.files)
304 }
305
306 #[inline]
310 pub fn indices_mut(&mut self) -> &mut AuxiliaryIndices {
311 Arc::make_mut(&mut self.indices)
312 }
313
314 #[inline]
318 pub fn macro_metadata_mut(&mut self) -> &mut NodeMetadataStore {
319 Arc::make_mut(&mut self.macro_metadata)
320 }
321
322 #[inline]
327 pub fn nodes_and_strings_mut(&mut self) -> (&mut NodeArena, &mut StringInterner) {
328 (
329 Arc::make_mut(&mut self.nodes),
330 Arc::make_mut(&mut self.strings),
331 )
332 }
333
334 pub fn rebuild_indices(&mut self) {
343 let nodes = &self.nodes;
344 Arc::make_mut(&mut self.indices).build_from_arena(nodes);
345 }
346
347 #[inline]
351 pub fn bump_epoch(&mut self) -> u64 {
352 self.epoch = self.epoch.wrapping_add(1);
353 self.epoch
354 }
355
356 #[inline]
360 pub fn set_epoch(&mut self, epoch: u64) {
361 self.epoch = epoch;
362 }
363
364 #[inline]
377 #[must_use]
378 pub fn node_count(&self) -> usize {
379 self.nodes.len()
380 }
381
382 #[inline]
395 #[must_use]
396 pub fn edge_count(&self) -> usize {
397 let stats = self.edges.stats();
398 stats.forward.csr_edge_count + stats.forward.delta_edge_count
399 }
400
401 #[inline]
414 #[must_use]
415 pub fn is_empty(&self) -> bool {
416 self.nodes.is_empty()
417 }
418
419 #[inline]
435 pub fn indexed_files(
436 &self,
437 ) -> impl Iterator<Item = (crate::graph::unified::file::FileId, &std::path::Path)> {
438 self.files
439 .iter()
440 .map(|(id, arc_path)| (id, arc_path.as_ref()))
441 }
442
443 #[inline]
448 #[must_use]
449 pub fn confidence(&self) -> &HashMap<String, ConfidenceMetadata> {
450 &self.confidence
451 }
452
453 pub fn merge_confidence(&mut self, language: &str, metadata: ConfidenceMetadata) {
459 use crate::confidence::ConfidenceLevel;
460
461 self.confidence
462 .entry(language.to_string())
463 .and_modify(|existing| {
464 let new_level = match (&existing.level, &metadata.level) {
466 (ConfidenceLevel::Verified, other) | (other, ConfidenceLevel::Verified) => {
467 *other
468 }
469 (ConfidenceLevel::Partial, ConfidenceLevel::AstOnly)
470 | (ConfidenceLevel::AstOnly, ConfidenceLevel::Partial) => {
471 ConfidenceLevel::AstOnly
472 }
473 (level, _) => *level,
474 };
475 existing.level = new_level;
476
477 for limitation in &metadata.limitations {
479 if !existing.limitations.contains(limitation) {
480 existing.limitations.push(limitation.clone());
481 }
482 }
483
484 for feature in &metadata.unavailable_features {
486 if !existing.unavailable_features.contains(feature) {
487 existing.unavailable_features.push(feature.clone());
488 }
489 }
490 })
491 .or_insert(metadata);
492 }
493
494 pub fn set_confidence(&mut self, confidence: HashMap<String, ConfidenceMetadata>) {
498 self.confidence = confidence;
499 }
500}
501
502impl Default for CodeGraph {
503 fn default() -> Self {
504 Self::new()
505 }
506}
507
508impl fmt::Debug for CodeGraph {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 f.debug_struct("CodeGraph")
511 .field("nodes", &self.nodes.len())
512 .field("epoch", &self.epoch)
513 .finish_non_exhaustive()
514 }
515}
516
517impl GraphMemorySize for CodeGraph {
518 fn heap_bytes(&self) -> usize {
527 let mut confidence_bytes = self.confidence.capacity()
528 * (std::mem::size_of::<String>()
529 + std::mem::size_of::<ConfidenceMetadata>()
530 + HASHMAP_ENTRY_OVERHEAD);
531 for (key, meta) in &self.confidence {
532 confidence_bytes += key.capacity();
533 confidence_bytes += meta.limitations.capacity() * std::mem::size_of::<String>();
536 for s in &meta.limitations {
537 confidence_bytes += s.capacity();
538 }
539 confidence_bytes +=
540 meta.unavailable_features.capacity() * std::mem::size_of::<String>();
541 for s in &meta.unavailable_features {
542 confidence_bytes += s.capacity();
543 }
544 }
545
546 self.nodes.heap_bytes()
547 + self.edges.heap_bytes()
548 + self.strings.heap_bytes()
549 + self.files.heap_bytes()
550 + self.indices.heap_bytes()
551 + self.macro_metadata.heap_bytes()
552 + self.node_provenance.heap_bytes()
553 + self.edge_provenance.heap_bytes()
554 + confidence_bytes
555 }
556}
557
558pub struct ConcurrentCodeGraph {
595 inner: RwLock<CodeGraph>,
597 epoch: AtomicU64,
599}
600
601impl ConcurrentCodeGraph {
602 #[must_use]
604 pub fn new() -> Self {
605 Self {
606 inner: RwLock::new(CodeGraph::new()),
607 epoch: AtomicU64::new(0),
608 }
609 }
610
611 #[must_use]
613 pub fn from_graph(graph: CodeGraph) -> Self {
614 let epoch = graph.epoch();
615 Self {
616 inner: RwLock::new(graph),
617 epoch: AtomicU64::new(epoch),
618 }
619 }
620
621 #[inline]
626 pub fn read(&self) -> RwLockReadGuard<'_, CodeGraph> {
627 self.inner.read()
628 }
629
630 #[inline]
635 pub fn write(&self) -> RwLockWriteGuard<'_, CodeGraph> {
636 self.epoch.fetch_add(1, Ordering::SeqCst);
638 let mut guard = self.inner.write();
639 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
641 guard
642 }
643
644 #[inline]
649 #[must_use]
650 pub fn epoch(&self) -> u64 {
651 self.epoch.load(Ordering::SeqCst)
652 }
653
654 #[must_use]
659 pub fn snapshot(&self) -> GraphSnapshot {
660 self.inner.read().snapshot()
661 }
662
663 #[must_use]
670 pub fn fact_epoch(&self) -> u64 {
671 self.inner.read().fact_epoch()
672 }
673
674 #[must_use]
676 pub fn node_provenance(
677 &self,
678 id: crate::graph::unified::node::id::NodeId,
679 ) -> Option<NodeProvenance> {
680 self.inner.read().node_provenance(id).copied()
681 }
682
683 #[must_use]
685 pub fn edge_provenance(
686 &self,
687 id: crate::graph::unified::edge::id::EdgeId,
688 ) -> Option<EdgeProvenance> {
689 self.inner.read().edge_provenance(id).copied()
690 }
691
692 #[must_use]
696 pub fn file_provenance(
697 &self,
698 id: crate::graph::unified::file::id::FileId,
699 ) -> Option<OwnedFileProvenanceView> {
700 let guard = self.inner.read();
701 guard.file_provenance(id).map(|v| OwnedFileProvenanceView {
702 content_hash: *v.content_hash,
703 indexed_at: v.indexed_at,
704 source_uri: v.source_uri,
705 is_external: v.is_external,
706 })
707 }
708
709 #[inline]
713 #[must_use]
714 pub fn try_read(&self) -> Option<RwLockReadGuard<'_, CodeGraph>> {
715 self.inner.try_read()
716 }
717
718 #[inline]
723 pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, CodeGraph>> {
724 self.inner.try_write().map(|mut guard| {
725 self.epoch.fetch_add(1, Ordering::SeqCst);
726 guard.set_epoch(self.epoch.load(Ordering::SeqCst));
727 guard
728 })
729 }
730}
731
732impl Default for ConcurrentCodeGraph {
733 fn default() -> Self {
734 Self::new()
735 }
736}
737
738impl fmt::Debug for ConcurrentCodeGraph {
739 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740 f.debug_struct("ConcurrentCodeGraph")
741 .field("epoch", &self.epoch.load(Ordering::SeqCst))
742 .finish_non_exhaustive()
743 }
744}
745
746#[derive(Debug, Clone, Copy, PartialEq, Eq)]
749pub struct OwnedFileProvenanceView {
750 pub content_hash: [u8; 32],
752 pub indexed_at: u64,
754 pub source_uri: Option<StringId>,
756 pub is_external: bool,
758}
759
760#[derive(Clone)]
789pub struct GraphSnapshot {
790 nodes: Arc<NodeArena>,
792 edges: Arc<BidirectionalEdgeStore>,
794 strings: Arc<StringInterner>,
796 files: Arc<FileRegistry>,
798 indices: Arc<AuxiliaryIndices>,
800 macro_metadata: Arc<NodeMetadataStore>,
802 node_provenance: Arc<NodeProvenanceStore>,
804 edge_provenance: Arc<EdgeProvenanceStore>,
806 fact_epoch: u64,
808 epoch: u64,
810}
811
812impl GraphSnapshot {
813 #[inline]
815 #[must_use]
816 pub fn nodes(&self) -> &NodeArena {
817 &self.nodes
818 }
819
820 #[inline]
822 #[must_use]
823 pub fn edges(&self) -> &BidirectionalEdgeStore {
824 &self.edges
825 }
826
827 #[inline]
829 #[must_use]
830 pub fn strings(&self) -> &StringInterner {
831 &self.strings
832 }
833
834 #[inline]
836 #[must_use]
837 pub fn files(&self) -> &FileRegistry {
838 &self.files
839 }
840
841 #[inline]
843 #[must_use]
844 pub fn indices(&self) -> &AuxiliaryIndices {
845 &self.indices
846 }
847
848 #[inline]
850 #[must_use]
851 pub fn macro_metadata(&self) -> &NodeMetadataStore {
852 &self.macro_metadata
853 }
854
855 #[inline]
861 #[must_use]
862 pub fn fact_epoch(&self) -> u64 {
863 self.fact_epoch
864 }
865
866 #[inline]
868 #[must_use]
869 pub fn node_provenance(
870 &self,
871 id: crate::graph::unified::node::id::NodeId,
872 ) -> Option<&NodeProvenance> {
873 self.node_provenance.lookup(id)
874 }
875
876 #[inline]
878 #[must_use]
879 pub fn edge_provenance(
880 &self,
881 id: crate::graph::unified::edge::id::EdgeId,
882 ) -> Option<&EdgeProvenance> {
883 self.edge_provenance.lookup(id)
884 }
885
886 #[inline]
888 #[must_use]
889 pub fn file_provenance(
890 &self,
891 id: crate::graph::unified::file::id::FileId,
892 ) -> Option<FileProvenanceView<'_>> {
893 self.files.file_provenance(id)
894 }
895
896 #[inline]
901 #[must_use]
902 pub fn epoch(&self) -> u64 {
903 self.epoch
904 }
905
906 #[inline]
910 #[must_use]
911 pub fn epoch_matches(&self, other_epoch: u64) -> bool {
912 self.epoch == other_epoch
913 }
914
915 #[must_use]
937 pub fn find_by_pattern(&self, pattern: &str) -> Vec<crate::graph::unified::node::NodeId> {
938 let mut matches = Vec::new();
939
940 for (str_id, s) in self.strings.iter() {
942 if s.contains(pattern) {
943 matches.extend_from_slice(self.indices.by_qualified_name(str_id));
946 matches.extend_from_slice(self.indices.by_name(str_id));
948 }
949 }
950
951 matches.sort_unstable();
953 matches.dedup();
954
955 matches
956 }
957
958 #[must_use]
970 pub fn get_callees(
971 &self,
972 node: crate::graph::unified::node::NodeId,
973 ) -> Vec<crate::graph::unified::node::NodeId> {
974 use crate::graph::unified::edge::EdgeKind;
975
976 self.edges
977 .edges_from(node)
978 .into_iter()
979 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
980 .map(|edge| edge.target)
981 .collect()
982 }
983
984 #[must_use]
996 pub fn get_callers(
997 &self,
998 node: crate::graph::unified::node::NodeId,
999 ) -> Vec<crate::graph::unified::node::NodeId> {
1000 use crate::graph::unified::edge::EdgeKind;
1001
1002 self.edges
1003 .edges_to(node)
1004 .into_iter()
1005 .filter(|edge| matches!(edge.kind, EdgeKind::Calls { .. }))
1006 .map(|edge| edge.source)
1007 .collect()
1008 }
1009
1010 pub fn iter_nodes(
1019 &self,
1020 ) -> impl Iterator<
1021 Item = (
1022 crate::graph::unified::node::NodeId,
1023 &crate::graph::unified::storage::arena::NodeEntry,
1024 ),
1025 > {
1026 self.nodes.iter()
1027 }
1028
1029 pub fn iter_edges(
1038 &self,
1039 ) -> impl Iterator<
1040 Item = (
1041 crate::graph::unified::node::NodeId,
1042 crate::graph::unified::node::NodeId,
1043 crate::graph::unified::edge::EdgeKind,
1044 ),
1045 > + '_ {
1046 self.nodes.iter().flat_map(move |(node_id, _entry)| {
1048 self.edges
1050 .edges_from(node_id)
1051 .into_iter()
1052 .map(move |edge| (node_id, edge.target, edge.kind))
1053 })
1054 }
1055
1056 #[must_use]
1069 pub fn get_node(
1070 &self,
1071 id: crate::graph::unified::node::NodeId,
1072 ) -> Option<&crate::graph::unified::storage::arena::NodeEntry> {
1073 self.nodes.get(id)
1074 }
1075}
1076
1077impl fmt::Debug for GraphSnapshot {
1078 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1079 f.debug_struct("GraphSnapshot")
1080 .field("nodes", &self.nodes.len())
1081 .field("epoch", &self.epoch)
1082 .finish_non_exhaustive()
1083 }
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088 use super::*;
1089 use crate::graph::unified::{
1090 FileScope, NodeId, ResolutionMode, SymbolCandidateOutcome, SymbolQuery,
1091 SymbolResolutionOutcome,
1092 };
1093
1094 fn resolve_symbol_strict(snapshot: &GraphSnapshot, symbol: &str) -> Option<NodeId> {
1095 match snapshot.resolve_symbol(&SymbolQuery {
1096 symbol,
1097 file_scope: FileScope::Any,
1098 mode: ResolutionMode::Strict,
1099 }) {
1100 SymbolResolutionOutcome::Resolved(node_id) => Some(node_id),
1101 SymbolResolutionOutcome::NotFound
1102 | SymbolResolutionOutcome::FileNotIndexed
1103 | SymbolResolutionOutcome::Ambiguous(_) => None,
1104 }
1105 }
1106
1107 fn candidate_nodes(snapshot: &GraphSnapshot, symbol: &str) -> Vec<NodeId> {
1108 match snapshot.find_symbol_candidates(&SymbolQuery {
1109 symbol,
1110 file_scope: FileScope::Any,
1111 mode: ResolutionMode::AllowSuffixCandidates,
1112 }) {
1113 SymbolCandidateOutcome::Candidates(candidates) => candidates,
1114 SymbolCandidateOutcome::NotFound | SymbolCandidateOutcome::FileNotIndexed => Vec::new(),
1115 }
1116 }
1117
1118 #[test]
1119 fn test_code_graph_new() {
1120 let graph = CodeGraph::new();
1121 assert_eq!(graph.epoch(), 0);
1122 assert_eq!(graph.nodes().len(), 0);
1123 }
1124
1125 #[test]
1126 fn test_code_graph_default() {
1127 let graph = CodeGraph::default();
1128 assert_eq!(graph.epoch(), 0);
1129 }
1130
1131 #[test]
1132 fn test_code_graph_snapshot() {
1133 let graph = CodeGraph::new();
1134 let snapshot = graph.snapshot();
1135 assert_eq!(snapshot.epoch(), 0);
1136 assert_eq!(snapshot.nodes().len(), 0);
1137 }
1138
1139 #[test]
1140 fn test_code_graph_bump_epoch() {
1141 let mut graph = CodeGraph::new();
1142 assert_eq!(graph.epoch(), 0);
1143 assert_eq!(graph.bump_epoch(), 1);
1144 assert_eq!(graph.epoch(), 1);
1145 assert_eq!(graph.bump_epoch(), 2);
1146 assert_eq!(graph.epoch(), 2);
1147 }
1148
1149 #[test]
1150 fn test_code_graph_set_epoch() {
1151 let mut graph = CodeGraph::new();
1152 graph.set_epoch(42);
1153 assert_eq!(graph.epoch(), 42);
1154 }
1155
1156 #[test]
1157 fn test_code_graph_from_components() {
1158 let nodes = NodeArena::new();
1159 let edges = BidirectionalEdgeStore::new();
1160 let strings = StringInterner::new();
1161 let files = FileRegistry::new();
1162 let indices = AuxiliaryIndices::new();
1163 let macro_metadata = NodeMetadataStore::new();
1164
1165 let graph =
1166 CodeGraph::from_components(nodes, edges, strings, files, indices, macro_metadata);
1167 assert_eq!(graph.epoch(), 0);
1168 }
1169
1170 #[test]
1171 fn test_code_graph_mut_accessors() {
1172 let mut graph = CodeGraph::new();
1173
1174 let _nodes = graph.nodes_mut();
1176 let _edges = graph.edges_mut();
1177 let _strings = graph.strings_mut();
1178 let _files = graph.files_mut();
1179 let _indices = graph.indices_mut();
1180 }
1181
1182 #[test]
1183 fn test_code_graph_snapshot_isolation() {
1184 let mut graph = CodeGraph::new();
1185 let snapshot1 = graph.snapshot();
1186
1187 graph.bump_epoch();
1189
1190 let snapshot2 = graph.snapshot();
1191
1192 assert_eq!(snapshot1.epoch(), 0);
1194 assert_eq!(snapshot2.epoch(), 1);
1195 }
1196
1197 #[test]
1198 fn test_code_graph_debug() {
1199 let graph = CodeGraph::new();
1200 let debug_str = format!("{graph:?}");
1201 assert!(debug_str.contains("CodeGraph"));
1202 assert!(debug_str.contains("epoch"));
1203 }
1204
1205 #[test]
1210 fn test_codegraph_heap_bytes_counts_confidence_inner_strings() {
1211 let mut graph = CodeGraph::new();
1212 graph.set_confidence({
1215 let mut m = HashMap::with_capacity(8);
1216 m.insert("seed".to_string(), ConfidenceMetadata::default());
1217 m
1218 });
1219 let before = graph.heap_bytes();
1220 let before_cap = graph.confidence.capacity();
1221
1222 let lim1 = String::from("no type inference");
1223 let lim2 = String::from("no generic specialization");
1224 let feat1 = String::from("rust-analyzer");
1225 let l1 = lim1.capacity();
1226 let l2 = lim2.capacity();
1227 let f1 = feat1.capacity();
1228
1229 let limitations = vec![lim1, lim2];
1230 let lim_vec_cap = limitations.capacity();
1231
1232 let unavailable_features = vec![feat1];
1233 let feat_vec_cap = unavailable_features.capacity();
1234
1235 let key = String::from("rust");
1236 let key_cap = key.capacity();
1237 graph.confidence.insert(
1238 key,
1239 ConfidenceMetadata {
1240 limitations,
1241 unavailable_features,
1242 ..Default::default()
1243 },
1244 );
1245 assert_eq!(
1246 graph.confidence.capacity(),
1247 before_cap,
1248 "prerequisite: confidence HashMap must not rehash during the test insert",
1249 );
1250
1251 let after = graph.heap_bytes();
1252 let expected_inner = key_cap
1253 + lim_vec_cap * std::mem::size_of::<String>()
1254 + l1
1255 + l2
1256 + feat_vec_cap * std::mem::size_of::<String>()
1257 + f1;
1258 assert_eq!(
1259 after - before,
1260 expected_inner,
1261 "CodeGraph::heap_bytes must count ConfidenceMetadata inner Vec<String> capacity exactly",
1262 );
1263 }
1264
1265 #[test]
1266 fn test_codegraph_heap_bytes_grows_with_content() {
1267 use crate::graph::unified::node::NodeKind;
1268 use crate::graph::unified::storage::arena::NodeEntry;
1269 use std::path::Path;
1270
1271 let empty = CodeGraph::new();
1275 let empty_bytes = empty.heap_bytes();
1276 assert!(
1277 empty_bytes < 100 * 1024 * 1024,
1278 "empty graph heap_bytes should be <100 MiB, got {empty_bytes}"
1279 );
1280
1281 let mut graph = CodeGraph::new();
1282 for i in 0..32u32 {
1283 let name = format!("sym_{i}");
1284 let qual = format!("module::sym_{i}");
1285 let file = format!("file_{i}.rs");
1286
1287 let name_id = graph.strings_mut().intern(&name).unwrap();
1288 let qual_id = graph.strings_mut().intern(&qual).unwrap();
1289 let file_id = graph.files_mut().register(Path::new(&file)).unwrap();
1290
1291 let entry =
1292 NodeEntry::new(NodeKind::Function, name_id, file_id).with_qualified_name(qual_id);
1293 let node_id = graph.nodes_mut().alloc(entry).unwrap();
1294 graph
1295 .indices_mut()
1296 .add(node_id, NodeKind::Function, name_id, Some(qual_id), file_id);
1297 }
1298
1299 let populated_bytes = graph.heap_bytes();
1300 assert!(
1301 populated_bytes > 0,
1302 "populated graph should report nonzero heap bytes"
1303 );
1304 assert!(
1305 populated_bytes > empty_bytes,
1306 "populated graph ({populated_bytes}) should exceed empty graph ({empty_bytes})"
1307 );
1308 assert!(
1309 populated_bytes < 100 * 1024 * 1024,
1310 "test graph heap_bytes should be <100 MiB, got {populated_bytes}"
1311 );
1312 }
1313
1314 #[test]
1315 fn test_concurrent_code_graph_new() {
1316 let graph = ConcurrentCodeGraph::new();
1317 assert_eq!(graph.epoch(), 0);
1318 }
1319
1320 #[test]
1321 fn test_concurrent_code_graph_default() {
1322 let graph = ConcurrentCodeGraph::default();
1323 assert_eq!(graph.epoch(), 0);
1324 }
1325
1326 #[test]
1327 fn test_concurrent_code_graph_from_graph() {
1328 let mut inner = CodeGraph::new();
1329 inner.set_epoch(10);
1330 let graph = ConcurrentCodeGraph::from_graph(inner);
1331 assert_eq!(graph.epoch(), 10);
1332 }
1333
1334 #[test]
1335 fn test_concurrent_code_graph_read() {
1336 let graph = ConcurrentCodeGraph::new();
1337 let guard = graph.read();
1338 assert_eq!(guard.epoch(), 0);
1339 assert_eq!(guard.nodes().len(), 0);
1340 }
1341
1342 #[test]
1343 fn test_concurrent_code_graph_write_increments_epoch() {
1344 let graph = ConcurrentCodeGraph::new();
1345 assert_eq!(graph.epoch(), 0);
1346
1347 {
1348 let guard = graph.write();
1349 assert_eq!(guard.epoch(), 1);
1350 }
1351
1352 assert_eq!(graph.epoch(), 1);
1353
1354 {
1355 let _guard = graph.write();
1356 }
1357
1358 assert_eq!(graph.epoch(), 2);
1359 }
1360
1361 #[test]
1362 fn test_concurrent_code_graph_snapshot() {
1363 let graph = ConcurrentCodeGraph::new();
1364
1365 {
1366 let _guard = graph.write();
1367 }
1368
1369 let snapshot = graph.snapshot();
1370 assert_eq!(snapshot.epoch(), 1);
1371 }
1372
1373 #[test]
1374 fn test_concurrent_code_graph_try_read() {
1375 let graph = ConcurrentCodeGraph::new();
1376 let guard = graph.try_read();
1377 assert!(guard.is_some());
1378 }
1379
1380 #[test]
1381 fn test_concurrent_code_graph_try_write() {
1382 let graph = ConcurrentCodeGraph::new();
1383 let guard = graph.try_write();
1384 assert!(guard.is_some());
1385 assert_eq!(graph.epoch(), 1);
1386 }
1387
1388 #[test]
1389 fn test_concurrent_code_graph_debug() {
1390 let graph = ConcurrentCodeGraph::new();
1391 let debug_str = format!("{graph:?}");
1392 assert!(debug_str.contains("ConcurrentCodeGraph"));
1393 assert!(debug_str.contains("epoch"));
1394 }
1395
1396 #[test]
1397 fn test_graph_snapshot_accessors() {
1398 let graph = CodeGraph::new();
1399 let snapshot = graph.snapshot();
1400
1401 let _nodes = snapshot.nodes();
1403 let _edges = snapshot.edges();
1404 let _strings = snapshot.strings();
1405 let _files = snapshot.files();
1406 let _indices = snapshot.indices();
1407 let _epoch = snapshot.epoch();
1408 }
1409
1410 #[test]
1411 fn test_graph_snapshot_epoch_matches() {
1412 let graph = CodeGraph::new();
1413 let snapshot = graph.snapshot();
1414
1415 assert!(snapshot.epoch_matches(0));
1416 assert!(!snapshot.epoch_matches(1));
1417 }
1418
1419 #[test]
1420 fn test_graph_snapshot_clone() {
1421 let graph = CodeGraph::new();
1422 let snapshot1 = graph.snapshot();
1423 let snapshot2 = snapshot1.clone();
1424
1425 assert_eq!(snapshot1.epoch(), snapshot2.epoch());
1426 }
1427
1428 #[test]
1429 fn test_graph_snapshot_debug() {
1430 let graph = CodeGraph::new();
1431 let snapshot = graph.snapshot();
1432 let debug_str = format!("{snapshot:?}");
1433 assert!(debug_str.contains("GraphSnapshot"));
1434 assert!(debug_str.contains("epoch"));
1435 }
1436
1437 #[test]
1438 fn test_multiple_readers() {
1439 let graph = ConcurrentCodeGraph::new();
1440
1441 let guard1 = graph.read();
1443 let guard2 = graph.read();
1444 let guard3 = graph.read();
1445
1446 assert_eq!(guard1.epoch(), 0);
1447 assert_eq!(guard2.epoch(), 0);
1448 assert_eq!(guard3.epoch(), 0);
1449 }
1450
1451 #[test]
1452 fn test_code_graph_clone() {
1453 let mut graph = CodeGraph::new();
1454 graph.bump_epoch();
1455
1456 let cloned = graph.clone();
1457 assert_eq!(cloned.epoch(), 1);
1458 }
1459
1460 #[test]
1461 fn test_epoch_wrapping() {
1462 let mut graph = CodeGraph::new();
1463 graph.set_epoch(u64::MAX);
1464 let new_epoch = graph.bump_epoch();
1465 assert_eq!(new_epoch, 0); }
1467
1468 #[test]
1473 fn test_snapshot_resolve_symbol() {
1474 use crate::graph::unified::node::NodeKind;
1475 use crate::graph::unified::storage::arena::NodeEntry;
1476 use std::path::Path;
1477
1478 let mut graph = CodeGraph::new();
1479
1480 let name_id = graph.strings_mut().intern("test_func").unwrap();
1482 let qual_name_id = graph.strings_mut().intern("module::test_func").unwrap();
1483 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1484
1485 let entry =
1486 NodeEntry::new(NodeKind::Function, name_id, file_id).with_qualified_name(qual_name_id);
1487
1488 let node_id = graph.nodes_mut().alloc(entry).unwrap();
1489 graph.indices_mut().add(
1490 node_id,
1491 NodeKind::Function,
1492 name_id,
1493 Some(qual_name_id),
1494 file_id,
1495 );
1496
1497 let snapshot = graph.snapshot();
1498
1499 let found = resolve_symbol_strict(&snapshot, "module::test_func");
1501 assert_eq!(found, Some(node_id));
1502
1503 let found2 = resolve_symbol_strict(&snapshot, "test_func");
1505 assert_eq!(found2, Some(node_id));
1506
1507 assert!(resolve_symbol_strict(&snapshot, "nonexistent").is_none());
1509 }
1510
1511 #[test]
1512 fn test_snapshot_find_by_pattern() {
1513 use crate::graph::unified::node::NodeKind;
1514 use crate::graph::unified::storage::arena::NodeEntry;
1515 use std::path::Path;
1516
1517 let mut graph = CodeGraph::new();
1518
1519 let name1 = graph.strings_mut().intern("foo_bar").unwrap();
1521 let name2 = graph.strings_mut().intern("baz_bar").unwrap();
1522 let name3 = graph.strings_mut().intern("qux_test").unwrap();
1523 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1524
1525 let node1 = graph
1526 .nodes_mut()
1527 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1528 .unwrap();
1529 let node2 = graph
1530 .nodes_mut()
1531 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1532 .unwrap();
1533 let node3 = graph
1534 .nodes_mut()
1535 .alloc(NodeEntry::new(NodeKind::Function, name3, file_id))
1536 .unwrap();
1537
1538 graph
1539 .indices_mut()
1540 .add(node1, NodeKind::Function, name1, None, file_id);
1541 graph
1542 .indices_mut()
1543 .add(node2, NodeKind::Function, name2, None, file_id);
1544 graph
1545 .indices_mut()
1546 .add(node3, NodeKind::Function, name3, None, file_id);
1547
1548 let snapshot = graph.snapshot();
1549
1550 let matches = snapshot.find_by_pattern("bar");
1552 assert_eq!(matches.len(), 2);
1553 assert!(matches.contains(&node1));
1554 assert!(matches.contains(&node2));
1555
1556 let matches = snapshot.find_by_pattern("qux");
1558 assert_eq!(matches.len(), 1);
1559 assert_eq!(matches[0], node3);
1560
1561 let matches = snapshot.find_by_pattern("nonexistent");
1563 assert!(matches.is_empty());
1564 }
1565
1566 #[test]
1567 fn test_snapshot_get_callees() {
1568 use crate::graph::unified::edge::EdgeKind;
1569 use crate::graph::unified::node::NodeKind;
1570 use crate::graph::unified::storage::arena::NodeEntry;
1571 use std::path::Path;
1572
1573 let mut graph = CodeGraph::new();
1574
1575 let caller_name = graph.strings_mut().intern("caller").unwrap();
1577 let callee1_name = graph.strings_mut().intern("callee1").unwrap();
1578 let callee2_name = graph.strings_mut().intern("callee2").unwrap();
1579 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1580
1581 let caller_id = graph
1582 .nodes_mut()
1583 .alloc(NodeEntry::new(NodeKind::Function, caller_name, file_id))
1584 .unwrap();
1585 let callee1_id = graph
1586 .nodes_mut()
1587 .alloc(NodeEntry::new(NodeKind::Function, callee1_name, file_id))
1588 .unwrap();
1589 let callee2_id = graph
1590 .nodes_mut()
1591 .alloc(NodeEntry::new(NodeKind::Function, callee2_name, file_id))
1592 .unwrap();
1593
1594 graph.edges_mut().add_edge(
1596 caller_id,
1597 callee1_id,
1598 EdgeKind::Calls {
1599 argument_count: 0,
1600 is_async: false,
1601 },
1602 file_id,
1603 );
1604 graph.edges_mut().add_edge(
1605 caller_id,
1606 callee2_id,
1607 EdgeKind::Calls {
1608 argument_count: 0,
1609 is_async: false,
1610 },
1611 file_id,
1612 );
1613
1614 let snapshot = graph.snapshot();
1615
1616 let callees = snapshot.get_callees(caller_id);
1618 assert_eq!(callees.len(), 2);
1619 assert!(callees.contains(&callee1_id));
1620 assert!(callees.contains(&callee2_id));
1621
1622 let callees = snapshot.get_callees(callee1_id);
1624 assert!(callees.is_empty());
1625 }
1626
1627 #[test]
1628 fn test_snapshot_get_callers() {
1629 use crate::graph::unified::edge::EdgeKind;
1630 use crate::graph::unified::node::NodeKind;
1631 use crate::graph::unified::storage::arena::NodeEntry;
1632 use std::path::Path;
1633
1634 let mut graph = CodeGraph::new();
1635
1636 let caller1_name = graph.strings_mut().intern("caller1").unwrap();
1638 let caller2_name = graph.strings_mut().intern("caller2").unwrap();
1639 let callee_name = graph.strings_mut().intern("callee").unwrap();
1640 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1641
1642 let caller1_id = graph
1643 .nodes_mut()
1644 .alloc(NodeEntry::new(NodeKind::Function, caller1_name, file_id))
1645 .unwrap();
1646 let caller2_id = graph
1647 .nodes_mut()
1648 .alloc(NodeEntry::new(NodeKind::Function, caller2_name, file_id))
1649 .unwrap();
1650 let callee_id = graph
1651 .nodes_mut()
1652 .alloc(NodeEntry::new(NodeKind::Function, callee_name, file_id))
1653 .unwrap();
1654
1655 graph.edges_mut().add_edge(
1657 caller1_id,
1658 callee_id,
1659 EdgeKind::Calls {
1660 argument_count: 0,
1661 is_async: false,
1662 },
1663 file_id,
1664 );
1665 graph.edges_mut().add_edge(
1666 caller2_id,
1667 callee_id,
1668 EdgeKind::Calls {
1669 argument_count: 0,
1670 is_async: false,
1671 },
1672 file_id,
1673 );
1674
1675 let snapshot = graph.snapshot();
1676
1677 let callers = snapshot.get_callers(callee_id);
1679 assert_eq!(callers.len(), 2);
1680 assert!(callers.contains(&caller1_id));
1681 assert!(callers.contains(&caller2_id));
1682
1683 let callers = snapshot.get_callers(caller1_id);
1685 assert!(callers.is_empty());
1686 }
1687
1688 #[test]
1689 fn test_snapshot_find_symbol_candidates() {
1690 use crate::graph::unified::node::NodeKind;
1691 use crate::graph::unified::storage::arena::NodeEntry;
1692 use std::path::Path;
1693
1694 let mut graph = CodeGraph::new();
1695
1696 let symbol_name = graph.strings_mut().intern("test").unwrap();
1698 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1699
1700 let node1 = graph
1701 .nodes_mut()
1702 .alloc(NodeEntry::new(NodeKind::Function, symbol_name, file_id))
1703 .unwrap();
1704 let node2 = graph
1705 .nodes_mut()
1706 .alloc(NodeEntry::new(NodeKind::Method, symbol_name, file_id))
1707 .unwrap();
1708
1709 let other_name = graph.strings_mut().intern("other").unwrap();
1711 let node3 = graph
1712 .nodes_mut()
1713 .alloc(NodeEntry::new(NodeKind::Function, other_name, file_id))
1714 .unwrap();
1715
1716 graph
1717 .indices_mut()
1718 .add(node1, NodeKind::Function, symbol_name, None, file_id);
1719 graph
1720 .indices_mut()
1721 .add(node2, NodeKind::Method, symbol_name, None, file_id);
1722 graph
1723 .indices_mut()
1724 .add(node3, NodeKind::Function, other_name, None, file_id);
1725
1726 let snapshot = graph.snapshot();
1727
1728 let matches = candidate_nodes(&snapshot, "test");
1730 assert_eq!(matches.len(), 2);
1731 assert!(matches.contains(&node1));
1732 assert!(matches.contains(&node2));
1733
1734 let matches = candidate_nodes(&snapshot, "other");
1736 assert_eq!(matches.len(), 1);
1737 assert_eq!(matches[0], node3);
1738
1739 let matches = candidate_nodes(&snapshot, "nonexistent");
1741 assert!(matches.is_empty());
1742 }
1743
1744 #[test]
1745 fn test_snapshot_iter_nodes() {
1746 use crate::graph::unified::node::NodeKind;
1747 use crate::graph::unified::storage::arena::NodeEntry;
1748 use std::path::Path;
1749
1750 let mut graph = CodeGraph::new();
1751
1752 let name1 = graph.strings_mut().intern("func1").unwrap();
1754 let name2 = graph.strings_mut().intern("func2").unwrap();
1755 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1756
1757 let node1 = graph
1758 .nodes_mut()
1759 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1760 .unwrap();
1761 let node2 = graph
1762 .nodes_mut()
1763 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1764 .unwrap();
1765
1766 let snapshot = graph.snapshot();
1767
1768 let snapshot_nodes: Vec<_> = snapshot.iter_nodes().collect();
1770 assert_eq!(snapshot_nodes.len(), 2);
1771
1772 let node_ids: Vec<_> = snapshot_nodes.iter().map(|(id, _)| *id).collect();
1773 assert!(node_ids.contains(&node1));
1774 assert!(node_ids.contains(&node2));
1775 }
1776
1777 #[test]
1778 fn test_snapshot_iter_edges() {
1779 use crate::graph::unified::edge::EdgeKind;
1780 use crate::graph::unified::node::NodeKind;
1781 use crate::graph::unified::storage::arena::NodeEntry;
1782 use std::path::Path;
1783
1784 let mut graph = CodeGraph::new();
1785
1786 let name1 = graph.strings_mut().intern("func1").unwrap();
1788 let name2 = graph.strings_mut().intern("func2").unwrap();
1789 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1790
1791 let node1 = graph
1792 .nodes_mut()
1793 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1794 .unwrap();
1795 let node2 = graph
1796 .nodes_mut()
1797 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1798 .unwrap();
1799
1800 graph.edges_mut().add_edge(
1802 node1,
1803 node2,
1804 EdgeKind::Calls {
1805 argument_count: 0,
1806 is_async: false,
1807 },
1808 file_id,
1809 );
1810
1811 let snapshot = graph.snapshot();
1812
1813 let edges: Vec<_> = snapshot.iter_edges().collect();
1815 assert_eq!(edges.len(), 1);
1816
1817 let (src, tgt, kind) = &edges[0];
1818 assert_eq!(*src, node1);
1819 assert_eq!(*tgt, node2);
1820 assert!(matches!(
1821 kind,
1822 EdgeKind::Calls {
1823 argument_count: 0,
1824 is_async: false
1825 }
1826 ));
1827 }
1828
1829 #[test]
1830 fn test_snapshot_get_node() {
1831 use crate::graph::unified::node::NodeId;
1832 use crate::graph::unified::node::NodeKind;
1833 use crate::graph::unified::storage::arena::NodeEntry;
1834 use std::path::Path;
1835
1836 let mut graph = CodeGraph::new();
1837
1838 let name = graph.strings_mut().intern("test_func").unwrap();
1840 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1841
1842 let node_id = graph
1843 .nodes_mut()
1844 .alloc(NodeEntry::new(NodeKind::Function, name, file_id))
1845 .unwrap();
1846
1847 let snapshot = graph.snapshot();
1848
1849 let entry = snapshot.get_node(node_id);
1851 assert!(entry.is_some());
1852 assert_eq!(entry.unwrap().kind, NodeKind::Function);
1853
1854 let invalid_id = NodeId::INVALID;
1856 assert!(snapshot.get_node(invalid_id).is_none());
1857 }
1858
1859 #[test]
1860 fn test_snapshot_query_empty_graph() {
1861 use crate::graph::unified::node::NodeId;
1862
1863 let graph = CodeGraph::new();
1864 let snapshot = graph.snapshot();
1865
1866 assert!(resolve_symbol_strict(&snapshot, "test").is_none());
1868 assert!(snapshot.find_by_pattern("test").is_empty());
1869 assert!(candidate_nodes(&snapshot, "test").is_empty());
1870
1871 let dummy_id = NodeId::new(0, 1);
1872 assert!(snapshot.get_callees(dummy_id).is_empty());
1873 assert!(snapshot.get_callers(dummy_id).is_empty());
1874
1875 assert_eq!(snapshot.iter_nodes().count(), 0);
1876 assert_eq!(snapshot.iter_edges().count(), 0);
1877 }
1878
1879 #[test]
1880 fn test_snapshot_edge_filtering_by_kind() {
1881 use crate::graph::unified::edge::EdgeKind;
1882 use crate::graph::unified::node::NodeKind;
1883 use crate::graph::unified::storage::arena::NodeEntry;
1884 use std::path::Path;
1885
1886 let mut graph = CodeGraph::new();
1887
1888 let name1 = graph.strings_mut().intern("func1").unwrap();
1890 let name2 = graph.strings_mut().intern("func2").unwrap();
1891 let file_id = graph.files_mut().register(Path::new("test.rs")).unwrap();
1892
1893 let node1 = graph
1894 .nodes_mut()
1895 .alloc(NodeEntry::new(NodeKind::Function, name1, file_id))
1896 .unwrap();
1897 let node2 = graph
1898 .nodes_mut()
1899 .alloc(NodeEntry::new(NodeKind::Function, name2, file_id))
1900 .unwrap();
1901
1902 graph.edges_mut().add_edge(
1904 node1,
1905 node2,
1906 EdgeKind::Calls {
1907 argument_count: 0,
1908 is_async: false,
1909 },
1910 file_id,
1911 );
1912 graph
1913 .edges_mut()
1914 .add_edge(node1, node2, EdgeKind::References, file_id);
1915
1916 let snapshot = graph.snapshot();
1917
1918 let callees = snapshot.get_callees(node1);
1920 assert_eq!(callees.len(), 1);
1921 assert_eq!(callees[0], node2);
1922
1923 let edges: Vec<_> = snapshot.iter_edges().collect();
1925 assert_eq!(edges.len(), 2);
1926 }
1927}