1use crate::declarations::{
4 cpp_callable_identity_suffix, cpp_callable_is_structural_constructor,
5 cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection, node_text,
6};
7use crate::graph::resolver::{
8 cpp_template_reference_arguments, cpp_type_name_components, declarator_name_node,
9 declarator_names_function_type, declared_name_binding, structured_alias_type_target,
10};
11use brokk_bifrost_core::analyzer::CodeUnit;
12use brokk_bifrost_core::analyzer::cpp_facts::*;
13use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
14use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
15use tree_sitter::Node;
16
17pub(crate) fn base_specifier_from_node(
18 node: Node<'_>,
19 source: &str,
20 is_virtual: bool,
21) -> Option<CppBaseSpecifierFact> {
22 let components = cpp_type_name_components(node, source)?;
23 (!components.is_empty()).then_some(CppBaseSpecifierFact {
24 absolute: node.child(0).is_some_and(|token| token.kind() == "::"),
25 components,
26 is_virtual,
27 })
28}
29
30pub(crate) fn declaration_name<'tree>(
31 node: Node<'tree>,
32 unit: &CodeUnit,
33 source: &str,
34) -> Option<Node<'tree>> {
35 if let Some(name) = node.child_by_field_name("name") {
36 return crate::structural::declarator_name_node(name);
37 }
38 let mut cursor = node.walk();
39 node.children_by_field_name("declarator", &mut cursor)
40 .find_map(|declarator| {
41 let name = crate::structural::declarator_name_node(declarator)?;
42 (node_text(name, source) == unit.identifier()).then_some(name)
43 })
44}
45
46pub(crate) fn capture_field_type(
49 node: Node<'_>,
50 field_name: &str,
51 source: &str,
52) -> (Option<CppDeclaredFieldTypeFact>, bool) {
53 if let Some(recovered) = crate::declarations::recovered_pyobject_head_field(node, source)
54 && node_text(recovered.name, source) == field_name
55 {
56 return (
57 Some(CppDeclaredFieldTypeFact {
58 type_text: node_text(recovered.type_node, source).to_owned(),
59 indirection: recovered.pointer_depth(),
60 binds_indirectly: recovered.pointer_depth() > 0,
61 template_arguments: None,
62 }),
63 false,
64 );
65 }
66 let Some(type_node) = node.child_by_field_name("type") else {
67 return (None, false);
68 };
69 let mut cursor = node.walk();
70 let declarator = node
71 .children_by_field_name("declarator", &mut cursor)
72 .find(|declarator| {
73 crate::structural::declarator_name_node(*declarator)
74 .is_some_and(|name| node_text(name, source) == field_name)
75 });
76 let names_function_type = declarator.is_some_and(declarator_names_function_type);
77 let field_type = declared_name_binding(node, type_node, field_name, source).map(|binding| {
78 let ty = if matches!(
79 type_node.kind(),
80 "class_specifier" | "struct_specifier" | "union_specifier"
81 ) {
82 type_node.child_by_field_name("name")
83 } else {
84 Some(type_node)
85 };
86 CppDeclaredFieldTypeFact {
87 type_text: ty.map_or_else(
88 || field_name.to_owned(),
89 |ty| node_text(ty, source).to_owned(),
90 ),
91 indirection: binding.pointer_depth,
92 binds_indirectly: binding.indirect,
93 template_arguments: ty.and_then(|ty| cpp_template_reference_arguments(ty, source)),
94 }
95 });
96 (field_type, names_function_type)
97}
98
99pub(crate) fn capture_declaration(
100 declaration: SourceDeclarationId,
101 node: Node<'_>,
102 unit: &CodeUnit,
103 source: &str,
104 ancestry: &ParentIndex<'_>,
105) -> CppDeclarationSourceFact {
106 let mut fact = CppDeclarationSourceFact::new(declaration);
107 if matches!(node.kind(), "alias_declaration" | "type_definition")
108 && crate::graph::resolver::alias_has_visible_file_scope(node)
109 && let Some(name) = declaration_name(node, unit, source)
110 && let Some(name) =
111 crate::graph::resolver::normalize_reference_name(node_text(name, source))
112 && let Some(target) = node.child_by_field_name("type")
113 && let Some(target) =
114 crate::graph::resolver::normalize_reference_name(node_text(target, source))
115 {
116 fact.file_scope_alias = Some(CppFileScopeAliasFact {
117 name,
118 target,
119 namespace: crate::graph::resolver::enclosing_namespace_context(node, source),
120 });
121 }
122 let mut owners = Vec::new();
123 let mut current = ancestry.parent(node);
124 while let Some(parent) = current {
125 if matches!(
126 parent.kind(),
127 "namespace_definition"
128 | "class_specifier"
129 | "struct_specifier"
130 | "union_specifier"
131 | "enum_specifier"
132 ) && let Some(name) = parent.child_by_field_name("name")
133 {
134 owners.push(node_text(name, source).to_owned());
135 }
136 current = ancestry.parent(parent);
137 }
138 owners.reverse();
139 owners.push(unit.identifier().to_owned());
140 fact.lexical_path = owners;
141 if matches!(
142 node.kind(),
143 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
144 ) {
145 fact.class_strength = if node.child_by_field_name("body").is_some() {
146 CppClassDeclarationStrength::Full
147 } else {
148 CppClassDeclarationStrength::Forward
149 };
150 if node.kind() == "enum_specifier" {
151 let mut cursor = node.walk();
152 fact.enum_kind = if node
153 .children(&mut cursor)
154 .any(|child| matches!(child.kind(), "class" | "struct"))
155 {
156 CppEnumOwnerKind::Scoped
157 } else {
158 CppEnumOwnerKind::Unscoped
159 };
160 }
161 let mut cursor = node.walk();
162 for base_clause in node
163 .named_children(&mut cursor)
164 .filter(|child| child.kind() == "base_class_clause")
165 {
166 let mut is_virtual = false;
167 let mut cursor = base_clause.walk();
168 for child in base_clause.children(&mut cursor) {
169 match child.kind() {
170 "," => is_virtual = false,
171 "virtual" => is_virtual = true,
172 "type_identifier"
173 | "qualified_identifier"
174 | "scoped_type_identifier"
175 | "template_type" => {
176 if let Some(base) = base_specifier_from_node(child, source, is_virtual) {
177 fact.bases.push(base);
178 }
179 is_virtual = false;
180 }
181 _ => {}
182 }
183 }
184 }
185 }
186 let mut cursor = node.walk();
187 let declarator = node
188 .children_by_field_name("declarator", &mut cursor)
189 .find(|declarator| {
190 crate::structural::declarator_name_node(*declarator)
191 .is_some_and(|name| node_text(name, source) == unit.identifier())
192 });
193 if unit.is_function() {
194 let callable = if node.kind() == "function_declarator" {
195 Some(node)
196 } else {
197 node.child_by_field_name("declarator")
198 };
199 if let Some(declarator) = callable {
200 if let Some(name) = declarator_name_node(declarator)
201 && let Some(mut components) = cpp_type_name_components(name, source)
202 {
203 components.pop();
204 fact.written_owner = components;
205 }
206 let mut chain = Some(declarator);
207 while let Some(current) = chain {
208 if current.kind() == "function_declarator" {
209 fact.callable_comparable_shapes =
210 Some(cpp_comparable_parameter_shapes(current, source, ancestry));
211 fact.callable_identity_suffix = cpp_callable_identity_suffix(current, source);
212 fact.callable_is_constructor =
213 cpp_callable_is_structural_constructor(current, source, ancestry);
214 fact.callable_is_deduction_guide =
215 cpp_callable_is_deduction_guide(node, current, unit, source);
216 fact.callable_is_template = ancestry.parent(node).is_some_and(|parent| {
217 parent.kind() == "template_declaration"
218 && parent
219 .named_child(parent.named_child_count().saturating_sub(1))
220 .is_some_and(|declaration| declaration.id() == node.id())
221 });
222 fact.trailing_qualifiers =
225 fact.callable_identity_suffix.clone().unwrap_or_default();
226 break;
227 }
228 chain = current.child_by_field_name("declarator");
229 }
230 }
231 }
232 if unit.is_field() {
233 let (field_type, names_function_type) = capture_field_type(node, unit.identifier(), source);
234 fact.field_type = field_type;
235 fact.names_function_type = names_function_type;
236 }
237 let alias_type = if matches!(node.kind(), "alias_declaration" | "type_definition") {
238 node.child_by_field_name("type")
239 } else if unit.is_class() {
240 crate::declarations::recovered_alias_type_node(node, unit.identifier(), source)
241 } else {
242 None
243 };
244 if let Some(alias_type) = alias_type {
245 let alias_declarator = if node.kind() == "type_definition" {
246 declarator
247 } else {
248 alias_type.child_by_field_name("declarator")
249 };
250 fact.names_function_type = alias_declarator.is_some_and(declarator_names_function_type);
251 fact.adds_indirection = alias_declarator.is_some_and(cpp_declarator_adds_indirection);
252 if !fact.names_function_type {
253 fact.alias_target = structured_alias_type_target(alias_type, source);
254 let mut target = alias_type;
255 while target.kind() == "type_descriptor" {
256 let Some(inner) = target.child_by_field_name("type") else {
257 break;
258 };
259 target = inner;
260 }
261 if matches!(
262 target.kind(),
263 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
264 ) && let Some(name) = target.child_by_field_name("name")
265 {
266 target = name;
267 }
268 fact.alias_target_text = Some(node_text(target, source).to_owned());
269 }
270 }
271 fact
272}
273
274fn cpp_callable_is_deduction_guide(
275 declaration: Node<'_>,
276 function_declarator: Node<'_>,
277 unit: &CodeUnit,
278 source: &str,
279) -> bool {
280 if declaration.kind() != "declaration"
281 || declaration.child_by_field_name("type").is_some()
282 || function_declarator.kind() != "function_declarator"
283 {
284 return false;
285 }
286 let mut cursor = function_declarator.walk();
287 function_declarator
288 .named_children(&mut cursor)
289 .any(|child| child.kind() == "trailing_return_type")
290 && declarator_name_node(function_declarator)
291 .is_some_and(|name| node_text(name, source) == unit.identifier())
292}
293
294#[derive(Debug, Clone)]
296pub struct CppFileSourceFacts {
297 pub source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
298 pub facts: CppSourceFacts,
299 pub declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
300 by_unit: brokk_bifrost_core::hash::HashMap<CodeUnit, Vec<usize>>,
301}
302
303impl CppFileSourceFacts {
304 pub fn new(
305 source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
306 facts: CppSourceFacts,
307 declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
308 ) -> Self {
309 assert!(
310 facts.valid_links(&source),
311 "invalid C++ canonical source links"
312 );
313 let mut by_unit: brokk_bifrost_core::hash::HashMap<_, Vec<_>> = Default::default();
314 for (index, fact) in facts.declarations.iter().enumerate() {
315 if let Some(units) = declaration_units.get(&fact.declaration) {
316 for unit in units {
317 by_unit.entry(unit.clone()).or_default().push(index);
318 }
319 }
320 }
321 Self {
322 source,
323 facts,
324 declaration_units,
325 by_unit,
326 }
327 }
328
329 pub fn for_unit(&self, unit: &CodeUnit) -> impl Iterator<Item = &CppDeclarationSourceFact> {
330 self.by_unit
331 .get(unit)
332 .into_iter()
333 .flatten()
334 .map(|index| &self.facts.declarations[*index])
335 }
336
337 pub fn estimated_retained_bytes(&self) -> usize {
338 self.source.estimated_bytes()
339 + self.facts.estimated_retained_bytes()
340 + self
341 .declaration_units
342 .values()
343 .map(|units| units.capacity() * std::mem::size_of::<CodeUnit>())
344 .sum::<usize>()
345 + self
346 .by_unit
347 .values()
348 .map(|indices| indices.capacity() * std::mem::size_of::<usize>())
349 .sum::<usize>()
350 }
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub struct CppNavigationOccurrence {
356 pub range: brokk_bifrost_core::analyzer::Range,
357 pub role: CppOccurrenceRole,
358 pub conditional_family: Option<(usize, usize)>,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
363pub struct CppDeclarationOccurrence {
364 pub fact: CppDeclarationSourceFact,
365 pub range: brokk_bifrost_core::analyzer::Range,
366 pub name_range: Option<brokk_bifrost_core::analyzer::Range>,
367 pub exhaustive_family: Option<(usize, usize)>,
368 pub displaced_namespace_closing_brace: Option<usize>,
369}
370
371impl CppFileSourceFacts {
372 pub fn declaration_occurrences(&self, unit: &CodeUnit) -> Vec<CppDeclarationOccurrence> {
373 self.for_unit(unit)
374 .map(|fact| {
375 let declaration = self.source.declaration(fact.declaration);
376 CppDeclarationOccurrence {
377 range: self.source.occurrence(declaration.occurrence).range,
378 name_range: declaration.name.map(|id| self.source.occurrence(id).range),
379 exhaustive_family: fact.exhaustive_conditional_family.map(|id| {
380 let range = self.source.occurrence(id).range;
381 (range.start_byte, range.end_byte)
382 }),
383 displaced_namespace_closing_brace: fact
384 .displaced_namespace_closing_brace
385 .map(|id| self.source.occurrence(id).range.start_byte),
386 fact: fact.clone(),
387 }
388 })
389 .collect()
390 }
391}