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 import_infos_for_files(
37 &self,
38 _files: &[ProjectFile],
39 ) -> Option<HashMap<ProjectFile, Vec<ImportInfo>>> {
40 None
41 }
42
43 fn import_info_of(&self, _token: QueryToken<'_>, _file: &ProjectFile) -> Vec<ImportInfo> {
48 Vec::new()
49 }
50
51 fn imported_code_units_from_infos(
54 &self,
55 _file: &ProjectFile,
56 _imports: &[ImportInfo],
57 ) -> Option<Arc<HashSet<CodeUnit>>> {
58 None
59 }
60
61 fn imported_files_from_infos(
65 &self,
66 _file: &ProjectFile,
67 _imports: &[ImportInfo],
68 ) -> Option<HashSet<ProjectFile>> {
69 None
70 }
71
72 fn relevant_imports_for(&self, _code_unit: &CodeUnit) -> HashSet<String> {
73 HashSet::default()
74 }
75
76 fn could_import_file(
82 &self,
83 _source_file: &ProjectFile,
84 _imports: &[ImportInfo],
85 _target: &ProjectFile,
86 ) -> bool {
87 false
88 }
89
90 fn prefetch_import_targets(
107 &self,
108 _files: &[ProjectFile],
109 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
110 _cancellation: &crate::cancellation::CancellationToken,
111 ) {
112 }
113
114 fn import_reachability(
127 &self,
128 source_file: &ProjectFile,
129 imports: &[ImportInfo],
130 target: &ProjectFile,
131 ) -> ImportReachability {
132 if self.could_import_file(source_file, imports, target) {
133 ImportReachability::Reaches
134 } else {
135 ImportReachability::Unknown
136 }
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub enum ImportReachability {
156 Reaches,
158 DoesNotReach,
160 Unknown,
163}
164
165pub fn resolve_imported_files_from_infos(
170 provider: &dyn ImportAnalysisProvider,
171 file: &ProjectFile,
172 imports: &[ImportInfo],
173) -> HashSet<ProjectFile> {
174 provider
175 .imported_files_from_infos(file, imports)
176 .unwrap_or_else(|| {
177 provider
178 .imported_code_units_from_infos(file, imports)
179 .unwrap_or_else(|| provider.imported_code_units_of(file))
180 .iter()
181 .map(|unit| unit.source().clone())
182 .collect()
183 })
184}
185
186pub fn build_reverse_import_index<F>(
187 files: &[ProjectFile],
188 resolve_imported: F,
189 parallel: bool,
190) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
191where
192 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync,
193{
194 build_reverse_file_index(
195 files,
196 |file| {
197 resolve_imported(file)
198 .iter()
199 .map(|code_unit| code_unit.source().clone())
200 .collect::<Vec<_>>()
201 },
202 parallel,
203 )
204}
205
206pub type ReverseFileIndex = HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>;
207
208pub fn memoized_reverse_import_index<F, Files>(
209 memo: &PoolSafeMemo<ReverseFileIndex>,
210 files: Files,
211 resolve_imported: F,
212) -> Arc<ReverseFileIndex>
213where
214 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync + Copy,
215 Files: Fn() -> Vec<ProjectFile> + Copy,
216{
217 memoized_reverse_file_index(memo, files, |file| {
218 resolve_imported(file)
219 .iter()
220 .map(|code_unit| code_unit.source().clone())
221 .collect::<Vec<_>>()
222 })
223}
224
225pub fn memoized_reverse_file_index<F, I, Files>(
226 memo: &PoolSafeMemo<ReverseFileIndex>,
227 files: Files,
228 resolve_targets: F,
229) -> Arc<ReverseFileIndex>
230where
231 F: Fn(&ProjectFile) -> I + Sync + Copy,
232 I: IntoIterator<Item = ProjectFile>,
233 Files: Fn() -> Vec<ProjectFile> + Copy,
234{
235 memo.get_or_build(
236 || {
237 let files = files();
238 build_reverse_file_index(&files, resolve_targets, true)
239 },
240 || {
241 let files = files();
242 build_reverse_file_index(&files, resolve_targets, false)
243 },
244 )
245}
246
247pub fn build_reverse_file_index<F, I>(
248 files: &[ProjectFile],
249 resolve_targets: F,
250 parallel: bool,
251) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
252where
253 F: Fn(&ProjectFile) -> I + Sync,
254 I: IntoIterator<Item = ProjectFile>,
255{
256 let collect_edges = |file: &ProjectFile| {
257 let source = file.clone();
258 resolve_targets(file)
259 .into_iter()
260 .filter_map(move |target| (target != source).then(|| (target, source.clone())))
261 .collect::<Vec<_>>()
262 };
263 let edges: Vec<_> = if parallel {
264 files.par_iter().flat_map(collect_edges).collect()
265 } else {
266 files.iter().flat_map(collect_edges).collect()
267 };
268
269 let mut reverse: HashMap<ProjectFile, HashSet<ProjectFile>> = HashMap::default();
270 for (target, source) in edges {
271 reverse.entry(target).or_default().insert(source);
272 }
273 reverse
274 .into_iter()
275 .map(|(file, refs)| (file, Arc::new(refs)))
276 .collect()
277}
278
279pub trait TypeAliasProvider: CapabilityProvider + Send + Sync {
283 fn is_type_alias(&self, _code_unit: &CodeUnit) -> bool {
284 false
285 }
286}
287
288pub trait TestDetectionProvider: CapabilityProvider {}
289
290#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
296pub enum DescendantIndexVariant {
297 WholeWorkspace,
299 ProductionOnly,
303}
304
305#[derive(Clone, Copy)]
322pub struct DescendantIndexScope<'a> {
323 cancellation: &'a CancellationToken,
324 excluded_source: Option<&'a dyn Fn(&ProjectFile) -> bool>,
325}
326
327impl<'a> DescendantIndexScope<'a> {
328 pub fn whole_workspace(cancellation: &'a CancellationToken) -> Self {
330 Self {
331 cancellation,
332 excluded_source: None,
333 }
334 }
335
336 pub fn excluding_sources(
340 cancellation: &'a CancellationToken,
341 excluded: &'a dyn Fn(&ProjectFile) -> bool,
342 ) -> Self {
343 Self {
344 cancellation,
345 excluded_source: Some(excluded),
346 }
347 }
348
349 pub fn cancellation(&self) -> &'a CancellationToken {
350 self.cancellation
351 }
352
353 pub fn variant(&self) -> DescendantIndexVariant {
354 match self.excluded_source {
355 Some(_) => DescendantIndexVariant::ProductionOnly,
356 None => DescendantIndexVariant::WholeWorkspace,
357 }
358 }
359
360 pub fn keep_going(&self) -> impl Fn() -> bool + use<'_> {
363 || !self.cancellation.is_cancelled()
364 }
365
366 pub fn admits(&self, declaration: &CodeUnit) -> bool {
368 self.excluded_source
369 .is_none_or(|excluded| !excluded(declaration.source()))
370 }
371}
372
373pub trait TypeHierarchyProvider: CapabilityProvider + Send + Sync {
374 fn get_direct_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit>;
375 fn get_direct_descendants(&self, code_unit: &CodeUnit) -> HashSet<CodeUnit>;
376
377 fn supports_type_hierarchy(&self, code_unit: &CodeUnit) -> bool {
378 code_unit.is_class()
379 }
380
381 fn get_ancestors(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
382 traverse_hierarchy(code_unit, |next| self.get_direct_ancestors(next))
383 }
384
385 fn get_descendants(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
386 traverse_hierarchy(code_unit, |next| {
387 self.get_direct_descendants(next).into_iter().collect()
388 })
389 }
390
391 fn get_direct_ancestors_within(
400 &self,
401 code_unit: &CodeUnit,
402 scope: &DescendantIndexScope<'_>,
403 ) -> Option<Vec<CodeUnit>> {
404 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_ancestors(code_unit))
405 }
406
407 fn get_direct_descendants_within(
426 &self,
427 code_unit: &CodeUnit,
428 scope: &DescendantIndexScope<'_>,
429 ) -> Option<HashSet<CodeUnit>> {
430 (!scope.cancellation().is_cancelled()).then(|| self.get_direct_descendants(code_unit))
431 }
432
433 fn get_descendants_within(
437 &self,
438 code_unit: &CodeUnit,
439 scope: &DescendantIndexScope<'_>,
440 ) -> Option<Vec<CodeUnit>> {
441 traverse_hierarchy_while(code_unit, scope.cancellation(), |next| {
442 self.get_direct_descendants_within(next, scope)
443 .map(|descendants| descendants.into_iter().collect())
444 })
445 }
446
447 fn get_polymorphic_matches<T: CodeUnitIndex>(
448 &self,
449 target: &CodeUnit,
450 analyzer: &T,
451 ) -> Vec<CodeUnit>
452 where
453 Self: Sized,
454 {
455 if !target.is_function() {
456 return Vec::new();
457 }
458
459 let Some(parent) = analyzer.parent_of(target) else {
460 return Vec::new();
461 };
462
463 self.get_descendants(&parent)
464 }
465}
466
467pub struct DirectDescendantIndex {
469 nodes: Box<[CodeUnit]>,
470 row_by_ancestor: HashMap<CodeUnit, u32>,
471 descendants: CompactRows<u32>,
472}
473
474impl DirectDescendantIndex {
475 pub fn from_indexed_nodes(
476 nodes: Vec<CodeUnit>,
477 index_by_node: HashMap<CodeUnit, u32>,
478 mut edges: Vec<(u32, u32)>,
479 ) -> Self {
480 assert_eq!(nodes.len(), index_by_node.len());
481 assert!(nodes.iter().enumerate().all(|(index, node)| {
482 index_by_node.get(node).copied()
483 == Some(
484 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
485 )
486 }));
487 assert!(edges.iter().all(|(ancestor, descendant)| {
488 (*ancestor as usize) < nodes.len() && (*descendant as usize) < nodes.len()
489 }));
490 edges.sort_unstable();
491 edges.dedup();
492
493 let row_count = usize::from(!edges.is_empty())
494 + edges
495 .windows(2)
496 .filter(|pair| pair[0].0 != pair[1].0)
497 .count();
498 let mut row_by_ancestor = HashMap::default();
499 let mut descendants = CompactRowsBuilder::with_capacity(row_count, edges.len());
500 let mut cursor = 0;
501 while cursor < edges.len() {
502 let ancestor = edges[cursor].0;
503 let start = cursor;
504 while cursor < edges.len() && edges[cursor].0 == ancestor {
505 cursor += 1;
506 }
507 let row =
508 u32::try_from(descendants.rows()).expect("hierarchy index rows must fit in a u32");
509 row_by_ancestor.insert(nodes[ancestor as usize].clone(), row);
510 descendants.push_row(
511 edges[start..cursor]
512 .iter()
513 .map(|(_, descendant)| *descendant),
514 );
515 }
516 Self {
517 nodes: nodes.into_boxed_slice(),
518 row_by_ancestor,
519 descendants: descendants.finish(),
520 }
521 }
522
523 pub fn descendants(&self, ancestor: &CodeUnit) -> HashSet<CodeUnit> {
524 let Some(row) = self.row_by_ancestor.get(ancestor).copied() else {
525 return HashSet::default();
526 };
527 self.descendants
528 .row(row as usize)
529 .iter()
530 .map(|descendant| self.nodes[*descendant as usize].clone())
531 .collect()
532 }
533}
534
535pub fn descendants_from_variant_index(
544 index: &KeyedPoolSafeMemo<DescendantIndexVariant, DirectDescendantIndex>,
545 scope: &DescendantIndexScope<'_>,
546 code_unit: &CodeUnit,
547 build: impl Fn() -> Option<DirectDescendantIndex>,
548) -> Option<HashSet<CodeUnit>> {
549 Some(
550 index
551 .cell(&scope.variant())
552 .get_or_build_while(&scope.keep_going(), &build, &build)?
555 .descendants(code_unit),
556 )
557}
558
559pub fn build_direct_descendant_index<A, P>(
567 analyzer: &A,
568 provider: &P,
569 scope: &DescendantIndexScope<'_>,
570) -> Option<DirectDescendantIndex>
571where
572 A: CodeUnitIndex,
573 P: TypeHierarchyProvider + ?Sized,
574{
575 build_direct_descendant_index_from_candidates(
576 analyzer
577 .all_declarations()
578 .filter(|candidate| candidate.is_class() && scope.admits(candidate))
579 .collect(),
580 |candidate| provider.get_direct_ancestors_within(candidate, scope),
581 &scope.keep_going(),
582 )
583}
584
585pub fn build_direct_descendant_index_from_candidates<F>(
595 mut candidates: Vec<CodeUnit>,
596 mut direct_ancestors: F,
597 keep_going: &dyn Fn() -> bool,
598) -> Option<DirectDescendantIndex>
599where
600 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
601{
602 candidates.sort();
603 candidates.dedup();
604 let mut types_by_fq_name: HashMap<String, Vec<CodeUnit>> = HashMap::default();
605 for candidate in &candidates {
606 types_by_fq_name
607 .entry(candidate.fq_name())
608 .or_default()
609 .push(candidate.clone());
610 }
611 let mut nodes = candidates.clone();
612 let mut index_by_node: HashMap<_, _> = nodes
613 .iter()
614 .enumerate()
615 .map(|(index, node)| {
616 (
617 node.clone(),
618 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
619 )
620 })
621 .collect();
622 let mut edges = Vec::new();
623 for candidate in candidates {
624 if !keep_going() {
625 return None;
626 }
627 let descendant = index_by_node[&candidate];
628 for ancestor in direct_ancestors(&candidate)? {
629 let ancestor = types_by_fq_name
630 .get(&ancestor.fq_name())
631 .and_then(|same_name| {
632 let mut same_source = same_name
633 .iter()
634 .filter(|unit| unit.source() == candidate.source());
635 let exact = same_source.next()?;
636 same_source.next().is_none().then(|| exact.clone())
637 })
638 .unwrap_or(ancestor);
639 let ancestor = *index_by_node.entry(ancestor.clone()).or_insert_with(|| {
640 let index = u32::try_from(nodes.len())
641 .expect("hierarchy index declarations must fit in a u32");
642 nodes.push(ancestor);
643 index
644 });
645 edges.push((ancestor, descendant));
646 }
647 }
648 Some(DirectDescendantIndex::from_indexed_nodes(
649 nodes,
650 index_by_node,
651 edges,
652 ))
653}
654
655fn traverse_hierarchy_while<F>(
660 root: &CodeUnit,
661 cancellation: &CancellationToken,
662 mut next: F,
663) -> Option<Vec<CodeUnit>>
664where
665 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
666{
667 let direct = next(root)?;
668 if direct.is_empty() {
669 return Some(Vec::new());
670 }
671
672 let mut seen = BTreeSet::new();
673 let mut result = Vec::new();
674 let mut queue = VecDeque::new();
675
676 for item in direct {
677 if seen.insert(item.fq_name()) {
678 queue.push_back(item.clone());
679 result.push(item);
680 }
681 }
682
683 while let Some(current) = queue.pop_front() {
684 if cancellation.is_cancelled() {
685 return None;
686 }
687 for item in next(¤t)? {
688 if seen.insert(item.fq_name()) {
689 queue.push_back(item.clone());
690 result.push(item);
691 }
692 }
693 }
694
695 Some(result)
696}
697
698fn traverse_hierarchy<F>(root: &CodeUnit, mut next: F) -> Vec<CodeUnit>
699where
700 F: FnMut(&CodeUnit) -> Vec<CodeUnit>,
701{
702 let direct = next(root);
703 if direct.is_empty() {
704 return Vec::new();
705 }
706
707 let mut seen = BTreeSet::new();
708 let mut result = Vec::new();
709 let mut queue = VecDeque::new();
710
711 for item in direct {
712 if seen.insert(item.fq_name()) {
713 queue.push_back(item.clone());
714 result.push(item);
715 }
716 }
717
718 while let Some(current) = queue.pop_front() {
719 for item in next(¤t) {
720 if seen.insert(item.fq_name()) {
721 queue.push_back(item.clone());
722 result.push(item);
723 }
724 }
725 }
726
727 result
728}