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