brokk_bifrost_cpp/graph_support.rs
1//! The analyzer-resident products C++'s language logic resolves through.
2//!
3//! `CppAnalyzer` owns five moka caches, three `Arc<OnceLock<..>>` cells and one
4//! `PoolSafeMemo`; every one of them stays in `brokk-bifrost-analysis` because
5//! `IAnalyzer::update`/`update_all` rebuild the analyzer wholesale through
6//! `Self::from_inner`. What crosses the crate line is the *decision* that fills
7//! each cell -- the builders in [`crate::hierarchy`], [`crate::identity`] and
8//! [`crate::imports`] -- plus this trait, which is how a free function reaches
9//! back for a memoized product without naming the analyzer type.
10//!
11//! The census found no re-entrancy among these accessors, so this is a single
12//! tier rather than the two-tier split some fleet languages needed: the #1134
13//! reconciliation reads `visible_type_units`, which reads `include_target_index`
14//! and the supertrait's `import_statements`, and none of those reads back into
15//! reconciliation.
16//!
17//! Three members are load-bearing beyond their signature:
18//!
19//! * [`CppSource::visible_type_units`] is the moka-cached include-closure
20//! class table. Its *builder* is [`crate::hierarchy::build_cpp_visible_type_units`];
21//! the cell and its `test-support` build counter stay analyzer-side, so this
22//! accessor is the only way the reconciler reaches a warm table.
23//! * [`CppSource::raw_supertypes_of`] is
24//! `TreeSitterAnalyzer::raw_supertypes_of`, whose rows are crate-private to
25//! analysis; the analyzer hands the decoded base-specifier strings across.
26
27use crate::compile_context::CppCompileContext;
28use crate::graph::CppWorkspaceSource;
29use crate::imports::IncludeTargetIndex;
30use brokk_bifrost_core::analyzer::capabilities::{TypeAliasProvider, TypeHierarchyProvider};
31use brokk_bifrost_core::analyzer::model::{CppFieldLinkage, CppTemplateMetadata};
32use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
33use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, ProjectFile};
34use std::sync::Arc;
35
36pub trait CppSource:
37 CodeUnitIndex + TypeAliasProvider + TypeHierarchyProvider + CppWorkspaceSource
38{
39 /// The workspace-wide `#include` resolution table, built once per analyzer
40 /// generation from [`IncludeTargetIndex::build`].
41 fn include_target_index(&self) -> &IncludeTargetIndex;
42
43 /// The declared base specifiers of `code_unit`, as written
44 /// (`TreeSitterAnalyzer::raw_supertypes_of`).
45 fn raw_supertypes_of(&self, code_unit: &CodeUnit) -> Vec<String>;
46
47 /// Every class-like or alias declaration reachable from `file` through its
48 /// `#include` closure, memoized per file. See this module's note.
49 fn visible_type_units(&self, file: &ProjectFile) -> Arc<Vec<CodeUnit>>;
50
51 /// [`Self::visible_type_units`] under a caller's deadline.
52 ///
53 /// `None` means the include-closure walk stopped short. Nothing is
54 /// memoized in that case: a truncated class table is indistinguishable
55 /// from a file that simply sees fewer types, so every later base-specifier
56 /// resolution reading it would silently lose ancestors.
57 ///
58 /// This is the shape issue #1748 needed. The closure walk is individually
59 /// cheap -- a fraction of a millisecond to about 180 ms -- but the
60 /// descendant-index build above it runs one per class in the workspace,
61 /// which on a large tree is tens of thousands of them inside a single
62 /// request that asked for thirty seconds.
63 fn visible_type_units_while(
64 &self,
65 file: &ProjectFile,
66 keep_going: &dyn Fn() -> bool,
67 ) -> Option<Arc<Vec<CodeUnit>>>;
68
69 /// The indexed source of `file` (`TreeSitterAnalyzer::file_source`).
70 fn file_source(&self, file: &ProjectFile) -> Option<String>;
71
72 /// The parsed tree and its source backing for `file`, from the analyzer's
73 /// query read cache.
74 ///
75 /// The single hottest member of this trait: the usage graph reaches it at
76 /// 27 call sites. The cache is *not* rebuilt per call -- `VisibilityIndex`
77 /// borrows the analyzer rather than cloning it precisely so this stays warm
78 /// across a scan (#1175), so an implementor must forward to the same
79 /// analyzer the query is running against.
80 fn prepared_syntax(&self, file: &ProjectFile) -> Option<Arc<PreparedSyntaxTree>>;
81
82 /// The persisted linkage fact for one C++ field, when the parser recorded
83 /// it. A missing fact requires the resolver's syntax fallback.
84 fn cpp_field_linkage(&self, code_unit: &CodeUnit) -> Option<CppFieldLinkage>;
85
86 /// The cached result of a preprocessor-visible include-reachability walk.
87 ///
88 /// The reference language affects only `__cplusplus` guards. Callers pass
89 /// that fact as a Boolean so cache keys do not retain the full reference
90 /// path.
91 fn cached_unconditional_include_reachability(
92 &self,
93 first: &ProjectFile,
94 donor_source: &ProjectFile,
95 reference_is_c: bool,
96 ) -> Option<bool>;
97
98 /// Store a completed preprocessor-visible include-reachability walk.
99 fn cache_unconditional_include_reachability(
100 &self,
101 first: &ProjectFile,
102 donor_source: &ProjectFile,
103 reference_is_c: bool,
104 reaches: bool,
105 );
106
107 /// The declaration's syntactic owner, which unlike
108 /// [`CodeUnitIndex::parent_of`] never falls back to a definition-row lookup.
109 fn structural_parent_of(&self, code_unit: &CodeUnit) -> Option<CodeUnit>;
110
111 /// The persisted C++ template metadata side table's row for `code_unit`.
112 fn template_metadata(&self, code_unit: &CodeUnit) -> Option<CppTemplateMetadata>;
113
114 /// Every distinct `compile_commands.json` configuration governing `file`,
115 /// empty when the workspace has no compile database entry naming it. The
116 /// only analyzer-resident product [`crate::diagnostics`] needs.
117 ///
118 /// A file the build compiles in several configurations yields several
119 /// contexts, because their include closures can disagree about a name.
120 fn compile_contexts_for(&self, file: &ProjectFile) -> &[CppCompileContext];
121
122 /// Count a precise-parent resolution against the analyzer's counter.
123 ///
124 /// Called from production code in [`crate::graph::resolver`], which is why
125 /// it is on the trait at all; the counter itself stays on the analyzer.
126 ///
127 /// The default is a no-op because the two gates are not the same gate:
128 /// Cargo turns this crate's `test-support` on for the whole build graph
129 /// whenever `brokk-bifrost-analysis`'s dev-dependencies are in play, while
130 /// the analyzer-side counter field is `#[cfg(any(test, feature =
131 /// "test-support"))]` on *that* crate. The implementor overrides this
132 /// exactly when it has a counter to record into -- which is the build the
133 /// tests reading the counter run in.
134 #[cfg(any(test, feature = "test-support"))]
135 fn record_cpp_parent_resolution_for_test(&self) {}
136
137 /// Count a class-declaration-strength parse. See
138 /// [`Self::record_cpp_parent_resolution_for_test`].
139 #[cfg(any(test, feature = "test-support"))]
140 fn record_cpp_class_strength_parse_for_test(&self) {}
141}