Skip to main content

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::graph::resolver::SourceUsingIndex;
30use crate::imports::IncludeTargetIndex;
31use brokk_bifrost_core::analyzer::capabilities::{TypeAliasProvider, TypeHierarchyProvider};
32use brokk_bifrost_core::analyzer::model::{CppFieldLinkage, CppTemplateMetadata};
33use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
34use brokk_bifrost_core::analyzer::query_token::QueryToken;
35use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, ProjectFile};
36use std::collections::BTreeSet;
37use std::sync::Arc;
38
39pub trait CppSource:
40    CodeUnitIndex + TypeAliasProvider + TypeHierarchyProvider + CppWorkspaceSource
41{
42    /// The workspace-wide `#include` resolution table, built once per analyzer
43    /// generation from [`IncludeTargetIndex::build`].
44    fn include_target_index(&self) -> &IncludeTargetIndex;
45
46    /// The declared base specifiers of `code_unit`, as written
47    /// (`TreeSitterAnalyzer::raw_supertypes_of`).
48    fn raw_supertypes_of(&self, code_unit: &CodeUnit) -> Vec<String>;
49
50    /// Every class-like or alias declaration reachable from `file` through its
51    /// `#include` closure, memoized per file. See this module's note.
52    fn visible_type_units(&self, file: &ProjectFile) -> Arc<Vec<CodeUnit>>;
53
54    /// [`Self::visible_type_units`] under a caller's deadline.
55    ///
56    /// `None` means the include-closure walk stopped short. Nothing is
57    /// memoized in that case: a truncated class table is indistinguishable
58    /// from a file that simply sees fewer types, so every later base-specifier
59    /// resolution reading it would silently lose ancestors.
60    ///
61    /// This is the shape issue #1748 needed. The closure walk is individually
62    /// cheap -- a fraction of a millisecond to about 180 ms -- but the
63    /// descendant-index build above it runs one per class in the workspace,
64    /// which on a large tree is tens of thousands of them inside a single
65    /// request that asked for thirty seconds.
66    fn visible_type_units_while(
67        &self,
68        file: &ProjectFile,
69        keep_going: &dyn Fn() -> bool,
70    ) -> Option<Arc<Vec<CodeUnit>>>;
71
72    /// The per-file structured using index (ordinary using declarations and
73    /// using-directives with their guard environments), memoized per file.
74    /// Built by [`crate::graph::extractor::build_source_using_index`], which is
75    /// a pure function of the file's parsed content.
76    ///
77    /// Memoized on the analyzer rather than on `VisibilityIndex` because a
78    /// fresh visibility index is built per usage query: rebuilding the index
79    /// per query re-walked a 9.5 MB amalgamation's AST once per candidate
80    /// (issue #1927).
81    fn source_using_index(
82        &self,
83        token: QueryToken<'_>,
84        file: &ProjectFile,
85    ) -> Arc<SourceUsingIndex>;
86
87    /// The indexed source of `file` (`TreeSitterAnalyzer::file_source`).
88    fn file_source(&self, file: &ProjectFile) -> Option<String>;
89
90    /// The parsed tree and its source backing for `file`, from the analyzer's
91    /// query read cache.
92    ///
93    /// The single hottest member of this trait: the usage graph reaches it at
94    /// 27 call sites. The cache is *not* rebuilt per call -- `VisibilityIndex`
95    /// borrows the analyzer rather than cloning it precisely so this stays warm
96    /// across a scan (#1175), so an implementor must forward to the same
97    /// analyzer the query is running against.
98    ///
99    /// The [`QueryToken`] is proof that a request scope is open, so the cache
100    /// this reads is live (issue #2414 step 3).
101    fn prepared_syntax(
102        &self,
103        token: QueryToken<'_>,
104        file: &ProjectFile,
105    ) -> Option<Arc<PreparedSyntaxTree>>;
106
107    /// The persisted linkage fact for one C++ field, when the parser recorded
108    /// it. A missing fact requires the resolver's syntax fallback.
109    fn cpp_field_linkage(&self, code_unit: &CodeUnit) -> Option<CppFieldLinkage>;
110
111    /// The cached result of a preprocessor-visible include-reachability walk.
112    ///
113    /// The reference language affects only `__cplusplus` guards. Callers pass
114    /// that fact as a Boolean so cache keys do not retain the full reference
115    /// path.
116    fn cached_unconditional_include_reachability(
117        &self,
118        first: &ProjectFile,
119        donor_source: &ProjectFile,
120        reference_is_c: bool,
121    ) -> Option<bool>;
122
123    /// Store a completed preprocessor-visible include-reachability walk.
124    fn cache_unconditional_include_reachability(
125        &self,
126        first: &ProjectFile,
127        donor_source: &ProjectFile,
128        reference_is_c: bool,
129        reaches: bool,
130    );
131
132    /// The declaration's syntactic owner, which unlike
133    /// [`CodeUnitIndex::parent_of`] never falls back to a definition-row lookup.
134    fn structural_parent_of(&self, code_unit: &CodeUnit) -> Option<CodeUnit>;
135
136    /// The persisted C++ template metadata side table's row for `code_unit`.
137    fn template_metadata(&self, code_unit: &CodeUnit) -> Option<CppTemplateMetadata>;
138
139    /// Whether a reference written in the header `file` reads the declarations
140    /// it can see with C semantics: every workspace translation unit that
141    /// provably compiles this header compiles it as C (issue #1970).
142    ///
143    /// Always false for a translation unit -- its own extension settles its
144    /// dialect, which [`crate::graph::resolver::is_c_source_file`] answers
145    /// without asking the analyzer. Both halves are combined by
146    /// [`crate::graph::resolver::reference_uses_c_semantics`], the one helper
147    /// resolution asks.
148    ///
149    /// A header both languages provably include reports false: forward
150    /// resolution from it reports the C++ identity, and the site-equivalence
151    /// union below is what keeps inverse in agreement.
152    fn header_uses_c_semantics(&self, file: &ProjectFile) -> bool;
153
154    /// The declarations of `file` as one of its two readings sees them.
155    ///
156    /// `c_semantics` false is the C++ reading, which is exactly
157    /// [`CodeUnitIndex::declarations`]. True is the C reading: the stored
158    /// `cpp:c` row-set when the blob has one, and otherwise the same C++
159    /// row-set, because "no C rows" means the two readings agree.
160    ///
161    /// Candidate enumeration selects a reading here and nowhere else: include
162    /// activation, preprocessor-guard compatibility, block-scope shadowing and
163    /// ambiguity all run unchanged over whichever set comes back.
164    fn declarations_in_reading(&self, file: &ProjectFile, c_semantics: bool) -> BTreeSet<CodeUnit>;
165
166    /// The identities of the other reading that share `code_unit`'s
167    /// declaration site -- the same source file and the same declaration byte
168    /// range -- empty when the two readings agree about it.
169    ///
170    /// Keyed on the range, never on a name. Inverse results are unioned across
171    /// these so a query against either identity of one declaration reports
172    /// every reference found under both readings.
173    fn site_equivalent_units(&self, code_unit: &CodeUnit) -> Vec<CodeUnit>;
174
175    /// Every distinct `compile_commands.json` configuration governing `file`,
176    /// empty when the workspace has no compile database entry naming it. The
177    /// only analyzer-resident product [`crate::diagnostics`] needs.
178    ///
179    /// A file the build compiles in several configurations yields several
180    /// contexts, because their include closures can disagree about a name.
181    fn compile_contexts_for(&self, file: &ProjectFile) -> &[CppCompileContext];
182
183    /// Every workspace translation unit whose `#include` closure transitively
184    /// reaches `file` -- the header-to-TU attribution map from #1970's
185    /// Milestone 2, which #2011 phase 2 reads for header-sited guard proof.
186    /// Empty when nothing reaches `file` or the implementor has no include
187    /// graph, which conservatively yields no compile-context facts.
188    fn reaching_translation_units(&self, _file: &ProjectFile) -> Vec<ProjectFile> {
189        Vec::new()
190    }
191
192    /// Count a precise-parent resolution against the analyzer's counter.
193    ///
194    /// Called from production code in [`crate::graph::resolver`], which is why
195    /// it is on the trait at all; the counter itself stays on the analyzer.
196    ///
197    /// The default is a no-op because the two gates are not the same gate:
198    /// Cargo turns this crate's `test-support` on for the whole build graph
199    /// whenever `brokk-bifrost-analysis`'s dev-dependencies are in play, while
200    /// the analyzer-side counter field is `#[cfg(any(test, feature =
201    /// "test-support"))]` on *that* crate. The implementor overrides this
202    /// exactly when it has a counter to record into -- which is the build the
203    /// tests reading the counter run in.
204    #[cfg(any(test, feature = "test-support"))]
205    fn record_cpp_parent_resolution_for_test(&self) {}
206
207    /// Count a class-declaration-strength parse. See
208    /// [`Self::record_cpp_parent_resolution_for_test`].
209    #[cfg(any(test, feature = "test-support"))]
210    fn record_cpp_class_strength_parse_for_test(&self) {}
211
212    /// Count a guard-ancestry inspection during a per-file using-index build.
213    /// See [`Self::record_cpp_parent_resolution_for_test`].
214    #[cfg(any(test, feature = "test-support"))]
215    fn record_using_guard_context_inspection_for_test(&self) {}
216}