1use std::mem::size_of;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::time::Duration;
5
6use super::records::RecordTable;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
9pub struct ResourceGeneration(u64);
10
11impl ResourceGeneration {
12 pub fn new(value: u64) -> Self {
13 Self(value)
14 }
15
16 pub fn value(self) -> u64 {
17 self.0
18 }
19}
20
21#[derive(Clone, Debug)]
22pub struct WorkspaceRequest {
23 pub label: String,
24 pub catalog: CatalogRequest,
25 cancellation: WorkspaceCancellation,
26 memory_source_refresh: Option<MemorySourceRefreshMetrics>,
27}
28
29impl PartialEq for WorkspaceRequest {
30 fn eq(&self, other: &Self) -> bool {
31 self.label == other.label
32 && self.catalog == other.catalog
33 && self.memory_source_refresh == other.memory_source_refresh
34 }
35}
36
37impl Eq for WorkspaceRequest {}
38
39#[derive(Clone, Debug, Default)]
40pub struct WorkspaceCancellation(Arc<AtomicBool>);
41
42impl WorkspaceCancellation {
43 pub fn cancel(&self) {
44 self.0.store(true, Ordering::Release);
45 }
46
47 pub fn is_cancelled(&self) -> bool {
48 self.0.load(Ordering::Acquire)
49 }
50
51 pub fn check(&self, resource: WorkspaceResource) -> WorkspaceResult<()> {
52 if self.is_cancelled() {
53 return Err(WorkspaceFailure::new(resource, "workspace build cancelled"));
54 }
55 Ok(())
56 }
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum CatalogRequest {
61 Refresh,
62 ReuseCurrent,
63}
64
65impl WorkspaceRequest {
66 pub fn new(label: impl Into<String>) -> Self {
67 Self {
68 label: label.into(),
69 catalog: CatalogRequest::Refresh,
70 cancellation: WorkspaceCancellation::default(),
71 memory_source_refresh: None,
72 }
73 }
74
75 pub fn with_cancellation(mut self, cancellation: WorkspaceCancellation) -> Self {
76 self.cancellation = cancellation;
77 self
78 }
79
80 pub fn cancellation(&self) -> &WorkspaceCancellation {
81 &self.cancellation
82 }
83
84 pub fn with_memory_source_refresh(mut self, refresh: MemorySourceRefreshMetrics) -> Self {
85 self.memory_source_refresh = Some(refresh);
86 self
87 }
88
89 pub fn memory_source_refresh(&self) -> Option<MemorySourceRefreshMetrics> {
90 self.memory_source_refresh
91 }
92
93 pub fn reuse_current_catalog(mut self) -> Self {
94 self.catalog = CatalogRequest::ReuseCurrent;
95 self
96 }
97
98 pub fn should_reuse_current_catalog(&self) -> bool {
99 self.catalog == CatalogRequest::ReuseCurrent
100 }
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
104pub struct SourceId {
105 file: u32,
106}
107
108impl SourceId {
109 pub fn at(file: usize) -> Self {
110 Self { file: file as u32 }
111 }
112
113 pub fn parse(value: &str) -> Option<Self> {
114 let rest = value.strip_prefix("source:")?;
115 let file = rest.split(':').next()?;
116 Some(Self {
117 file: file.parse().ok()?,
118 })
119 }
120
121 pub fn file(self) -> usize {
122 self.file as usize
123 }
124}
125
126impl std::fmt::Display for SourceId {
127 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(formatter, "source:{}", self.file)
129 }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct SourceUnit {
134 pub id: SourceId,
135 pub display_name: String,
136 pub language: Option<String>,
137}
138
139impl SourceUnit {
140 pub fn new(id: SourceId, display_name: impl Into<String>) -> Self {
141 Self {
142 id,
143 display_name: display_name.into(),
144 language: None,
145 }
146 }
147
148 pub fn with_language(
149 id: SourceId,
150 display_name: impl Into<String>,
151 language: impl Into<String>,
152 ) -> Self {
153 Self {
154 id,
155 display_name: display_name.into(),
156 language: Some(language.into()),
157 }
158 }
159}
160
161#[derive(Clone, Debug, Eq, PartialEq)]
162pub struct SourceCatalog {
163 pub generation: ResourceGeneration,
164 pub sources: Vec<SourceUnit>,
165}
166
167impl SourceCatalog {
168 pub fn new(generation: ResourceGeneration, mut sources: Vec<SourceUnit>) -> Self {
169 sources.shrink_to_fit();
170 Self {
171 generation,
172 sources,
173 }
174 }
175}
176
177#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
178pub struct SymbolId {
179 file: u32,
180 def: u32,
181}
182
183impl SymbolId {
184 pub fn at(file: usize, def: usize) -> Self {
185 Self {
186 file: file as u32,
187 def: def as u32,
188 }
189 }
190
191 pub fn parse(value: &str) -> Option<Self> {
192 let rest = value.strip_prefix("symbol:")?;
193 let (file, def) = rest.split_once(':')?;
194 Some(Self {
195 file: file.parse().ok()?,
196 def: def.parse().ok()?,
197 })
198 }
199
200 pub fn file(self) -> usize {
201 self.file as usize
202 }
203
204 pub fn def(self) -> usize {
205 self.def as usize
206 }
207}
208
209impl std::fmt::Display for SymbolId {
210 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 write!(formatter, "symbol:{}:{}", self.file, self.def)
212 }
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
216pub struct SymbolLocation {
217 pub file: usize,
218 pub symbol: usize,
219}
220
221#[derive(Clone, Debug, Eq, PartialEq)]
222pub struct SymbolRecord {
223 pub id: SymbolId,
224 pub source: SourceId,
225 pub identity: Arc<str>,
226 pub name: String,
227 pub kind: String,
228 pub visibility: String,
229 pub signature: String,
230 pub call_name: Option<String>,
231 pub call_arity: Option<usize>,
232 pub navigable: bool,
233 pub line_range: Option<(u32, u32)>,
234 pub parent: Option<SymbolId>,
235}
236
237impl SymbolRecord {
238 pub fn new(
239 id: SymbolId,
240 source: SourceId,
241 name: impl Into<String>,
242 kind: impl Into<String>,
243 ) -> Self {
244 Self {
245 identity: Arc::from(id.to_string()),
246 id,
247 source,
248 name: name.into(),
249 kind: kind.into(),
250 visibility: String::new(),
251 signature: String::new(),
252 call_name: None,
253 call_arity: None,
254 navigable: true,
255 line_range: None,
256 parent: None,
257 }
258 }
259}
260
261#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
262pub struct ReferenceId {
263 file: u32,
264 reference: u32,
265}
266
267impl ReferenceId {
268 pub fn at(file: usize, reference: usize) -> Self {
269 Self {
270 file: file as u32,
271 reference: reference as u32,
272 }
273 }
274
275 pub fn parse(value: &str) -> Option<Self> {
276 let rest = value.strip_prefix("reference:")?;
277 let (file, reference) = rest.split_once(':')?;
278 Some(Self {
279 file: file.parse().ok()?,
280 reference: reference.parse().ok()?,
281 })
282 }
283
284 pub fn file(self) -> usize {
285 self.file as usize
286 }
287
288 pub fn reference(self) -> usize {
289 self.reference as usize
290 }
291}
292
293impl std::fmt::Display for ReferenceId {
294 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 write!(formatter, "reference:{}:{}", self.file, self.reference)
296 }
297}
298
299#[derive(Clone, Debug, Eq, PartialEq)]
300pub struct ReferenceRecord {
301 pub id: ReferenceId,
302 pub source: SourceId,
303 pub source_symbol: SymbolId,
304 pub target_identity: Arc<str>,
305 pub kind: String,
306 pub call_name: Option<String>,
307 pub call_arity: Option<usize>,
308 pub confidence: Option<String>,
309 pub receiver: Option<String>,
310 pub alias: Option<String>,
311 pub line_range: Option<(u32, u32)>,
312}
313
314impl ReferenceRecord {
315 pub fn new(
316 id: ReferenceId,
317 source: SourceId,
318 source_symbol: SymbolId,
319 target_identity: impl Into<Arc<str>>,
320 kind: impl Into<String>,
321 line_range: Option<(u32, u32)>,
322 ) -> Self {
323 Self {
324 id,
325 source,
326 source_symbol,
327 target_identity: target_identity.into(),
328 kind: kind.into(),
329 call_name: None,
330 call_arity: None,
331 confidence: None,
332 receiver: None,
333 alias: None,
334 line_range,
335 }
336 }
337
338 pub fn with_call_metadata(
339 mut self,
340 call_name: Option<String>,
341 call_arity: Option<usize>,
342 ) -> Self {
343 self.call_name = call_name;
344 self.call_arity = call_arity;
345 self
346 }
347
348 pub fn with_metadata(
349 mut self,
350 confidence: Option<String>,
351 receiver: Option<String>,
352 alias: Option<String>,
353 ) -> Self {
354 self.confidence = confidence;
355 self.receiver = receiver;
356 self.alias = alias;
357 self
358 }
359}
360
361#[derive(Clone, Debug, Eq, PartialEq)]
362pub struct SourceFileRecord {
363 pub id: SourceId,
364 pub uri: String,
365 pub source_root: usize,
366 pub path: String,
367 pub rel_path: String,
368 pub anchor: String,
369 pub language: String,
370 pub text: String,
371}
372
373#[derive(Clone, Debug, Eq, PartialEq)]
374pub struct CodeIndex {
375 pub generation: ResourceGeneration,
376 pub catalog_generation: ResourceGeneration,
377 pub identity_scheme: String,
378 pub sources: Vec<SourceFileRecord>,
379 pub symbols: RecordTable<SymbolRecord>,
380 pub references: RecordTable<ReferenceRecord>,
381 pub inventory: Arc<super::SymbolInventoryIndex>,
382 pub timings: CodeIndexTimings,
383}
384
385#[derive(Clone, Debug, Default, Eq, PartialEq)]
386pub struct CodeIndexTimings {
387 pub extract_sources: Duration,
388 pub semantic_index: Duration,
389 pub total: Duration,
390 pub extraction: Vec<ExtractionMeasurement>,
391 pub extraction_jobs: usize,
392 pub extraction_workers: usize,
393}
394
395#[derive(Clone, Debug, Default, Eq, PartialEq)]
396pub struct ExtractionMeasurement {
397 pub language: &'static str,
398 pub cache: &'static str,
399 pub files: usize,
400 pub source_bytes: usize,
401 pub duration: Duration,
402}
403
404impl CodeIndex {
405 pub fn new(
406 generation: ResourceGeneration,
407 catalog_generation: ResourceGeneration,
408 symbols: Vec<SymbolRecord>,
409 ) -> Self {
410 Self::with_references(generation, catalog_generation, symbols, Vec::new())
411 }
412
413 pub fn with_references(
414 generation: ResourceGeneration,
415 catalog_generation: ResourceGeneration,
416 symbols: Vec<SymbolRecord>,
417 references: Vec<ReferenceRecord>,
418 ) -> Self {
419 let sources = Vec::new();
420 let symbols = RecordTable::from_records(symbols);
421 let inventory = Arc::new(super::SymbolInventoryIndex::build(
422 generation, &sources, &symbols,
423 ));
424 Self {
425 generation,
426 catalog_generation,
427 identity_scheme: crate::DEFAULT_IDENTITY_SCHEME.to_string(),
428 sources,
429 symbols,
430 references: RecordTable::from_records(references),
431 inventory,
432 timings: CodeIndexTimings::default(),
433 }
434 }
435}
436
437#[derive(Clone, Debug, Eq, PartialEq)]
438pub struct LinkageEdge {
439 pub reference: ReferenceId,
440 pub target: SymbolId,
441 pub evidence: ResolutionEvidence,
442}
443
444impl LinkageEdge {
445 pub fn new(reference: ReferenceId, target: SymbolId) -> Self {
446 Self::with_evidence(reference, target, ResolutionEvidence::ExactBinding)
447 }
448
449 pub fn with_evidence(
450 reference: ReferenceId,
451 target: SymbolId,
452 evidence: ResolutionEvidence,
453 ) -> Self {
454 Self {
455 reference,
456 target,
457 evidence,
458 }
459 }
460}
461
462#[derive(Clone, Copy, Debug, Eq, PartialEq)]
463pub enum ResolutionEvidence {
464 ExactBinding,
465 LocalBinding,
466 GlobalBinding,
467 TypeConstraint,
468 Mro,
469 Injected,
470 NameMatch,
471}
472
473impl ResolutionEvidence {
474 pub fn as_str(self) -> &'static str {
475 match self {
476 Self::ExactBinding => "exact_binding",
477 Self::LocalBinding => "local_binding",
478 Self::GlobalBinding => "global_binding",
479 Self::TypeConstraint => "type_constraint",
480 Self::Mro => "mro",
481 Self::Injected => "injected",
482 Self::NameMatch => "name_match",
483 }
484 }
485
486 pub fn rank(self) -> u8 {
487 match self {
488 Self::ExactBinding => 100,
489 Self::LocalBinding | Self::TypeConstraint => 90,
490 Self::Mro => 85,
491 Self::GlobalBinding | Self::Injected => 80,
492 Self::NameMatch => 10,
493 }
494 }
495}
496
497#[derive(Clone, Copy, Debug, Eq, PartialEq)]
498pub enum CandidateReason {
499 WeakNameMatch,
500 MultipleTargets,
501 AmbiguousLookup,
502}
503
504impl CandidateReason {
505 pub fn as_str(self) -> &'static str {
506 match self {
507 Self::WeakNameMatch => "weak_name_match",
508 Self::MultipleTargets => "multiple_targets",
509 Self::AmbiguousLookup => "ambiguous_lookup",
510 }
511 }
512}
513
514#[derive(Clone, Copy, Debug, Eq, PartialEq)]
515pub enum CandidateScope {
516 Local,
517 Global,
518 Builtin,
519 Injected,
520 Unknown,
521}
522
523impl CandidateScope {
524 pub fn as_str(self) -> &'static str {
525 match self {
526 Self::Local => "local",
527 Self::Global => "global",
528 Self::Builtin => "builtin",
529 Self::Injected => "injected",
530 Self::Unknown => "unknown",
531 }
532 }
533}
534
535#[derive(Clone, Debug, Eq, PartialEq)]
536pub struct CandidateReference {
537 pub reference: ReferenceId,
538 pub targets: Vec<SymbolId>,
539 pub reason: CandidateReason,
540 pub scope: CandidateScope,
541 pub evidence: ResolutionEvidence,
542}
543
544impl CandidateReference {
545 pub fn new(
546 reference: ReferenceId,
547 targets: Vec<SymbolId>,
548 reason: CandidateReason,
549 scope: CandidateScope,
550 evidence: ResolutionEvidence,
551 ) -> Self {
552 Self {
553 reference,
554 targets,
555 reason,
556 scope,
557 evidence,
558 }
559 }
560}
561
562#[derive(Clone, Copy, Debug, Eq, PartialEq)]
563pub enum DynamicReason {
564 ConditionalCompilation,
565 DynamicAttribute,
566 DescriptorOrFrameworkInjected,
567 DuckTypedCandidateSet,
568 MixinContract,
569 ExternalDependencyUnindexed,
570 RuntimeImport,
571 RuntimeMutation,
572 PreprocessorExpansion,
573 InsufficientLocalFacts,
574}
575
576impl DynamicReason {
577 pub fn as_str(self) -> &'static str {
578 match self {
579 Self::ConditionalCompilation => "conditional_compilation",
580 Self::DynamicAttribute => "dynamic_attribute",
581 Self::DescriptorOrFrameworkInjected => "descriptor_or_framework_injected",
582 Self::DuckTypedCandidateSet => "duck_typed_candidate_set",
583 Self::MixinContract => "mixin_contract",
584 Self::ExternalDependencyUnindexed => "external_dependency_unindexed",
585 Self::RuntimeImport => "runtime_import",
586 Self::RuntimeMutation => "runtime_mutation",
587 Self::PreprocessorExpansion => "preprocessor_expansion",
588 Self::InsufficientLocalFacts => "insufficient_local_facts",
589 }
590 }
591}
592
593#[derive(Clone, Debug, Eq, PartialEq)]
594pub struct DynamicReference {
595 pub reference: ReferenceId,
596 pub target_identity: Arc<str>,
597 pub reason: DynamicReason,
598 pub candidates: Vec<SymbolId>,
599}
600
601impl DynamicReference {
602 pub fn new(
603 reference: ReferenceId,
604 target_identity: impl Into<Arc<str>>,
605 reason: DynamicReason,
606 candidates: Vec<SymbolId>,
607 ) -> Self {
608 Self {
609 reference,
610 target_identity: target_identity.into(),
611 reason,
612 candidates,
613 }
614 }
615}
616
617#[derive(Clone, Copy, Debug, Eq, PartialEq)]
618pub enum ExternalReferenceOrigin {
619 Sdk,
620 Dependency,
621 Injected,
622 UnknownExternal,
623}
624
625impl ExternalReferenceOrigin {
626 pub fn label(self) -> &'static str {
627 match self {
628 Self::Sdk => "sdk",
629 Self::Dependency => "dependency",
630 Self::Injected => "injected",
631 Self::UnknownExternal => "unknown_external",
632 }
633 }
634}
635
636#[derive(Clone, Debug, Eq, PartialEq)]
637pub struct ExternalReference {
638 pub reference: ReferenceId,
639 pub target_identity: Arc<str>,
640 pub origin: ExternalReferenceOrigin,
641}
642
643impl ExternalReference {
644 pub fn new(
645 reference: ReferenceId,
646 target_identity: impl Into<Arc<str>>,
647 origin: ExternalReferenceOrigin,
648 ) -> Self {
649 Self {
650 reference,
651 target_identity: target_identity.into(),
652 origin,
653 }
654 }
655}
656
657#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
661pub enum UnresolvedReason {
662 ManifestBlocked,
663 Visibility,
664 LanguageBoundary,
665 MissingQuery,
666 NoCandidate,
667 Ambiguous,
668 UnsupportedLanguageRule,
669 IncompleteExtractorMetadata,
670}
671
672impl UnresolvedReason {
673 pub fn as_str(&self) -> &'static str {
674 match self {
675 Self::ManifestBlocked => "manifest_blocked",
676 Self::Visibility => "visibility",
677 Self::LanguageBoundary => "language_boundary",
678 Self::MissingQuery => "missing_query",
679 Self::NoCandidate => "no_candidate",
680 Self::Ambiguous => "ambiguous",
681 Self::UnsupportedLanguageRule => "unsupported_language_rule",
682 Self::IncompleteExtractorMetadata => "incomplete_extractor_metadata",
683 }
684 }
685}
686
687#[derive(Clone, Debug, Eq, PartialEq)]
688pub struct UnresolvedReference {
689 pub reference: ReferenceId,
690 pub target_identity: Arc<str>,
691 pub reason: UnresolvedReason,
692}
693
694impl UnresolvedReference {
695 pub fn new(
696 reference: ReferenceId,
697 target_identity: impl Into<Arc<str>>,
698 reason: UnresolvedReason,
699 ) -> Self {
700 Self {
701 reference,
702 target_identity: target_identity.into(),
703 reason,
704 }
705 }
706}
707
708#[derive(Clone, Debug, Eq, PartialEq)]
709pub struct LinkageSnapshot {
710 pub generation: ResourceGeneration,
711 pub index_generation: ResourceGeneration,
712 pub resolved_refs: usize,
713 pub candidate_refs: usize,
714 pub external_refs: usize,
715 pub dynamic_refs: usize,
716 pub blocked_refs: usize,
717 pub manifest_blocked_refs: usize,
719 pub unresolved_refs: usize,
720 pub ambiguous_refs: usize,
723 pub resolved: Vec<LinkageEdge>,
724 pub candidates: Vec<CandidateReference>,
725 pub external: Vec<ExternalReference>,
726 pub dynamic: Vec<DynamicReference>,
727 pub blocked: Vec<UnresolvedReference>,
728 pub manifest_blocked: Vec<UnresolvedReference>,
730 pub unresolved: Vec<UnresolvedReference>,
731 pub read_index: LinkageReadIndexHandle,
732}
733
734#[derive(Debug)]
735pub struct LinkageReadIndex {
736 pub(crate) incoming: rustc_hash::FxHashMap<SymbolId, Vec<ReferenceId>>,
737 pub(crate) targets: rustc_hash::FxHashMap<ReferenceId, SymbolId>,
738 pub(crate) ordinals: rustc_hash::FxHashMap<SymbolId, u32>,
739 pub(crate) symbols: rustc_hash::FxHashMap<u32, SymbolId>,
740 pub(crate) outgoing: OutgoingPathIndex,
741}
742
743type OutgoingPathIndex =
744 rustc_hash::FxHashMap<u32, rustc_hash::FxHashMap<Arc<str>, Vec<ReferenceId>>>;
745
746impl LinkageReadIndex {
747 pub(crate) fn estimated_heap_bytes(&self) -> usize {
748 let incoming = self.incoming.capacity()
749 * (size_of::<SymbolId>() + size_of::<Vec<ReferenceId>>())
750 + self
751 .incoming
752 .values()
753 .map(|references| references.capacity() * size_of::<ReferenceId>())
754 .sum::<usize>();
755 let targets = self.targets.capacity() * (size_of::<ReferenceId>() + size_of::<SymbolId>());
756 let ordinals = self.ordinals.capacity() * (size_of::<SymbolId>() + size_of::<u32>());
757 let symbols = self.symbols.capacity() * (size_of::<u32>() + size_of::<SymbolId>());
758 let mut relation_strings = std::collections::HashSet::<(usize, usize)>::new();
759 let outgoing = self.outgoing.capacity()
760 * (size_of::<u32>() + size_of::<rustc_hash::FxHashMap<Arc<str>, Vec<ReferenceId>>>())
761 + self
762 .outgoing
763 .values()
764 .map(|relations| {
765 relations.capacity() * (size_of::<Arc<str>>() + size_of::<Vec<ReferenceId>>())
766 + relations
767 .iter()
768 .map(|(relation, references)| {
769 let string_bytes = if relation_strings
770 .insert((relation.as_ptr() as usize, relation.len()))
771 {
772 relation.len()
773 } else {
774 0
775 };
776 string_bytes + references.capacity() * size_of::<ReferenceId>()
777 })
778 .sum::<usize>()
779 })
780 .sum::<usize>();
781 incoming + targets + ordinals + symbols + outgoing
782 }
783
784 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
785 let mut incoming = rustc_hash::FxHashMap::<SymbolId, Vec<ReferenceId>>::default();
786 let mut targets = rustc_hash::FxHashMap::<ReferenceId, SymbolId>::default();
787 for edge in edges {
788 let LinkageEdge {
789 reference, target, ..
790 } = edge.clone();
791 targets.entry(reference).or_insert(target);
792 incoming.entry(target).or_default().push(reference);
793 }
794 Self {
795 incoming,
796 targets,
797 ordinals: rustc_hash::FxHashMap::default(),
798 symbols: rustc_hash::FxHashMap::default(),
799 outgoing: rustc_hash::FxHashMap::default(),
800 }
801 }
802
803 pub(crate) fn from_edges_with_ordinals(
804 edges: &[LinkageEdge],
805 references: &RecordTable<ReferenceRecord>,
806 ordinals: impl IntoIterator<Item = (u32, SymbolId)>,
807 ) -> Self {
808 let mut index = Self::from_edges(edges);
809 for (ordinal, symbol) in ordinals {
810 index.symbols.insert(ordinal, symbol);
811 index.ordinals.insert(symbol, ordinal);
812 }
813 index.outgoing = outgoing_path_index(references, &index.ordinals);
814 index
815 }
816
817 pub fn incoming(&self, symbol: &SymbolId) -> &[ReferenceId] {
818 self.incoming.get(symbol).map(Vec::as_slice).unwrap_or(&[])
819 }
820
821 pub fn resolved_target(&self, reference: &ReferenceId) -> Option<&SymbolId> {
822 self.targets.get(reference)
823 }
824
825 pub(crate) fn ordinal(&self, symbol: &SymbolId) -> Option<u32> {
826 self.ordinals.get(symbol).copied()
827 }
828
829 pub(crate) fn symbol(&self, ordinal: u32) -> Option<SymbolId> {
830 self.symbols.get(&ordinal).copied()
831 }
832
833 pub(crate) fn outgoing(&self, ordinal: u32, relation: &str) -> &[ReferenceId] {
834 self.outgoing
835 .get(&ordinal)
836 .and_then(|by_relation| by_relation.get(relation))
837 .map(Vec::as_slice)
838 .unwrap_or(&[])
839 }
840
841 pub(crate) fn outgoing_relations(&self, ordinal: u32) -> impl Iterator<Item = &str> {
842 self.outgoing
843 .get(&ordinal)
844 .into_iter()
845 .flat_map(|by_relation| by_relation.keys().map(AsRef::as_ref))
846 }
847
848 #[cfg(test)]
849 pub(crate) fn active_symbol_slots(&self) -> usize {
850 self.symbols.len()
851 }
852}
853
854fn outgoing_path_index(
855 references: &RecordTable<ReferenceRecord>,
856 ordinals: &rustc_hash::FxHashMap<SymbolId, u32>,
857) -> OutgoingPathIndex {
858 let mut index = OutgoingPathIndex::default();
859 for reference in references.iter() {
860 let Some(source_ordinal) = ordinals.get(&reference.source_symbol).copied() else {
861 continue;
862 };
863 let by_relation = index.entry(source_ordinal).or_default();
864 if let Some(outgoing) = by_relation.get_mut(reference.kind.as_str()) {
865 outgoing.push(reference.id);
866 } else {
867 by_relation.insert(Arc::from(reference.kind.as_str()), vec![reference.id]);
868 }
869 }
870 index
871}
872
873#[derive(Clone, Debug, Default)]
874pub struct LinkageReadIndexHandle(Option<Arc<LinkageReadIndex>>);
875
876impl LinkageReadIndexHandle {
877 pub(crate) fn estimated_heap_bytes(&self) -> usize {
878 self.0.as_deref().map_or(0, |index| {
879 size_of::<LinkageReadIndex>() + index.estimated_heap_bytes()
880 })
881 }
882
883 pub fn from_edges(edges: &[LinkageEdge]) -> Self {
884 Self(Some(Arc::new(LinkageReadIndex::from_edges(edges))))
885 }
886
887 pub(crate) fn from_edges_with_ordinals(
888 edges: &[LinkageEdge],
889 references: &RecordTable<ReferenceRecord>,
890 ordinals: impl IntoIterator<Item = (u32, SymbolId)>,
891 ) -> Self {
892 Self(Some(Arc::new(LinkageReadIndex::from_edges_with_ordinals(
893 edges, references, ordinals,
894 ))))
895 }
896
897 pub fn get(&self) -> Option<&LinkageReadIndex> {
898 self.0.as_deref()
899 }
900}
901
902impl PartialEq for LinkageReadIndexHandle {
903 fn eq(&self, _other: &Self) -> bool {
904 true
905 }
906}
907
908impl Eq for LinkageReadIndexHandle {}
909
910impl LinkageSnapshot {
911 pub fn new(
912 generation: ResourceGeneration,
913 index_generation: ResourceGeneration,
914 resolved_refs: usize,
915 unresolved_refs: usize,
916 ) -> Self {
917 Self {
918 generation,
919 index_generation,
920 resolved_refs,
921 candidate_refs: 0,
922 external_refs: 0,
923 dynamic_refs: 0,
924 blocked_refs: 0,
925 manifest_blocked_refs: 0,
926 unresolved_refs,
927 ambiguous_refs: 0,
928 resolved: Vec::new(),
929 candidates: Vec::new(),
930 external: Vec::new(),
931 dynamic: Vec::new(),
932 blocked: Vec::new(),
933 manifest_blocked: Vec::new(),
934 unresolved: Vec::new(),
935 read_index: LinkageReadIndexHandle::default(),
936 }
937 }
938
939 pub fn with_refs(
940 generation: ResourceGeneration,
941 index_generation: ResourceGeneration,
942 mut resolved: Vec<LinkageEdge>,
943 mut unresolved: Vec<UnresolvedReference>,
944 ) -> Self {
945 resolved.shrink_to_fit();
946 unresolved.shrink_to_fit();
947 let read_index = LinkageReadIndexHandle::from_edges(&resolved);
948 Self {
949 generation,
950 index_generation,
951 resolved_refs: resolved.len(),
952 candidate_refs: 0,
953 external_refs: 0,
954 dynamic_refs: 0,
955 blocked_refs: 0,
956 manifest_blocked_refs: 0,
957 unresolved_refs: unresolved.len(),
958 ambiguous_refs: 0,
959 resolved,
960 candidates: Vec::new(),
961 external: Vec::new(),
962 dynamic: Vec::new(),
963 blocked: Vec::new(),
964 manifest_blocked: Vec::new(),
965 unresolved,
966 read_index,
967 }
968 }
969}
970
971#[derive(Clone, Debug, Eq, PartialEq)]
972pub struct ChangeOverlay {
973 pub generation: ResourceGeneration,
974 pub catalog_generation: ResourceGeneration,
975 pub index_generation: ResourceGeneration,
976 pub scope: String,
977 pub resources: Vec<ChangeResource>,
978 pub diagnostics: Vec<String>,
979 pub changed_symbols: Vec<SymbolId>,
980 pub changes: Vec<ChangeRecord>,
981}
982
983pub struct ChangeOverlayReport {
984 pub generation: ResourceGeneration,
985 pub catalog_generation: ResourceGeneration,
986 pub index_generation: ResourceGeneration,
987 pub scope: String,
988 pub resources: Vec<ChangeResource>,
989 pub diagnostics: Vec<String>,
990 pub changes: Vec<ChangeRecord>,
991}
992
993impl ChangeOverlay {
994 pub fn new(
995 generation: ResourceGeneration,
996 catalog_generation: ResourceGeneration,
997 index_generation: ResourceGeneration,
998 mut changed_symbols: Vec<SymbolId>,
999 ) -> Self {
1000 changed_symbols.shrink_to_fit();
1001 Self {
1002 generation,
1003 catalog_generation,
1004 index_generation,
1005 scope: "HEAD..worktree".to_string(),
1006 resources: Vec::new(),
1007 diagnostics: Vec::new(),
1008 changed_symbols,
1009 changes: Vec::new(),
1010 }
1011 }
1012
1013 pub fn with_records(
1014 generation: ResourceGeneration,
1015 catalog_generation: ResourceGeneration,
1016 index_generation: ResourceGeneration,
1017 mut changes: Vec<ChangeRecord>,
1018 ) -> Self {
1019 changes.shrink_to_fit();
1020 let changed_symbols = changes.iter().filter_map(|change| change.symbol).fold(
1021 Vec::new(),
1022 |mut out, symbol| {
1023 if !out.contains(&symbol) {
1024 out.push(symbol);
1025 }
1026 out
1027 },
1028 );
1029 let mut changed_symbols = changed_symbols;
1030 changed_symbols.shrink_to_fit();
1031 Self {
1032 generation,
1033 catalog_generation,
1034 index_generation,
1035 scope: "HEAD..worktree".to_string(),
1036 resources: Vec::new(),
1037 diagnostics: Vec::new(),
1038 changed_symbols,
1039 changes,
1040 }
1041 }
1042
1043 pub fn from_report(report: ChangeOverlayReport) -> Self {
1044 let mut resources = report.resources;
1045 let mut diagnostics = report.diagnostics;
1046 resources.shrink_to_fit();
1047 diagnostics.shrink_to_fit();
1048 let mut overlay = Self::with_records(
1049 report.generation,
1050 report.catalog_generation,
1051 report.index_generation,
1052 report.changes,
1053 );
1054 overlay.scope = report.scope;
1055 overlay.resources = resources;
1056 overlay.diagnostics = diagnostics;
1057 overlay
1058 }
1059}
1060
1061#[derive(Clone, Debug, Eq, PartialEq)]
1062pub struct ChangeResource {
1063 pub available: bool,
1064 pub label: String,
1065 pub message: String,
1066}
1067
1068#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1069pub enum ChangeStatus {
1070 Added,
1071 Modified,
1072 Removed,
1073}
1074
1075impl ChangeStatus {
1076 pub fn label(self) -> &'static str {
1077 match self {
1078 Self::Added => "added",
1079 Self::Modified => "modified",
1080 Self::Removed => "removed",
1081 }
1082 }
1083}
1084
1085#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
1086pub struct ChangeId(String);
1087
1088impl ChangeId {
1089 pub fn new(value: impl Into<String>) -> Self {
1090 Self(value.into())
1091 }
1092
1093 pub fn as_str(&self) -> &str {
1094 &self.0
1095 }
1096}
1097
1098#[derive(Clone, Debug, Eq, PartialEq)]
1099pub struct ChangeRecord {
1100 pub id: ChangeId,
1101 pub status: ChangeStatus,
1102 pub source: Option<SourceId>,
1103 pub source_uri: Option<String>,
1104 pub symbol: Option<SymbolId>,
1105 pub identity: String,
1106 pub language: String,
1107 pub file_path: String,
1108 pub name: String,
1109 pub kind: String,
1110 pub line_range: Option<(u32, u32)>,
1111 pub hunk_count: usize,
1112}
1113
1114#[derive(Clone, Debug, Eq, PartialEq)]
1115pub struct ChangeRecordCoreFields {
1116 pub id: ChangeId,
1117 pub status: ChangeStatus,
1118 pub identity: String,
1119 pub language: String,
1120 pub file_path: String,
1121 pub name: String,
1122 pub kind: String,
1123 pub line_range: Option<(u32, u32)>,
1124 pub hunk_count: usize,
1125}
1126
1127impl ChangeRecord {
1128 pub fn new(fields: ChangeRecordCoreFields) -> Self {
1129 Self {
1130 id: fields.id,
1131 status: fields.status,
1132 source: None,
1133 source_uri: None,
1134 symbol: None,
1135 identity: fields.identity,
1136 language: fields.language,
1137 file_path: fields.file_path,
1138 name: fields.name,
1139 kind: fields.kind,
1140 line_range: fields.line_range,
1141 hunk_count: fields.hunk_count,
1142 }
1143 }
1144
1145 pub fn with_source(mut self, source: SourceId, source_uri: impl Into<String>) -> Self {
1146 self.source = Some(source);
1147 self.source_uri = Some(source_uri.into());
1148 self
1149 }
1150
1151 pub fn with_symbol(mut self, symbol: SymbolId) -> Self {
1152 self.symbol = Some(symbol);
1153 self
1154 }
1155}
1156
1157#[derive(Clone, Debug, Eq, PartialEq)]
1158pub struct WorkspaceSnapshot {
1159 pub generation: ResourceGeneration,
1160 pub catalog: SourceCatalog,
1161 pub index: CodeIndex,
1162 pub linkage: LinkageSnapshot,
1163 pub changes: ChangeOverlay,
1164 pub timings: WorkspaceTimings,
1165}
1166
1167#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1168pub struct WorkspaceTimings {
1169 pub source_catalog: Duration,
1170 pub extract_sources: Duration,
1171 pub semantic_index: Duration,
1172 pub code_index: Duration,
1173 pub linkage: Duration,
1174 pub change_overlay: Duration,
1175 pub total: Duration,
1176 pub memory_source_refresh: Option<MemorySourceRefreshMetrics>,
1177}
1178
1179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1180pub enum MemorySourceRefreshMode {
1181 Bulk,
1182 Incremental,
1183}
1184
1185impl MemorySourceRefreshMode {
1186 pub fn as_str(self) -> &'static str {
1187 match self {
1188 Self::Bulk => "bulk",
1189 Self::Incremental => "incremental",
1190 }
1191 }
1192}
1193
1194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1195pub struct MemorySourceRefreshMetrics {
1196 pub mode: MemorySourceRefreshMode,
1197 pub documents_total: usize,
1198 pub added: usize,
1199 pub modified: usize,
1200 pub removed: usize,
1201 pub unchanged: usize,
1202 pub extraction_jobs: usize,
1203 pub extraction_workers: usize,
1204 pub linkage_invocations: usize,
1205}
1206
1207impl MemorySourceRefreshMetrics {
1208 pub fn with_execution(mut self, index: &CodeIndex, linkage_invocations: usize) -> Self {
1209 self.extraction_jobs = index.timings.extraction_jobs;
1210 self.extraction_workers = index.timings.extraction_workers;
1211 self.linkage_invocations = linkage_invocations;
1212 self
1213 }
1214}
1215
1216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1217pub enum WorkspaceResource {
1218 SourceCatalog,
1219 CodeIndex,
1220 LinkageSnapshot,
1221 ChangeOverlay,
1222}
1223
1224#[derive(Clone, Debug, Eq, PartialEq)]
1225pub struct WorkspaceFailure {
1226 pub resource: WorkspaceResource,
1227 pub message: String,
1228}
1229
1230impl WorkspaceFailure {
1231 pub fn new(resource: WorkspaceResource, message: impl Into<String>) -> Self {
1232 Self {
1233 resource,
1234 message: message.into(),
1235 }
1236 }
1237}
1238
1239pub type WorkspaceResult<T> = Result<T, WorkspaceFailure>;
1240
1241#[derive(Clone, Debug, Eq, PartialEq)]
1242pub enum WorkspaceTransition {
1243 Ready {
1244 generation: ResourceGeneration,
1245 },
1246 Failed {
1247 failure: WorkspaceFailure,
1248 preserved_generation: Option<ResourceGeneration>,
1249 },
1250}