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, normalize_indexed_edges};
7use crate::hash::{HashMap, HashSet};
8use std::any::Any;
9use std::collections::{BTreeSet, VecDeque};
10use std::sync::Arc;
11
12use rayon::prelude::*;
13
14use serde_json::Value;
15
16pub trait CapabilityProvider: Any {
17 fn as_any(&self) -> &dyn Any;
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct FileDependencyFacts {
29 pub imports: Vec<ImportInfo>,
30 pub contains_tests: Option<bool>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct AdditionalFileDependencies {
41 pub dependencies: HashMap<ProjectFile, HashSet<ProjectFile>>,
42 pub complete: bool,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub struct ImportLookupDependency {
51 pub import_ordinal: u32,
52 pub consulted_file: ProjectFile,
53 pub name: String,
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq)]
60pub struct ImportResolutionDependencies {
61 pub lookups: Vec<ImportLookupDependency>,
62 pub missing_module_path_keys: Vec<(u32, String)>,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub struct ImportGraphBindingSurface {
72 pub module_path: String,
73 pub name: String,
74 pub namespace: String,
75 pub visibility_kind: String,
76 pub visibility_scope: String,
77 pub origin_file: ProjectFile,
78 pub origin_module_path: String,
79 pub origin_name: String,
80 pub route_files: Vec<ProjectFile>,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct ImportGraphInputDigests {
87 pub binding_surface: [u8; 32],
88 pub import_rows: [u8; 32],
89}
90
91pub type ImportedFileEdgeTargets = HashMap<ProjectFile, Vec<Value>>;
93
94#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ImportedFileEdgeFacts {
99 pub import_ordinal: u32,
100 pub edges: ImportedFileEdgeTargets,
101}
102
103impl AdditionalFileDependencies {
104 pub fn complete(dependencies: HashMap<ProjectFile, HashSet<ProjectFile>>) -> Self {
105 Self {
106 dependencies,
107 complete: true,
108 }
109 }
110
111 pub fn incomplete(dependencies: HashMap<ProjectFile, HashSet<ProjectFile>>) -> Self {
112 Self {
113 dependencies,
114 complete: false,
115 }
116 }
117}
118
119impl<T: Any> CapabilityProvider for T {
120 fn as_any(&self) -> &dyn Any {
121 self
122 }
123}
124
125pub trait ImportAnalysisProvider: Send + Sync {
126 fn imported_code_units_of(&self, file: &ProjectFile) -> Arc<HashSet<CodeUnit>>;
132 fn referencing_files_of(&self, file: &ProjectFile) -> HashSet<ProjectFile>;
133
134 fn referencing_files_of_targets(
142 &self,
143 targets: &HashSet<ProjectFile>,
144 cancellation: &CancellationToken,
145 ) -> HashSet<ProjectFile> {
146 let mut referencing = HashSet::default();
147 for target in targets {
148 if cancellation.is_cancelled() {
149 break;
150 }
151 referencing.extend(self.referencing_files_of(target));
152 }
153 referencing
154 }
155
156 fn import_infos_for_files(
160 &self,
161 _files: &[ProjectFile],
162 ) -> Option<HashMap<ProjectFile, Vec<ImportInfo>>> {
163 None
164 }
165
166 fn file_dependency_facts_for_files(
173 &self,
174 files: &[ProjectFile],
175 ) -> Option<HashMap<ProjectFile, FileDependencyFacts>> {
176 self.import_infos_for_files(files).map(|infos| {
177 infos
178 .into_iter()
179 .map(|(file, imports)| {
180 (
181 file,
182 FileDependencyFacts {
183 imports,
184 contains_tests: None,
185 },
186 )
187 })
188 .collect()
189 })
190 }
191
192 fn has_complete_import_resolution_dependencies(&self) -> bool {
195 false
196 }
197
198 fn import_graph_module_path_keys_for_files(
202 &self,
203 _files: &[ProjectFile],
204 ) -> Option<HashMap<ProjectFile, Vec<String>>> {
205 None
206 }
207
208 fn import_graph_binding_surfaces_for_files(
212 &self,
213 _files: &[ProjectFile],
214 _cancellation: Option<&CancellationToken>,
215 ) -> Option<HashMap<ProjectFile, Vec<ImportGraphBindingSurface>>> {
216 None
217 }
218
219 fn import_graph_input_digests_for_files(
223 &self,
224 _files: &[ProjectFile],
225 _cancellation: Option<&CancellationToken>,
226 ) -> Option<HashMap<ProjectFile, ImportGraphInputDigests>> {
227 None
228 }
229
230 fn import_resolution_dependencies_for_files(
235 &self,
236 _files: &[ProjectFile],
237 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
238 _cancellation: Option<&CancellationToken>,
239 ) -> Option<HashMap<ProjectFile, ImportResolutionDependencies>> {
240 None
241 }
242
243 fn for_each_import_edge_facts_from_infos(
250 &self,
251 files: &[ProjectFile],
252 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
253 selected_ordinals: &HashMap<ProjectFile, Vec<u32>>,
254 cancellation: Option<&CancellationToken>,
255 visit: &mut dyn FnMut(&ProjectFile, Vec<ImportedFileEdgeFacts>) -> bool,
256 ) -> Option<()> {
257 if !selected_ordinals.is_empty() {
258 return None;
259 }
260 self.for_each_imported_file_edges_from_infos(
261 files,
262 import_infos,
263 cancellation,
264 &mut |file, edges| {
265 visit(
266 file,
267 vec![ImportedFileEdgeFacts {
268 import_ordinal: u32::MAX,
269 edges,
270 }],
271 )
272 },
273 )
274 }
275
276 fn import_resolution_dependencies_for_ordinals(
280 &self,
281 files: &[ProjectFile],
282 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
283 selected_ordinals: &HashMap<ProjectFile, Vec<u32>>,
284 cancellation: Option<&CancellationToken>,
285 ) -> Option<HashMap<ProjectFile, ImportResolutionDependencies>> {
286 if !selected_ordinals.is_empty() {
287 return None;
288 }
289 self.import_resolution_dependencies_for_files(files, import_infos, cancellation)
290 }
291
292 fn additional_direct_file_dependencies(
301 &self,
302 _files: &[ProjectFile],
303 _cancellation: &CancellationToken,
304 ) -> Option<AdditionalFileDependencies> {
305 Some(AdditionalFileDependencies::complete(HashMap::default()))
306 }
307
308 fn import_info_of(&self, _token: QueryToken<'_>, _file: &ProjectFile) -> Vec<ImportInfo> {
313 Vec::new()
314 }
315
316 fn import_info_of_checked(
324 &self,
325 _token: QueryToken<'_>,
326 _file: &ProjectFile,
327 ) -> Option<Vec<ImportInfo>> {
328 None
329 }
330
331 fn imported_code_units_from_infos(
334 &self,
335 _file: &ProjectFile,
336 _imports: &[ImportInfo],
337 ) -> Option<Arc<HashSet<CodeUnit>>> {
338 None
339 }
340
341 fn imported_files_from_infos(
345 &self,
346 _file: &ProjectFile,
347 _imports: &[ImportInfo],
348 ) -> Option<HashSet<ProjectFile>> {
349 None
350 }
351
352 fn imported_file_edge_provenance_from_infos(
357 &self,
358 _file: &ProjectFile,
359 _imports: &[ImportInfo],
360 ) -> Option<ImportedFileEdgeTargets> {
361 None
362 }
363
364 fn for_each_imported_files_from_infos(
370 &self,
371 _files: &[ProjectFile],
372 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
373 _cancellation: Option<&CancellationToken>,
374 _visit: &mut dyn FnMut(&ProjectFile, HashSet<ProjectFile>) -> bool,
375 ) -> Option<()> {
376 None
377 }
378
379 fn for_each_imported_file_edges_from_infos(
383 &self,
384 files: &[ProjectFile],
385 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
386 cancellation: Option<&CancellationToken>,
387 visit: &mut dyn FnMut(&ProjectFile, ImportedFileEdgeTargets) -> bool,
388 ) -> Option<()> {
389 let mut visit_targets = |file: &ProjectFile, targets: HashSet<ProjectFile>| {
390 let facts = import_infos.and_then(|infos| infos.get(file)).map_or_else(
391 || serde_json::json!({ "kind": "provider_resolved_import_edge" }),
392 |imports| {
393 serde_json::json!({
394 "kind": "structured_import_facts",
395 "facts": imports
396 })
397 },
398 );
399 let edges = targets
400 .into_iter()
401 .map(|target| (target, vec![facts.clone()]))
402 .collect();
403 visit(file, edges)
404 };
405 self.for_each_imported_files_from_infos(
406 files,
407 import_infos,
408 cancellation,
409 &mut visit_targets,
410 )
411 }
412
413 fn relevant_imports_for(&self, _code_unit: &CodeUnit) -> HashSet<String> {
414 HashSet::default()
415 }
416
417 fn could_import_file(
423 &self,
424 _source_file: &ProjectFile,
425 _imports: &[ImportInfo],
426 _target: &ProjectFile,
427 ) -> bool {
428 false
429 }
430
431 fn prefetch_import_targets(
448 &self,
449 _files: &[ProjectFile],
450 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
451 _cancellation: &crate::cancellation::CancellationToken,
452 ) {
453 }
454
455 fn prefetch_file_dependency_targets(
463 &self,
464 _files: &[ProjectFile],
465 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
466 _cancellation: &crate::cancellation::CancellationToken,
467 ) {
468 }
469
470 fn import_reachability(
483 &self,
484 source_file: &ProjectFile,
485 imports: &[ImportInfo],
486 target: &ProjectFile,
487 ) -> ImportReachability {
488 if self.could_import_file(source_file, imports, target) {
489 ImportReachability::Reaches
490 } else {
491 ImportReachability::Unknown
492 }
493 }
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
511pub enum ImportReachability {
512 Reaches,
514 DoesNotReach,
516 Unknown,
519}
520
521pub fn resolve_imported_files_from_infos(
526 provider: &dyn ImportAnalysisProvider,
527 file: &ProjectFile,
528 imports: &[ImportInfo],
529) -> HashSet<ProjectFile> {
530 provider
531 .imported_files_from_infos(file, imports)
532 .unwrap_or_else(|| {
533 provider
534 .imported_code_units_from_infos(file, imports)
535 .unwrap_or_else(|| provider.imported_code_units_of(file))
536 .iter()
537 .map(|unit| unit.source().clone())
538 .collect()
539 })
540}
541
542pub fn build_reverse_import_index<F>(
543 files: &[ProjectFile],
544 resolve_imported: F,
545 parallel: bool,
546) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
547where
548 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync,
549{
550 build_reverse_file_index(
551 files,
552 |file| imported_source_files(&resolve_imported(file)),
553 parallel,
554 )
555}
556
557fn imported_source_files(imported: &HashSet<CodeUnit>) -> Vec<ProjectFile> {
558 imported
559 .iter()
560 .map(|code_unit| code_unit.source().clone())
561 .collect()
562}
563
564pub type ReverseFileIndex = HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>;
565
566pub fn memoized_reverse_import_index<F, Files>(
567 memo: &PoolSafeMemo<ReverseFileIndex>,
568 files: Files,
569 resolve_imported: F,
570) -> Arc<ReverseFileIndex>
571where
572 F: Fn(&ProjectFile) -> Arc<HashSet<CodeUnit>> + Sync + Copy,
573 Files: Fn() -> Vec<ProjectFile> + Copy,
574{
575 memoized_reverse_file_index(memo, files, |file| {
576 imported_source_files(&resolve_imported(file))
577 })
578}
579
580pub fn memoized_reverse_file_index<F, I, Files>(
581 memo: &PoolSafeMemo<ReverseFileIndex>,
582 files: Files,
583 resolve_targets: F,
584) -> Arc<ReverseFileIndex>
585where
586 F: Fn(&ProjectFile) -> I + Sync + Copy,
587 I: IntoIterator<Item = ProjectFile>,
588 Files: Fn() -> Vec<ProjectFile> + Copy,
589{
590 memo.get_or_build(
591 || {
592 let files = files();
593 build_reverse_file_index(&files, resolve_targets, true)
594 },
595 || {
596 let files = files();
597 build_reverse_file_index(&files, resolve_targets, false)
598 },
599 )
600}
601
602pub fn build_reverse_file_index<F, I>(
603 files: &[ProjectFile],
604 resolve_targets: F,
605 parallel: bool,
606) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
607where
608 F: Fn(&ProjectFile) -> I + Sync,
609 I: IntoIterator<Item = ProjectFile>,
610{
611 let context = super::carried_context::CarriedContext::current();
612 let collect_edges = |file: &ProjectFile| {
613 context.run(|| {
614 let source = file.clone();
615 resolve_targets(file)
616 .into_iter()
617 .filter_map(move |target| (target != source).then(|| (target, source.clone())))
618 .collect::<Vec<_>>()
619 })
620 };
621 let edges: Vec<_> = if parallel {
622 files.par_iter().flat_map(collect_edges).collect()
623 } else {
624 files.iter().flat_map(collect_edges).collect()
625 };
626
627 let mut reverse: HashMap<ProjectFile, HashSet<ProjectFile>> = HashMap::default();
628 for (target, source) in edges {
629 reverse.entry(target).or_default().insert(source);
630 }
631 reverse
632 .into_iter()
633 .map(|(file, refs)| (file, Arc::new(refs)))
634 .collect()
635}
636
637pub trait TypeAliasProvider: CapabilityProvider + Send + Sync {
641 fn is_type_alias(&self, _code_unit: &CodeUnit) -> bool {
642 false
643 }
644}
645
646pub fn provider_declares_type_alias(
648 provider: Option<&dyn TypeAliasProvider>,
649 code_unit: &CodeUnit,
650) -> bool {
651 provider.is_some_and(|provider| provider.is_type_alias(code_unit))
652}
653
654pub trait TestDetectionProvider: CapabilityProvider {}
655
656#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
662pub enum DescendantIndexVariant {
663 WholeWorkspace,
665 ProductionOnly,
669}
670
671#[derive(Clone, Copy)]
688pub struct DescendantIndexScope<'a> {
689 cancellation: &'a CancellationToken,
690 excluded_source: Option<&'a dyn Fn(&ProjectFile) -> bool>,
691}
692
693impl<'a> DescendantIndexScope<'a> {
694 pub fn whole_workspace(cancellation: &'a CancellationToken) -> Self {
696 Self {
697 cancellation,
698 excluded_source: None,
699 }
700 }
701
702 pub fn excluding_sources(
706 cancellation: &'a CancellationToken,
707 excluded: &'a dyn Fn(&ProjectFile) -> bool,
708 ) -> Self {
709 Self {
710 cancellation,
711 excluded_source: Some(excluded),
712 }
713 }
714
715 pub fn cancellation(&self) -> &'a CancellationToken {
716 self.cancellation
717 }
718
719 pub fn variant(&self) -> DescendantIndexVariant {
720 match self.excluded_source {
721 Some(_) => DescendantIndexVariant::ProductionOnly,
722 None => DescendantIndexVariant::WholeWorkspace,
723 }
724 }
725
726 pub fn keep_going(&self) -> impl Fn() -> bool + use<'_> {
729 || !self.cancellation.is_cancelled()
730 }
731
732 pub fn admits(&self, declaration: &CodeUnit) -> bool {
734 self.excluded_source
735 .is_none_or(|excluded| !excluded(declaration.source()))
736 }
737}
738
739#[derive(Debug, Clone, Copy, PartialEq, Eq)]
741pub struct Cancelled;
742
743fn query_within_scope<T>(
744 scope: &DescendantIndexScope<'_>,
745 query: impl FnOnce() -> T,
746) -> Result<T, Cancelled> {
747 if scope.cancellation().is_cancelled() {
748 Err(Cancelled)
749 } else {
750 Ok(query())
751 }
752}
753
754#[derive(Debug, Clone, Copy)]
756pub struct PlatformInventoryMethod {
757 pub name: &'static str,
758 pub parameters: &'static [&'static str],
759 pub returns: &'static str,
760}
761
762#[derive(Debug, Clone)]
764pub struct UnreadMemberFamilyAncestor {
765 pub name: String,
766 pub inventory: Option<&'static [PlatformInventoryMethod]>,
767}
768
769#[derive(Debug, Default)]
770pub struct MemberFamilyAncestors {
771 pub resolved: Vec<CodeUnit>,
772 pub ambiguous: Vec<Vec<CodeUnit>>,
773 pub unread: Vec<UnreadMemberFamilyAncestor>,
774}
775
776#[derive(Debug, Clone)]
778pub enum HierarchyOpenReason {
779 MemberFamily(crate::analyzer::structural::resolution::MemberFamilyReason),
782 DescendantIndexUnavailable,
784 UnreadAncestor(UnreadMemberFamilyAncestor),
786}
787
788impl HierarchyOpenReason {
789 pub fn description(&self) -> String {
791 match self {
792 Self::UnreadAncestor(ancestor) => format!("{}: {}", self.as_str(), ancestor.name),
793 _ => self.as_str().to_string(),
794 }
795 }
796
797 pub fn as_str(&self) -> &'static str {
799 match self {
800 Self::MemberFamily(reason) => reason.label(),
801 Self::DescendantIndexUnavailable => "descendant_index_unavailable",
802 Self::UnreadAncestor(_) => "unread_ancestor",
803 }
804 }
805}
806
807#[derive(Debug, Default)]
813pub struct HierarchyAnswer {
814 pub edges: Vec<CodeUnit>,
815 pub ambiguous: Vec<Vec<CodeUnit>>,
816 pub open: Vec<HierarchyOpenReason>,
817}
818
819impl HierarchyAnswer {
820 pub fn complete(edges: Vec<CodeUnit>) -> Self {
822 Self {
823 edges,
824 ambiguous: Vec::new(),
825 open: Vec::new(),
826 }
827 }
828
829 pub fn merge(&mut self, other: Self) {
831 self.edges.extend(other.edges);
832 self.edges.sort();
833 self.edges.dedup();
834 self.ambiguous.extend(other.ambiguous);
835 self.open.extend(other.open);
836 }
837
838 pub fn is_complete(&self) -> bool {
839 self.ambiguous.is_empty() && self.open.is_empty()
840 }
841
842 pub fn definite(&self) -> &[CodeUnit] {
845 &self.edges
846 }
847}
848
849pub trait HierarchyQuery {
852 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer;
853 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer;
854 fn member_family_ancestors(&mut self, unit: &CodeUnit) -> MemberFamilyAncestors;
855
856 fn direct_ancestors_within(
857 &mut self,
858 unit: &CodeUnit,
859 scope: &DescendantIndexScope<'_>,
860 ) -> Result<HierarchyAnswer, Cancelled> {
861 query_within_scope(scope, || self.direct_ancestors(unit))
862 }
863
864 fn direct_descendants_within(
865 &mut self,
866 unit: &CodeUnit,
867 scope: &DescendantIndexScope<'_>,
868 ) -> Result<HierarchyAnswer, Cancelled> {
869 query_within_scope(scope, || self.direct_descendants(unit))
870 }
871}
872
873pub struct ForwardingHierarchyQuery<'a, P: ?Sized>(pub &'a P);
876
877impl<P: TypeHierarchyProvider + ?Sized> HierarchyQuery for ForwardingHierarchyQuery<'_, P> {
878 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
879 self.0.get_direct_ancestors(unit)
880 }
881 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
882 self.0.get_direct_descendants(unit)
883 }
884 fn member_family_ancestors(&mut self, unit: &CodeUnit) -> MemberFamilyAncestors {
885 self.0.member_family_ancestors(unit)
886 }
887 fn direct_ancestors_within(
888 &mut self,
889 unit: &CodeUnit,
890 scope: &DescendantIndexScope<'_>,
891 ) -> Result<HierarchyAnswer, Cancelled> {
892 self.0.get_direct_ancestors_within(unit, scope)
893 }
894 fn direct_descendants_within(
895 &mut self,
896 unit: &CodeUnit,
897 scope: &DescendantIndexScope<'_>,
898 ) -> Result<HierarchyAnswer, Cancelled> {
899 self.0.get_direct_descendants_within(unit, scope)
900 }
901}
902
903pub trait TypeHierarchyProvider: Send + Sync {
904 fn with_hierarchy_query(&self, run: &mut dyn FnMut(&mut dyn HierarchyQuery)) {
906 run(&mut ForwardingHierarchyQuery(self));
907 }
908
909 fn with_hierarchy_query_for(
912 &self,
913 _root: &CodeUnit,
914 run: &mut dyn FnMut(&mut dyn HierarchyQuery),
915 ) {
916 self.with_hierarchy_query(run);
917 }
918
919 fn get_direct_ancestors(&self, code_unit: &CodeUnit) -> HierarchyAnswer;
920 fn get_direct_descendants(&self, code_unit: &CodeUnit) -> HierarchyAnswer;
921
922 fn member_family_ancestors(&self, owner: &CodeUnit) -> MemberFamilyAncestors {
925 let answer = self.get_direct_ancestors(owner);
926 MemberFamilyAncestors {
927 resolved: answer.edges,
928 ambiguous: answer.ambiguous,
929 unread: Vec::new(),
930 }
931 }
932
933 fn member_family_inventory_matches(
936 &self,
937 _member: &CodeUnit,
938 _inventory: &[PlatformInventoryMethod],
939 ) -> bool {
940 true
941 }
942
943 fn supports_type_hierarchy(&self, code_unit: &CodeUnit) -> bool {
944 code_unit.is_class()
945 }
946
947 fn get_ancestors(&self, code_unit: &CodeUnit) -> HierarchyAnswer {
948 let mut result = None;
949 self.with_hierarchy_query_for(code_unit, &mut |query| {
950 result = Some(traverse_hierarchy(code_unit, query, |query, next| {
951 query.direct_ancestors(next)
952 }));
953 });
954 result.expect("hierarchy query callback runs once")
955 }
956
957 fn get_descendants(&self, code_unit: &CodeUnit) -> HierarchyAnswer {
958 let mut result = None;
959 self.with_hierarchy_query_for(code_unit, &mut |query| {
960 result = Some(traverse_hierarchy(code_unit, query, |query, next| {
961 query.direct_descendants(next)
962 }));
963 });
964 let mut answer = result.expect("hierarchy query callback runs once");
965 answer.edges.sort();
966 answer
967 }
968
969 fn get_direct_ancestors_within(
978 &self,
979 code_unit: &CodeUnit,
980 scope: &DescendantIndexScope<'_>,
981 ) -> Result<HierarchyAnswer, Cancelled> {
982 query_within_scope(scope, || self.get_direct_ancestors(code_unit))
983 }
984
985 fn get_direct_descendants_within(
1004 &self,
1005 code_unit: &CodeUnit,
1006 scope: &DescendantIndexScope<'_>,
1007 ) -> Result<HierarchyAnswer, Cancelled> {
1008 query_within_scope(scope, || self.get_direct_descendants(code_unit))
1009 }
1010
1011 fn get_descendants_within(
1015 &self,
1016 code_unit: &CodeUnit,
1017 scope: &DescendantIndexScope<'_>,
1018 ) -> Result<HierarchyAnswer, Cancelled> {
1019 let mut result = None;
1020 self.with_hierarchy_query_for(code_unit, &mut |query| {
1021 result = Some(traverse_hierarchy_while(
1022 code_unit,
1023 query,
1024 scope.cancellation(),
1025 |query, next| query.direct_descendants_within(next, scope),
1026 ));
1027 });
1028 let mut answer = result.expect("hierarchy query callback runs once")?;
1029 answer.edges.sort();
1030 Ok(answer)
1031 }
1032
1033 fn get_polymorphic_matches<T: CodeUnitIndex>(
1034 &self,
1035 target: &CodeUnit,
1036 analyzer: &T,
1037 ) -> HierarchyAnswer
1038 where
1039 Self: Sized,
1040 {
1041 if !target.is_function() {
1042 return HierarchyAnswer::complete(Vec::new());
1043 }
1044
1045 let Some(parent) = analyzer.parent_of(target) else {
1046 return HierarchyAnswer::complete(Vec::new());
1047 };
1048
1049 self.get_descendants(&parent)
1050 }
1051}
1052
1053pub struct DirectDescendantIndex {
1055 nodes: Box<[CodeUnit]>,
1056 row_by_ancestor: HashMap<CodeUnit, u32>,
1057 descendants: CompactRows<u32>,
1058}
1059
1060impl DirectDescendantIndex {
1061 pub fn estimated_retained_bytes(&self) -> usize {
1062 let node_bytes = self
1063 .nodes
1064 .iter()
1065 .map(|unit| {
1066 std::mem::size_of::<CodeUnit>()
1067 + unit.fq_name().len()
1068 + unit.signature().map_or(0, str::len)
1069 + unit.source().rel_path().as_os_str().len()
1070 + unit.source().root().as_os_str().len()
1071 })
1072 .sum::<usize>();
1073 std::mem::size_of::<Self>()
1074 + node_bytes * 2
1075 + self.row_by_ancestor.capacity()
1076 * (std::mem::size_of::<CodeUnit>() + std::mem::size_of::<u32>() + 1)
1077 + self.descendants.estimated_bytes() as usize
1078 }
1079
1080 pub fn from_indexed_nodes(
1081 nodes: Vec<CodeUnit>,
1082 index_by_node: HashMap<CodeUnit, u32>,
1083 mut edges: Vec<(u32, u32)>,
1084 ) -> Self {
1085 normalize_indexed_edges(&nodes, &index_by_node, &mut edges);
1086
1087 let row_count = usize::from(!edges.is_empty())
1088 + edges
1089 .windows(2)
1090 .filter(|pair| pair[0].0 != pair[1].0)
1091 .count();
1092 let mut row_by_ancestor = HashMap::default();
1093 let mut descendants = CompactRowsBuilder::with_capacity(row_count, edges.len());
1094 let mut cursor = 0;
1095 while cursor < edges.len() {
1096 let ancestor = edges[cursor].0;
1097 let start = cursor;
1098 while cursor < edges.len() && edges[cursor].0 == ancestor {
1099 cursor += 1;
1100 }
1101 let row =
1102 u32::try_from(descendants.rows()).expect("hierarchy index rows must fit in a u32");
1103 row_by_ancestor.insert(nodes[ancestor as usize].clone(), row);
1104 descendants.push_row(
1105 edges[start..cursor]
1106 .iter()
1107 .map(|(_, descendant)| *descendant),
1108 );
1109 }
1110 Self {
1111 nodes: nodes.into_boxed_slice(),
1112 row_by_ancestor,
1113 descendants: descendants.finish(),
1114 }
1115 }
1116
1117 pub fn descendants(&self, ancestor: &CodeUnit) -> HashSet<CodeUnit> {
1118 let Some(row) = self.row_by_ancestor.get(ancestor).copied() else {
1119 return HashSet::default();
1120 };
1121 self.descendants
1122 .row(row as usize)
1123 .iter()
1124 .map(|descendant| self.nodes[*descendant as usize].clone())
1125 .collect()
1126 }
1127}
1128
1129pub fn descendants_from_variant_index(
1138 index: &KeyedPoolSafeMemo<DescendantIndexVariant, DirectDescendantIndex>,
1139 scope: &DescendantIndexScope<'_>,
1140 code_unit: &CodeUnit,
1141 build: impl Fn() -> Option<DirectDescendantIndex>,
1142) -> Option<HashSet<CodeUnit>> {
1143 Some(
1144 index
1145 .cell(&scope.variant())
1146 .get_or_build_while(&scope.keep_going(), &build, &build)?
1149 .descendants(code_unit),
1150 )
1151}
1152
1153pub fn build_direct_descendant_index<A, P>(
1161 analyzer: &A,
1162 provider: &P,
1163 scope: &DescendantIndexScope<'_>,
1164) -> Option<DirectDescendantIndex>
1165where
1166 A: CodeUnitIndex,
1167 P: TypeHierarchyProvider + ?Sized,
1168{
1169 let mut result = None;
1170 provider.with_hierarchy_query(&mut |query| {
1171 result = Some(build_direct_descendant_index_in_query(
1172 analyzer, query, scope,
1173 ));
1174 });
1175 result.expect("hierarchy query callback runs once")
1176}
1177
1178pub fn build_direct_descendant_index_in_query<A: CodeUnitIndex>(
1180 analyzer: &A,
1181 query: &mut dyn HierarchyQuery,
1182 scope: &DescendantIndexScope<'_>,
1183) -> Option<DirectDescendantIndex> {
1184 build_direct_descendant_index_from_candidates(
1185 analyzer
1186 .all_declarations()
1187 .filter(|candidate| candidate.is_class() && scope.admits(candidate))
1188 .collect(),
1189 |candidate| {
1190 let answer = query.direct_ancestors_within(candidate, scope).ok()?;
1191 answer.is_complete().then_some(answer.edges)
1194 },
1195 &scope.keep_going(),
1196 )
1197}
1198
1199pub fn build_direct_descendant_index_from_candidates<F>(
1209 mut candidates: Vec<CodeUnit>,
1210 mut direct_ancestors: F,
1211 keep_going: &dyn Fn() -> bool,
1212) -> Option<DirectDescendantIndex>
1213where
1214 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
1215{
1216 candidates.sort();
1217 candidates.dedup();
1218 let mut types_by_fq_name: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1219 for candidate in &candidates {
1220 types_by_fq_name
1221 .entry(candidate.fq_name())
1222 .or_default()
1223 .push(candidate.clone());
1224 }
1225 let mut nodes = candidates.clone();
1226 let mut index_by_node: HashMap<_, _> = nodes
1227 .iter()
1228 .enumerate()
1229 .map(|(index, node)| {
1230 (
1231 node.clone(),
1232 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
1233 )
1234 })
1235 .collect();
1236 let mut edges = Vec::new();
1237 for candidate in candidates {
1238 if !keep_going() {
1239 return None;
1240 }
1241 let descendant = index_by_node[&candidate];
1242 for ancestor in direct_ancestors(&candidate)? {
1243 let ancestor = types_by_fq_name
1244 .get(&ancestor.fq_name())
1245 .and_then(|same_name| {
1246 let mut same_source = same_name
1247 .iter()
1248 .filter(|unit| unit.source() == candidate.source());
1249 let exact = same_source.next()?;
1250 same_source.next().is_none().then(|| exact.clone())
1251 })
1252 .unwrap_or(ancestor);
1253 let ancestor = *index_by_node.entry(ancestor.clone()).or_insert_with(|| {
1254 let index = u32::try_from(nodes.len())
1255 .expect("hierarchy index declarations must fit in a u32");
1256 nodes.push(ancestor);
1257 index
1258 });
1259 edges.push((ancestor, descendant));
1260 }
1261 }
1262 Some(DirectDescendantIndex::from_indexed_nodes(
1263 nodes,
1264 index_by_node,
1265 edges,
1266 ))
1267}
1268
1269fn traverse_hierarchy(
1270 root: &CodeUnit,
1271 query: &mut dyn HierarchyQuery,
1272 mut next: impl FnMut(&mut dyn HierarchyQuery, &CodeUnit) -> HierarchyAnswer,
1273) -> HierarchyAnswer {
1274 traverse_hierarchy_edges(root, |unit| next(query, unit))
1275}
1276
1277fn traverse_hierarchy_while(
1278 root: &CodeUnit,
1279 query: &mut dyn HierarchyQuery,
1280 cancellation: &CancellationToken,
1281 mut next: impl FnMut(&mut dyn HierarchyQuery, &CodeUnit) -> Result<HierarchyAnswer, Cancelled>,
1282) -> Result<HierarchyAnswer, Cancelled> {
1283 traverse_hierarchy_edges_while(root, cancellation, |unit| next(query, unit))
1284}
1285
1286fn traverse_hierarchy_edges_while<F>(
1291 root: &CodeUnit,
1292 cancellation: &CancellationToken,
1293 mut next: F,
1294) -> Result<HierarchyAnswer, Cancelled>
1295where
1296 F: FnMut(&CodeUnit) -> Result<HierarchyAnswer, Cancelled>,
1297{
1298 let mut result = HierarchyAnswer::default();
1299 let mut seen = BTreeSet::new();
1300 let mut queue = VecDeque::from([root.clone()]);
1301 while let Some(current) = queue.pop_front() {
1302 let answer = next(¤t)?;
1303 result.ambiguous.extend(answer.ambiguous);
1304 result.open.extend(answer.open);
1305 for item in answer.edges {
1306 if seen.insert(item.fq_name()) {
1307 queue.push_back(item.clone());
1308 result.edges.push(item);
1309 }
1310 }
1311 if !queue.is_empty() && cancellation.is_cancelled() {
1312 return Err(Cancelled);
1313 }
1314 }
1315 Ok(result)
1316}
1317
1318fn traverse_hierarchy_edges<F>(root: &CodeUnit, mut next: F) -> HierarchyAnswer
1319where
1320 F: FnMut(&CodeUnit) -> HierarchyAnswer,
1321{
1322 traverse_hierarchy_edges_while(root, &CancellationToken::new(), |unit| Ok(next(unit)))
1325 .expect("unscoped hierarchy traversal cannot stop")
1326}
1327
1328#[cfg(test)]
1329mod hierarchy_traversal_properties {
1330 use super::*;
1331 use crate::analyzer::model::CodeUnitType;
1332 use proptest::prelude::*;
1333 use std::collections::BTreeMap;
1334
1335 const NODES: usize = 8;
1336
1337 fn node(index: usize) -> CodeUnit {
1338 CodeUnit::new(
1339 ProjectFile::new(std::env::temp_dir(), "Hierarchy.java"),
1340 CodeUnitType::Class,
1341 "",
1342 format!("N{index}"),
1343 )
1344 }
1345
1346 #[test]
1347 fn transitive_walks_borrow_one_query_and_preserve_scoped_dispatch() {
1348 use std::sync::atomic::{AtomicUsize, Ordering};
1349 struct Provider(AtomicUsize);
1350 struct Query;
1351 impl HierarchyQuery for Query {
1352 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
1353 let index = index_of(unit);
1354 HierarchyAnswer::complete(if index < 2 {
1355 vec![node(index + 1)]
1356 } else {
1357 Vec::new()
1358 })
1359 }
1360 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
1361 self.direct_ancestors(unit)
1362 }
1363 fn member_family_ancestors(&mut self, _unit: &CodeUnit) -> MemberFamilyAncestors {
1364 MemberFamilyAncestors::default()
1365 }
1366 fn direct_descendants_within(
1367 &mut self,
1368 _unit: &CodeUnit,
1369 _scope: &DescendantIndexScope<'_>,
1370 ) -> Result<HierarchyAnswer, Cancelled> {
1371 Err(Cancelled)
1372 }
1373 }
1374 impl TypeHierarchyProvider for Provider {
1375 fn get_direct_ancestors(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1376 panic!("the walk must use its query")
1377 }
1378 fn get_direct_descendants(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1379 panic!("the walk must use its query")
1380 }
1381 fn with_hierarchy_query(&self, run: &mut dyn FnMut(&mut dyn HierarchyQuery)) {
1382 self.0.fetch_add(1, Ordering::Relaxed);
1383 run(&mut Query);
1384 }
1385 }
1386 let provider = Provider(AtomicUsize::new(0));
1387 assert_eq!(
1388 provider.get_ancestors(&node(0)).edges,
1389 vec![node(1), node(2)]
1390 );
1391 assert_eq!(provider.0.load(Ordering::Relaxed), 1);
1392 assert_eq!(
1393 provider.get_descendants(&node(0)).edges,
1394 vec![node(1), node(2)]
1395 );
1396 assert_eq!(provider.0.load(Ordering::Relaxed), 2);
1397 let cancellation = CancellationToken::new();
1398 assert!(
1399 provider
1400 .get_descendants_within(
1401 &node(0),
1402 &DescendantIndexScope::whole_workspace(&cancellation)
1403 )
1404 .is_err()
1405 );
1406 assert_eq!(provider.0.load(Ordering::Relaxed), 3);
1407 }
1408
1409 #[test]
1410 fn default_query_forwards_scoped_provider_overrides() {
1411 struct Provider;
1412 impl TypeHierarchyProvider for Provider {
1413 fn get_direct_ancestors(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1414 HierarchyAnswer::complete(vec![node(1)])
1415 }
1416 fn get_direct_descendants(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1417 HierarchyAnswer::complete(vec![node(2)])
1418 }
1419 fn get_direct_ancestors_within(
1420 &self,
1421 _unit: &CodeUnit,
1422 _scope: &DescendantIndexScope<'_>,
1423 ) -> Result<HierarchyAnswer, Cancelled> {
1424 Err(Cancelled)
1425 }
1426 fn member_family_ancestors(&self, _unit: &CodeUnit) -> MemberFamilyAncestors {
1427 MemberFamilyAncestors {
1428 resolved: vec![node(3)],
1429 ..Default::default()
1430 }
1431 }
1432 }
1433 let cancellation = CancellationToken::new();
1434 Provider.with_hierarchy_query(&mut |query| {
1435 assert_eq!(query.direct_ancestors(&node(0)).edges, vec![node(1)]);
1436 assert_eq!(query.direct_descendants(&node(0)).edges, vec![node(2)]);
1437 assert_eq!(
1438 query.member_family_ancestors(&node(0)).resolved,
1439 vec![node(3)]
1440 );
1441 assert!(
1442 query
1443 .direct_ancestors_within(
1444 &node(0),
1445 &DescendantIndexScope::whole_workspace(&cancellation)
1446 )
1447 .is_err()
1448 );
1449 });
1450 }
1451
1452 fn index_of(unit: &CodeUnit) -> usize {
1453 unit.fq_name()[1..].parse().expect("test node name")
1454 }
1455
1456 #[test]
1457 fn closure_preserves_uncertainty_without_traversing_alternatives() {
1458 let mut visited = Vec::new();
1459 let answer = traverse_hierarchy_edges(&node(0), |unit| {
1460 let id = index_of(unit);
1461 visited.push(id);
1462 match id {
1463 0 => HierarchyAnswer {
1464 edges: vec![node(1), node(1)],
1465 ambiguous: vec![vec![node(2), node(3)]],
1466 open: Vec::new(),
1467 },
1468 1 => HierarchyAnswer {
1469 edges: Vec::new(),
1470 ambiguous: vec![vec![node(4), node(5)]],
1471 open: vec![HierarchyOpenReason::UnreadAncestor(
1472 UnreadMemberFamilyAncestor {
1473 name: "External".into(),
1474 inventory: None,
1475 },
1476 )],
1477 },
1478 _ => panic!("ambiguous alternatives must not be traversed"),
1479 }
1480 });
1481 assert_eq!(visited, vec![0, 1]);
1482 assert_eq!(answer.edges, vec![node(1)]);
1483 assert_eq!(
1484 answer.ambiguous,
1485 vec![vec![node(2), node(3)], vec![node(4), node(5)]]
1486 );
1487 assert!(
1488 matches!(answer.open.as_slice(), [HierarchyOpenReason::UnreadAncestor(ancestor)] if ancestor.name == "External")
1489 );
1490 assert!(!answer.is_complete());
1491 }
1492
1493 fn graphs() -> impl Strategy<Value = Vec<Vec<usize>>> {
1495 proptest::collection::vec(proptest::collection::vec(0..NODES, 0..5), NODES)
1496 }
1497
1498 fn reference_distances(graph: &[Vec<usize>], root: usize) -> BTreeMap<usize, usize> {
1502 let mut distance: BTreeMap<usize, usize> = BTreeMap::new();
1503 for &next in &graph[root] {
1504 distance.insert(next, 1);
1505 }
1506 loop {
1507 let mut changed = false;
1508 for (from, edges) in graph.iter().enumerate() {
1509 let Some(&base) = distance.get(&from) else {
1510 continue;
1511 };
1512 for &to in edges {
1513 if distance.get(&to).is_none_or(|&known| known > base + 1) {
1514 distance.insert(to, base + 1);
1515 changed = true;
1516 }
1517 }
1518 }
1519 if !changed {
1520 return distance;
1521 }
1522 }
1523 }
1524
1525 proptest! {
1526 #![proptest_config(ProptestConfig::with_cases(128))]
1527
1528 #[test]
1533 fn visits_exactly_the_reachable_set_once_in_breadth_first_order(
1534 graph in graphs(),
1535 root in 0..NODES,
1536 ) {
1537 let mut calls: BTreeMap<usize, usize> = BTreeMap::new();
1538 let result = traverse_hierarchy_edges(&node(root), |unit| {
1539 let id = index_of(unit);
1540 *calls.entry(id).or_default() += 1;
1541 HierarchyAnswer::complete(graph[id].iter().map(|&to| node(to)).collect())
1542 });
1543 let found: Vec<usize> = result.edges.iter().map(index_of).collect();
1544
1545 let distance = reference_distances(&graph, root);
1546 let unique: BTreeSet<usize> = found.iter().copied().collect();
1547 prop_assert_eq!(found.len(), unique.len(), "a declaration was returned twice: {:?}", found);
1548 prop_assert_eq!(unique, distance.keys().copied().collect::<BTreeSet<_>>());
1549 prop_assert!(
1550 found.windows(2).all(|pair| distance[&pair[0]] <= distance[&pair[1]]),
1551 "not breadth-first: {:?} with distances {:?}", found, distance
1552 );
1553 prop_assert!(
1556 calls.iter().all(|(&id, &count)| count == 1 || (id == root && count == 2)),
1557 "repeated expansion: {:?}", calls
1558 );
1559
1560 let live = CancellationToken::new();
1562 let guarded = traverse_hierarchy_edges_while(&node(root), &live, |unit| {
1563 Ok(HierarchyAnswer::complete(graph[index_of(unit)].iter().map(|&to| node(to)).collect()))
1564 });
1565 let guarded = guarded.expect("live traversal");
1566 prop_assert!(guarded.is_complete());
1567 prop_assert_eq!(guarded.edges, result.edges);
1568 }
1569
1570 #[test]
1574 fn guarded_traversal_never_returns_a_partial_answer(
1575 graph in graphs(),
1576 root in 0..NODES,
1577 fail_on in 0..NODES,
1578 ) {
1579 let reachable = reference_distances(&graph, root);
1580 let failing = traverse_hierarchy_edges_while(&node(root), &CancellationToken::new(), |unit| {
1581 let id = index_of(unit);
1582 if id == fail_on { Err(Cancelled) } else { Ok(HierarchyAnswer::complete(graph[id].iter().map(|&to| node(to)).collect())) }
1583 });
1584 let requested = fail_on == root || reachable.contains_key(&fail_on);
1587 prop_assert_eq!(failing.is_err(), requested);
1588
1589 let cancelled = CancellationToken::new();
1590 cancelled.cancel();
1591 let stopped = traverse_hierarchy_edges_while(&node(root), &cancelled, |unit| {
1592 Ok(HierarchyAnswer::complete(graph[index_of(unit)].iter().map(|&to| node(to)).collect()))
1593 });
1594 prop_assert_eq!(stopped.is_err(), !graph[root].is_empty());
1595 if graph[root].is_empty() {
1596 let stopped = stopped.expect("empty traversal");
1597 prop_assert!(stopped.is_complete());
1598 prop_assert!(stopped.edges.is_empty());
1599 }
1600 }
1601 }
1602}