Skip to main content

brokk_bifrost_cpp/graph/
mod.rs

1//! The C++ usage graph's language knowledge.
2//!
3//! The forward scan ([`extractor`] plus [`hits`]), the visibility/macro/include
4//! resolver ([`resolver`]) and the whole-workspace inverted per-file walk
5//! ([`inverted`]) are one body of code and crossed together: `extractor` glob
6//! imports `resolver`, `hits` reads `extractor`'s scan context, and `inverted`
7//! names forty items from the other two.
8//!
9//! No analyzer handle appears here. `brokk-bifrost-analysis` downcasts once and
10//! hands over a [`CppGraphSource`] -- the *dispatching* analyzer's side of a
11//! scan -- which carries the [`CppSource`] the memoized C++ products
12//! come from.
13
14pub mod extractor;
15pub mod hits;
16pub mod inverted;
17mod macro_lexical;
18pub mod namespace_alias;
19pub mod resolver;
20pub mod syntax;
21
22use crate::graph_support::CppSource;
23use brokk_bifrost_core::analyzer::capabilities::{TypeAliasProvider, TypeHierarchyProvider};
24use brokk_bifrost_core::analyzer::fq_name::FqName;
25use brokk_bifrost_core::analyzer::model::{CppFieldLinkage, SignatureMetadata};
26use brokk_bifrost_core::analyzer::query_token::QueryToken;
27use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, ProjectFile, Range};
28use std::collections::BTreeSet;
29
30pub(super) fn cpp_namespace_path_is_ordered_subsequence(
31    namespace: &[String],
32    owner_scope: &[String],
33) -> bool {
34    let mut owner_components = owner_scope.iter();
35    namespace
36        .iter()
37        .all(|component| owner_components.any(|candidate| candidate == component))
38}
39
40/// The workspace-wide questions a C++ scan asks of the *dispatching* analyzer
41/// rather than of the C++ analyzer.
42///
43/// `import_statements` is `IAnalyzer`'s raw `#include` lines. Definition reads
44/// also stay the dispatching analyzer's job: the language crate can issue the
45/// core relational request but cannot own SQLite, and a mixed workspace must
46/// coordinate its language-local snapshots through `MultiAnalyzer`.
47pub trait CppWorkspaceSource {
48    /// The raw import (`#include`) lines recorded for `file`.
49    fn import_statements(&self, file: &ProjectFile) -> Vec<String>;
50
51    /// Exact lookup for a name already carried as extractor-owned segments.
52    /// Results are owned because the relational store, not a generation-wide
53    /// Rust map, owns the rows.
54    fn definitions_by_name(&self, token: QueryToken<'_>, name: &FqName) -> Vec<CodeUnit>;
55
56    /// Identifier-bounded candidates for a structurally segmented source path
57    /// whose intermediate kinds are not known until resolution.
58    fn definitions_by_identifier(&self, token: QueryToken<'_>, name: &FqName) -> Vec<CodeUnit>;
59}
60
61/// The workspace definition query surface, spelled so the C++ resolver does
62/// not depend on the analysis crate's store implementation.
63///
64/// Carries the request scope's [`QueryToken`] alongside the source, so a
65/// lookup made through it is proof-carrying without every C++ call site
66/// re-threading the token (issue #2423 milestone B).
67#[derive(Clone, Copy)]
68pub struct CppWorkspaceDefinitions<'a>(&'a dyn CppWorkspaceSource, QueryToken<'a>);
69
70impl<'a> CppWorkspaceDefinitions<'a> {
71    pub fn exact(&self, name: &FqName) -> Vec<CodeUnit> {
72        self.0.definitions_by_name(self.1, name)
73    }
74
75    pub fn identifier(&self, name: &FqName) -> Vec<CodeUnit> {
76        self.0.definitions_by_identifier(self.1, name)
77    }
78}
79
80/// The *dispatching* analyzer's side of a C++ usage-graph scan.
81///
82/// Deliberately not the C++ analyzer, for the reason recorded on
83/// `PythonGraphSource` and `CSharpGraphSource`: in a mixed workspace the query
84/// is issued against a `MultiAnalyzer`, whose `definitions` merges every
85/// language's shards and whose provider accessors cross language boundaries.
86/// The C++ analyzer that answers the C++-only questions rides along in
87/// [`Self::cpp`], resolved once by the shim's `resolve_analyzer::<CppAnalyzer>`
88/// downcast instead of once per call site as before the move; `None` is the
89/// same answer that downcast's `else` arm gave.
90#[derive(Clone, Copy)]
91pub struct CppGraphSource<'a> {
92    pub index: &'a dyn CodeUnitIndex,
93    pub cpp: Option<&'a dyn CppSource>,
94    pub aliases: Option<&'a dyn TypeAliasProvider>,
95    pub hierarchy: Option<&'a dyn TypeHierarchyProvider>,
96    pub workspace: &'a dyn CppWorkspaceSource,
97    /// Proof that the request scope this bundle serves is open. The bundle is
98    /// a per-query object built at a query boundary, so the syntax accessors
99    /// its resolution paths reach can take the proof from here rather than
100    /// from ninety extra parameters (issue #2414 step 3).
101    pub token: QueryToken<'a>,
102}
103
104impl<'a> CppGraphSource<'a> {
105    /// The C++ source standing in for the dispatching analyzer.
106    ///
107    /// For the four resolution paths that only ever had the concrete C++
108    /// analyzer in hand: they passed `&CppAnalyzer` where a `&dyn IAnalyzer`
109    /// was wanted, and its `type_alias_provider()`/`type_hierarchy_provider()`
110    /// both answered `Some(self)`, so every field is the same object here too.
111    pub fn from_source(source: &'a dyn CppSource, token: QueryToken<'a>) -> Self {
112        Self {
113            index: source,
114            cpp: Some(source),
115            aliases: Some(source),
116            hierarchy: Some(source),
117            workspace: source,
118            token,
119        }
120    }
121
122    pub fn type_alias_provider(&self) -> Option<&'a dyn TypeAliasProvider> {
123        self.aliases
124    }
125
126    pub fn type_hierarchy_provider(&self) -> Option<&'a dyn TypeHierarchyProvider> {
127        self.hierarchy
128    }
129
130    pub fn import_statements(&self, file: &ProjectFile) -> Vec<String> {
131        self.workspace.import_statements(file)
132    }
133
134    /// Canonical AST-owned include paths for the file's primary reading.
135    /// `None` means the source-facts mount is unavailable.
136    pub fn canonical_include_paths(&self, file: &ProjectFile) -> Option<Vec<String>> {
137        self.cpp?.canonical_include_paths(self.token, file)
138    }
139
140    pub fn workspace_definitions(&self) -> CppWorkspaceDefinitions<'a> {
141        CppWorkspaceDefinitions(self.workspace, self.token)
142    }
143
144    pub fn parent_of(&self, code_unit: &CodeUnit) -> Option<CodeUnit> {
145        self.index.parent_of(code_unit)
146    }
147
148    pub fn ranges(&self, code_unit: &CodeUnit) -> Vec<Range> {
149        self.index.ranges(code_unit)
150    }
151
152    pub fn enclosing_code_unit(&self, file: &ProjectFile, range: &Range) -> Option<CodeUnit> {
153        self.index.enclosing_code_unit(file, range)
154    }
155
156    pub fn declaration_source_occurrences(
157        &self,
158        unit: &CodeUnit,
159    ) -> Option<Vec<crate::source_facts::CppDeclarationOccurrence>> {
160        self.cpp?.declaration_source_occurrences(self.token, unit)
161    }
162
163    pub fn declaration_source_properties(
164        &self,
165        unit: &CodeUnit,
166    ) -> Option<Vec<brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact>> {
167        self.cpp?.declaration_source_properties(self.token, unit)
168    }
169
170    pub fn signature_metadata(&self, code_unit: &CodeUnit) -> Vec<SignatureMetadata> {
171        self.index.signature_metadata(code_unit)
172    }
173
174    pub fn cpp_field_linkage(&self, code_unit: &CodeUnit) -> Option<CppFieldLinkage> {
175        self.cpp?.cpp_field_linkage(code_unit)
176    }
177
178    pub fn signatures(&self, code_unit: &CodeUnit) -> Vec<String> {
179        self.index.signatures(code_unit)
180    }
181
182    pub fn get_source(&self, code_unit: &CodeUnit, include_comments: bool) -> Option<String> {
183        self.index.get_source(code_unit, include_comments)
184    }
185
186    pub fn indexed_source(&self, file: &ProjectFile) -> Option<String> {
187        self.index.indexed_source(file)
188    }
189
190    pub fn declarations(&self, file: &ProjectFile) -> BTreeSet<CodeUnit> {
191        self.index.declarations(file)
192    }
193
194    /// Whether a reference written in `file` reads C++ source with C semantics
195    /// (issue #1970). Without a C++ analyzer behind this source only the path
196    /// evidence is available, which is exactly what
197    /// [`resolver::is_c_source_file`] answered before headers gained a second
198    /// reading.
199    pub fn reference_uses_c_semantics(&self, file: &ProjectFile) -> bool {
200        match self.cpp {
201            Some(cpp) => resolver::reference_uses_c_semantics(cpp, file),
202            None => resolver::is_c_source_file(file),
203        }
204    }
205
206    /// [`crate::graph_support::CppSource::declarations_in_reading`], falling
207    /// back to the single reading a bare index can serve.
208    pub fn declarations_in_reading(
209        &self,
210        file: &ProjectFile,
211        c_semantics: bool,
212    ) -> BTreeSet<CodeUnit> {
213        match self.cpp {
214            Some(cpp) if c_semantics => cpp.declarations_in_reading(file, true),
215            _ => self.index.declarations(file),
216        }
217    }
218
219    /// [`crate::graph_support::CppSource::site_equivalent_units`], empty
220    /// without a C++ analyzer.
221    pub fn site_equivalent_units(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
222        match self.cpp {
223            Some(cpp) => cpp.site_equivalent_units(code_unit),
224            None => Vec::new(),
225        }
226    }
227
228    pub fn direct_children(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
229        self.index.direct_children(code_unit)
230    }
231
232    pub fn definitions(&self, fq_name: &str) -> Box<dyn Iterator<Item = CodeUnit> + '_> {
233        self.index.definitions(fq_name)
234    }
235}
236
237/// [`crate::identity::cpp_callable_definitions_share_identity_evidence`] with
238/// its header/implementation evidence root supplied from the graph source.
239///
240/// The searchtools consumers reach the same predicate through the shim wrapper
241/// that owns the `resolve_analyzer` downcast; the scan already holds the
242/// resolved C++ source, so it passes the include index in directly. A source
243/// without a C++ analyzer answers `false`, exactly as the downcast's `else` arm
244/// did.
245pub fn callable_definitions_share_identity_evidence(
246    analyzer: &CppGraphSource<'_>,
247    left: &CodeUnit,
248    right: &CodeUnit,
249) -> bool {
250    crate::identity::cpp_callable_definitions_share_identity_evidence(
251        analyzer.index,
252        left,
253        right,
254        |left_source, right_source| {
255            let Some(cpp) = analyzer.cpp else {
256                return false;
257            };
258            crate::identity::cpp_header_body_files_are_related(
259                cpp,
260                analyzer.token,
261                left_source,
262                right_source,
263            )
264        },
265    )
266}
267
268/// The structured-parameter variant of
269/// [`callable_definitions_share_identity_evidence`].
270///
271/// Use this when the two declarations may differ in non-identity parameter
272/// syntax, such as a default argument written only on the header prototype.
273pub fn callable_definitions_share_identity_evidence_with_visibility(
274    analyzer: &CppGraphSource<'_>,
275    visibility: &resolver::VisibilityIndex<'_>,
276    left: &CodeUnit,
277    right: &CodeUnit,
278) -> bool {
279    crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
280        analyzer,
281        visibility,
282        left,
283        right,
284        |left_source, right_source| {
285            let Some(cpp) = analyzer.cpp else {
286                return false;
287            };
288            crate::identity::cpp_header_body_files_are_related(
289                cpp,
290                analyzer.token,
291                left_source,
292                right_source,
293            )
294        },
295    )
296}