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