1use std::collections::BTreeSet;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use std::time::Instant;
7
8use code_moniker_core::core::moniker::Moniker;
9use rayon::prelude::*;
10use rustc_hash::FxHashMap;
11
12use crate::code::{def_kind, is_navigable_def, last_name, ref_kind};
13use crate::environment;
14use crate::lines::LineIndex;
15use crate::snapshot::{
16 CodeIndex, CodeIndexTimings, RecordTable, ReferenceId, ReferenceRecord, SourceCatalog,
17 SourceFileRecord, SourceId, SymbolId, SymbolRecord, WorkspaceFailure, WorkspaceResource,
18 WorkspaceResult,
19};
20use crate::source::{
21 CodeIndexMaterial, IndexedSourceFile, LocalResourceCache, SourceCatalogMaterial,
22};
23
24use crate::source::LocalIdentityResolver;
25
26pub trait CodeIndexPort {
27 fn build_index(&mut self, catalog: &SourceCatalog) -> WorkspaceResult<CodeIndex>;
28 fn refresh_paths(
29 &mut self,
30 current: &CodeIndex,
31 paths: &[PathBuf],
32 ) -> WorkspaceResult<CodeIndexRefresh>;
33 fn refresh_catalog_paths(
34 &mut self,
35 current: &CodeIndex,
36 catalog: &SourceCatalog,
37 paths: &[PathBuf],
38 ) -> WorkspaceResult<CodeIndexRefresh>;
39}
40
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct CodeIndexRefresh {
43 pub index: CodeIndex,
44 pub changed_sources: Vec<SourceId>,
45 pub graph_diff: CodeIndexGraphDiff,
46}
47
48#[derive(Clone, Debug, Default, Eq, PartialEq)]
49pub struct CodeIndexGraphDiff {
50 pub added_symbols: Vec<SymbolId>,
51 pub modified_symbols: Vec<SymbolId>,
52 pub changed_symbols: Vec<SymbolId>,
53 pub removed_symbols: Vec<SymbolId>,
54 pub modified_symbol_identities: Vec<String>,
55 pub removed_symbol_identities: Vec<String>,
56 pub changed_references: Vec<ReferenceId>,
57 pub removed_references: Vec<ReferenceId>,
58 pub symbol_id_remaps: Vec<(SymbolId, SymbolId)>,
59 pub reference_id_remaps: Vec<(ReferenceId, ReferenceId)>,
60 pub unchanged_symbols: usize,
61 pub unchanged_references: usize,
62}
63
64impl CodeIndexGraphDiff {
65 pub fn changed_symbol_count(&self) -> usize {
66 self.changed_symbols.len() + self.removed_symbols.len()
67 }
68
69 pub fn changed_reference_count(&self) -> usize {
70 self.changed_references.len() + self.removed_references.len()
71 }
72}
73
74#[derive(Clone, Debug, Default, Eq, PartialEq)]
75pub struct LocalCodeIndexOptions {
76 pub cache_dir: Option<PathBuf>,
77}
78
79impl LocalCodeIndexOptions {
80 pub fn new(cache_dir: Option<PathBuf>) -> Self {
81 Self { cache_dir }
82 }
83}
84
85pub struct LocalCodeIndex {
86 options: LocalCodeIndexOptions,
87 cache: LocalResourceCache,
88}
89
90impl LocalCodeIndex {
91 pub fn new(options: LocalCodeIndexOptions, cache: LocalResourceCache) -> Self {
92 Self { options, cache }
93 }
94}
95
96impl CodeIndexPort for LocalCodeIndex {
97 fn build_index(&mut self, catalog: &SourceCatalog) -> WorkspaceResult<CodeIndex> {
98 build_local_code_index(&self.cache, &self.options, catalog)
99 }
100
101 fn refresh_paths(
102 &mut self,
103 current: &CodeIndex,
104 paths: &[PathBuf],
105 ) -> WorkspaceResult<CodeIndexRefresh> {
106 refresh_local_code_index(&self.cache, &self.options, current, None, paths)
107 }
108
109 fn refresh_catalog_paths(
110 &mut self,
111 current: &CodeIndex,
112 catalog: &SourceCatalog,
113 paths: &[PathBuf],
114 ) -> WorkspaceResult<CodeIndexRefresh> {
115 refresh_local_code_index(&self.cache, &self.options, current, Some(catalog), paths)
116 }
117}
118
119fn build_local_code_index(
120 cache: &LocalResourceCache,
121 options: &LocalCodeIndexOptions,
122 catalog: &SourceCatalog,
123) -> WorkspaceResult<CodeIndex> {
124 let total_timer = Instant::now();
125 let source_material = source_material(cache, catalog)?;
126 let generation = cache.next_generation();
127 let extract_timer = Instant::now();
128 let files = extract_source_files(&source_material, options.cache_dir.as_deref())?;
129 let extract_sources = extract_timer.elapsed();
130 let semantic_timer = Instant::now();
131 let (symbols, references, material) = build_semantic_index(source_material, files);
132 let semantic_index = semantic_timer.elapsed();
133 let mut sources = source_records(&material.files);
134 let identity_scheme = material.identity.scheme().to_string();
135 cache.insert_index(generation, material);
136 sources.shrink_to_fit();
137 Ok(CodeIndex {
138 generation,
139 catalog_generation: catalog.generation,
140 identity_scheme,
141 sources,
142 symbols,
143 references,
144 timings: CodeIndexTimings {
145 extract_sources,
146 semantic_index,
147 total: total_timer.elapsed(),
148 },
149 })
150}
151
152fn refresh_local_code_index(
153 cache: &LocalResourceCache,
154 options: &LocalCodeIndexOptions,
155 current: &CodeIndex,
156 extended_catalog: Option<&SourceCatalog>,
157 paths: &[PathBuf],
158) -> WorkspaceResult<CodeIndexRefresh> {
159 let total_timer = Instant::now();
160 let current_material = cache.index_material(current.generation).ok_or_else(|| {
161 WorkspaceFailure::new(
162 WorkspaceResource::CodeIndex,
163 "code index material is unavailable",
164 )
165 })?;
166 let source_catalog = match extended_catalog {
167 Some(catalog) => cache.source_material(catalog.generation).ok_or_else(|| {
168 WorkspaceFailure::new(
169 WorkspaceResource::CodeIndex,
170 "extended source catalog material is unavailable",
171 )
172 })?,
173 None => current_material.source_catalog.clone(),
174 };
175 let mut files = current_material.files.clone();
176 let mut changed_sources = Vec::new();
177 let mut changed_file_indexes = BTreeSet::new();
178 let extract_timer = Instant::now();
179 refresh_retired_slots(RetiredSlotRefresh {
180 previous_catalog: ¤t_material.source_catalog,
181 source_catalog: &source_catalog,
182 cache_dir: options.cache_dir.as_deref(),
183 files: &mut files,
184 changed_sources: &mut changed_sources,
185 changed_file_indexes: &mut changed_file_indexes,
186 })?;
187 for file_idx in files.len()..source_catalog.sources.files.len() {
188 let file = &source_catalog.sources.files[file_idx];
189 let indexed = extract_source_file(
190 &source_catalog,
191 file_idx,
192 &file.path.clone(),
193 options.cache_dir.as_deref(),
194 )?;
195 push_unique_source(&mut changed_sources, indexed.source_id);
196 changed_file_indexes.insert(file_idx);
197 files.push(Arc::new(indexed));
198 }
199 for path in paths {
200 let Some(source) = source_catalog.resolve_source(path) else {
201 continue;
202 };
203 let Some(file_idx) = source.eager_index else {
204 continue;
205 };
206 if changed_file_indexes.contains(&file_idx) {
207 continue;
208 }
209 let indexed = extract_source_file(
210 &source_catalog,
211 file_idx,
212 &source.path,
213 options.cache_dir.as_deref(),
214 )?;
215 if let Some(slot) = files.get_mut(file_idx) {
216 push_unique_source(&mut changed_sources, indexed.source_id);
217 changed_file_indexes.insert(file_idx);
218 *slot = Arc::new(indexed);
219 }
220 }
221 let extract_sources = extract_timer.elapsed();
222 if changed_sources.is_empty() {
223 return Ok(CodeIndexRefresh {
224 index: current.clone(),
225 changed_sources,
226 graph_diff: CodeIndexGraphDiff::default(),
227 });
228 }
229 let semantic_timer = Instant::now();
230 let material = material_from_files(source_catalog, files);
231 let sources = source_records(&material.files);
232 let graph_diff = graph_diff(current_material.as_ref(), &material, &changed_file_indexes);
233 let mut symbols = current.symbols.clone();
234 let mut references = current.references.clone();
235 for file_idx in &changed_file_indexes {
236 let (file_symbols, file_references) =
237 records_for_file(*file_idx, &material.files[*file_idx]);
238 symbols.replace(*file_idx, Arc::from(file_symbols));
239 references.replace(*file_idx, Arc::from(file_references));
240 }
241 let semantic_index = semantic_timer.elapsed();
242 let generation = cache.next_generation();
243 let identity_scheme = material.identity.scheme().to_string();
244 cache.insert_index(generation, material);
245 Ok(CodeIndexRefresh {
246 index: CodeIndex {
247 generation,
248 catalog_generation: extended_catalog
249 .map(|catalog| catalog.generation)
250 .unwrap_or(current.catalog_generation),
251 identity_scheme,
252 sources,
253 symbols,
254 references,
255 timings: CodeIndexTimings {
256 extract_sources,
257 semantic_index,
258 total: total_timer.elapsed(),
259 },
260 },
261 changed_sources,
262 graph_diff,
263 })
264}
265
266struct RetiredSlotRefresh<'a> {
267 previous_catalog: &'a SourceCatalogMaterial,
268 source_catalog: &'a SourceCatalogMaterial,
269 cache_dir: Option<&'a Path>,
270 files: &'a mut Vec<Arc<IndexedSourceFile>>,
271 changed_sources: &'a mut Vec<SourceId>,
272 changed_file_indexes: &'a mut BTreeSet<usize>,
273}
274
275fn refresh_retired_slots(refresh: RetiredSlotRefresh<'_>) -> WorkspaceResult<()> {
276 let slots = refresh
277 .files
278 .len()
279 .min(refresh.source_catalog.sources.files.len());
280 for file_idx in 0..slots {
281 let was_retired = refresh.previous_catalog.sources.files[file_idx].retired;
282 let is_retired = refresh.source_catalog.sources.files[file_idx].retired;
283 if was_retired == is_retired {
284 continue;
285 }
286 let indexed = if is_retired {
287 tombstone_file(&refresh.files[file_idx])
288 } else {
289 extract_source_file(
290 refresh.source_catalog,
291 file_idx,
292 &refresh.source_catalog.sources.files[file_idx].path.clone(),
293 refresh.cache_dir,
294 )?
295 };
296 push_unique_source(refresh.changed_sources, indexed.source_id);
297 refresh.changed_file_indexes.insert(file_idx);
298 refresh.files[file_idx] = Arc::new(indexed);
299 }
300 Ok(())
301}
302
303fn tombstone_file(previous: &IndexedSourceFile) -> IndexedSourceFile {
304 IndexedSourceFile {
305 source_root: previous.source_root,
306 source_id: previous.source_id,
307 source_uri: previous.source_uri.clone(),
308 identity: previous.identity.clone(),
309 path: previous.path.clone(),
310 rel_path: previous.rel_path.clone(),
311 anchor: previous.anchor.clone(),
312 lang: previous.lang,
313 graph: code_moniker_core::core::code_graph::CodeGraph::from_records(Vec::new(), Vec::new()),
314 source: String::new(),
315 }
316}
317
318fn source_material(
319 cache: &LocalResourceCache,
320 catalog: &SourceCatalog,
321) -> WorkspaceResult<SourceCatalogMaterial> {
322 cache.source_material(catalog.generation).ok_or_else(|| {
323 WorkspaceFailure::new(
324 WorkspaceResource::CodeIndex,
325 "source catalog material is unavailable",
326 )
327 })
328}
329
330fn extract_source_files(
331 source_material: &SourceCatalogMaterial,
332 cache_dir: Option<&std::path::Path>,
333) -> WorkspaceResult<Vec<Arc<IndexedSourceFile>>> {
334 source_material
335 .sources
336 .files
337 .par_iter()
338 .enumerate()
339 .map(|(file_idx, file)| {
340 extract_source_file(source_material, file_idx, &file.path, cache_dir).map(Arc::new)
341 })
342 .collect()
343}
344
345fn extract_source_file(
346 source_material: &SourceCatalogMaterial,
347 file_idx: usize,
348 path: &Path,
349 cache_dir: Option<&Path>,
350) -> WorkspaceResult<IndexedSourceFile> {
351 let file = source_material.sources.files.get(file_idx).ok_or_else(|| {
352 WorkspaceFailure::new(
353 WorkspaceResource::CodeIndex,
354 format!("source file index {file_idx} is unavailable"),
355 )
356 })?;
357 let ctx = &source_material.sources.roots[file.source].ctx;
358 let (graph, extracted_source) =
359 environment::load_or_extract_source(path, &file.anchor, file.lang, cache_dir, ctx)
360 .map_err(|err| {
361 WorkspaceFailure::new(
362 WorkspaceResource::CodeIndex,
363 format!("cannot extract {}: {err}", path.display()),
364 )
365 })?;
366 let source = match extracted_source {
367 Some(source) => source,
368 None => std::fs::read_to_string(path).map_err(|err| {
369 WorkspaceFailure::new(
370 WorkspaceResource::CodeIndex,
371 format!("cannot read {}: {err}", path.display()),
372 )
373 })?,
374 };
375 Ok(IndexedSourceFile {
376 source_root: file.source,
377 source_id: source_material
378 .source_id_for_file(file_idx)
379 .ok_or_else(|| {
380 WorkspaceFailure::new(
381 WorkspaceResource::CodeIndex,
382 format!("source id is unavailable for {}", file.rel_path.display()),
383 )
384 })?,
385 source_uri: source_material
386 .source_uri_for_path(&file.path)
387 .ok_or_else(|| {
388 WorkspaceFailure::new(
389 WorkspaceResource::CodeIndex,
390 format!("source uri is unavailable for {}", file.path.display()),
391 )
392 })?,
393 identity: source_material.identity.clone(),
394 path: file.path.clone(),
395 rel_path: file.rel_path.clone(),
396 anchor: file.anchor.clone(),
397 lang: file.lang,
398 graph,
399 source,
400 })
401}
402
403fn build_semantic_index(
404 source_material: SourceCatalogMaterial,
405 files: Vec<Arc<IndexedSourceFile>>,
406) -> (
407 RecordTable<SymbolRecord>,
408 RecordTable<ReferenceRecord>,
409 CodeIndexMaterial,
410) {
411 let mut symbol_shards = Vec::with_capacity(files.len());
412 let mut reference_shards = Vec::with_capacity(files.len());
413 for (file_idx, file) in files.iter().enumerate() {
414 let (symbols, references) = records_for_file(file_idx, file);
415 symbol_shards.push(Arc::from(symbols));
416 reference_shards.push(Arc::from(references));
417 }
418 let material = material_from_files(source_material, files);
419 (
420 RecordTable::from_shards(symbol_shards),
421 RecordTable::from_shards(reference_shards),
422 material,
423 )
424}
425
426fn material_from_files(
427 source_material: SourceCatalogMaterial,
428 mut files: Vec<Arc<IndexedSourceFile>>,
429) -> CodeIndexMaterial {
430 let symbol_count = files.iter().map(|file| file.graph.def_count()).sum();
431 let mut symbols_by_moniker = rustc_hash::FxHashMap::default();
432 symbols_by_moniker.reserve(symbol_count);
433 for (file_idx, file) in files.iter().enumerate() {
434 for (def_idx, def) in file.graph.defs().enumerate() {
435 symbols_by_moniker.insert(
436 def.moniker.clone(),
437 file.graph_identity().symbol_id(file_idx, def_idx),
438 );
439 }
440 }
441 symbols_by_moniker.shrink_to_fit();
442 files.shrink_to_fit();
443 let identity = source_material.identity.clone();
444 CodeIndexMaterial {
445 source_catalog: source_material,
446 files,
447 identity,
448 symbols_by_moniker,
449 }
450}
451
452fn graph_diff(
453 previous: &CodeIndexMaterial,
454 next: &CodeIndexMaterial,
455 changed_files: &BTreeSet<usize>,
456) -> CodeIndexGraphDiff {
457 let mut diff = CodeIndexGraphDiff::default();
458 for file_idx in changed_files {
459 let Some(next_file) = next.files.get(*file_idx) else {
460 continue;
461 };
462 let (previous_symbols, previous_references) = match previous.files.get(*file_idx) {
463 Some(previous_file) => records_for_file(*file_idx, previous_file),
464 None => (Vec::new(), Vec::new()),
465 };
466 let (next_symbols, next_references) = records_for_file(*file_idx, next_file);
467 diff_symbols(&previous_symbols, &next_symbols, &mut diff);
468 diff_references(
469 &previous_references,
470 previous,
471 &next_references,
472 next,
473 &mut diff,
474 );
475 }
476 diff
477}
478
479fn records_for_file(
480 file_idx: usize,
481 file: &IndexedSourceFile,
482) -> (Vec<SymbolRecord>, Vec<ReferenceRecord>) {
483 let line_index = LineIndex::new(&file.source);
484 let mut symbols = Vec::with_capacity(file.graph.def_count());
485 collect_symbols(file_idx, file, &line_index, &mut symbols);
486 let mut reference_identity_pool = TargetIdentityPool::default();
487 let mut references = Vec::with_capacity(file.graph.ref_count());
488 collect_references(
489 file_idx,
490 file,
491 &line_index,
492 &mut references,
493 &mut reference_identity_pool,
494 );
495 (symbols, references)
496}
497
498fn diff_symbols(previous: &[SymbolRecord], next: &[SymbolRecord], diff: &mut CodeIndexGraphDiff) {
499 let mut next_by_key = symbol_record_indexes(next);
500 for previous_symbol in previous {
501 let key = symbol_key(previous_symbol);
502 let Some(next_idx) = pop_index(&mut next_by_key, &key) else {
503 diff.removed_symbols.push(previous_symbol.id);
504 diff.removed_symbol_identities
505 .push(previous_symbol.identity.to_string());
506 continue;
507 };
508 let next_symbol = &next[next_idx];
509 if symbol_linkage_fields_changed(previous_symbol, next_symbol) {
510 diff.modified_symbols.push(next_symbol.id);
511 diff.modified_symbol_identities
512 .push(next_symbol.identity.to_string());
513 diff.changed_symbols.push(next_symbol.id);
514 continue;
515 }
516 if previous_symbol.id != next_symbol.id {
517 diff.symbol_id_remaps
518 .push((previous_symbol.id, next_symbol.id));
519 }
520 diff.unchanged_symbols += 1;
521 }
522 for indexes in next_by_key.into_values() {
523 for idx in indexes {
524 diff.added_symbols.push(next[idx].id);
525 diff.changed_symbols.push(next[idx].id);
526 }
527 }
528}
529
530fn diff_references(
531 previous: &[ReferenceRecord],
532 previous_material: &CodeIndexMaterial,
533 next: &[ReferenceRecord],
534 next_material: &CodeIndexMaterial,
535 diff: &mut CodeIndexGraphDiff,
536) {
537 let mut next_by_key = reference_record_indexes(next, next_material);
538 for previous_reference in previous {
539 let Some(key) = reference_key(previous_reference, previous_material) else {
540 diff.removed_references.push(previous_reference.id);
541 continue;
542 };
543 let Some(next_idx) = pop_index(&mut next_by_key, &key) else {
544 diff.removed_references.push(previous_reference.id);
545 continue;
546 };
547 let next_reference = &next[next_idx];
548 if previous_reference.id != next_reference.id {
549 diff.reference_id_remaps
550 .push((previous_reference.id, next_reference.id));
551 }
552 diff.unchanged_references += 1;
553 }
554 for indexes in next_by_key.into_values() {
555 for idx in indexes {
556 diff.changed_references.push(next[idx].id);
557 }
558 }
559}
560
561fn symbol_record_indexes(records: &[SymbolRecord]) -> FxHashMap<Arc<str>, Vec<usize>> {
562 let mut by_key = FxHashMap::<Arc<str>, Vec<usize>>::default();
563 for (idx, record) in records.iter().enumerate() {
564 by_key.entry(symbol_key(record)).or_default().push(idx);
565 }
566 by_key
567}
568
569fn reference_record_indexes(
570 records: &[ReferenceRecord],
571 material: &CodeIndexMaterial,
572) -> FxHashMap<ReferenceKey, Vec<usize>> {
573 let mut by_key = FxHashMap::<ReferenceKey, Vec<usize>>::default();
574 for (idx, record) in records.iter().enumerate() {
575 if let Some(key) = reference_key(record, material) {
576 by_key.entry(key).or_default().push(idx);
577 }
578 }
579 by_key
580}
581
582fn pop_index<K: Eq + std::hash::Hash>(
583 by_key: &mut FxHashMap<K, Vec<usize>>,
584 key: &K,
585) -> Option<usize> {
586 let indexes = by_key.get_mut(key)?;
587 let idx = indexes.remove(0);
588 if indexes.is_empty() {
589 by_key.remove(key);
590 }
591 Some(idx)
592}
593
594fn symbol_key(symbol: &SymbolRecord) -> Arc<str> {
595 Arc::clone(&symbol.identity)
596}
597
598fn symbol_linkage_fields_changed(previous: &SymbolRecord, next: &SymbolRecord) -> bool {
599 previous.identity != next.identity
600 || previous.name != next.name
601 || previous.kind != next.kind
602 || previous.visibility != next.visibility
603 || previous.signature != next.signature
604 || previous.navigable != next.navigable
605}
606
607#[derive(Clone, Debug, Eq, Hash, PartialEq)]
608struct ReferenceKey {
609 source_symbol_identity: String,
610 target_identity: String,
611 kind: String,
612 call_name: Option<String>,
613 call_arity: Option<usize>,
614 confidence: Option<String>,
615 receiver: Option<String>,
616 alias: Option<String>,
617}
618
619fn reference_key(
620 reference: &ReferenceRecord,
621 material: &CodeIndexMaterial,
622) -> Option<ReferenceKey> {
623 let source_symbol_identity = material
624 .symbol_moniker(&reference.source_symbol)
625 .map(|moniker| material.identity.moniker_uri(moniker))?;
626 Some(ReferenceKey {
627 source_symbol_identity,
628 target_identity: reference.target_identity.to_string(),
629 kind: reference.kind.clone(),
630 call_name: reference.call_name.clone(),
631 call_arity: reference.call_arity,
632 confidence: reference.confidence.clone(),
633 receiver: reference.receiver.clone(),
634 alias: reference.alias.clone(),
635 })
636}
637
638fn push_unique_source(sources: &mut Vec<SourceId>, source: SourceId) {
639 if !sources.iter().any(|existing| existing == &source) {
640 sources.push(source);
641 }
642}
643
644fn collect_symbols(
645 file_idx: usize,
646 file: &IndexedSourceFile,
647 line_index: &LineIndex,
648 symbols: &mut Vec<SymbolRecord>,
649) {
650 for (def_idx, def) in file.graph.defs().enumerate() {
651 let id = file.graph_identity().symbol_id(file_idx, def_idx);
652 let parent = def
653 .parent
654 .map(|parent_idx| file.graph_identity().symbol_id(file_idx, parent_idx));
655 symbols.push(SymbolRecord {
656 id,
657 source: file.source_id,
658 identity: Arc::from(file.graph_identity().moniker_uri(&def.moniker)),
659 name: last_name(&def.moniker),
660 kind: def_kind(def),
661 visibility: def_visibility(def),
662 signature: String::from_utf8_lossy(&def.signature).to_string(),
663 navigable: is_navigable_def(file.lang, def),
664 line_range: def
665 .position
666 .map(|(start, end)| line_index.line_range(start, end)),
667 parent,
668 });
669 }
670}
671
672fn def_visibility(def: &code_moniker_core::core::code_graph::DefRecord) -> String {
673 std::str::from_utf8(&def.visibility)
674 .unwrap_or("")
675 .to_string()
676}
677
678fn collect_references(
679 file_idx: usize,
680 file: &IndexedSourceFile,
681 line_index: &LineIndex,
682 references: &mut Vec<ReferenceRecord>,
683 reference_identity_pool: &mut TargetIdentityPool,
684) {
685 for (ref_idx, reference) in file.graph.refs().enumerate() {
686 let id = file.graph_identity().reference_id(file_idx, ref_idx);
687 let source_symbol = file.graph_identity().symbol_id(file_idx, reference.source);
688 let target_identity =
689 reference_identity_pool.intern(file.graph_identity(), &reference.target);
690 references.push(
691 ReferenceRecord::new(
692 id,
693 file.source_id,
694 source_symbol,
695 target_identity,
696 ref_kind(reference),
697 reference
698 .position
699 .map(|(start, end)| line_index.line_range(start, end)),
700 )
701 .with_call_metadata(ref_attr(&reference.call_name), reference.call_arity)
702 .with_metadata(
703 ref_attr(&reference.confidence),
704 ref_attr(&reference.receiver_hint),
705 ref_attr(&reference.alias),
706 ),
707 );
708 }
709}
710
711#[derive(Default)]
712struct TargetIdentityPool {
713 values: rustc_hash::FxHashMap<Moniker, Arc<str>>,
714}
715
716impl TargetIdentityPool {
717 fn intern(&mut self, identity: &LocalIdentityResolver, target: &Moniker) -> Arc<str> {
718 if let Some(existing) = self.values.get(target) {
719 return Arc::clone(existing);
720 }
721 let shared = Arc::<str>::from(identity.moniker_uri(target));
722 self.values.insert(target.clone(), Arc::clone(&shared));
723 shared
724 }
725}
726
727fn source_records(files: &[Arc<IndexedSourceFile>]) -> Vec<SourceFileRecord> {
728 files
729 .iter()
730 .map(|file| SourceFileRecord {
731 id: file.source_id,
732 uri: file.source_uri.clone(),
733 source_root: file.source_root,
734 path: file.path.display().to_string(),
735 rel_path: file.rel_path.display().to_string(),
736 anchor: file.anchor.display().to_string(),
737 language: file.lang.tag().to_string(),
738 text: String::new(),
739 })
740 .collect()
741}
742
743fn ref_attr(bytes: &[u8]) -> Option<String> {
744 if bytes.is_empty() {
745 return None;
746 }
747 std::str::from_utf8(bytes)
748 .ok()
749 .filter(|value| !value.is_empty())
750 .map(ToOwned::to_owned)
751}
752
753trait IndexedSourceIdentity {
754 fn graph_identity(&self) -> &LocalIdentityResolver;
755}
756
757impl IndexedSourceIdentity for IndexedSourceFile {
758 fn graph_identity(&self) -> &LocalIdentityResolver {
759 &self.identity
760 }
761}