1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
//! The analyzer-resident products C++'s language logic resolves through.
//!
//! `CppAnalyzer` owns five moka caches, three `Arc<OnceLock<..>>` cells and one
//! `PoolSafeMemo`; every one of them stays in `brokk-bifrost-analysis` because
//! `IAnalyzer::update`/`update_all` rebuild the analyzer wholesale through
//! `Self::from_inner`. What crosses the crate line is the *decision* that fills
//! each cell -- the builders in [`crate::hierarchy`], [`crate::identity`] and
//! [`crate::imports`] -- plus this trait, which is how a free function reaches
//! back for a memoized product without naming the analyzer type.
//!
//! The census found no re-entrancy among these accessors, so this is a single
//! tier rather than the two-tier split some fleet languages needed: the #1134
//! reconciliation reads `visible_type_units`, which reads `include_target_index`
//! and the supertrait's `import_statements`, and none of those reads back into
//! reconciliation.
//!
//! The visible-type table remains analyzer-owned:
//!
//! * [`CppSource::visible_type_units`] is the moka-cached include-closure
//! class table. Its *builder* is [`crate::hierarchy::build_cpp_visible_type_units`];
//! the cell and its `test-support` build counter stay analyzer-side, so this
//! accessor is the only way the reconciler reaches a warm table.
use crate::compile_context::CppCompileContext;
use crate::declarations::CppRecoveredExportClassIndex;
use crate::graph::CppWorkspaceSource;
use crate::graph::resolver::{
CppClassDeclarationStrength, OrphanedNamespaceScopeIndex, SourceUsingIndex,
};
use crate::identity::CppCallableUnitRole;
use crate::imports::{IncludeTargetIndex, canonical_include_paths, canonical_include_statements};
use brokk_bifrost_core::analyzer::capabilities::{TypeAliasProvider, TypeHierarchyProvider};
use brokk_bifrost_core::analyzer::model::{CppFieldLinkage, CppTemplateMetadata};
use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
use brokk_bifrost_core::analyzer::query_token::QueryToken;
use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, ProjectFile};
use brokk_bifrost_core::cancellation::CancellationToken;
use brokk_bifrost_core::hash::{HashMap, HashSet};
use std::collections::BTreeSet;
use std::sync::Arc;
pub trait CppSource:
CodeUnitIndex + TypeAliasProvider + TypeHierarchyProvider + CppWorkspaceSource
{
/// Canonical declaration syntax for the file's primary compilation reading.
fn declaration_source_facts(
&self,
_token: QueryToken<'_>,
_file: &ProjectFile,
) -> Option<Arc<crate::source_facts::CppFileSourceFacts>> {
None
}
/// Canonical AST-owned include facts for the file's primary reading.
/// `None` means source-facts publication is unavailable; an empty vector
/// is an authoritative include-free file.
fn canonical_include_facts(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Option<Vec<brokk_bifrost_core::analyzer::cpp_facts::CppIncludeFact>> {
self.declaration_source_facts(token, file)
.map(|source| source.facts.includes.clone())
}
/// Canonical include paths for a file's primary reading.
fn canonical_include_paths(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Option<Vec<String>> {
self.declaration_source_facts(token, file)
.map(|source| canonical_include_paths(&source))
}
/// Exact source alternatives for a mounted declaration. Unavailable source
/// publication stays unknown; consumers must never reparse the candidate.
fn declaration_source_properties(
&self,
token: QueryToken<'_>,
unit: &CodeUnit,
) -> Option<Vec<brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact>> {
let source = self.declaration_source_facts(token, unit.source())?;
let facts: Vec<_> = source.for_unit(unit).cloned().collect();
(!facts.is_empty()).then_some(facts)
}
fn declaration_source_occurrences(
&self,
token: QueryToken<'_>,
unit: &CodeUnit,
) -> Option<Vec<crate::source_facts::CppDeclarationOccurrence>> {
let source = self.declaration_source_facts(token, unit.source())?;
let occurrences = source.declaration_occurrences(unit);
(!occurrences.is_empty()).then_some(occurrences)
}
/// Navigation alternatives come from the same canonical occurrences as
/// declaration properties. An analyzer that selects alternate dialects must
/// override this method to join against the selected dialect's own arena.
fn declaration_navigation_occurrences(
&self,
token: QueryToken<'_>,
unit: &CodeUnit,
) -> Option<Vec<crate::source_facts::CppNavigationOccurrence>> {
let source = self.declaration_source_facts(token, unit.source())?;
let facts = self.declaration_source_properties(token, unit)?;
Some(
facts
.into_iter()
.map(|fact| {
let declaration = source.source.declaration(fact.declaration);
let range = source.source.occurrence(declaration.occurrence).range;
let conditional_family = fact.conditional_family.map(|id| {
let range = source.source.occurrence(id).range;
(range.start_byte, range.end_byte)
});
crate::source_facts::CppNavigationOccurrence {
range,
role: fact.occurrence_role,
conditional_family,
}
})
.collect(),
)
}
/// Structured includes for a disposable visibility traversal.
fn visibility_import_statements(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Vec<String> {
self.declaration_source_facts(token, file)
.map(|source| canonical_include_statements(&source))
.unwrap_or_default()
}
/// Physical identifier candidates used only to discover source files for
/// a bounded visibility traversal. Re-keyed out-of-line definitions do not
/// add an includable source, so analyzer implementations may answer this
/// from their stored declaration index without global identity
/// reconciliation.
fn visibility_identifier_candidates(&self, identifier: &str) -> BTreeSet<CodeUnit> {
self.lookup_candidates_by_identifier(identifier)
}
/// Batched form of [`Self::visibility_identifier_candidates`].
fn visibility_identifier_candidates_batch(
&self,
identifiers: &HashSet<String>,
cancellation: Option<&CancellationToken>,
) -> HashMap<String, BTreeSet<CodeUnit>> {
identifiers
.iter()
.take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
.map(|identifier| {
(
identifier.clone(),
self.visibility_identifier_candidates(identifier),
)
})
.collect()
}
/// The callable role recorded for a physical stored declaration, without
/// consulting reconciliation fallbacks that may themselves be building.
fn stored_callable_unit_role(&self, callable: &CodeUnit) -> CppCallableUnitRole;
/// The workspace-wide `#include` resolution table, built once per analyzer
/// generation from [`IncludeTargetIndex::build`].
fn include_target_index(&self) -> &IncludeTargetIndex;
/// Every class-like or alias declaration reachable from `file` through its
/// `#include` closure, memoized per file. See this module's note.
fn visible_type_units(&self, file: &ProjectFile) -> Arc<Vec<CodeUnit>>;
/// [`Self::visible_type_units`] under a caller's deadline.
///
/// `None` means the include-closure walk stopped short. Nothing is
/// memoized in that case: a truncated class table is indistinguishable
/// from a file that simply sees fewer types, so every later base-specifier
/// resolution reading it would silently lose ancestors.
///
/// This is the shape issue #1748 needed. The closure walk is individually
/// cheap -- a fraction of a millisecond to about 180 ms -- but the
/// descendant-index build above it runs one per class in the workspace,
/// which on a large tree is tens of thousands of them inside a single
/// request that asked for thirty seconds.
fn visible_type_units_while(
&self,
file: &ProjectFile,
keep_going: &dyn Fn() -> bool,
) -> Option<Arc<Vec<CodeUnit>>>;
/// The per-file structured using index (ordinary using declarations and
/// using-directives with their guard environments), memoized per file.
/// Built by [`crate::graph::extractor::build_source_using_index`], which is
/// a pure function of the file's parsed content.
///
/// Memoized on the analyzer rather than on `VisibilityIndex` because a
/// fresh visibility index is built per usage query: rebuilding the index
/// per query re-walked a 9.5 MB amalgamation's AST once per candidate
/// (issue #1927).
fn source_using_index(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Arc<SourceUsingIndex>;
/// The indexed source of `file` (`TreeSitterAnalyzer::file_source`).
fn file_source(&self, file: &ProjectFile) -> Option<String>;
/// The parsed tree and its source backing for `file`, from the analyzer's
/// query read cache.
///
/// The single hottest member of this trait: the usage graph reaches it at
/// 27 call sites. The cache is *not* rebuilt per call -- `VisibilityIndex`
/// borrows the analyzer rather than cloning it precisely so this stays warm
/// across a scan (#1175), so an implementor must forward to the same
/// analyzer the query is running against.
///
/// The [`QueryToken`] is proof that a request scope is open, so the cache
/// this reads is live (issue #2414 step 3).
fn prepared_syntax(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Option<Arc<PreparedSyntaxTree>>;
/// The persisted linkage fact for one C++ field, when the parser recorded
/// it. A missing fact is unavailable linkage evidence.
fn cpp_field_linkage(&self, code_unit: &CodeUnit) -> Option<CppFieldLinkage>;
/// The cached result of a preprocessor-visible include-reachability walk.
///
/// The reference language affects only `__cplusplus` guards. Callers pass
/// that fact as a Boolean so cache keys do not retain the full reference
/// path.
fn cached_unconditional_include_reachability(
&self,
first: &ProjectFile,
donor_source: &ProjectFile,
reference_is_c: bool,
) -> Option<bool>;
/// Store a completed preprocessor-visible include-reachability walk.
fn cache_unconditional_include_reachability(
&self,
first: &ProjectFile,
donor_source: &ProjectFile,
reference_is_c: bool,
reaches: bool,
);
/// The file's embedded export-macro class recovery, resolved once per file.
///
/// Built by [`crate::declarations::CppRecoveredExportClassIndex::build`],
/// which is a pure function of the file's parsed content. Memoized on the
/// analyzer for the same reason as [`Self::source_using_index`]: the
/// declaration-strength question asks it once per class-like unit, and
/// re-deriving it walks and sorts every `ERROR` subtree in the file (#1496).
fn recovered_export_class_index(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Arc<CppRecoveredExportClassIndex>;
/// The namespaces C++ parse recovery dropped from `file`'s tree, resolved
/// once per file.
///
/// Built by [`OrphanedNamespaceScopeIndex::build`], a pure function of the
/// file's parsed content, and empty for a file without parse errors. Both
/// lookup directions derive every lexical scope in the file through it, so
/// it is memoized on the analyzer like [`Self::source_using_index`] rather
/// than rebuilt per query or per reference (issue #1537).
fn orphaned_namespace_scopes(
&self,
token: QueryToken<'_>,
file: &ProjectFile,
) -> Arc<OrphanedNamespaceScopeIndex>;
/// The memoized answer of
/// [`crate::graph::resolver::cpp_class_declaration_strength`] for one
/// class-like unit, when a previous ask stored it.
///
/// The answer is a pure function of the unit's declaration ranges and its
/// file's parse tree, so it is stable for an analyzer generation. Memoized
/// on the analyzer for the same reason as [`Self::source_using_index`]: the
/// inverse scan asks it once per declaration seed, and on a translation
/// unit the parser could not fully recover each ask re-derives the
/// export-macro recovery shapes from the file's `ERROR` subtrees, which is
/// quadratic in the file's size (#1496).
fn cached_class_declaration_strength(
&self,
candidate: &CodeUnit,
) -> Option<CppClassDeclarationStrength>;
/// Store a completed declaration-strength answer.
fn cache_class_declaration_strength(
&self,
candidate: &CodeUnit,
strength: CppClassDeclarationStrength,
);
/// The declaration's syntactic owner, which unlike
/// [`CodeUnitIndex::parent_of`] never falls back to a definition-row lookup.
fn structural_parent_of(&self, code_unit: &CodeUnit) -> Option<CodeUnit>;
/// The persisted C++ template metadata side table's row for `code_unit`.
fn template_metadata(&self, code_unit: &CodeUnit) -> Option<CppTemplateMetadata>;
/// Whether a reference written in the header `file` reads the declarations
/// it can see with C semantics: every workspace translation unit that
/// provably compiles this header compiles it as C (issue #1970).
///
/// Always false for a translation unit -- its own extension settles its
/// dialect, which [`crate::graph::resolver::is_c_source_file`] answers
/// without asking the analyzer. Both halves are combined by
/// [`crate::graph::resolver::reference_uses_c_semantics`], the one helper
/// resolution asks.
///
/// A header both languages provably include reports false: forward
/// resolution from it reports the C++ identity, and the site-equivalence
/// union below is what keeps inverse in agreement.
fn header_uses_c_semantics(&self, file: &ProjectFile) -> bool;
/// The declarations of `file` as one of its two readings sees them.
///
/// `c_semantics` false is the C++ reading, which is exactly
/// [`CodeUnitIndex::declarations`]. True is the C reading: the stored
/// `cpp:c` row-set when the blob has one, and otherwise the same C++
/// row-set, because "no C rows" means the two readings agree.
///
/// Candidate enumeration selects a reading here and nowhere else: include
/// activation, preprocessor-guard compatibility, block-scope shadowing and
/// ambiguity all run unchanged over whichever set comes back.
fn declarations_in_reading(&self, file: &ProjectFile, c_semantics: bool) -> BTreeSet<CodeUnit>;
/// The identities of the other reading that share `code_unit`'s
/// declaration site -- the same source file and the same declaration byte
/// range -- empty when the two readings agree about it.
///
/// Keyed on the range, never on a name. Inverse results are unioned across
/// these so a query against either identity of one declaration reports
/// every reference found under both readings.
fn site_equivalent_units(&self, code_unit: &CodeUnit) -> Vec<CodeUnit>;
/// Every distinct `compile_commands.json` configuration governing `file`,
/// empty when the workspace has no compile database entry naming it. The
/// only analyzer-resident product [`crate::diagnostics`] needs.
///
/// A file the build compiles in several configurations yields several
/// contexts, because their include closures can disagree about a name.
fn compile_contexts_for(&self, file: &ProjectFile) -> &[CppCompileContext];
/// Every workspace translation unit whose `#include` closure transitively
/// reaches `file` -- the header-to-TU attribution map from #1970's
/// Milestone 2, which #2011 phase 2 reads for header-sited guard proof.
/// Empty when nothing reaches `file` or the implementor has no include
/// graph, which conservatively yields no compile-context facts.
fn reaching_translation_units(&self, _file: &ProjectFile) -> Vec<ProjectFile> {
Vec::new()
}
/// Count a precise-parent resolution against the analyzer's counter.
///
/// Called from production code in [`crate::graph::resolver`], which is why
/// it is on the trait at all; the counter itself stays on the analyzer.
///
/// The default is a no-op because the two gates are not the same gate:
/// Cargo turns this crate's `test-support` on for the whole build graph
/// whenever `brokk-bifrost-analysis`'s dev-dependencies are in play, while
/// the analyzer-side counter field is `#[cfg(any(test, feature =
/// "test-support"))]` on *that* crate. The implementor overrides this
/// exactly when it has a counter to record into -- which is the build the
/// tests reading the counter run in.
#[cfg(any(test, feature = "test-support"))]
fn record_cpp_parent_resolution_for_test(&self) {}
/// Count a class-declaration-strength parse. See
/// [`Self::record_cpp_parent_resolution_for_test`].
#[cfg(any(test, feature = "test-support"))]
fn record_cpp_class_strength_parse_for_test(&self) {}
/// Count a guard-ancestry inspection during a per-file using-index build.
/// See [`Self::record_cpp_parent_resolution_for_test`].
#[cfg(any(test, feature = "test-support"))]
fn record_using_guard_context_inspection_for_test(&self) {}
}