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::model::{CppFieldLinkage, SignatureMetadata};
23use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, ProjectFile, Range};
24use std::collections::BTreeSet;
25
26/// The workspace-wide questions a C++ scan asks of the *dispatching* analyzer
27/// rather than of the C++ analyzer.
28///
29/// Two of them have no core capability to sit on: `import_statements` is
30/// `IAnalyzer`'s raw `#include` lines, and the workspace definition index is
31/// reached through an analysis-side `DefinitionIndexHandle` that is built per
32/// call and so cannot be borrowed as a `&dyn BoundedDefinitionLookup`. Both
33/// stay the dispatching analyzer's job -- in a mixed workspace the query is
34/// issued against a `MultiAnalyzer` whose shards span languages, and the C++
35/// owner resolution depends on that reach.
36pub trait CppWorkspaceSource {
37    /// The raw import (`#include`) lines recorded for `file`.
38    fn import_statements(&self, file: &ProjectFile) -> Vec<String>;
39
40    /// Declarations in the workspace usage-definition index whose fq name is
41    /// exactly `fqn`, across every shard.
42    ///
43    /// Borrows the shard-owned units rather than cloning them. Two constraints
44    /// make that the only workable shape. Every owner-resolution caller filters
45    /// the result and clones at most one survivor, so cloning every match per
46    /// reference was pure waste; and the global-field linkage walk returns its
47    /// matches to a caller that outlives the lookup, so they must borrow the
48    /// analyzer. Both are why the impls read the index shard-by-shard: the
49    /// per-call `DefinitionIndexHandle` dies with the call.
50    fn definitions_by_fqn(&self, fqn: &str) -> Vec<&CodeUnit>;
51}
52
53/// The workspace definition index, spelled so a call reads exactly as it did
54/// against `IAnalyzer::global_usage_definition_index`.
55#[derive(Clone, Copy)]
56pub struct CppWorkspaceDefinitions<'a>(&'a dyn CppWorkspaceSource);
57
58impl<'a> CppWorkspaceDefinitions<'a> {
59    // `self.0` is copied out rather than reborrowed through `&self`, so the
60    // returned borrows carry the source's `'a` and can outlive this call.
61    pub fn fqn(&self, fqn: &str) -> Vec<&'a CodeUnit> {
62        self.0.definitions_by_fqn(fqn)
63    }
64}
65
66/// The *dispatching* analyzer's side of a C++ usage-graph scan.
67///
68/// Deliberately not the C++ analyzer, for the reason recorded on
69/// `PythonGraphSource` and `CSharpGraphSource`: in a mixed workspace the query
70/// is issued against a `MultiAnalyzer`, whose `definitions` merges every
71/// language's shards and whose provider accessors cross language boundaries.
72/// The C++ analyzer that answers the C++-only questions rides along in
73/// [`Self::cpp`], resolved once by the shim's `resolve_analyzer::<CppAnalyzer>`
74/// downcast instead of once per call site as before the move; `None` is the
75/// same answer that downcast's `else` arm gave.
76#[derive(Clone, Copy)]
77pub struct CppGraphSource<'a> {
78    pub index: &'a dyn CodeUnitIndex,
79    pub cpp: Option<&'a dyn CppSource>,
80    pub aliases: Option<&'a dyn TypeAliasProvider>,
81    pub hierarchy: Option<&'a dyn TypeHierarchyProvider>,
82    pub workspace: &'a dyn CppWorkspaceSource,
83}
84
85impl<'a> CppGraphSource<'a> {
86    /// The C++ source standing in for the dispatching analyzer.
87    ///
88    /// For the four resolution paths that only ever had the concrete C++
89    /// analyzer in hand: they passed `&CppAnalyzer` where a `&dyn IAnalyzer`
90    /// was wanted, and its `type_alias_provider()`/`type_hierarchy_provider()`
91    /// both answered `Some(self)`, so every field is the same object here too.
92    pub fn from_source(source: &'a dyn CppSource) -> Self {
93        Self {
94            index: source,
95            cpp: Some(source),
96            aliases: Some(source),
97            hierarchy: Some(source),
98            workspace: source,
99        }
100    }
101
102    pub fn type_alias_provider(&self) -> Option<&'a dyn TypeAliasProvider> {
103        self.aliases
104    }
105
106    pub fn type_hierarchy_provider(&self) -> Option<&'a dyn TypeHierarchyProvider> {
107        self.hierarchy
108    }
109
110    pub fn import_statements(&self, file: &ProjectFile) -> Vec<String> {
111        self.workspace.import_statements(file)
112    }
113
114    pub fn global_usage_definition_index(&self) -> CppWorkspaceDefinitions<'a> {
115        CppWorkspaceDefinitions(self.workspace)
116    }
117
118    pub fn parent_of(&self, code_unit: &CodeUnit) -> Option<CodeUnit> {
119        self.index.parent_of(code_unit)
120    }
121
122    pub fn ranges(&self, code_unit: &CodeUnit) -> Vec<Range> {
123        self.index.ranges(code_unit)
124    }
125
126    pub fn enclosing_code_unit(&self, file: &ProjectFile, range: &Range) -> Option<CodeUnit> {
127        self.index.enclosing_code_unit(file, range)
128    }
129
130    pub fn signature_metadata(&self, code_unit: &CodeUnit) -> Vec<SignatureMetadata> {
131        self.index.signature_metadata(code_unit)
132    }
133
134    pub fn cpp_field_linkage(&self, code_unit: &CodeUnit) -> Option<CppFieldLinkage> {
135        self.cpp?.cpp_field_linkage(code_unit)
136    }
137
138    pub fn signatures(&self, code_unit: &CodeUnit) -> Vec<String> {
139        self.index.signatures(code_unit)
140    }
141
142    pub fn get_source(&self, code_unit: &CodeUnit, include_comments: bool) -> Option<String> {
143        self.index.get_source(code_unit, include_comments)
144    }
145
146    pub fn indexed_source(&self, file: &ProjectFile) -> Option<String> {
147        self.index.indexed_source(file)
148    }
149
150    pub fn declarations(&self, file: &ProjectFile) -> BTreeSet<CodeUnit> {
151        self.index.declarations(file)
152    }
153
154    pub fn direct_children(&self, code_unit: &CodeUnit) -> Vec<CodeUnit> {
155        self.index.direct_children(code_unit)
156    }
157
158    pub fn definitions(&self, fq_name: &str) -> Box<dyn Iterator<Item = CodeUnit> + '_> {
159        self.index.definitions(fq_name)
160    }
161}
162
163/// [`crate::identity::cpp_callable_definitions_share_identity_evidence`] with
164/// its header/implementation evidence root supplied from the graph source.
165///
166/// The searchtools consumers reach the same predicate through the shim wrapper
167/// that owns the `resolve_analyzer` downcast; the scan already holds the
168/// resolved C++ source, so it passes the include index in directly. A source
169/// without a C++ analyzer answers `false`, exactly as the downcast's `else` arm
170/// did.
171pub fn callable_definitions_share_identity_evidence(
172    analyzer: &CppGraphSource<'_>,
173    left: &CodeUnit,
174    right: &CodeUnit,
175) -> bool {
176    crate::identity::cpp_callable_definitions_share_identity_evidence(
177        analyzer.index,
178        left,
179        right,
180        |left_source, right_source| {
181            let Some(implementation) =
182                crate::identity::cpp_header_body_implementation_file(left_source, right_source)
183            else {
184                return false;
185            };
186            let Some(cpp) = analyzer.cpp else {
187                return false;
188            };
189            crate::identity::cpp_header_body_files_are_related(
190                left_source,
191                right_source,
192                &analyzer.import_statements(implementation),
193                cpp.include_target_index(),
194            )
195        },
196    )
197}