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(
42 &self,
43 targets: &HashSet<ProjectFile>,
44 candidates: &[ProjectFile],
45 cancellation: &CancellationToken,
46 ) -> HashSet<ProjectFile> {
47 let candidate_set: HashSet<ProjectFile> = candidates.iter().cloned().collect();
48 let mut referencing = HashSet::default();
49 for target in targets {
50 if cancellation.is_cancelled() {
51 break;
52 }
53 referencing.extend(
54 self.referencing_files_of(target)
55 .into_iter()
56 .filter(|file| candidate_set.contains(file)),
57 );
58 }
59 referencing
60 }
61
62 fn import_infos_for_files(
66 &self,
67 _files: &[ProjectFile],
68 ) -> Option<HashMap<ProjectFile, Vec<ImportInfo>>> {
69 None
70 }
71
72 fn import_info_of(&self, _token: QueryToken<'_>, _file: &ProjectFile) -> Vec<ImportInfo> {
77 Vec::new()
78 }
79
80 fn imported_code_units_from_infos(
83 &self,
84 _file: &ProjectFile,
85 _imports: &[ImportInfo],
86 ) -> Option<Arc<HashSet<CodeUnit>>> {
87 None
88 }
89
90 fn imported_files_from_infos(
94 &self,
95 _file: &ProjectFile,
96 _imports: &[ImportInfo],
97 ) -> Option<HashSet<ProjectFile>> {
98 None
99 }
100
101 fn relevant_imports_for(&self, _code_unit: &CodeUnit) -> HashSet<String> {
102 HashSet::default()
103 }
104
105 fn could_import_file(
111 &self,
112 _source_file: &ProjectFile,
113 _imports: &[ImportInfo],
114 _target: &ProjectFile,
115 ) -> bool {
116 false
117 }
118
119 fn prefetch_import_targets(
136 &self,
137 _files: &[ProjectFile],
138 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
139 _cancellation: &crate::cancellation::CancellationToken,
140 ) {
141 }
142
143 fn import_reachability(
156 &self,
157 source_file: &ProjectFile,
158 imports: &[ImportInfo],
159 target: &ProjectFile,
160 ) -> ImportReachability {
161 if self.could_import_file(source_file, imports, target) {
162 ImportReachability::Reaches
163 } else {
164 ImportReachability::Unknown
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum ImportReachability {
185 Reaches,
187 DoesNotReach,
189 Unknown,
192}
193
194pub fn resolve_imported_files_from_infos(
199 provider: &dyn ImportAnalysisProvider,
200 file: &ProjectFile,
201 imports: &[ImportInfo],
202) -> HashSet<ProjectFile> {
203 provider
204 .imported_files_from_infos(file, imports)
205 .unwrap_or_else(|| {
206 provider
207 .imported_code_units_from_infos(file, imports)
208 .unwrap_or_else(|| provider.imported_code_units_of(file))
209 .iter()
210 .map(|unit| unit.source().clone())
211 .collect()
212 })
213}
214
215pub fn build_reverse_import_index<F>(
216 files: &[ProjectFile],
217 resolve_imported: F,
218 parallel: bool,
219) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
220where
221 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync,
222{
223 build_reverse_file_index(
224 files,
225 |file| {
226 resolve_imported(file)
227 .iter()
228 .map(|code_unit| code_unit.source().clone())
229 .collect::<Vec<_>>()
230 },
231 parallel,
232 )
233}
234
235pub type ReverseFileIndex = HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>;
236
237pub fn memoized_reverse_import_index<F, Files>(
238 memo: &PoolSafeMemo<ReverseFileIndex>,
239 files: Files,
240 resolve_imported: F,
241) -> Arc<ReverseFileIndex>
242where
243 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync + Copy,
244 Files: Fn() -> Vec<ProjectFile> + Copy,
245{
246 memoized_reverse_file_index(memo, files, |file| {
247 resolve_imported(file)
248 .iter()
249 .map(|code_unit| code_unit.source().clone())
250 .collect::<Vec<_>>()
251 })
252}
253
254pub fn memoized_reverse_file_index<F, I, Files>(
255 memo: &PoolSafeMemo<ReverseFileIndex>,
256 files: Files,
257 resolve_targets: F,
258) -> Arc<ReverseFileIndex>
259where
260 F: Fn(&ProjectFile) -> I + Sync + Copy,
261 I: IntoIterator<Item = ProjectFile>,
262 Files: Fn() -> Vec<ProjectFile> + Copy,
263{
264 memo.get_or_build(
265 || {
266 let files = files();
267 build_reverse_file_index(&files, resolve_targets, true)
268 },
269 || {
270 let files = files();
271 build_reverse_file_index(&files, resolve_targets, false)
272 },
273 )
274}
275
276pub fn build_reverse_file_index<F, I>(
277 files: &[ProjectFile],
278 resolve_targets: F,
279 parallel: bool,
280) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
281where
282 F: Fn(&ProjectFile) -> I + Sync,
283 I: IntoIterator<Item = ProjectFile>,
284{
285 let collect_edges = |file: &ProjectFile| {
286 let source = file.clone();
287 resolve_targets(file)
288 .into_iter()
289 .filter_map(move |target| (target != source).then(|| (target, source.clone())))
290 .collect::<Vec<_>>()
291 };
292 let edges: Vec<_> = if parallel {
293 files.par_iter().flat_map(collect_edges).collect()
294 } else {
295 files.iter().flat_map(collect_edges).collect()
296 };
297
298 let mut reverse: HashMap<ProjectFile, HashSet<ProjectFile>> = HashMap::default();
299 for (target, source) in edges {
300 reverse.entry(target).or_default().insert(source);
301 }
302 reverse
303 .into_iter()
304 .map(|(file, refs)| (file, Arc::new(refs)))
305 .collect()
306}
307
308pub trait TypeAliasProvider: CapabilityProvider + Send + Sync {
312 fn is_type_alias(&self, _code_unit: &CodeUnit) -> bool {
313 false
314 }
315}
316
317pub trait TestDetectionProvider: CapabilityProvider {}
318
319#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
325pub enum DescendantIndexVariant {
326 WholeWorkspace,
328 ProductionOnly,
332}
333
334#[derive(Clone, Copy)]
351pub struct DescendantIndexScope<'a> {
352 cancellation: &'a CancellationToken,
353 excluded_source: Option<&'a dyn Fn(&ProjectFile) -> bool>,
354}
355
356impl<'a> DescendantIndexScope<'a> {
357 pub fn whole_workspace(cancellation: &'a CancellationToken) -> Self {
359 Self {
360 cancellation,
361 excluded_source: None,
362 }
363 }
364
365 pub fn excluding_sources(
369 cancellation: &'a CancellationToken,
370 excluded: &'a dyn Fn(&ProjectFile) -> bool,
371 ) -> Self {
372 Self {
373 cancellation,
374 excluded_source: Some(excluded),
375 }
376 }
377
378 pub fn cancellation(&self) -> &'a CancellationToken {
379 self.cancellation
380 }
381
382 pub fn variant(&self) -> DescendantIndexVariant {
383 match self.excluded_source {
384 Some(_) => DescendantIndexVariant::ProductionOnly,
385 None => DescendantIndexVariant::WholeWorkspace,
386 }
387 }
388
389 pub fn keep_going(&self) -> impl Fn() -> bool + use<'_> {
392 || !self.cancellation.is_cancelled()
393 }
394
395 pub fn admits(&self, declaration: &CodeUnit) -> bool {
397 self.excluded_source
398 .is_none_or(|excluded| !excluded(declaration.source()))
399 }
400}
401
402pub trait TypeHierarchyProvider: CapabilityProvider + Send + Sync {
403 fn get_direct_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit>;
404 fn get_direct_descendants(&self, code_unit: &CodeUnit) -> HashSet<CodeUnit>;
405
406 fn supports_type_hierarchy(&self, code_unit: &CodeUnit) -> bool {
407 code_unit.is_class()
408 }
409
410 fn get_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
411 traverse_hierarchy(code_unit, |next| self.get_direct_ancestors(next))
412 }
413
414 fn get_descendants(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
415 traverse_hierarchy(code_unit, |next| {
416 self.get_direct_descendants(next).into_iter().collect()
417 })
418 }
419
420 fn get_direct_ancestors_within(
429 &self,
430 code_unit: &CodeUnit,
431 scope: &DescendantIndexScope<'_>,
432 ) -> Option<Vec<CodeUnit>> {
433 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_ancestors(code_unit))
434 }
435
436 fn get_direct_descendants_within(
455 &self,
456 code_unit: &CodeUnit,
457 scope: &DescendantIndexScope<'_>,
458 ) -> Option<HashSet<CodeUnit>> {
459 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_descendants(code_unit))
460 }
461
462 fn get_descendants_within(
466 &self,
467 code_unit: &CodeUnit,
468 scope: &DescendantIndexScope<'_>,
469 ) -> Option<Vec<CodeUnit>> {
470 traverse_hierarchy_while(code_unit, scope.cancellation(), |next| {
471 self.get_direct_descendants_within(next, scope)
472 .map(|descendants| descendants.into_iter().collect())
473 })
474 }
475
476 fn get_polymorphic_matches<T: CodeUnitIndex>(
477 &self,
478 target: &CodeUnit,
479 analyzer: &T,
480 ) -> Vec<CodeUnit>
481 where
482 Self: Sized,
483 {
484 if !target.is_function() {
485 return Vec::new();
486 }
487
488 let Some(parent) = analyzer.parent_of(target) else {
489 return Vec::new();
490 };
491
492 self.get_descendants(&parent)
493 }
494}
495
496pub struct DirectDescendantIndex {
498 nodes: Box<[CodeUnit]>,
499 row_by_ancestor: HashMap<CodeUnit, u32>,
500 descendants: CompactRows<u32>,
501}
502
503impl DirectDescendantIndex {
504 pub fn from_indexed_nodes(
505 nodes: Vec<CodeUnit>,
506 index_by_node: HashMap<CodeUnit, u32>,
507 mut edges: Vec<(u32, u32)>,
508 ) -> Self {
509 assert_eq!(nodes.len(), index_by_node.len());
510 assert!(nodes.iter().enumerate().all(|(index, node)| {
511 index_by_node.get(node).copied()
512 == Some(
513 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
514 )
515 }));
516 assert!(edges.iter().all(|(ancestor, descendant)| {
517 (*ancestor as usize) < nodes.len() && (*descendant as usize) < nodes.len()
518 }));
519 edges.sort_unstable();
520 edges.dedup();
521
522 let row_count = usize::from(!edges.is_empty())
523 + edges
524 .windows(2)
525 .filter(|pair| pair[0].0 != pair[1].0)
526 .count();
527 let mut row_by_ancestor = HashMap::default();
528 let mut descendants = CompactRowsBuilder::with_capacity(row_count, edges.len());
529 let mut cursor = 0;
530 while cursor < edges.len() {
531 let ancestor = edges[cursor].0;
532 let start = cursor;
533 while cursor < edges.len() && edges[cursor].0 == ancestor {
534 cursor += 1;
535 }
536 let row =
537 u32::try_from(descendants.rows()).expect("hierarchy index rows must fit in a u32");
538 row_by_ancestor.insert(nodes[ancestor as usize].clone(), row);
539 descendants.push_row(
540 edges[start..cursor]
541 .iter()
542 .map(|(_, descendant)| *descendant),
543 );
544 }
545 Self {
546 nodes: nodes.into_boxed_slice(),
547 row_by_ancestor,
548 descendants: descendants.finish(),
549 }
550 }
551
552 pub fn descendants(&self, ancestor: &CodeUnit) -> HashSet<CodeUnit> {
553 let Some(row) = self.row_by_ancestor.get(ancestor).copied() else {
554 return HashSet::default();
555 };
556 self.descendants
557 .row(row as usize)
558 .iter()
559 .map(|descendant| self.nodes[*descendant as usize].clone())
560 .collect()
561 }
562}
563
564pub fn descendants_from_variant_index(
573 index: &KeyedPoolSafeMemo<DescendantIndexVariant, DirectDescendantIndex>,
574 scope: &DescendantIndexScope<'_>,
575 code_unit: &CodeUnit,
576 build: impl Fn() -> Option<DirectDescendantIndex>,
577) -> Option<HashSet<CodeUnit>> {
578 Some(
579 index
580 .cell(&scope.variant())
581 .get_or_build_while(&scope.keep_going(), &build, &build)?
584 .descendants(code_unit),
585 )
586}
587
588pub fn build_direct_descendant_index<A, P>(
596 analyzer: &A,
597 provider: &P,
598 scope: &DescendantIndexScope<'_>,
599) -> Option<DirectDescendantIndex>
600where
601 A: CodeUnitIndex,
602 P: TypeHierarchyProvider + ?Sized,
603{
604 build_direct_descendant_index_from_candidates(
605 analyzer
606 .all_declarations()
607 .filter(|candidate| candidate.is_class() && scope.admits(candidate))
608 .collect(),
609 |candidate| provider.get_direct_ancestors_within(candidate, scope),
610 &scope.keep_going(),
611 )
612}
613
614pub fn build_direct_descendant_index_from_candidates<F>(
624 mut candidates: Vec<CodeUnit>,
625 mut direct_ancestors: F,
626 keep_going: &dyn Fn() -> bool,
627) -> Option<DirectDescendantIndex>
628where
629 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
630{
631 candidates.sort();
632 candidates.dedup();
633 let mut types_by_fq_name: HashMap<String, Vec<CodeUnit>> = HashMap::default();
634 for candidate in &candidates {
635 types_by_fq_name
636 .entry(candidate.fq_name())
637 .or_default()
638 .push(candidate.clone());
639 }
640 let mut nodes = candidates.clone();
641 let mut index_by_node: HashMap<_, _> = nodes
642 .iter()
643 .enumerate()
644 .map(|(index, node)| {
645 (
646 node.clone(),
647 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
648 )
649 })
650 .collect();
651 let mut edges = Vec::new();
652 for candidate in candidates {
653 if !keep_going() {
654 return None;
655 }
656 let descendant = index_by_node[&candidate];
657 for ancestor in direct_ancestors(&candidate)? {
658 let ancestor = types_by_fq_name
659 .get(&ancestor.fq_name())
660 .and_then(|same_name| {
661 let mut same_source = same_name
662 .iter()
663 .filter(|unit| unit.source() == candidate.source());
664 let exact = same_source.next()?;
665 same_source.next().is_none().then(|| exact.clone())
666 })
667 .unwrap_or(ancestor);
668 let ancestor = *index_by_node.entry(ancestor.clone()).or_insert_with(|| {
669 let index = u32::try_from(nodes.len())
670 .expect("hierarchy index declarations must fit in a u32");
671 nodes.push(ancestor);
672 index
673 });
674 edges.push((ancestor, descendant));
675 }
676 }
677 Some(DirectDescendantIndex::from_indexed_nodes(
678 nodes,
679 index_by_node,
680 edges,
681 ))
682}
683
684fn traverse_hierarchy_while<F>(
689 root: &CodeUnit,
690 cancellation: &CancellationToken,
691 mut next: F,
692) -> Option<Vec<CodeUnit>>
693where
694 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
695{
696 let direct = next(root)?;
697 if direct.is_empty() {
698 return Some(Vec::new());
699 }
700
701 let mut seen = BTreeSet::new();
702 let mut result = Vec::new();
703 let mut queue = VecDeque::new();
704
705 for item in direct {
706 if seen.insert(item.fq_name()) {
707 queue.push_back(item.clone());
708 result.push(item);
709 }
710 }
711
712 while let Some(current) = queue.pop_front() {
713 if cancellation.is_cancelled() {
714 return None;
715 }
716 for item in next(¤t)? {
717 if seen.insert(item.fq_name()) {
718 queue.push_back(item.clone());
719 result.push(item);
720 }
721 }
722 }
723
724 Some(result)
725}
726
727fn traverse_hierarchy<F>(root: &CodeUnit, mut next: F) -> Vec<CodeUnit>
728where
729 F: FnMut(&CodeUnit) -> Vec<CodeUnit>,
730{
731 let direct = next(root);
732 if direct.is_empty() {
733 return Vec::new();
734 }
735
736 let mut seen = BTreeSet::new();
737 let mut result = Vec::new();
738 let mut queue = VecDeque::new();
739
740 for item in direct {
741 if seen.insert(item.fq_name()) {
742 queue.push_back(item.clone());
743 result.push(item);
744 }
745 }
746
747 while let Some(current) = queue.pop_front() {
748 for item in next(¤t) {
749 if seen.insert(item.fq_name()) {
750 queue.push_back(item.clone());
751 result.push(item);
752 }
753 }
754 }
755
756 result
757}