1use crate::analyzer::code_unit_index::CodeUnitIndex;
2use crate::analyzer::model::{CodeUnit, ImportInfo, ProjectFile};
3use crate::analyzer::pool_memo::{KeyedPoolSafeMemo, PoolSafeMemo};
4use crate::analyzer::query_token::QueryToken;
5use crate::cancellation::CancellationToken;
6use crate::compact_graph::{CompactRows, CompactRowsBuilder};
7use crate::hash::{HashMap, HashSet};
8use std::any::Any;
9use std::collections::{BTreeSet, VecDeque};
10use std::sync::Arc;
11
12use rayon::prelude::*;
13
14pub trait CapabilityProvider: Any {
15 fn as_any(&self) -> &dyn Any;
16}
17
18impl<T: Any> CapabilityProvider for T {
19 fn as_any(&self) -> &dyn Any {
20 self
21 }
22}
23
24pub trait ImportAnalysisProvider: CapabilityProvider + Send + Sync {
25 fn imported_code_units_of(&self, file: &ProjectFile) -> Arc<HashSet<CodeUnit>>;
31 fn referencing_files_of(&self, file: &ProjectFile) -> HashSet<ProjectFile>;
32
33 fn referencing_files_of_targets(
41 &self,
42 targets: &HashSet<ProjectFile>,
43 cancellation: &CancellationToken,
44 ) -> HashSet<ProjectFile> {
45 let mut referencing = HashSet::default();
46 for target in targets {
47 if cancellation.is_cancelled() {
48 break;
49 }
50 referencing.extend(self.referencing_files_of(target));
51 }
52 referencing
53 }
54
55 fn import_infos_for_files(
59 &self,
60 _files: &[ProjectFile],
61 ) -> Option<HashMap<ProjectFile, Vec<ImportInfo>>> {
62 None
63 }
64
65 fn import_info_of(&self, _token: QueryToken<'_>, _file: &ProjectFile) -> Vec<ImportInfo> {
70 Vec::new()
71 }
72
73 fn imported_code_units_from_infos(
76 &self,
77 _file: &ProjectFile,
78 _imports: &[ImportInfo],
79 ) -> Option<Arc<HashSet<CodeUnit>>> {
80 None
81 }
82
83 fn imported_files_from_infos(
87 &self,
88 _file: &ProjectFile,
89 _imports: &[ImportInfo],
90 ) -> Option<HashSet<ProjectFile>> {
91 None
92 }
93
94 fn relevant_imports_for(&self, _code_unit: &CodeUnit) -> HashSet<String> {
95 HashSet::default()
96 }
97
98 fn could_import_file(
104 &self,
105 _source_file: &ProjectFile,
106 _imports: &[ImportInfo],
107 _target: &ProjectFile,
108 ) -> bool {
109 false
110 }
111
112 fn prefetch_import_targets(
129 &self,
130 _files: &[ProjectFile],
131 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
132 _cancellation: &crate::cancellation::CancellationToken,
133 ) {
134 }
135
136 fn import_reachability(
149 &self,
150 source_file: &ProjectFile,
151 imports: &[ImportInfo],
152 target: &ProjectFile,
153 ) -> ImportReachability {
154 if self.could_import_file(source_file, imports, target) {
155 ImportReachability::Reaches
156 } else {
157 ImportReachability::Unknown
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum ImportReachability {
178 Reaches,
180 DoesNotReach,
182 Unknown,
185}
186
187pub fn resolve_imported_files_from_infos(
192 provider: &dyn ImportAnalysisProvider,
193 file: &ProjectFile,
194 imports: &[ImportInfo],
195) -> HashSet<ProjectFile> {
196 provider
197 .imported_files_from_infos(file, imports)
198 .unwrap_or_else(|| {
199 provider
200 .imported_code_units_from_infos(file, imports)
201 .unwrap_or_else(|| provider.imported_code_units_of(file))
202 .iter()
203 .map(|unit| unit.source().clone())
204 .collect()
205 })
206}
207
208pub fn build_reverse_import_index<F>(
209 files: &[ProjectFile],
210 resolve_imported: F,
211 parallel: bool,
212) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
213where
214 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync,
215{
216 build_reverse_file_index(
217 files,
218 |file| {
219 resolve_imported(file)
220 .iter()
221 .map(|code_unit| code_unit.source().clone())
222 .collect::<Vec<_>>()
223 },
224 parallel,
225 )
226}
227
228pub type ReverseFileIndex = HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>;
229
230pub fn memoized_reverse_import_index<F, Files>(
231 memo: &PoolSafeMemo<ReverseFileIndex>,
232 files: Files,
233 resolve_imported: F,
234) -> Arc<ReverseFileIndex>
235where
236 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync + Copy,
237 Files: Fn() -> Vec<ProjectFile> + Copy,
238{
239 memoized_reverse_file_index(memo, files, |file| {
240 resolve_imported(file)
241 .iter()
242 .map(|code_unit| code_unit.source().clone())
243 .collect::<Vec<_>>()
244 })
245}
246
247pub fn memoized_reverse_file_index<F, I, Files>(
248 memo: &PoolSafeMemo<ReverseFileIndex>,
249 files: Files,
250 resolve_targets: F,
251) -> Arc<ReverseFileIndex>
252where
253 F: Fn(&ProjectFile) -> I + Sync + Copy,
254 I: IntoIterator<Item = ProjectFile>,
255 Files: Fn() -> Vec<ProjectFile> + Copy,
256{
257 memo.get_or_build(
258 || {
259 let files = files();
260 build_reverse_file_index(&files, resolve_targets, true)
261 },
262 || {
263 let files = files();
264 build_reverse_file_index(&files, resolve_targets, false)
265 },
266 )
267}
268
269pub fn build_reverse_file_index<F, I>(
270 files: &[ProjectFile],
271 resolve_targets: F,
272 parallel: bool,
273) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
274where
275 F: Fn(&ProjectFile) -> I + Sync,
276 I: IntoIterator<Item = ProjectFile>,
277{
278 let collect_edges = |file: &ProjectFile| {
279 let source = file.clone();
280 resolve_targets(file)
281 .into_iter()
282 .filter_map(move |target| (target != source).then(|| (target, source.clone())))
283 .collect::<Vec<_>>()
284 };
285 let edges: Vec<_> = if parallel {
286 files.par_iter().flat_map(collect_edges).collect()
287 } else {
288 files.iter().flat_map(collect_edges).collect()
289 };
290
291 let mut reverse: HashMap<ProjectFile, HashSet<ProjectFile>> = HashMap::default();
292 for (target, source) in edges {
293 reverse.entry(target).or_default().insert(source);
294 }
295 reverse
296 .into_iter()
297 .map(|(file, refs)| (file, Arc::new(refs)))
298 .collect()
299}
300
301pub trait TypeAliasProvider: CapabilityProvider + Send + Sync {
305 fn is_type_alias(&self, _code_unit: &CodeUnit) -> bool {
306 false
307 }
308}
309
310pub trait TestDetectionProvider: CapabilityProvider {}
311
312#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
318pub enum DescendantIndexVariant {
319 WholeWorkspace,
321 ProductionOnly,
325}
326
327#[derive(Clone, Copy)]
344pub struct DescendantIndexScope<'a> {
345 cancellation: &'a CancellationToken,
346 excluded_source: Option<&'a dyn Fn(&ProjectFile) -> bool>,
347}
348
349impl<'a> DescendantIndexScope<'a> {
350 pub fn whole_workspace(cancellation: &'a CancellationToken) -> Self {
352 Self {
353 cancellation,
354 excluded_source: None,
355 }
356 }
357
358 pub fn excluding_sources(
362 cancellation: &'a CancellationToken,
363 excluded: &'a dyn Fn(&ProjectFile) -> bool,
364 ) -> Self {
365 Self {
366 cancellation,
367 excluded_source: Some(excluded),
368 }
369 }
370
371 pub fn cancellation(&self) -> &'a CancellationToken {
372 self.cancellation
373 }
374
375 pub fn variant(&self) -> DescendantIndexVariant {
376 match self.excluded_source {
377 Some(_) => DescendantIndexVariant::ProductionOnly,
378 None => DescendantIndexVariant::WholeWorkspace,
379 }
380 }
381
382 pub fn keep_going(&self) -> impl Fn() -> bool + use<'_> {
385 || !self.cancellation.is_cancelled()
386 }
387
388 pub fn admits(&self, declaration: &CodeUnit) -> bool {
390 self.excluded_source
391 .is_none_or(|excluded| !excluded(declaration.source()))
392 }
393}
394
395pub trait TypeHierarchyProvider: CapabilityProvider + Send + Sync {
396 fn get_direct_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit>;
397 fn get_direct_descendants(&self, code_unit: &CodeUnit) -> HashSet<CodeUnit>;
398
399 fn supports_type_hierarchy(&self, code_unit: &CodeUnit) -> bool {
400 code_unit.is_class()
401 }
402
403 fn get_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
404 traverse_hierarchy(code_unit, |next| self.get_direct_ancestors(next))
405 }
406
407 fn get_descendants(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
408 traverse_hierarchy(code_unit, |next| {
409 self.get_direct_descendants(next).into_iter().collect()
410 })
411 }
412
413 fn get_direct_ancestors_within(
422 &self,
423 code_unit: &CodeUnit,
424 scope: &DescendantIndexScope<'_>,
425 ) -> Option<Vec<CodeUnit>> {
426 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_ancestors(code_unit))
427 }
428
429 fn get_direct_descendants_within(
448 &self,
449 code_unit: &CodeUnit,
450 scope: &DescendantIndexScope<'_>,
451 ) -> Option<HashSet<CodeUnit>> {
452 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_descendants(code_unit))
453 }
454
455 fn get_descendants_within(
459 &self,
460 code_unit: &CodeUnit,
461 scope: &DescendantIndexScope<'_>,
462 ) -> Option<Vec<CodeUnit>> {
463 traverse_hierarchy_while(code_unit, scope.cancellation(), |next| {
464 self.get_direct_descendants_within(next, scope)
465 .map(|descendants| descendants.into_iter().collect())
466 })
467 }
468
469 fn get_polymorphic_matches<T: CodeUnitIndex>(
470 &self,
471 target: &CodeUnit,
472 analyzer: &T,
473 ) -> Vec<CodeUnit>
474 where
475 Self: Sized,
476 {
477 if !target.is_function() {
478 return Vec::new();
479 }
480
481 let Some(parent) = analyzer.parent_of(target) else {
482 return Vec::new();
483 };
484
485 self.get_descendants(&parent)
486 }
487}
488
489pub struct DirectDescendantIndex {
491 nodes: Box<[CodeUnit]>,
492 row_by_ancestor: HashMap<CodeUnit, u32>,
493 descendants: CompactRows<u32>,
494}
495
496impl DirectDescendantIndex {
497 pub fn from_indexed_nodes(
498 nodes: Vec<CodeUnit>,
499 index_by_node: HashMap<CodeUnit, u32>,
500 mut edges: Vec<(u32, u32)>,
501 ) -> Self {
502 assert_eq!(nodes.len(), index_by_node.len());
503 assert!(nodes.iter().enumerate().all(|(index, node)| {
504 index_by_node.get(node).copied()
505 == Some(
506 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
507 )
508 }));
509 assert!(edges.iter().all(|(ancestor, descendant)| {
510 (*ancestor as usize) < nodes.len() && (*descendant as usize) < nodes.len()
511 }));
512 edges.sort_unstable();
513 edges.dedup();
514
515 let row_count = usize::from(!edges.is_empty())
516 + edges
517 .windows(2)
518 .filter(|pair| pair[0].0 != pair[1].0)
519 .count();
520 let mut row_by_ancestor = HashMap::default();
521 let mut descendants = CompactRowsBuilder::with_capacity(row_count, edges.len());
522 let mut cursor = 0;
523 while cursor < edges.len() {
524 let ancestor = edges[cursor].0;
525 let start = cursor;
526 while cursor < edges.len() && edges[cursor].0 == ancestor {
527 cursor += 1;
528 }
529 let row =
530 u32::try_from(descendants.rows()).expect("hierarchy index rows must fit in a u32");
531 row_by_ancestor.insert(nodes[ancestor as usize].clone(), row);
532 descendants.push_row(
533 edges[start..cursor]
534 .iter()
535 .map(|(_, descendant)| *descendant),
536 );
537 }
538 Self {
539 nodes: nodes.into_boxed_slice(),
540 row_by_ancestor,
541 descendants: descendants.finish(),
542 }
543 }
544
545 pub fn descendants(&self, ancestor: &CodeUnit) -> HashSet<CodeUnit> {
546 let Some(row) = self.row_by_ancestor.get(ancestor).copied() else {
547 return HashSet::default();
548 };
549 self.descendants
550 .row(row as usize)
551 .iter()
552 .map(|descendant| self.nodes[*descendant as usize].clone())
553 .collect()
554 }
555}
556
557pub fn descendants_from_variant_index(
566 index: &KeyedPoolSafeMemo<DescendantIndexVariant, DirectDescendantIndex>,
567 scope: &DescendantIndexScope<'_>,
568 code_unit: &CodeUnit,
569 build: impl Fn() -> Option<DirectDescendantIndex>,
570) -> Option<HashSet<CodeUnit>> {
571 Some(
572 index
573 .cell(&scope.variant())
574 .get_or_build_while(&scope.keep_going(), &build, &build)?
577 .descendants(code_unit),
578 )
579}
580
581pub fn build_direct_descendant_index<A, P>(
589 analyzer: &A,
590 provider: &P,
591 scope: &DescendantIndexScope<'_>,
592) -> Option<DirectDescendantIndex>
593where
594 A: CodeUnitIndex,
595 P: TypeHierarchyProvider + ?Sized,
596{
597 build_direct_descendant_index_from_candidates(
598 analyzer
599 .all_declarations()
600 .filter(|candidate| candidate.is_class() && scope.admits(candidate))
601 .collect(),
602 |candidate| provider.get_direct_ancestors_within(candidate, scope),
603 &scope.keep_going(),
604 )
605}
606
607pub fn build_direct_descendant_index_from_candidates<F>(
617 mut candidates: Vec<CodeUnit>,
618 mut direct_ancestors: F,
619 keep_going: &dyn Fn() -> bool,
620) -> Option<DirectDescendantIndex>
621where
622 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
623{
624 candidates.sort();
625 candidates.dedup();
626 let mut types_by_fq_name: HashMap<String, Vec<CodeUnit>> = HashMap::default();
627 for candidate in &candidates {
628 types_by_fq_name
629 .entry(candidate.fq_name())
630 .or_default()
631 .push(candidate.clone());
632 }
633 let mut nodes = candidates.clone();
634 let mut index_by_node: HashMap<_, _> = nodes
635 .iter()
636 .enumerate()
637 .map(|(index, node)| {
638 (
639 node.clone(),
640 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
641 )
642 })
643 .collect();
644 let mut edges = Vec::new();
645 for candidate in candidates {
646 if !keep_going() {
647 return None;
648 }
649 let descendant = index_by_node[&candidate];
650 for ancestor in direct_ancestors(&candidate)? {
651 let ancestor = types_by_fq_name
652 .get(&ancestor.fq_name())
653 .and_then(|same_name| {
654 let mut same_source = same_name
655 .iter()
656 .filter(|unit| unit.source() == candidate.source());
657 let exact = same_source.next()?;
658 same_source.next().is_none().then(|| exact.clone())
659 })
660 .unwrap_or(ancestor);
661 let ancestor = *index_by_node.entry(ancestor.clone()).or_insert_with(|| {
662 let index = u32::try_from(nodes.len())
663 .expect("hierarchy index declarations must fit in a u32");
664 nodes.push(ancestor);
665 index
666 });
667 edges.push((ancestor, descendant));
668 }
669 }
670 Some(DirectDescendantIndex::from_indexed_nodes(
671 nodes,
672 index_by_node,
673 edges,
674 ))
675}
676
677fn traverse_hierarchy_while<F>(
682 root: &CodeUnit,
683 cancellation: &CancellationToken,
684 mut next: F,
685) -> Option<Vec<CodeUnit>>
686where
687 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
688{
689 let direct = next(root)?;
690 if direct.is_empty() {
691 return Some(Vec::new());
692 }
693
694 let mut seen = BTreeSet::new();
695 let mut result = Vec::new();
696 let mut queue = VecDeque::new();
697
698 for item in direct {
699 if seen.insert(item.fq_name()) {
700 queue.push_back(item.clone());
701 result.push(item);
702 }
703 }
704
705 while let Some(current) = queue.pop_front() {
706 if cancellation.is_cancelled() {
707 return None;
708 }
709 for item in next(¤t)? {
710 if seen.insert(item.fq_name()) {
711 queue.push_back(item.clone());
712 result.push(item);
713 }
714 }
715 }
716
717 Some(result)
718}
719
720fn traverse_hierarchy<F>(root: &CodeUnit, mut next: F) -> Vec<CodeUnit>
721where
722 F: FnMut(&CodeUnit) -> Vec<CodeUnit>,
723{
724 let direct = next(root);
725 if direct.is_empty() {
726 return Vec::new();
727 }
728
729 let mut seen = BTreeSet::new();
730 let mut result = Vec::new();
731 let mut queue = VecDeque::new();
732
733 for item in direct {
734 if seen.insert(item.fq_name()) {
735 queue.push_back(item.clone());
736 result.push(item);
737 }
738 }
739
740 while let Some(current) = queue.pop_front() {
741 for item in next(¤t) {
742 if seen.insert(item.fq_name()) {
743 queue.push_back(item.clone());
744 result.push(item);
745 }
746 }
747 }
748
749 result
750}