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 fn uncharged_imported_code_units(
127 build: impl FnOnce() -> HashSet<CodeUnit>,
128) -> Arc<crate::retention_budget::Accounted<HashSet<CodeUnit>>> {
129 Arc::new(crate::retention_budget::Accounted::uncharged(build()))
130}
131
132pub trait ImportAnalysisProvider: Send + Sync {
133 fn imported_code_units_of(
139 &self,
140 file: &ProjectFile,
141 ) -> Arc<crate::retention_budget::Accounted<HashSet<CodeUnit>>>;
142 fn referencing_files_of(&self, file: &ProjectFile) -> HashSet<ProjectFile>;
143
144 fn referencing_files_of_targets(
152 &self,
153 targets: &HashSet<ProjectFile>,
154 cancellation: &CancellationToken,
155 ) -> HashSet<ProjectFile> {
156 let mut referencing = HashSet::default();
157 for target in targets {
158 if cancellation.is_cancelled() {
159 break;
160 }
161 referencing.extend(self.referencing_files_of(target));
162 }
163 referencing
164 }
165
166 fn import_infos_for_files(
170 &self,
171 _files: &[ProjectFile],
172 ) -> Option<HashMap<ProjectFile, Vec<ImportInfo>>> {
173 None
174 }
175
176 fn file_dependency_facts_for_files(
183 &self,
184 files: &[ProjectFile],
185 ) -> Option<HashMap<ProjectFile, FileDependencyFacts>> {
186 self.import_infos_for_files(files).map(|infos| {
187 infos
188 .into_iter()
189 .map(|(file, imports)| {
190 (
191 file,
192 FileDependencyFacts {
193 imports,
194 contains_tests: None,
195 },
196 )
197 })
198 .collect()
199 })
200 }
201
202 fn has_complete_import_resolution_dependencies(&self) -> bool {
205 false
206 }
207
208 fn import_graph_module_path_keys_for_files(
212 &self,
213 _files: &[ProjectFile],
214 ) -> Option<HashMap<ProjectFile, Vec<String>>> {
215 None
216 }
217
218 fn import_graph_binding_surfaces_for_files(
222 &self,
223 _files: &[ProjectFile],
224 _cancellation: Option<&CancellationToken>,
225 ) -> Option<HashMap<ProjectFile, Vec<ImportGraphBindingSurface>>> {
226 None
227 }
228
229 fn import_graph_input_digests_for_files(
233 &self,
234 _files: &[ProjectFile],
235 _cancellation: Option<&CancellationToken>,
236 ) -> Option<HashMap<ProjectFile, ImportGraphInputDigests>> {
237 None
238 }
239
240 fn import_resolution_dependencies_for_files(
245 &self,
246 _files: &[ProjectFile],
247 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
248 _cancellation: Option<&CancellationToken>,
249 ) -> Option<HashMap<ProjectFile, ImportResolutionDependencies>> {
250 None
251 }
252
253 fn for_each_import_edge_facts_from_infos(
260 &self,
261 files: &[ProjectFile],
262 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
263 selected_ordinals: &HashMap<ProjectFile, Vec<u32>>,
264 cancellation: Option<&CancellationToken>,
265 visit: &mut dyn FnMut(&ProjectFile, Vec<ImportedFileEdgeFacts>) -> bool,
266 ) -> Option<()> {
267 if !selected_ordinals.is_empty() {
268 return None;
269 }
270 self.for_each_imported_file_edges_from_infos(
271 files,
272 import_infos,
273 cancellation,
274 &mut |file, edges| {
275 visit(
276 file,
277 vec![ImportedFileEdgeFacts {
278 import_ordinal: u32::MAX,
279 edges,
280 }],
281 )
282 },
283 )
284 }
285
286 fn import_resolution_dependencies_for_ordinals(
290 &self,
291 files: &[ProjectFile],
292 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
293 selected_ordinals: &HashMap<ProjectFile, Vec<u32>>,
294 cancellation: Option<&CancellationToken>,
295 ) -> Option<HashMap<ProjectFile, ImportResolutionDependencies>> {
296 if !selected_ordinals.is_empty() {
297 return None;
298 }
299 self.import_resolution_dependencies_for_files(files, import_infos, cancellation)
300 }
301
302 fn additional_direct_file_dependencies(
311 &self,
312 _files: &[ProjectFile],
313 _cancellation: &CancellationToken,
314 ) -> Option<AdditionalFileDependencies> {
315 Some(AdditionalFileDependencies::complete(HashMap::default()))
316 }
317
318 fn import_info_of(&self, _token: QueryToken<'_>, _file: &ProjectFile) -> Vec<ImportInfo> {
323 Vec::new()
324 }
325
326 fn import_info_of_checked(
334 &self,
335 _token: QueryToken<'_>,
336 _file: &ProjectFile,
337 ) -> Option<Vec<ImportInfo>> {
338 None
339 }
340
341 fn imported_code_units_from_infos(
344 &self,
345 _file: &ProjectFile,
346 _imports: &[ImportInfo],
347 ) -> Option<Arc<HashSet<CodeUnit>>> {
348 None
349 }
350
351 fn imported_files_from_infos(
355 &self,
356 _file: &ProjectFile,
357 _imports: &[ImportInfo],
358 ) -> Option<HashSet<ProjectFile>> {
359 None
360 }
361
362 fn imported_file_edge_provenance_from_infos(
367 &self,
368 _file: &ProjectFile,
369 _imports: &[ImportInfo],
370 ) -> Option<ImportedFileEdgeTargets> {
371 None
372 }
373
374 fn for_each_imported_files_from_infos(
380 &self,
381 _files: &[ProjectFile],
382 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
383 _cancellation: Option<&CancellationToken>,
384 _visit: &mut dyn FnMut(&ProjectFile, HashSet<ProjectFile>) -> bool,
385 ) -> Option<()> {
386 None
387 }
388
389 fn for_each_imported_file_edges_from_infos(
393 &self,
394 files: &[ProjectFile],
395 import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
396 cancellation: Option<&CancellationToken>,
397 visit: &mut dyn FnMut(&ProjectFile, ImportedFileEdgeTargets) -> bool,
398 ) -> Option<()> {
399 let mut visit_targets = |file: &ProjectFile, targets: HashSet<ProjectFile>| {
400 let facts = import_infos.and_then(|infos| infos.get(file)).map_or_else(
401 || serde_json::json!({ "kind": "provider_resolved_import_edge" }),
402 |imports| {
403 serde_json::json!({
404 "kind": "structured_import_facts",
405 "facts": imports
406 })
407 },
408 );
409 let edges = targets
410 .into_iter()
411 .map(|target| (target, vec![facts.clone()]))
412 .collect();
413 visit(file, edges)
414 };
415 self.for_each_imported_files_from_infos(
416 files,
417 import_infos,
418 cancellation,
419 &mut visit_targets,
420 )
421 }
422
423 fn relevant_imports_for(&self, _code_unit: &CodeUnit) -> HashSet<String> {
424 HashSet::default()
425 }
426
427 fn could_import_file(
433 &self,
434 _source_file: &ProjectFile,
435 _imports: &[ImportInfo],
436 _target: &ProjectFile,
437 ) -> bool {
438 false
439 }
440
441 fn prefetch_import_targets(
458 &self,
459 _files: &[ProjectFile],
460 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
461 _cancellation: &crate::cancellation::CancellationToken,
462 ) {
463 }
464
465 fn prefetch_file_dependency_targets(
473 &self,
474 _files: &[ProjectFile],
475 _import_infos: Option<&HashMap<ProjectFile, Vec<ImportInfo>>>,
476 _cancellation: &crate::cancellation::CancellationToken,
477 ) {
478 }
479
480 fn import_reachability(
493 &self,
494 source_file: &ProjectFile,
495 imports: &[ImportInfo],
496 target: &ProjectFile,
497 ) -> ImportReachability {
498 if self.could_import_file(source_file, imports, target) {
499 ImportReachability::Reaches
500 } else {
501 ImportReachability::Unknown
502 }
503 }
504}
505
506#[derive(Debug, Clone, Copy, PartialEq, Eq)]
521pub enum ImportReachability {
522 Reaches,
524 DoesNotReach,
526 Unknown,
529}
530
531pub fn resolve_imported_files_from_infos(
536 provider: &dyn ImportAnalysisProvider,
537 file: &ProjectFile,
538 imports: &[ImportInfo],
539) -> HashSet<ProjectFile> {
540 provider
541 .imported_files_from_infos(file, imports)
542 .unwrap_or_else(|| {
543 if let Some(units) = provider.imported_code_units_from_infos(file, imports) {
544 units.iter().map(|unit| unit.source().clone()).collect()
545 } else {
546 provider
547 .imported_code_units_of(file)
548 .iter()
549 .map(|unit| unit.source().clone())
550 .collect()
551 }
552 })
553}
554
555pub fn build_reverse_import_index<F>(
556 files: &[ProjectFile],
557 resolve_imported: F,
558 parallel: bool,
559) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
560where
561 F: Fn(&ProjectFile) -> Arc<crate::retention_budget::Accounted<HashSet<CodeUnit>>> + Sync,
562{
563 build_reverse_file_index(
564 files,
565 |file| imported_source_files(&resolve_imported(file)),
566 parallel,
567 )
568}
569
570fn imported_source_files(imported: &HashSet<CodeUnit>) -> Vec<ProjectFile> {
571 imported
572 .iter()
573 .map(|code_unit| code_unit.source().clone())
574 .collect()
575}
576
577pub type ReverseFileIndex = HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>;
578
579pub fn memoized_reverse_import_index<F, Files>(
580 memo: &PoolSafeMemo<ReverseFileIndex>,
581 files: Files,
582 resolve_imported: F,
583) -> Arc<ReverseFileIndex>
584where
585 F: Fn(&ProjectFile) -> Arc<crate::retention_budget::Accounted<HashSet<CodeUnit>>> + Sync + Copy,
586 Files: Fn() -> Vec<ProjectFile> + Copy,
587{
588 memoized_reverse_file_index(memo, files, |file| {
589 imported_source_files(&resolve_imported(file))
590 })
591}
592
593pub fn memoized_reverse_file_index<F, I, Files>(
594 memo: &PoolSafeMemo<ReverseFileIndex>,
595 files: Files,
596 resolve_targets: F,
597) -> Arc<ReverseFileIndex>
598where
599 F: Fn(&ProjectFile) -> I + Sync + Copy,
600 I: IntoIterator<Item = ProjectFile>,
601 Files: Fn() -> Vec<ProjectFile> + Copy,
602{
603 memo.get_or_build(
604 || {
605 let files = files();
606 build_reverse_file_index(&files, resolve_targets, true)
607 },
608 || {
609 let files = files();
610 build_reverse_file_index(&files, resolve_targets, false)
611 },
612 )
613}
614
615pub fn build_reverse_file_index<F, I>(
616 files: &[ProjectFile],
617 resolve_targets: F,
618 parallel: bool,
619) -> HashMap<ProjectFile, Arc<HashSet<ProjectFile>>>
620where
621 F: Fn(&ProjectFile) -> I + Sync,
622 I: IntoIterator<Item = ProjectFile>,
623{
624 let context = super::carried_context::CarriedContext::current();
625 let collect_edges = |file: &ProjectFile| {
626 context.run(|| {
627 let source = file.clone();
628 resolve_targets(file)
629 .into_iter()
630 .filter_map(move |target| (target != source).then(|| (target, source.clone())))
631 .collect::<Vec<_>>()
632 })
633 };
634 let edges: Vec<_> = if parallel {
635 files.par_iter().flat_map(collect_edges).collect()
636 } else {
637 files.iter().flat_map(collect_edges).collect()
638 };
639
640 let mut reverse: HashMap<ProjectFile, HashSet<ProjectFile>> = HashMap::default();
641 for (target, source) in edges {
642 reverse.entry(target).or_default().insert(source);
643 }
644 reverse
645 .into_iter()
646 .map(|(file, refs)| (file, Arc::new(refs)))
647 .collect()
648}
649
650pub trait TypeAliasProvider: CapabilityProvider + Send + Sync {
654 fn is_type_alias(&self, _code_unit: &CodeUnit) -> bool {
655 false
656 }
657}
658
659pub fn provider_declares_type_alias(
661 provider: Option<&dyn TypeAliasProvider>,
662 code_unit: &CodeUnit,
663) -> bool {
664 provider.is_some_and(|provider| provider.is_type_alias(code_unit))
665}
666
667pub trait TestDetectionProvider: CapabilityProvider {}
668
669#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
675pub enum DescendantIndexVariant {
676 WholeWorkspace,
678 ProductionOnly,
682}
683
684#[derive(Clone, Copy)]
701pub struct DescendantIndexScope<'a> {
702 cancellation: &'a CancellationToken,
703 excluded_source: Option<&'a dyn Fn(&ProjectFile) -> bool>,
704}
705
706impl<'a> DescendantIndexScope<'a> {
707 pub fn whole_workspace(cancellation: &'a CancellationToken) -> Self {
709 Self {
710 cancellation,
711 excluded_source: None,
712 }
713 }
714
715 pub fn excluding_sources(
719 cancellation: &'a CancellationToken,
720 excluded: &'a dyn Fn(&ProjectFile) -> bool,
721 ) -> Self {
722 Self {
723 cancellation,
724 excluded_source: Some(excluded),
725 }
726 }
727
728 pub fn cancellation(&self) -> &'a CancellationToken {
729 self.cancellation
730 }
731
732 pub fn variant(&self) -> DescendantIndexVariant {
733 match self.excluded_source {
734 Some(_) => DescendantIndexVariant::ProductionOnly,
735 None => DescendantIndexVariant::WholeWorkspace,
736 }
737 }
738
739 pub fn keep_going(&self) -> impl Fn() -> bool + use<'_> {
742 || !self.cancellation.is_cancelled()
743 }
744
745 pub fn admits(&self, declaration: &CodeUnit) -> bool {
747 self.excluded_source
748 .is_none_or(|excluded| !excluded(declaration.source()))
749 }
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Eq)]
754pub struct Cancelled;
755
756fn query_within_scope<T>(
757 scope: &DescendantIndexScope<'_>,
758 query: impl FnOnce() -> T,
759) -> Result<T, Cancelled> {
760 if scope.cancellation().is_cancelled() {
761 Err(Cancelled)
762 } else {
763 Ok(query())
764 }
765}
766
767#[derive(Debug, Clone, Copy)]
769pub struct PlatformInventoryMethod {
770 pub name: &'static str,
771 pub parameters: &'static [&'static str],
772 pub returns: &'static str,
773}
774
775#[derive(Debug, Clone, PartialEq, Eq)]
784pub enum MemberFamilyParameterType {
785 Primitive(String),
787 Nominal(String),
789 Unresolved(Vec<String>),
796 Array(Box<MemberFamilyParameterType>),
797 OwnerTypeParameter {
799 index: usize,
800 erasure: Box<MemberFamilyParameterType>,
801 },
802}
803
804#[derive(Debug, Clone, PartialEq, Eq)]
807pub enum MemberFamilySupertypeArguments {
808 Raw,
810 Arguments(Vec<MemberFamilyParameterType>),
811 Unknown,
813}
814
815#[derive(Debug, Clone)]
817pub struct UnreadMemberFamilyAncestor {
818 pub name: String,
819 pub inventory: Option<&'static [PlatformInventoryMethod]>,
820}
821
822#[derive(Debug, Default)]
823pub struct MemberFamilyAncestors {
824 pub resolved: Vec<CodeUnit>,
825 pub ambiguous: Vec<Vec<CodeUnit>>,
826 pub unread: Vec<UnreadMemberFamilyAncestor>,
827}
828
829#[derive(Debug, Clone)]
831pub enum HierarchyOpenReason {
832 MemberFamily(crate::analyzer::structural::resolution::MemberFamilyReason),
835 DescendantIndexUnavailable,
837 ForwardSatisfactionIncomplete,
840 UnreadAncestor(UnreadMemberFamilyAncestor),
842}
843
844impl HierarchyOpenReason {
845 pub fn description(&self) -> String {
847 match self {
848 Self::UnreadAncestor(ancestor) => format!("{}: {}", self.as_str(), ancestor.name),
849 _ => self.as_str().to_string(),
850 }
851 }
852
853 pub fn as_str(&self) -> &'static str {
855 match self {
856 Self::MemberFamily(reason) => reason.label(),
857 Self::DescendantIndexUnavailable => "descendant_index_unavailable",
858 Self::ForwardSatisfactionIncomplete => "forward_satisfaction_incomplete",
859 Self::UnreadAncestor(_) => "unread_ancestor",
860 }
861 }
862}
863
864#[derive(Debug, Default)]
870pub struct HierarchyAnswer {
871 pub edges: Vec<CodeUnit>,
872 pub ambiguous: Vec<Vec<CodeUnit>>,
873 pub open: Vec<HierarchyOpenReason>,
874}
875
876impl HierarchyAnswer {
877 pub fn complete(edges: Vec<CodeUnit>) -> Self {
879 Self {
880 edges,
881 ambiguous: Vec::new(),
882 open: Vec::new(),
883 }
884 }
885
886 pub fn merge(&mut self, other: Self) {
888 self.edges.extend(other.edges);
889 self.edges.sort();
890 self.edges.dedup();
891 self.ambiguous.extend(other.ambiguous);
892 self.open.extend(other.open);
893 }
894
895 pub fn is_complete(&self) -> bool {
896 self.ambiguous.is_empty() && self.open.is_empty()
897 }
898
899 pub fn definite(&self) -> &[CodeUnit] {
902 &self.edges
903 }
904}
905
906pub trait HierarchyQuery {
909 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer;
910 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer;
911 fn member_family_ancestors(&mut self, unit: &CodeUnit) -> MemberFamilyAncestors;
912
913 fn direct_ancestors_within(
914 &mut self,
915 unit: &CodeUnit,
916 scope: &DescendantIndexScope<'_>,
917 ) -> Result<HierarchyAnswer, Cancelled> {
918 query_within_scope(scope, || self.direct_ancestors(unit))
919 }
920
921 fn direct_descendants_within(
922 &mut self,
923 unit: &CodeUnit,
924 scope: &DescendantIndexScope<'_>,
925 ) -> Result<HierarchyAnswer, Cancelled> {
926 query_within_scope(scope, || self.direct_descendants(unit))
927 }
928}
929
930pub struct ForwardingHierarchyQuery<'a, P: ?Sized>(pub &'a P);
933
934impl<P: TypeHierarchyProvider + ?Sized> HierarchyQuery for ForwardingHierarchyQuery<'_, P> {
935 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
936 self.0.get_direct_ancestors(unit)
937 }
938 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
939 self.0.get_direct_descendants(unit)
940 }
941 fn member_family_ancestors(&mut self, unit: &CodeUnit) -> MemberFamilyAncestors {
942 self.0.member_family_ancestors(unit)
943 }
944 fn direct_ancestors_within(
945 &mut self,
946 unit: &CodeUnit,
947 scope: &DescendantIndexScope<'_>,
948 ) -> Result<HierarchyAnswer, Cancelled> {
949 self.0.get_direct_ancestors_within(unit, scope)
950 }
951 fn direct_descendants_within(
952 &mut self,
953 unit: &CodeUnit,
954 scope: &DescendantIndexScope<'_>,
955 ) -> Result<HierarchyAnswer, Cancelled> {
956 self.0.get_direct_descendants_within(unit, scope)
957 }
958}
959
960pub trait TypeHierarchyProvider: Send + Sync {
961 fn with_hierarchy_query(&self, run: &mut dyn FnMut(&mut dyn HierarchyQuery)) {
963 run(&mut ForwardingHierarchyQuery(self));
964 }
965
966 fn with_hierarchy_query_for(
969 &self,
970 _root: &CodeUnit,
971 run: &mut dyn FnMut(&mut dyn HierarchyQuery),
972 ) {
973 self.with_hierarchy_query(run);
974 }
975
976 fn get_direct_ancestors(&self, code_unit: &CodeUnit) -> HierarchyAnswer;
977 fn get_direct_descendants(&self, code_unit: &CodeUnit) -> HierarchyAnswer;
978
979 fn member_family_ancestors(&self, owner: &CodeUnit) -> MemberFamilyAncestors {
982 let answer = self.get_direct_ancestors(owner);
983 MemberFamilyAncestors {
984 resolved: answer.edges,
985 ambiguous: answer.ambiguous,
986 unread: Vec::new(),
987 }
988 }
989
990 fn member_family_inventory_matches(
994 &self,
995 member: &CodeUnit,
996 inventory: &[PlatformInventoryMethod],
997 ) -> bool {
998 let methods: Vec<_> = inventory
999 .iter()
1000 .filter(|method| method.name == member.identifier())
1001 .collect();
1002 if methods.is_empty() {
1003 return false;
1004 }
1005 let Some(arity) = self.member_family_parameter_arity(member) else {
1006 return true;
1007 };
1008 let methods: Vec<_> = methods
1009 .into_iter()
1010 .filter(|method| method.parameters.len() == arity)
1011 .collect();
1012 if methods.is_empty() {
1013 return false;
1014 }
1015 if methods.iter().any(|method| method.parameters.is_empty()) {
1016 return true;
1017 }
1018 let Some(parameters) = self.member_family_erased_parameters(member) else {
1019 return true;
1020 };
1021 methods.iter().any(|method| {
1022 parameters
1023 .iter()
1024 .map(String::as_str)
1025 .eq(method.parameters.iter().copied())
1026 })
1027 }
1028
1029 fn member_family_parameter_arity(&self, _member: &CodeUnit) -> Option<usize> {
1031 None
1032 }
1033
1034 fn member_family_erased_parameters(&self, _member: &CodeUnit) -> Option<Vec<String>> {
1037 None
1038 }
1039
1040 fn member_family_parameter_types(
1045 &self,
1046 _member: &CodeUnit,
1047 ) -> Option<Vec<MemberFamilyParameterType>> {
1048 None
1049 }
1050
1051 fn member_family_supertype_arguments(
1053 &self,
1054 _subtype: &CodeUnit,
1055 _supertype: &CodeUnit,
1056 ) -> MemberFamilySupertypeArguments {
1057 MemberFamilySupertypeArguments::Unknown
1058 }
1059
1060 fn supports_type_hierarchy(&self, code_unit: &CodeUnit) -> bool {
1061 code_unit.is_class()
1062 }
1063
1064 fn get_ancestors(&self, code_unit: &CodeUnit) -> HierarchyAnswer {
1065 let mut result = None;
1066 self.with_hierarchy_query_for(code_unit, &mut |query| {
1067 result = Some(traverse_hierarchy(code_unit, query, |query, next| {
1068 query.direct_ancestors(next)
1069 }));
1070 });
1071 result.expect("hierarchy query callback runs once")
1072 }
1073
1074 fn get_descendants(&self, code_unit: &CodeUnit) -> HierarchyAnswer {
1075 let mut result = None;
1076 self.with_hierarchy_query_for(code_unit, &mut |query| {
1077 result = Some(traverse_hierarchy(code_unit, query, |query, next| {
1078 query.direct_descendants(next)
1079 }));
1080 });
1081 let mut answer = result.expect("hierarchy query callback runs once");
1082 answer.edges.sort();
1083 answer
1084 }
1085
1086 fn get_direct_ancestors_within(
1095 &self,
1096 code_unit: &CodeUnit,
1097 scope: &DescendantIndexScope<'_>,
1098 ) -> Result<HierarchyAnswer, Cancelled> {
1099 query_within_scope(scope, || self.get_direct_ancestors(code_unit))
1100 }
1101
1102 fn get_direct_descendants_within(
1121 &self,
1122 code_unit: &CodeUnit,
1123 scope: &DescendantIndexScope<'_>,
1124 ) -> Result<HierarchyAnswer, Cancelled> {
1125 query_within_scope(scope, || self.get_direct_descendants(code_unit))
1126 }
1127
1128 fn get_descendants_within(
1132 &self,
1133 code_unit: &CodeUnit,
1134 scope: &DescendantIndexScope<'_>,
1135 ) -> Result<HierarchyAnswer, Cancelled> {
1136 let mut result = None;
1137 self.with_hierarchy_query_for(code_unit, &mut |query| {
1138 result = Some(traverse_hierarchy_while(
1139 code_unit,
1140 query,
1141 scope.cancellation(),
1142 |query, next| query.direct_descendants_within(next, scope),
1143 ));
1144 });
1145 let mut answer = result.expect("hierarchy query callback runs once")?;
1146 answer.edges.sort();
1147 Ok(answer)
1148 }
1149
1150 fn get_polymorphic_matches<T: CodeUnitIndex>(
1151 &self,
1152 target: &CodeUnit,
1153 analyzer: &T,
1154 ) -> HierarchyAnswer
1155 where
1156 Self: Sized,
1157 {
1158 if !target.is_function() {
1159 return HierarchyAnswer::complete(Vec::new());
1160 }
1161
1162 let Some(parent) = analyzer.parent_of(target) else {
1163 return HierarchyAnswer::complete(Vec::new());
1164 };
1165
1166 self.get_descendants(&parent)
1167 }
1168}
1169
1170pub struct DirectDescendantIndex {
1172 nodes: Box<[CodeUnit]>,
1173 row_by_ancestor: HashMap<CodeUnit, u32>,
1174 descendants: CompactRows<u32>,
1175}
1176
1177impl DirectDescendantIndex {
1178 pub fn estimated_retained_bytes(&self) -> usize {
1179 let node_bytes = self
1180 .nodes
1181 .iter()
1182 .map(|unit| {
1183 std::mem::size_of::<CodeUnit>()
1184 + unit.fq_name().len()
1185 + unit.signature().map_or(0, str::len)
1186 + unit.source().rel_path().as_os_str().len()
1187 + unit.source().root().as_os_str().len()
1188 })
1189 .sum::<usize>();
1190 std::mem::size_of::<Self>()
1191 + node_bytes * 2
1192 + self.row_by_ancestor.capacity()
1193 * (std::mem::size_of::<CodeUnit>() + std::mem::size_of::<u32>() + 1)
1194 + self.descendants.estimated_bytes() as usize
1195 }
1196
1197 pub fn from_indexed_nodes(
1198 nodes: Vec<CodeUnit>,
1199 index_by_node: HashMap<CodeUnit, u32>,
1200 mut edges: Vec<(u32, u32)>,
1201 ) -> Self {
1202 normalize_indexed_edges(&nodes, &index_by_node, &mut edges);
1203
1204 let row_count = usize::from(!edges.is_empty())
1205 + edges
1206 .windows(2)
1207 .filter(|pair| pair[0].0 != pair[1].0)
1208 .count();
1209 let mut row_by_ancestor = HashMap::default();
1210 let mut descendants = CompactRowsBuilder::with_capacity(row_count, edges.len());
1211 let mut cursor = 0;
1212 while cursor < edges.len() {
1213 let ancestor = edges[cursor].0;
1214 let start = cursor;
1215 while cursor < edges.len() && edges[cursor].0 == ancestor {
1216 cursor += 1;
1217 }
1218 let row =
1219 u32::try_from(descendants.rows()).expect("hierarchy index rows must fit in a u32");
1220 row_by_ancestor.insert(nodes[ancestor as usize].clone(), row);
1221 descendants.push_row(
1222 edges[start..cursor]
1223 .iter()
1224 .map(|(_, descendant)| *descendant),
1225 );
1226 }
1227 Self {
1228 nodes: nodes.into_boxed_slice(),
1229 row_by_ancestor,
1230 descendants: descendants.finish(),
1231 }
1232 }
1233
1234 pub fn descendants(&self, ancestor: &CodeUnit) -> HashSet<CodeUnit> {
1235 let Some(row) = self.row_by_ancestor.get(ancestor).copied() else {
1236 return HashSet::default();
1237 };
1238 self.descendants
1239 .row(row as usize)
1240 .iter()
1241 .map(|descendant| self.nodes[*descendant as usize].clone())
1242 .collect()
1243 }
1244}
1245
1246pub fn descendants_from_variant_index(
1255 index: &KeyedPoolSafeMemo<DescendantIndexVariant, DirectDescendantIndex>,
1256 scope: &DescendantIndexScope<'_>,
1257 code_unit: &CodeUnit,
1258 build: impl Fn() -> Option<DirectDescendantIndex>,
1259) -> Option<HashSet<CodeUnit>> {
1260 Some(
1261 index
1262 .cell(&scope.variant())
1263 .get_or_build_while(&scope.keep_going(), &build, &build)?
1266 .descendants(code_unit),
1267 )
1268}
1269
1270pub fn build_direct_descendant_index<A, P>(
1278 analyzer: &A,
1279 provider: &P,
1280 scope: &DescendantIndexScope<'_>,
1281) -> Option<DirectDescendantIndex>
1282where
1283 A: CodeUnitIndex,
1284 P: TypeHierarchyProvider + ?Sized,
1285{
1286 let mut result = None;
1287 provider.with_hierarchy_query(&mut |query| {
1288 result = Some(build_direct_descendant_index_in_query(
1289 analyzer, query, scope,
1290 ));
1291 });
1292 result.expect("hierarchy query callback runs once")
1293}
1294
1295pub fn build_direct_descendant_index_in_query<A: CodeUnitIndex>(
1297 analyzer: &A,
1298 query: &mut dyn HierarchyQuery,
1299 scope: &DescendantIndexScope<'_>,
1300) -> Option<DirectDescendantIndex> {
1301 build_direct_descendant_index_from_candidates(
1302 analyzer
1303 .all_declarations()
1304 .filter(|candidate| candidate.is_class() && scope.admits(candidate))
1305 .collect(),
1306 |candidate| {
1307 let answer = query.direct_ancestors_within(candidate, scope).ok()?;
1308 answer.is_complete().then_some(answer.edges)
1311 },
1312 &scope.keep_going(),
1313 )
1314}
1315
1316pub fn build_direct_descendant_index_from_candidates<F>(
1326 mut candidates: Vec<CodeUnit>,
1327 mut direct_ancestors: F,
1328 keep_going: &dyn Fn() -> bool,
1329) -> Option<DirectDescendantIndex>
1330where
1331 F: FnMut(&CodeUnit) -> Option<Vec<CodeUnit>>,
1332{
1333 candidates.sort();
1334 candidates.dedup();
1335 let mut types_by_fq_name: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1336 for candidate in &candidates {
1337 types_by_fq_name
1338 .entry(candidate.fq_name())
1339 .or_default()
1340 .push(candidate.clone());
1341 }
1342 let mut nodes = candidates.clone();
1343 let mut index_by_node: HashMap<_, _> = nodes
1344 .iter()
1345 .enumerate()
1346 .map(|(index, node)| {
1347 (
1348 node.clone(),
1349 u32::try_from(index).expect("hierarchy index declarations must fit in a u32"),
1350 )
1351 })
1352 .collect();
1353 let mut edges = Vec::new();
1354 for candidate in candidates {
1355 if !keep_going() {
1356 return None;
1357 }
1358 let descendant = index_by_node[&candidate];
1359 for ancestor in direct_ancestors(&candidate)? {
1360 let ancestor = types_by_fq_name
1361 .get(&ancestor.fq_name())
1362 .and_then(|same_name| {
1363 let mut same_source = same_name
1364 .iter()
1365 .filter(|unit| unit.source() == candidate.source());
1366 let exact = same_source.next()?;
1367 same_source.next().is_none().then(|| exact.clone())
1368 })
1369 .unwrap_or(ancestor);
1370 let ancestor = *index_by_node.entry(ancestor.clone()).or_insert_with(|| {
1371 let index = u32::try_from(nodes.len())
1372 .expect("hierarchy index declarations must fit in a u32");
1373 nodes.push(ancestor);
1374 index
1375 });
1376 edges.push((ancestor, descendant));
1377 }
1378 }
1379 Some(DirectDescendantIndex::from_indexed_nodes(
1380 nodes,
1381 index_by_node,
1382 edges,
1383 ))
1384}
1385
1386fn traverse_hierarchy(
1387 root: &CodeUnit,
1388 query: &mut dyn HierarchyQuery,
1389 mut next: impl FnMut(&mut dyn HierarchyQuery, &CodeUnit) -> HierarchyAnswer,
1390) -> HierarchyAnswer {
1391 traverse_hierarchy_edges(root, |unit| next(query, unit))
1392}
1393
1394fn traverse_hierarchy_while(
1395 root: &CodeUnit,
1396 query: &mut dyn HierarchyQuery,
1397 cancellation: &CancellationToken,
1398 mut next: impl FnMut(&mut dyn HierarchyQuery, &CodeUnit) -> Result<HierarchyAnswer, Cancelled>,
1399) -> Result<HierarchyAnswer, Cancelled> {
1400 traverse_hierarchy_edges_while(root, cancellation, |unit| next(query, unit))
1401}
1402
1403fn traverse_hierarchy_edges_while<F>(
1408 root: &CodeUnit,
1409 cancellation: &CancellationToken,
1410 mut next: F,
1411) -> Result<HierarchyAnswer, Cancelled>
1412where
1413 F: FnMut(&CodeUnit) -> Result<HierarchyAnswer, Cancelled>,
1414{
1415 let mut result = HierarchyAnswer::default();
1416 let mut seen = BTreeSet::new();
1417 let mut queue = VecDeque::from([root.clone()]);
1418 while let Some(current) = queue.pop_front() {
1419 let answer = next(¤t)?;
1420 result.ambiguous.extend(answer.ambiguous);
1421 result.open.extend(answer.open);
1422 for item in answer.edges {
1423 if seen.insert(item.fq_name()) {
1424 queue.push_back(item.clone());
1425 result.edges.push(item);
1426 }
1427 }
1428 if !queue.is_empty() && cancellation.is_cancelled() {
1429 return Err(Cancelled);
1430 }
1431 }
1432 Ok(result)
1433}
1434
1435fn traverse_hierarchy_edges<F>(root: &CodeUnit, mut next: F) -> HierarchyAnswer
1436where
1437 F: FnMut(&CodeUnit) -> HierarchyAnswer,
1438{
1439 traverse_hierarchy_edges_while(root, &CancellationToken::new(), |unit| Ok(next(unit)))
1442 .expect("unscoped hierarchy traversal cannot stop")
1443}
1444
1445#[cfg(test)]
1446mod hierarchy_traversal_properties {
1447 use super::*;
1448 use crate::analyzer::model::CodeUnitType;
1449 use proptest::prelude::*;
1450 use std::collections::BTreeMap;
1451
1452 const NODES: usize = 8;
1453
1454 fn node(index: usize) -> CodeUnit {
1455 CodeUnit::new(
1456 ProjectFile::new(std::env::temp_dir(), "Hierarchy.java"),
1457 CodeUnitType::Class,
1458 "",
1459 format!("N{index}"),
1460 )
1461 }
1462
1463 #[test]
1464 fn transitive_walks_borrow_one_query_and_preserve_scoped_dispatch() {
1465 use std::sync::atomic::{AtomicUsize, Ordering};
1466 struct Provider(AtomicUsize);
1467 struct Query;
1468 impl HierarchyQuery for Query {
1469 fn direct_ancestors(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
1470 let index = index_of(unit);
1471 HierarchyAnswer::complete(if index < 2 {
1472 vec![node(index + 1)]
1473 } else {
1474 Vec::new()
1475 })
1476 }
1477 fn direct_descendants(&mut self, unit: &CodeUnit) -> HierarchyAnswer {
1478 self.direct_ancestors(unit)
1479 }
1480 fn member_family_ancestors(&mut self, _unit: &CodeUnit) -> MemberFamilyAncestors {
1481 MemberFamilyAncestors::default()
1482 }
1483 fn direct_descendants_within(
1484 &mut self,
1485 _unit: &CodeUnit,
1486 _scope: &DescendantIndexScope<'_>,
1487 ) -> Result<HierarchyAnswer, Cancelled> {
1488 Err(Cancelled)
1489 }
1490 }
1491 impl TypeHierarchyProvider for Provider {
1492 fn get_direct_ancestors(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1493 panic!("the walk must use its query")
1494 }
1495 fn get_direct_descendants(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1496 panic!("the walk must use its query")
1497 }
1498 fn with_hierarchy_query(&self, run: &mut dyn FnMut(&mut dyn HierarchyQuery)) {
1499 self.0.fetch_add(1, Ordering::Relaxed);
1500 run(&mut Query);
1501 }
1502 }
1503 let provider = Provider(AtomicUsize::new(0));
1504 assert_eq!(
1505 provider.get_ancestors(&node(0)).edges,
1506 vec![node(1), node(2)]
1507 );
1508 assert_eq!(provider.0.load(Ordering::Relaxed), 1);
1509 assert_eq!(
1510 provider.get_descendants(&node(0)).edges,
1511 vec![node(1), node(2)]
1512 );
1513 assert_eq!(provider.0.load(Ordering::Relaxed), 2);
1514 let cancellation = CancellationToken::new();
1515 assert!(
1516 provider
1517 .get_descendants_within(
1518 &node(0),
1519 &DescendantIndexScope::whole_workspace(&cancellation)
1520 )
1521 .is_err()
1522 );
1523 assert_eq!(provider.0.load(Ordering::Relaxed), 3);
1524 }
1525
1526 #[test]
1527 fn default_query_forwards_scoped_provider_overrides() {
1528 struct Provider;
1529 impl TypeHierarchyProvider for Provider {
1530 fn get_direct_ancestors(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1531 HierarchyAnswer::complete(vec![node(1)])
1532 }
1533 fn get_direct_descendants(&self, _unit: &CodeUnit) -> HierarchyAnswer {
1534 HierarchyAnswer::complete(vec![node(2)])
1535 }
1536 fn get_direct_ancestors_within(
1537 &self,
1538 _unit: &CodeUnit,
1539 _scope: &DescendantIndexScope<'_>,
1540 ) -> Result<HierarchyAnswer, Cancelled> {
1541 Err(Cancelled)
1542 }
1543 fn member_family_ancestors(&self, _unit: &CodeUnit) -> MemberFamilyAncestors {
1544 MemberFamilyAncestors {
1545 resolved: vec![node(3)],
1546 ..Default::default()
1547 }
1548 }
1549 }
1550 let cancellation = CancellationToken::new();
1551 Provider.with_hierarchy_query(&mut |query| {
1552 assert_eq!(query.direct_ancestors(&node(0)).edges, vec![node(1)]);
1553 assert_eq!(query.direct_descendants(&node(0)).edges, vec![node(2)]);
1554 assert_eq!(
1555 query.member_family_ancestors(&node(0)).resolved,
1556 vec![node(3)]
1557 );
1558 assert!(
1559 query
1560 .direct_ancestors_within(
1561 &node(0),
1562 &DescendantIndexScope::whole_workspace(&cancellation)
1563 )
1564 .is_err()
1565 );
1566 });
1567 }
1568
1569 fn index_of(unit: &CodeUnit) -> usize {
1570 unit.fq_name()[1..].parse().expect("test node name")
1571 }
1572
1573 #[test]
1574 fn closure_preserves_uncertainty_without_traversing_alternatives() {
1575 let mut visited = Vec::new();
1576 let answer = traverse_hierarchy_edges(&node(0), |unit| {
1577 let id = index_of(unit);
1578 visited.push(id);
1579 match id {
1580 0 => HierarchyAnswer {
1581 edges: vec![node(1), node(1)],
1582 ambiguous: vec![vec![node(2), node(3)]],
1583 open: Vec::new(),
1584 },
1585 1 => HierarchyAnswer {
1586 edges: Vec::new(),
1587 ambiguous: vec![vec![node(4), node(5)]],
1588 open: vec![HierarchyOpenReason::UnreadAncestor(
1589 UnreadMemberFamilyAncestor {
1590 name: "External".into(),
1591 inventory: None,
1592 },
1593 )],
1594 },
1595 _ => panic!("ambiguous alternatives must not be traversed"),
1596 }
1597 });
1598 assert_eq!(visited, vec![0, 1]);
1599 assert_eq!(answer.edges, vec![node(1)]);
1600 assert_eq!(
1601 answer.ambiguous,
1602 vec![vec![node(2), node(3)], vec![node(4), node(5)]]
1603 );
1604 assert!(
1605 matches!(answer.open.as_slice(), [HierarchyOpenReason::UnreadAncestor(ancestor)] if ancestor.name == "External")
1606 );
1607 assert!(!answer.is_complete());
1608 }
1609
1610 fn graphs() -> impl Strategy<Value = Vec<Vec<usize>>> {
1612 proptest::collection::vec(proptest::collection::vec(0..NODES, 0..5), NODES)
1613 }
1614
1615 fn reference_distances(graph: &[Vec<usize>], root: usize) -> BTreeMap<usize, usize> {
1619 let mut distance: BTreeMap<usize, usize> = BTreeMap::new();
1620 for &next in &graph[root] {
1621 distance.insert(next, 1);
1622 }
1623 loop {
1624 let mut changed = false;
1625 for (from, edges) in graph.iter().enumerate() {
1626 let Some(&base) = distance.get(&from) else {
1627 continue;
1628 };
1629 for &to in edges {
1630 if distance.get(&to).is_none_or(|&known| known > base + 1) {
1631 distance.insert(to, base + 1);
1632 changed = true;
1633 }
1634 }
1635 }
1636 if !changed {
1637 return distance;
1638 }
1639 }
1640 }
1641
1642 proptest! {
1643 #![proptest_config(ProptestConfig::with_cases(128))]
1644
1645 #[test]
1650 fn visits_exactly_the_reachable_set_once_in_breadth_first_order(
1651 graph in graphs(),
1652 root in 0..NODES,
1653 ) {
1654 let mut calls: BTreeMap<usize, usize> = BTreeMap::new();
1655 let result = traverse_hierarchy_edges(&node(root), |unit| {
1656 let id = index_of(unit);
1657 *calls.entry(id).or_default() += 1;
1658 HierarchyAnswer::complete(graph[id].iter().map(|&to| node(to)).collect())
1659 });
1660 let found: Vec<usize> = result.edges.iter().map(index_of).collect();
1661
1662 let distance = reference_distances(&graph, root);
1663 let unique: BTreeSet<usize> = found.iter().copied().collect();
1664 prop_assert_eq!(found.len(), unique.len(), "a declaration was returned twice: {:?}", found);
1665 prop_assert_eq!(unique, distance.keys().copied().collect::<BTreeSet<_>>());
1666 prop_assert!(
1667 found.windows(2).all(|pair| distance[&pair[0]] <= distance[&pair[1]]),
1668 "not breadth-first: {:?} with distances {:?}", found, distance
1669 );
1670 prop_assert!(
1673 calls.iter().all(|(&id, &count)| count == 1 || (id == root && count == 2)),
1674 "repeated expansion: {:?}", calls
1675 );
1676
1677 let live = CancellationToken::new();
1679 let guarded = traverse_hierarchy_edges_while(&node(root), &live, |unit| {
1680 Ok(HierarchyAnswer::complete(graph[index_of(unit)].iter().map(|&to| node(to)).collect()))
1681 });
1682 let guarded = guarded.expect("live traversal");
1683 prop_assert!(guarded.is_complete());
1684 prop_assert_eq!(guarded.edges, result.edges);
1685 }
1686
1687 #[test]
1691 fn guarded_traversal_never_returns_a_partial_answer(
1692 graph in graphs(),
1693 root in 0..NODES,
1694 fail_on in 0..NODES,
1695 ) {
1696 let reachable = reference_distances(&graph, root);
1697 let failing = traverse_hierarchy_edges_while(&node(root), &CancellationToken::new(), |unit| {
1698 let id = index_of(unit);
1699 if id == fail_on { Err(Cancelled) } else { Ok(HierarchyAnswer::complete(graph[id].iter().map(|&to| node(to)).collect())) }
1700 });
1701 let requested = fail_on == root || reachable.contains_key(&fail_on);
1704 prop_assert_eq!(failing.is_err(), requested);
1705
1706 let cancelled = CancellationToken::new();
1707 cancelled.cancel();
1708 let stopped = traverse_hierarchy_edges_while(&node(root), &cancelled, |unit| {
1709 Ok(HierarchyAnswer::complete(graph[index_of(unit)].iter().map(|&to| node(to)).collect()))
1710 });
1711 prop_assert_eq!(stopped.is_err(), !graph[root].is_empty());
1712 if graph[root].is_empty() {
1713 let stopped = stopped.expect("empty traversal");
1714 prop_assert!(stopped.is_complete());
1715 prop_assert!(stopped.edges.is_empty());
1716 }
1717 }
1718 }
1719}