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