1use crate::declarations::{
4 cpp_callable_identity_suffix, cpp_callable_is_structural_constructor,
5 cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
6 cpp_macro_swallowed_access_label, extract_function_declarator, node_text,
7};
8use crate::graph::resolver::{
9 cpp_declared_field_type_fact, cpp_type_name_components, declarator_name_node,
10 declarator_names_function_type, structured_alias_type_target,
11};
12use brokk_bifrost_core::analyzer::cpp_facts::*;
13use brokk_bifrost_core::analyzer::source_facts::{
14 PrimarySourceFactCollector, SourceDeclarationId, SourceOccurrenceId,
15};
16use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_range};
17use brokk_bifrost_core::analyzer::{CodeUnit, Range};
18use brokk_bifrost_core::hash::HashMap;
19use tree_sitter::{Node, Parser};
20
21pub fn parse_cpp_expression_source_facts(
25 source: &str,
26) -> brokk_bifrost_core::analyzer::cpp_facts::CppExpressionSourceFacts {
27 let mut parser = Parser::new();
28 parser
29 .set_language(&tree_sitter_cpp::LANGUAGE.into())
30 .expect("the linked tree-sitter-cpp grammar matches this tree-sitter version");
31 let tree = parser.parse(source, None).expect("uncancelled C++ parse");
32 let mut collector = PrimarySourceFactCollector::new(source);
33 let named_record_sizes = named_record_size_index(tree.root_node(), source);
34 let mut expressions = Vec::new();
35 let mut stack = vec![tree.root_node()];
36 while let Some(node) = stack.pop() {
37 if node.is_named() {
38 let occurrence = collector.intern_node(node);
39 if let Some(fact) =
40 capture_expression_fact(node, source, occurrence, &named_record_sizes)
41 {
42 expressions.push(fact);
43 }
44 }
45 let mut cursor = node.walk();
46 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
47 stack.extend(children.into_iter().rev());
48 }
49 brokk_bifrost_core::analyzer::cpp_facts::CppExpressionSourceFacts {
50 source: collector.finish(),
51 expressions,
52 }
53}
54
55pub(crate) fn capture_expression_fact(
59 node: Node<'_>,
60 source: &str,
61 occurrence: SourceOccurrenceId,
62 named_record_sizes: &HashMap<String, Option<u64>>,
63) -> Option<CppExpressionFact> {
64 match node.kind() {
65 "number_literal" => {
66 let literal_text = node_text(node, source).trim().to_owned();
67 Some(
68 CppExpressionFact::new(occurrence, CppExpressionKind::IntegerLiteral)
69 .with_literal(literal_text),
70 )
71 }
72 "string_literal" | "raw_string_literal" => {
73 let literal_text = node_text(node, source).trim().to_owned();
74 Some(
75 CppExpressionFact::new(occurrence, CppExpressionKind::StringLiteral)
76 .with_literal(literal_text),
77 )
78 }
79 "parameter_declaration" => capture_parameter(node, source, occurrence, named_record_sizes),
80 "field_declaration" => capture_bit_field(node, source, occurrence)
81 .or_else(|| capture_function_signature(node, source, occurrence)),
82 "enum_specifier" => capture_enum_definition(node, source, occurrence),
83 "enumerator" => capture_enumerator(node, source, occurrence),
84 "function_definition" | "declaration" => {
85 capture_function_signature(node, source, occurrence)
86 }
87 "for_statement" | "for_range_loop" => capture_loop_variable(node, source, occurrence),
88 "sizeof_expression" => capture_sizeof(node, source, occurrence),
89 _ => None,
90 }
91}
92
93trait CppExpressionFactBuilder {
94 fn with_literal(self, literal: String) -> Self;
95}
96
97impl CppExpressionFactBuilder for CppExpressionFact {
98 fn with_literal(mut self, literal: String) -> Self {
99 self.integer_value = parse_integer_literal(&literal);
100 self.literal_text = Some(literal);
101 self.complete()
102 }
103}
104
105fn capture_parameter(
106 node: Node<'_>,
107 source: &str,
108 occurrence: SourceOccurrenceId,
109 named_record_sizes: &HashMap<String, Option<u64>>,
110) -> Option<CppExpressionFact> {
111 let declarator = node.child_by_field_name("declarator")?;
112 let shape = declarator_shape(declarator, source);
113 let (type_text, _type_form, signedness, width_bits) =
114 type_properties(node.child_by_field_name("type"), source);
115 let name = crate::structural::declarator_name_node(declarator)
116 .map(|name| node_text(name, source).to_owned());
117 let kind = if shape.has_array {
118 CppExpressionKind::ArrayParameter
119 } else {
120 CppExpressionKind::FunctionParameter
121 };
122 let mut fact = CppExpressionFact::new(occurrence, kind);
123 fact.name = name;
124 fact.type_text = type_text;
125 fact.type_form = shape.top;
126 fact.signedness = signedness;
127 fact.width_bits = width_bits;
128 fact.array_extent = shape.array_extent;
129 fact.array_is_reference = Some(shape.has_reference);
130 fact.parameter_by_value = Some(!shape.has_reference);
131 fact.member_function = Some(enclosing_member_function(node, declarator));
132 let type_size = exact_parameter_type_size(node, source, named_record_sizes);
133 fact.type_size_bytes = type_size;
134 fact.type_size_exact = Some(type_size.is_some());
135 if shape.has_array {
136 Some(if shape.array_extent.is_some() {
137 fact.complete()
138 } else {
139 fact.open("array extent is not a literal")
140 })
141 } else {
142 Some(match type_size {
143 Some(_) => fact.complete(),
144 None => fact.open("parameter type size is not exact"),
145 })
146 }
147}
148
149fn capture_bit_field(
150 node: Node<'_>,
151 source: &str,
152 occurrence: SourceOccurrenceId,
153) -> Option<CppExpressionFact> {
154 let clause = named_child(node, "bitfield_clause")?;
155 let width_node = clause
156 .child_by_field_name("value")
157 .or_else(|| clause.child_by_field_name("width"))
158 .or_else(|| {
159 let mut cursor = clause.walk();
160 clause
161 .named_children(&mut cursor)
162 .find(|child| child.kind() == "number_literal")
163 });
164 let declarator = node.child_by_field_name("declarator");
165 let name = declarator
166 .and_then(crate::structural::declarator_name_node)
167 .map(|name| node_text(name, source).to_owned());
168 let name = name.filter(|name| !name.is_empty());
169 let (type_text, type_form, signedness, width_bits) =
170 type_properties(node.child_by_field_name("type"), source);
171 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::BitField);
172 fact.name = name;
173 fact.type_text = type_text;
174 fact.type_form = type_form;
175 fact.signedness = signedness;
176 fact.width_bits = width_bits;
177 fact.member_function =
178 Some(declarator.is_some_and(|declarator| enclosing_member_function(node, declarator)));
179 fact.bit_field_anonymous = Some(fact.name.is_none());
180 fact.bit_field_width = width_node
181 .map(|width| node_text(width, source))
182 .and_then(parse_integer_literal)
183 .and_then(|width| u64::try_from(width).ok());
184 Some(match fact.bit_field_width {
185 Some(_) => fact.complete(),
186 None => fact.open("bit-field width is not a literal"),
187 })
188}
189
190fn capture_enum_definition(
191 node: Node<'_>,
192 source: &str,
193 occurrence: SourceOccurrenceId,
194) -> Option<CppExpressionFact> {
195 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::EnumDefinition);
196 fact.name = node
197 .child_by_field_name("name")
198 .map(|name| node_text(name, source).to_owned());
199 fact.enum_scoped = Some({
200 let mut cursor = node.walk();
201 node.children(&mut cursor)
202 .any(|child| matches!(child.kind(), "class" | "struct"))
203 });
204 Some(fact.complete())
205}
206
207fn capture_enumerator(
208 node: Node<'_>,
209 source: &str,
210 occurrence: SourceOccurrenceId,
211) -> Option<CppExpressionFact> {
212 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::Enumerator);
213 fact.name = node
214 .child_by_field_name("name")
215 .map(|name| node_text(name, source).to_owned())
216 .or_else(|| {
217 let mut cursor = node.walk();
218 node.named_children(&mut cursor)
219 .find(|child| child.kind() == "identifier")
220 .map(|name| node_text(name, source).to_owned())
221 });
222 let value = node.child_by_field_name("value");
223 fact.has_initializer = Some(value.is_some());
224 fact.enum_value =
225 value.and_then(|value| parse_integer_literal(node_text(value, source).trim()));
226 fact.enum_has_implicit_before_nonreference = enum_has_implicit_before_nonreference(node);
227 fact.enum_ordinal = node.parent().and_then(|parent| {
228 let mut ordinal = 0u32;
229 let mut cursor = parent.walk();
230 for sibling in parent.named_children(&mut cursor) {
231 if sibling.id() == node.id() {
232 return Some(ordinal);
233 }
234 if sibling.kind() == "enumerator" {
235 ordinal = ordinal.saturating_add(1);
236 }
237 }
238 None
239 });
240 Some(if fact.name.is_some() {
241 fact.complete()
242 } else {
243 fact.open("enumerator name is unavailable")
244 })
245}
246
247fn capture_function_signature(
248 node: Node<'_>,
249 source: &str,
250 occurrence: SourceOccurrenceId,
251) -> Option<CppExpressionFact> {
252 let declarator = first_descendant_kind(node, "function_declarator")?;
253 let (type_text, type_form, signedness, width_bits) =
254 type_properties(node.child_by_field_name("type"), source);
255 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::FunctionSignature);
256 fact.name = crate::structural::declarator_name_node(declarator)
257 .map(|name| node_text(name, source).to_owned());
258 fact.type_text = type_text;
259 fact.type_form = type_form;
260 fact.signedness = signedness;
261 fact.width_bits = width_bits;
262 fact.member_function = Some(enclosing_member_function(node, declarator));
263 fact.return_top_level_const = Some(declaration_has_direct_const_qualifier(node, source));
264 fact.member_function_const = Some(function_has_const_qualifier(declarator, source));
265 Some(fact.complete())
266}
267
268fn capture_loop_variable(
269 node: Node<'_>,
270 source: &str,
271 occurrence: SourceOccurrenceId,
272) -> Option<CppExpressionFact> {
273 let binding = node
274 .child_by_field_name("left")
275 .or_else(|| node.child_by_field_name("initializer"))
276 .and_then(|binding| first_declaration_or_name(binding));
277 let name = binding
278 .and_then(|binding| crate::structural::declarator_name_node(binding))
279 .map(|name| node_text(name, source).to_owned());
280 let type_node = node
281 .child_by_field_name("left")
282 .and_then(|left| left.child_by_field_name("type"))
283 .or_else(|| {
284 node.child_by_field_name("initializer")
285 .and_then(|init| init.child_by_field_name("type"))
286 });
287 let (type_text, type_form, signedness, width_bits) = type_properties(type_node, source);
288 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::LoopVariable);
289 fact.name = name;
290 fact.type_text = type_text;
291 fact.type_form = type_form;
292 fact.signedness = signedness;
293 fact.width_bits = width_bits;
294 fact.loop_has_short_ancestor = fact
295 .name
296 .as_deref()
297 .map(|_| has_short_loop_ancestor(node, source));
298 Some(if fact.name.is_some() {
299 fact.complete()
300 } else {
301 fact.open("loop binding name is unavailable")
302 })
303}
304
305fn capture_sizeof(
306 node: Node<'_>,
307 source: &str,
308 occurrence: SourceOccurrenceId,
309) -> Option<CppExpressionFact> {
310 let operand_kind = if node.child_by_field_name("type").is_some() {
311 CppSizeofOperandKind::Type
312 } else if node.child_by_field_name("value").is_some() {
313 CppSizeofOperandKind::Expression
314 } else {
315 return Some(
316 CppExpressionFact::new(occurrence, CppExpressionKind::SizeofExpression)
317 .open("sizeof operand is unavailable"),
318 );
319 };
320 let operand = node
321 .child_by_field_name("value")
322 .or_else(|| node.child_by_field_name("type"));
323 let mut side_effect = false;
324 let mut stack = operand.into_iter().collect::<Vec<_>>();
325 while let Some(current) = stack.pop() {
326 if matches!(
327 current.kind(),
328 "call_expression"
329 | "assignment_expression"
330 | "update_expression"
331 | "new_expression"
332 | "delete_expression"
333 | "comma_expression"
334 ) {
335 side_effect = true;
336 break;
337 }
338 let mut cursor = current.walk();
339 stack.extend(current.named_children(&mut cursor));
340 }
341 let mut fact = CppExpressionFact::new(occurrence, CppExpressionKind::SizeofExpression);
342 fact.sizeof_operand_kind = Some(operand_kind);
343 fact.sizeof_operand_is_parameter = sizeof_operand_is_parameter(node, operand, source);
344 fact.sizeof_has_side_effect = Some(side_effect);
345 Some(fact.complete())
346}
347
348fn enclosing_member_function(node: Node<'_>, declarator: Node<'_>) -> bool {
349 let mut ancestor = node.parent();
350 while let Some(current) = ancestor {
351 if matches!(
352 current.kind(),
353 "class_specifier" | "struct_specifier" | "union_specifier"
354 ) {
355 return true;
356 }
357 ancestor = current.parent();
358 }
359 first_descendant_kind(declarator, "qualified_identifier")
360 .and_then(|qualified| qualified.child_by_field_name("scope"))
361 .is_some_and(|scope| first_descendant_kind(scope, "type_identifier").is_some())
362}
363
364fn enum_has_implicit_before_nonreference(node: Node<'_>) -> Option<bool> {
365 let mut ancestor = node.parent();
366 while let Some(current) = ancestor {
367 if current.kind() == "enum_specifier" {
368 let list = first_descendant_kind(current, "enumerator_list")?;
369 let mut cursor = list.walk();
370 let enumerators = list
371 .named_children(&mut cursor)
372 .filter(|child| child.kind() == "enumerator")
373 .collect::<Vec<_>>();
374 let current_index = enumerators
375 .iter()
376 .position(|enumerator| enumerator.id() == node.id())?;
377 if node.child_by_field_name("value").is_none()
378 || current_index == 0
379 || current_index + 1 >= enumerators.len()
380 {
381 return Some(false);
382 }
383 let mut implicit_before_nonreference = false;
384 for (index, enumerator) in enumerators.iter().enumerate() {
385 if enumerator.child_by_field_name("value").is_none()
386 && enumerators[index + 1..]
387 .iter()
388 .any(|later| later.child_by_field_name("value").is_some())
389 {
390 implicit_before_nonreference = true;
391 break;
392 }
393 }
394 return Some(implicit_before_nonreference);
395 }
396 ancestor = current.parent();
397 }
398 None
399}
400
401fn loop_binding_name(node: Node<'_>, source: &str) -> Option<String> {
402 let binding = node
403 .child_by_field_name("left")
404 .or_else(|| node.child_by_field_name("initializer"))
405 .and_then(first_declaration_or_name)?;
406 let name = crate::structural::declarator_name_node(binding)?;
407 Some(node_text(name, source).to_owned())
408}
409
410fn has_short_loop_ancestor(node: Node<'_>, source: &str) -> bool {
411 let mut ancestor = node.parent();
412 while let Some(current) = ancestor {
413 if matches!(current.kind(), "for_statement" | "for_range_loop")
414 && loop_binding_name(current, source).is_some_and(|name| name.chars().count() == 1)
415 {
416 return true;
417 }
418 ancestor = current.parent();
419 }
420 false
421}
422
423fn sizeof_operand_is_parameter(
424 node: Node<'_>,
425 operand: Option<Node<'_>>,
426 source: &str,
427) -> Option<bool> {
428 let operand = operand?;
429 let name_node = if matches!(operand.kind(), "identifier" | "field_identifier") {
430 Some(operand)
431 } else {
432 first_descendant_kind(operand, "identifier")
433 }?;
434 let name = node_text(name_node, source);
435 let mut ancestor = node.parent();
436 while let Some(current) = ancestor {
437 if current.kind() == "function_definition" {
438 let mut stack = vec![current];
439 while let Some(candidate) = stack.pop() {
440 if candidate.kind() == "parameter_declaration"
441 && candidate
442 .child_by_field_name("declarator")
443 .and_then(crate::structural::declarator_name_node)
444 .is_some_and(|candidate_name| node_text(candidate_name, source) == name)
445 {
446 return Some(true);
447 }
448 let mut cursor = candidate.walk();
449 let children = candidate.named_children(&mut cursor).collect::<Vec<_>>();
450 stack.extend(children.into_iter().rev());
451 }
452 return Some(false);
453 }
454 ancestor = current.parent();
455 }
456 None
457}
458
459fn exact_parameter_type_size(
460 node: Node<'_>,
461 source: &str,
462 named_record_sizes: &HashMap<String, Option<u64>>,
463) -> Option<u64> {
464 let type_node = node.child_by_field_name("type")?;
465 let type_text = node_text(type_node, source).trim();
466 match type_text {
467 "char" | "signed char" | "unsigned char" => Some(1),
468 _ if matches!(
469 type_node.kind(),
470 "type_identifier" | "qualified_identifier" | "scoped_type_identifier"
471 ) =>
472 {
473 named_record_sizes.get(type_text).copied().flatten()
474 }
475 _ => None,
476 }
477}
478
479pub(crate) fn named_record_size_index(
480 root: Node<'_>,
481 source: &str,
482) -> HashMap<String, Option<u64>> {
483 let mut sizes = HashMap::default();
484 let mut stack = vec![root];
485 while let Some(node) = stack.pop() {
486 if matches!(
487 node.kind(),
488 "class_specifier" | "struct_specifier" | "union_specifier"
489 ) && node.child_by_field_name("name").is_some()
490 {
491 let name = node
492 .child_by_field_name("name")
493 .expect("record name was checked above");
494 let name = node_text(name, source).trim();
495 if !name.is_empty() {
496 sizes
497 .entry(name.to_owned())
498 .or_insert_with(|| exact_char_record_size(node, source));
499 }
500 }
501 let mut cursor = node.walk();
502 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
503 stack.extend(children.into_iter().rev());
504 }
505 sizes
506}
507
508fn exact_char_record_size(record: Node<'_>, source: &str) -> Option<u64> {
509 let fields = first_descendant_kind(record, "field_declaration_list")?;
510 let mut cursor = fields.walk();
511 let declarations = fields
512 .named_children(&mut cursor)
513 .filter(|child| child.kind() == "field_declaration")
514 .collect::<Vec<_>>();
515 if declarations.is_empty() {
516 return None;
517 }
518 let mut size = 0u64;
519 for field in declarations {
520 if named_child(field, "bitfield_clause").is_some() {
521 return None;
522 }
523 let type_node = field.child_by_field_name("type")?;
524 let type_text = node_text(type_node, source).trim();
525 if !matches!(type_text, "char" | "signed char" | "unsigned char") {
526 return None;
527 }
528 let declarator = field.child_by_field_name("declarator")?;
529 let shape = declarator_shape(declarator, source);
530 let field_size = if shape.has_array {
531 shape.array_extent?
532 } else if shape.top == CppTypeForm::Unknown {
533 1
534 } else {
535 return None;
536 };
537 size = size.checked_add(field_size)?;
538 }
539 Some(size)
540}
541
542fn named_child<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
543 let mut cursor = node.walk();
544 node.named_children(&mut cursor)
545 .find(|child| child.kind() == kind)
546}
547
548fn first_descendant_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
549 let mut stack = vec![node];
550 while let Some(current) = stack.pop() {
551 if current.kind() == kind {
552 return Some(current);
553 }
554 let mut cursor = current.walk();
555 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
556 stack.extend(children.into_iter().rev());
557 }
558 None
559}
560
561fn first_declaration_or_name(node: Node<'_>) -> Option<Node<'_>> {
562 if matches!(node.kind(), "declaration" | "parameter_declaration") {
563 return node.child_by_field_name("declarator");
564 }
565 if matches!(
566 node.kind(),
567 "identifier" | "field_identifier" | "reference_declarator" | "array_declarator"
568 ) {
569 return Some(node);
570 }
571 first_descendant_kind(node, "declaration")
572 .and_then(|declaration| declaration.child_by_field_name("declarator"))
573 .or_else(|| first_descendant_kind(node, "identifier"))
574}
575
576struct DeclaratorShape {
577 top: CppTypeForm,
578 has_array: bool,
579 has_reference: bool,
580 array_extent: Option<u64>,
581}
582
583fn declarator_shape(declarator: Node<'_>, source: &str) -> DeclaratorShape {
584 let mut current = Some(declarator);
585 let mut top = CppTypeForm::Unknown;
586 let mut has_array = false;
587 let mut has_reference = false;
588 let mut array_extent = None;
589 while let Some(node) = current {
590 match node.kind() {
591 "pointer_declarator" => {
592 if top == CppTypeForm::Unknown {
593 top = CppTypeForm::Pointer;
594 }
595 current = node.child_by_field_name("declarator");
596 }
597 "reference_declarator" => {
598 has_reference = true;
599 if top == CppTypeForm::Unknown {
600 top = CppTypeForm::Reference;
601 }
602 current = node
603 .child_by_field_name("declarator")
604 .or_else(|| node.named_child(0));
605 }
606 "array_declarator" => {
607 has_array = true;
608 if top == CppTypeForm::Unknown {
609 top = CppTypeForm::Array;
610 }
611 array_extent = node
612 .child_by_field_name("size")
613 .or_else(|| node.child_by_field_name("value"))
614 .and_then(|size| parse_integer_literal(node_text(size, source)))
615 .and_then(|size| u64::try_from(size).ok());
616 current = node.child_by_field_name("declarator");
617 }
618 "function_declarator" => {
619 if top == CppTypeForm::Unknown {
620 top = CppTypeForm::Function;
621 }
622 current = node.child_by_field_name("declarator");
623 }
624 "init_declarator" | "parenthesized_declarator" | "attributed_declarator" => {
625 current = node
626 .child_by_field_name("declarator")
627 .or_else(|| node.named_child(0));
628 }
629 _ => current = None,
630 }
631 }
632 DeclaratorShape {
633 top,
634 has_array,
635 has_reference,
636 array_extent,
637 }
638}
639
640fn type_properties(
641 type_node: Option<Node<'_>>,
642 source: &str,
643) -> (Option<String>, CppTypeForm, CppSignedness, Option<u16>) {
644 let Some(type_node) = type_node else {
645 return (None, CppTypeForm::Unknown, CppSignedness::Unknown, None);
646 };
647 let text = node_text(type_node, source).trim().to_owned();
648 let kind = type_node.kind();
649 let builtin = matches!(kind, "primitive_type" | "sized_type_specifier")
650 || matches!(
651 text.as_str(),
652 "bool"
653 | "char"
654 | "signed char"
655 | "unsigned char"
656 | "short"
657 | "short int"
658 | "unsigned short"
659 | "unsigned short int"
660 | "int"
661 | "unsigned"
662 | "unsigned int"
663 | "long"
664 | "long int"
665 | "unsigned long"
666 | "unsigned long int"
667 | "long long"
668 | "long long int"
669 | "unsigned long long"
670 | "unsigned long long int"
671 );
672 let type_form = if builtin {
673 CppTypeForm::Builtin
674 } else if matches!(
675 kind,
676 "type_identifier"
677 | "qualified_identifier"
678 | "scoped_type_identifier"
679 | "template_type"
680 | "enum_specifier"
681 ) {
682 CppTypeForm::Named
683 } else {
684 CppTypeForm::Unknown
685 };
686 let signedness = if text.split_whitespace().any(|part| part == "unsigned") {
687 CppSignedness::Unsigned
688 } else if text.split_whitespace().any(|part| part == "signed") {
689 CppSignedness::Signed
690 } else if builtin
691 && matches!(
692 text.as_str(),
693 "char" | "int" | "short" | "long" | "long long"
694 )
695 {
696 CppSignedness::Ambiguous
697 } else {
698 CppSignedness::Unknown
699 };
700 let width_bits = match text.as_str() {
701 "bool" => Some(1),
702 "char" | "signed char" | "unsigned char" => Some(8),
703 "short" | "short int" | "signed short" | "signed short int" | "unsigned short"
704 | "unsigned short int" => Some(16),
705 "int" | "signed int" | "unsigned" | "unsigned int" => Some(32),
706 "long long"
707 | "long long int"
708 | "signed long long"
709 | "signed long long int"
710 | "unsigned long long"
711 | "unsigned long long int" => Some(64),
712 _ => None,
713 };
714 (Some(text), type_form, signedness, width_bits)
715}
716
717fn function_has_const_qualifier(declarator: Node<'_>, source: &str) -> bool {
718 let mut stack = vec![declarator];
719 while let Some(current) = stack.pop() {
720 if current.kind() == "type_qualifier" && node_text(current, source).trim() == "const" {
721 return true;
722 }
723 let mut cursor = current.walk();
724 stack.extend(current.named_children(&mut cursor));
725 }
726 false
727}
728
729fn declaration_has_direct_const_qualifier(node: Node<'_>, source: &str) -> bool {
730 let mut cursor = node.walk();
731 node.named_children(&mut cursor)
732 .any(|child| child.kind() == "type_qualifier" && node_text(child, source).trim() == "const")
733}
734
735fn parse_integer_literal(text: &str) -> Option<i128> {
736 let text = text.trim().replace('\'', "");
737 for candidate in std::iter::once(text.as_str()).chain(
738 ["ull", "llu", "ll", "ul", "lu", "u", "l"]
739 .into_iter()
740 .filter_map(|suffix| {
741 text.strip_suffix(suffix)
742 .or_else(|| text.strip_suffix(&suffix.to_ascii_uppercase()))
743 }),
744 ) {
745 let (digits, radix) = if let Some(hex) = candidate.strip_prefix("0x") {
746 (hex, 16)
747 } else if let Some(binary) = candidate.strip_prefix("0b") {
748 (binary, 2)
749 } else if candidate.len() > 1 && candidate.starts_with('0') {
750 (candidate, 8)
751 } else {
752 (candidate, 10)
753 };
754 if let Ok(value) = i128::from_str_radix(digits, radix) {
755 return Some(value);
756 }
757 }
758 None
759}
760
761pub fn parse_statement_fact_rows(source: &str) -> CppStatementFactRows {
764 let mut parser = Parser::new();
765 parser
766 .set_language(&tree_sitter_cpp::LANGUAGE.into())
767 .expect("the linked tree-sitter-cpp grammar matches this tree-sitter version");
768 let tree = parser.parse(source, None).expect("uncancelled C++ parse");
769 let mut rows = CppStatementFactRows::default();
770 let mut stack = vec![tree.root_node()];
771 while let Some(node) = stack.pop() {
772 if node.is_named() {
773 crate::structural::capture_statement_fact_rows(node, source, &mut rows);
774 }
775 let mut cursor = node.walk();
776 let children: Vec<_> = node.named_children(&mut cursor).collect();
777 stack.extend(children.into_iter().rev());
778 }
779 rows
780}
781
782pub fn parse_cpp_class_fact_rows(source: &str) -> CppClassFactRows {
785 let mut parser = Parser::new();
786 parser
787 .set_language(&tree_sitter_cpp::LANGUAGE.into())
788 .expect("the linked tree-sitter-cpp grammar matches this tree-sitter version");
789 let tree = parser.parse(source, None).expect("uncancelled C++ parse");
790 let root = tree.root_node();
791 let ancestry = ParentIndex::new(root);
792 let mut rows = CppClassFactRows::default();
793 let mut stack = vec![root];
794 while let Some(node) = stack.pop() {
795 if node.is_named() {
796 capture_class_fact_rows(node, source, &ancestry, &mut rows);
797 }
798 let mut cursor = node.walk();
799 let children: Vec<_> = node.named_children(&mut cursor).collect();
800 stack.extend(children.into_iter().rev());
801 }
802 rows
803}
804
805pub(crate) fn base_specifier_from_node(
806 node: Node<'_>,
807 source: &str,
808 is_virtual: bool,
809 access: CppAccessSpecifier,
810) -> Option<CppBaseSpecifierFact> {
811 let components = cpp_type_name_components(node, source)?;
812 base_specifier_fact(node, components, is_virtual, access)
813}
814
815pub(crate) fn base_specifier_fact(
816 node: Node<'_>,
817 components: Vec<String>,
818 is_virtual: bool,
819 access: CppAccessSpecifier,
820) -> Option<CppBaseSpecifierFact> {
821 (!components.is_empty()).then_some(CppBaseSpecifierFact {
822 absolute: node.child(0).is_some_and(|token| token.kind() == "::"),
823 components,
824 is_virtual,
825 access,
826 })
827}
828
829pub(crate) fn declaration_name<'tree>(
830 node: Node<'tree>,
831 unit: &CodeUnit,
832 source: &str,
833) -> Option<Node<'tree>> {
834 if let Some(name) = node.child_by_field_name("name") {
835 return crate::structural::declarator_name_node(name);
836 }
837 let mut cursor = node.walk();
838 node.children_by_field_name("declarator", &mut cursor)
839 .find_map(|declarator| {
840 let name = crate::structural::declarator_name_node(declarator)?;
841 (node_text(name, source) == unit.identifier()).then_some(name)
842 })
843}
844
845pub(crate) fn capture_field_type(
848 node: Node<'_>,
849 field_name: &str,
850 source: &str,
851) -> (Option<CppDeclaredFieldTypeFact>, bool) {
852 if let Some(recovered) = crate::declarations::recovered_pyobject_head_field(node, source)
853 && node_text(recovered.name, source) == field_name
854 {
855 return (
856 Some(CppDeclaredFieldTypeFact {
857 type_text: node_text(recovered.type_node, source).to_owned(),
858 indirection: recovered.pointer_depth(),
859 binds_indirectly: recovered.pointer_depth() > 0,
860 template_arguments: None,
861 }),
862 false,
863 );
864 }
865 let Some(type_node) = node.child_by_field_name("type") else {
866 return (None, false);
867 };
868 let mut cursor = node.walk();
869 let declarator = node
870 .children_by_field_name("declarator", &mut cursor)
871 .find(|declarator| {
872 crate::structural::declarator_name_node(*declarator)
873 .is_some_and(|name| node_text(name, source) == field_name)
874 });
875 let names_function_type = declarator.is_some_and(declarator_names_function_type);
876 let field_type = cpp_declared_field_type_fact(node, type_node, field_name, source);
877 (field_type, names_function_type)
878}
879
880pub(crate) fn capture_declaration(
881 declaration: SourceDeclarationId,
882 node: Node<'_>,
883 unit: &CodeUnit,
884 source: &str,
885 ancestry: &ParentIndex<'_>,
886) -> CppDeclarationSourceFact {
887 let mut fact = CppDeclarationSourceFact::new(declaration);
888 if matches!(node.kind(), "alias_declaration" | "type_definition")
889 && crate::graph::resolver::alias_has_visible_file_scope(node)
890 && let Some(name) = declaration_name(node, unit, source)
891 && let Some(name) =
892 crate::graph::resolver::normalize_reference_name(node_text(name, source))
893 && let Some(target) = node.child_by_field_name("type")
894 && let Some(target) =
895 crate::graph::resolver::normalize_reference_name(node_text(target, source))
896 {
897 fact.file_scope_alias = Some(CppFileScopeAliasFact {
898 name,
899 target,
900 namespace: crate::graph::resolver::enclosing_namespace_context(node, source),
901 });
902 }
903 let mut owners = Vec::new();
904 let mut current = ancestry.parent(node);
905 while let Some(parent) = current {
906 if matches!(
907 parent.kind(),
908 "namespace_definition"
909 | "class_specifier"
910 | "struct_specifier"
911 | "union_specifier"
912 | "enum_specifier"
913 ) && let Some(name) = parent.child_by_field_name("name")
914 {
915 owners.push(node_text(name, source).to_owned());
916 }
917 current = ancestry.parent(parent);
918 }
919 owners.reverse();
920 owners.push(unit.identifier().to_owned());
921 fact.lexical_path = owners;
922 if matches!(
923 node.kind(),
924 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
925 ) {
926 fact.class_strength = if node.child_by_field_name("body").is_some() {
927 CppClassDeclarationStrength::Full
928 } else {
929 CppClassDeclarationStrength::Forward
930 };
931 if node.kind() == "enum_specifier" {
932 let mut cursor = node.walk();
933 fact.enum_kind = if node
934 .children(&mut cursor)
935 .any(|child| matches!(child.kind(), "class" | "struct"))
936 {
937 CppEnumOwnerKind::Scoped
938 } else {
939 CppEnumOwnerKind::Unscoped
940 };
941 }
942 let mut cursor = node.walk();
943 for base_clause in node
944 .named_children(&mut cursor)
945 .filter(|child| child.kind() == "base_class_clause")
946 {
947 let mut is_virtual = false;
948 let mut cursor = base_clause.walk();
949 for child in base_clause.children(&mut cursor) {
950 match child.kind() {
951 "," => is_virtual = false,
952 "virtual" => is_virtual = true,
953 "type_identifier"
954 | "qualified_identifier"
955 | "scoped_type_identifier"
956 | "template_type" => {
957 if let Some(base) = base_specifier_from_node(
958 child,
959 source,
960 is_virtual,
961 base_access_specifier(base_clause, child, node.kind()),
962 ) {
963 fact.bases.push(base);
964 }
965 is_virtual = false;
966 }
967 _ => {}
968 }
969 }
970 }
971 }
972 let mut cursor = node.walk();
973 let declarator = node
974 .children_by_field_name("declarator", &mut cursor)
975 .find(|declarator| {
976 crate::structural::declarator_name_node(*declarator)
977 .is_some_and(|name| node_text(name, source) == unit.identifier())
978 });
979 if unit.is_function() {
980 let callable = if node.kind() == "function_declarator" {
981 Some(node)
982 } else {
983 node.child_by_field_name("declarator")
984 };
985 if let Some(declarator) = callable {
986 if let Some(name) = declarator_name_node(declarator)
987 && let Some(mut components) = cpp_type_name_components(name, source)
988 {
989 components.pop();
990 fact.written_owner = components;
991 }
992 let mut chain = Some(declarator);
993 while let Some(current) = chain {
994 if current.kind() == "function_declarator" {
995 fact.callable_comparable_shapes =
996 Some(cpp_comparable_parameter_shapes(current, source, ancestry));
997 fact.callable_identity_suffix = cpp_callable_identity_suffix(current, source);
998 fact.callable_is_constructor =
999 cpp_callable_is_structural_constructor(current, source, ancestry);
1000 fact.callable_is_deduction_guide =
1001 cpp_callable_is_deduction_guide(node, current, unit, source);
1002 fact.callable_is_template = ancestry.parent(node).is_some_and(|parent| {
1003 parent.kind() == "template_declaration"
1004 && parent
1005 .named_child(parent.named_child_count().saturating_sub(1))
1006 .is_some_and(|declaration| declaration.id() == node.id())
1007 });
1008 fact.trailing_qualifiers =
1011 fact.callable_identity_suffix.clone().unwrap_or_default();
1012 break;
1013 }
1014 chain = current.child_by_field_name("declarator");
1015 }
1016 }
1017 }
1018 if unit.is_field() {
1019 let (field_type, names_function_type) = capture_field_type(node, unit.identifier(), source);
1020 fact.field_type = field_type;
1021 fact.names_function_type = names_function_type;
1022 }
1023 let alias_type = if matches!(node.kind(), "alias_declaration" | "type_definition") {
1024 node.child_by_field_name("type")
1025 } else if unit.is_class() {
1026 crate::declarations::recovered_alias_type_node(node, unit.identifier(), source)
1027 } else {
1028 None
1029 };
1030 if let Some(alias_type) = alias_type {
1031 let alias_declarator = if node.kind() == "type_definition" {
1032 declarator
1033 } else {
1034 alias_type.child_by_field_name("declarator")
1035 };
1036 fact.names_function_type = alias_declarator.is_some_and(declarator_names_function_type);
1037 fact.adds_indirection = alias_declarator.is_some_and(cpp_declarator_adds_indirection);
1038 if !fact.names_function_type {
1039 fact.alias_target = structured_alias_type_target(alias_type, source);
1040 let mut target = alias_type;
1041 while target.kind() == "type_descriptor" {
1042 let Some(inner) = target.child_by_field_name("type") else {
1043 break;
1044 };
1045 target = inner;
1046 }
1047 if matches!(
1048 target.kind(),
1049 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
1050 ) && let Some(name) = target.child_by_field_name("name")
1051 {
1052 target = name;
1053 }
1054 fact.alias_target_text = Some(node_text(target, source).to_owned());
1055 }
1056 }
1057 fact
1058}
1059
1060pub(crate) fn capture_class_fact_rows(
1064 node: Node<'_>,
1065 source: &str,
1066 ancestry: &ParentIndex<'_>,
1067 rows: &mut CppClassFactRows,
1068) {
1069 match node.kind() {
1070 "class_specifier" | "struct_specifier" | "union_specifier" => {
1071 let mut path = enclosing_owner_path(node, ancestry, source);
1072 if let Some(name) = node.child_by_field_name("name") {
1073 path.push(node_text(name, source).to_owned());
1074 }
1075 let is_interface = node
1076 .child_by_field_name("body")
1077 .is_some_and(|body| tree_contains_pure_virtual(body, source));
1078 rows.classes.push(CppClassFact {
1079 range: node_range(node),
1080 path,
1081 is_interface,
1082 });
1083 }
1084 "function_definition" | "declaration" | "field_declaration" => {
1085 if let Some(member) = capture_member_fact(node, source, ancestry) {
1086 rows.members.push(member);
1087 }
1088 }
1089 "catch_clause" => {
1090 rows.catches.push(capture_catch_fact(node, source));
1091 }
1092 "throw_statement" => {
1093 rows.throws.push(capture_throw_fact(node, source));
1094 }
1095 _ => {}
1096 }
1097}
1098
1099fn capture_member_fact(
1100 node: Node<'_>,
1101 source: &str,
1102 ancestry: &ParentIndex<'_>,
1103) -> Option<CppMemberFact> {
1104 let function_declarator = extract_function_declarator(node)?;
1105 let name_node = declarator_name_node(function_declarator)?;
1106 let name = node_text(name_node, source).to_owned();
1107 let mut owner = enclosing_owner_path(node, ancestry, source);
1108 let written_owner = cpp_type_name_components(name_node, source)
1109 .map(|mut components| {
1110 components.pop();
1111 components
1112 })
1113 .unwrap_or_default();
1114 append_owner_components(&mut owner, &written_owner);
1115 let owner_name = owner.last().map(String::as_str);
1116 let role = if name == "operator=" {
1117 CppMemberRole::AssignmentOperator
1118 } else if name.strip_prefix('~').is_some() {
1119 CppMemberRole::Destructor
1120 } else if owner_name == Some(name.as_str()) {
1121 CppMemberRole::Constructor
1122 } else {
1123 CppMemberRole::Method
1124 };
1125 let (copy_parameter, move_parameter, remaining_parameters_defaulted) =
1126 callable_parameter_shape(function_declarator, source, ancestry, owner_name);
1127 let special_member = match role {
1128 CppMemberRole::Destructor => CppSpecialMemberKind::Destructor,
1129 CppMemberRole::Constructor if copy_parameter => CppSpecialMemberKind::CopyConstructor,
1130 CppMemberRole::Constructor if move_parameter => CppSpecialMemberKind::MoveConstructor,
1131 CppMemberRole::Constructor if function_parameter_count(function_declarator) == 0 => {
1132 CppSpecialMemberKind::DefaultConstructor
1133 }
1134 CppMemberRole::AssignmentOperator if copy_parameter => CppSpecialMemberKind::CopyAssignment,
1135 CppMemberRole::AssignmentOperator if move_parameter => CppSpecialMemberKind::MoveAssignment,
1136 _ => CppSpecialMemberKind::None,
1137 };
1138 let state = if tree_contains_kind(node, "delete_method_clause") {
1139 CppSpecialMemberState::Deleted
1140 } else if tree_contains_kind(node, "default_method_clause") {
1141 CppSpecialMemberState::Defaulted
1142 } else {
1143 CppSpecialMemberState::Declared
1144 };
1145 let access = member_access_specifier(node, source, ancestry);
1146 let assignment_return = if role == CppMemberRole::AssignmentOperator {
1147 assignment_return_shape(node, source, owner_name)
1148 } else {
1149 CppAssignmentReturnShape::Unknown
1150 };
1151 let initializer_target = if role == CppMemberRole::Constructor {
1152 constructor_initializer_target(node, source, owner_name)
1153 } else {
1154 None
1155 };
1156 let delegation_calls = if role == CppMemberRole::Constructor {
1157 constructor_delegation_calls(node, source, ancestry, owner_name)
1158 } else {
1159 Vec::new()
1160 };
1161 Some(CppMemberFact {
1162 range: node_range(node),
1163 owner,
1164 name,
1165 role,
1166 special_member,
1167 state,
1168 access,
1169 copy_parameter,
1170 remaining_parameters_defaulted,
1171 assignment_return,
1172 initializer_target,
1173 delegation_calls,
1174 pure_virtual: tree_contains_pure_virtual(node, source),
1175 })
1176}
1177
1178fn capture_catch_fact(node: Node<'_>, source: &str) -> CppCatchFact {
1179 let parameter = node
1180 .child_by_field_name("parameters")
1181 .and_then(|parameters| parameters.named_child(0));
1182 let type_node = parameter.and_then(|parameter| parameter.child_by_field_name("type"));
1183 let declarator = parameter.and_then(|parameter| parameter.child_by_field_name("declarator"));
1184 let value_category = declarator
1185 .map(classify_parameter_declarator)
1186 .unwrap_or(CppParameterValueCategory::Value);
1187 let type_name = type_node
1188 .and_then(|type_node| cpp_type_name_components(type_node, source))
1189 .unwrap_or_default();
1190 let class_type = type_node.is_some_and(|type_node| {
1191 !matches!(
1192 type_node.kind(),
1193 "primitive_type" | "sized_type_specifier" | "auto" | "decltype"
1194 )
1195 });
1196 CppCatchFact {
1197 range: node_range(node),
1198 value_category,
1199 type_name,
1200 class_type,
1201 }
1202}
1203
1204fn capture_throw_fact(node: Node<'_>, source: &str) -> CppThrowFact {
1205 let expression = node.named_child(0);
1206 let (operand_shape, thrown_type) = match expression {
1207 None => (CppThrowOperandShape::Rethrow, Vec::new()),
1208 Some(expression) if expression.kind() == "new_expression" => (
1209 CppThrowOperandShape::Pointer,
1210 expression
1211 .child_by_field_name("type")
1212 .and_then(|type_node| cpp_type_name_components(type_node, source))
1213 .unwrap_or_default(),
1214 ),
1215 Some(_) => (CppThrowOperandShape::Value, Vec::new()),
1216 };
1217 CppThrowFact {
1218 range: node_range(node),
1219 operand_shape,
1220 thrown_type,
1221 }
1222}
1223
1224fn function_parameter_count(function: Node<'_>) -> usize {
1225 function
1226 .child_by_field_name("parameters")
1227 .map_or(0, |parameters| parameters.named_child_count())
1228}
1229
1230fn callable_parameter_shape(
1231 function: Node<'_>,
1232 source: &str,
1233 ancestry: &ParentIndex<'_>,
1234 owner_name: Option<&str>,
1235) -> (bool, bool, bool) {
1236 let Some(parameters) = function.child_by_field_name("parameters") else {
1237 return (false, false, true);
1238 };
1239 let parameter_nodes: Vec<_> = parameters.named_children(&mut parameters.walk()).collect();
1240 let remaining_parameters_defaulted = parameter_nodes.len() > 1
1241 && parameter_nodes
1242 .iter()
1243 .skip(1)
1244 .all(|parameter| parameter.child_by_field_name("default_value").is_some());
1245 let Some(first) = parameter_nodes.first().copied() else {
1246 return (false, false, remaining_parameters_defaulted);
1247 };
1248 let Some(_type_node) = first.child_by_field_name("type") else {
1249 return (false, false, remaining_parameters_defaulted);
1250 };
1251 let Some(owner_name) = owner_name else {
1252 return (false, false, remaining_parameters_defaulted);
1253 };
1254 let comparable = cpp_comparable_parameter_shapes(function, source, ancestry);
1255 let Some(CppComparableSlot::Shape(parameter)) = comparable.first() else {
1256 return (false, false, remaining_parameters_defaulted);
1257 };
1258 let reference = match parameter.node(parameter.root()) {
1259 CppComparableNode::Reference { inner } => Some(*inner),
1260 _ => None,
1261 };
1262 let Some(inner) = reference else {
1263 return (false, false, remaining_parameters_defaulted);
1264 };
1265 let CppComparableNode::Named { name, konst, .. } = parameter.node(inner) else {
1266 return (false, false, remaining_parameters_defaulted);
1267 };
1268 let same_owner = name.path().last().is_some_and(|name| name == owner_name);
1269 if !same_owner {
1270 return (false, false, remaining_parameters_defaulted);
1271 }
1272 let rvalue = first
1273 .child_by_field_name("declarator")
1274 .is_some_and(|declarator| declarator_contains_rvalue_reference(declarator));
1275 let copy = *konst && !rvalue;
1276 let move_parameter = rvalue;
1277 (copy, move_parameter, remaining_parameters_defaulted)
1278}
1279
1280fn declarator_contains_rvalue_reference(node: Node<'_>) -> bool {
1281 let mut stack = vec![node];
1282 while let Some(current) = stack.pop() {
1283 if current.kind() == "&&" {
1284 return true;
1285 }
1286 let mut cursor = current.walk();
1287 stack.extend(current.named_children(&mut cursor));
1288 }
1289 false
1290}
1291
1292fn classify_parameter_declarator(node: Node<'_>) -> CppParameterValueCategory {
1293 if declarator_contains_rvalue_reference(node) {
1294 CppParameterValueCategory::RValueReference
1295 } else {
1296 let mut stack = vec![node];
1297 while let Some(current) = stack.pop() {
1298 if current.kind() == "reference_declarator" {
1299 return CppParameterValueCategory::LValueReference;
1300 }
1301 if current.kind() == "pointer_declarator" {
1302 return CppParameterValueCategory::Pointer;
1303 }
1304 let mut cursor = current.walk();
1305 stack.extend(current.named_children(&mut cursor));
1306 }
1307 CppParameterValueCategory::Value
1308 }
1309}
1310
1311fn assignment_return_shape(
1312 node: Node<'_>,
1313 source: &str,
1314 owner_name: Option<&str>,
1315) -> CppAssignmentReturnShape {
1316 let mut current = node.child_by_field_name("declarator");
1317 let mut reference = false;
1318 let mut pointer = false;
1319 while let Some(declarator) = current {
1320 reference |= declarator.kind() == "reference_declarator";
1321 pointer |= declarator.kind() == "pointer_declarator";
1322 current = declarator.child_by_field_name("declarator");
1323 }
1324 if !reference {
1325 return if pointer {
1326 CppAssignmentReturnShape::Unknown
1327 } else {
1328 CppAssignmentReturnShape::Value
1329 };
1330 }
1331 let return_type = node.child_by_field_name("type");
1332 if owner_name.is_some_and(|owner| {
1333 return_type
1334 .and_then(|type_node| cpp_type_name_components(type_node, source))
1335 .and_then(|components| components.last().cloned())
1336 .is_some_and(|name| name == owner)
1337 }) {
1338 CppAssignmentReturnShape::ReferenceToThis
1339 } else {
1340 CppAssignmentReturnShape::ReferenceOther
1341 }
1342}
1343
1344fn constructor_initializer_target(
1345 node: Node<'_>,
1346 source: &str,
1347 owner_name: Option<&str>,
1348) -> Option<Vec<String>> {
1349 let owner_name = owner_name?;
1350 let mut cursor = node.walk();
1351 let initializer_list = node
1352 .named_children(&mut cursor)
1353 .find(|child| child.kind() == "field_initializer_list")?;
1354 let mut cursor = initializer_list.walk();
1355 initializer_list
1356 .named_children(&mut cursor)
1357 .filter(|child| child.kind() == "field_initializer")
1358 .find_map(|initializer| {
1359 let name = initializer
1360 .child_by_field_name("name")
1361 .or_else(|| initializer.child_by_field_name("field"))
1362 .or_else(|| {
1363 let mut cursor = initializer.walk();
1364 initializer.named_children(&mut cursor).find(|child| {
1365 matches!(
1366 child.kind(),
1367 "field_identifier"
1368 | "type_identifier"
1369 | "qualified_identifier"
1370 | "scoped_type_identifier"
1371 )
1372 })
1373 })?;
1374 let components = cpp_type_name_components(name, source)?;
1375 components
1376 .last()
1377 .is_some_and(|component| component == owner_name)
1378 .then_some(components)
1379 })
1380}
1381
1382fn constructor_delegation_calls(
1383 node: Node<'_>,
1384 source: &str,
1385 ancestry: &ParentIndex<'_>,
1386 owner_name: Option<&str>,
1387) -> Vec<Range> {
1388 let Some(owner_name) = owner_name else {
1389 return Vec::new();
1390 };
1391 let Some(body) = node.child_by_field_name("body") else {
1392 return Vec::new();
1393 };
1394 let mut stack = vec![body];
1395 let mut calls = Vec::new();
1396 while let Some(current) = stack.pop() {
1397 if current.kind() == "call_expression"
1398 && ancestry
1399 .parent(current)
1400 .is_some_and(|parent| parent.kind() == "expression_statement")
1401 && let Some(function) = current.child_by_field_name("function")
1402 && function.kind() == "identifier"
1403 && node_text(function, source) == owner_name
1404 {
1405 calls.push(node_range(current));
1406 }
1407 let mut cursor = current.walk();
1408 stack.extend(current.named_children(&mut cursor));
1409 }
1410 calls
1411}
1412
1413fn enclosing_owner_path(node: Node<'_>, ancestry: &ParentIndex<'_>, source: &str) -> Vec<String> {
1414 let mut owners = Vec::new();
1415 let mut current = ancestry.parent(node);
1416 while let Some(parent) = current {
1417 if matches!(
1418 parent.kind(),
1419 "namespace_definition"
1420 | "class_specifier"
1421 | "struct_specifier"
1422 | "union_specifier"
1423 | "enum_specifier"
1424 ) && let Some(name) = parent.child_by_field_name("name")
1425 {
1426 owners.push(node_text(name, source).to_owned());
1427 }
1428 current = ancestry.parent(parent);
1429 }
1430 owners.reverse();
1431 owners
1432}
1433
1434fn append_owner_components(owner: &mut Vec<String>, written: &[String]) {
1435 let overlap = (0..=owner.len().min(written.len()))
1436 .rev()
1437 .find(|length| owner[owner.len().saturating_sub(*length)..] == written[..*length])
1438 .unwrap_or(0);
1439 owner.extend(written[overlap..].iter().cloned());
1440}
1441
1442fn member_access_specifier(
1443 node: Node<'_>,
1444 source: &str,
1445 ancestry: &ParentIndex<'_>,
1446) -> CppAccessSpecifier {
1447 let mut current = ancestry.parent(node);
1448 while let Some(parent) = current {
1449 if matches!(
1450 parent.kind(),
1451 "class_specifier" | "struct_specifier" | "union_specifier"
1452 ) {
1453 let default = if parent.kind() == "class_specifier" {
1454 CppAccessSpecifier::Private
1455 } else {
1456 CppAccessSpecifier::Public
1457 };
1458 let Some(body) = parent.child_by_field_name("body") else {
1459 return CppAccessSpecifier::Unknown;
1460 };
1461 let mut access = default;
1462 let mut cursor = body.walk();
1463 for child in body.named_children(&mut cursor) {
1464 if child.start_byte() >= node.start_byte() {
1465 break;
1466 }
1467 if child.kind() == "access_specifier" {
1468 access = match child.child(0).map(|keyword| keyword.kind()) {
1469 Some("public") => CppAccessSpecifier::Public,
1470 Some("protected") => CppAccessSpecifier::Protected,
1471 Some("private") => CppAccessSpecifier::Private,
1472 _ => CppAccessSpecifier::Unknown,
1473 };
1474 } else if let Some(label) = cpp_macro_swallowed_access_label(child, source) {
1475 access = match label.access {
1476 "public" => CppAccessSpecifier::Public,
1477 "protected" => CppAccessSpecifier::Protected,
1478 _ => CppAccessSpecifier::Private,
1479 };
1480 }
1481 }
1482 return access;
1483 }
1484 current = ancestry.parent(parent);
1485 }
1486 CppAccessSpecifier::Unknown
1487}
1488
1489pub(crate) fn base_access_specifier(
1490 clause: Node<'_>,
1491 base: Node<'_>,
1492 class_kind: &str,
1493) -> CppAccessSpecifier {
1494 let default = if matches!(class_kind, "struct_specifier" | "union_specifier") {
1495 CppAccessSpecifier::Public
1496 } else {
1497 CppAccessSpecifier::Private
1498 };
1499 let mut access = default;
1500 let mut cursor = clause.walk();
1501 for child in clause.children(&mut cursor) {
1502 if child.start_byte() >= base.start_byte() {
1503 break;
1504 }
1505 if child.kind() == "access_specifier" {
1506 access = match child.child(0).map(|keyword| keyword.kind()) {
1507 Some("public") => CppAccessSpecifier::Public,
1508 Some("protected") => CppAccessSpecifier::Protected,
1509 Some("private") => CppAccessSpecifier::Private,
1510 _ => CppAccessSpecifier::Unknown,
1511 };
1512 }
1513 }
1514 access
1515}
1516
1517fn tree_contains_kind(node: Node<'_>, kind: &str) -> bool {
1518 let mut stack = vec![node];
1519 while let Some(current) = stack.pop() {
1520 if current.kind() == kind {
1521 return true;
1522 }
1523 let mut cursor = current.walk();
1524 stack.extend(current.named_children(&mut cursor));
1525 }
1526 false
1527}
1528
1529fn tree_contains_pure_virtual(node: Node<'_>, source: &str) -> bool {
1530 let mut stack = vec![node];
1531 while let Some(current) = stack.pop() {
1532 if current.kind() == "field_declaration"
1533 && current
1534 .child_by_field_name("default_value")
1535 .is_some_and(|value| {
1536 value.kind() == "number_literal" && node_text(value, source).trim() == "0"
1537 })
1538 && current
1539 .child_by_field_name("declarator")
1540 .and_then(extract_function_declarator)
1541 .is_some()
1542 {
1543 return true;
1544 }
1545 let mut cursor = current.walk();
1546 stack.extend(current.named_children(&mut cursor));
1547 }
1548 tree_contains_kind(node, "pure_virtual_clause")
1549}
1550
1551fn cpp_callable_is_deduction_guide(
1552 declaration: Node<'_>,
1553 function_declarator: Node<'_>,
1554 unit: &CodeUnit,
1555 source: &str,
1556) -> bool {
1557 if declaration.kind() != "declaration"
1558 || declaration.child_by_field_name("type").is_some()
1559 || function_declarator.kind() != "function_declarator"
1560 {
1561 return false;
1562 }
1563 let mut cursor = function_declarator.walk();
1564 function_declarator
1565 .named_children(&mut cursor)
1566 .any(|child| child.kind() == "trailing_return_type")
1567 && declarator_name_node(function_declarator)
1568 .is_some_and(|name| node_text(name, source) == unit.identifier())
1569}
1570
1571#[derive(Debug, Clone)]
1573pub struct CppFileSourceFacts {
1574 pub source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
1575 pub facts: CppSourceFacts,
1576 pub declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
1577 by_unit: brokk_bifrost_core::hash::HashMap<CodeUnit, Vec<usize>>,
1578}
1579
1580impl CppFileSourceFacts {
1581 pub fn new(
1582 source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
1583 facts: CppSourceFacts,
1584 declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
1585 ) -> Self {
1586 assert!(
1587 facts.valid_links(&source),
1588 "invalid C++ canonical source links"
1589 );
1590 let mut by_unit: brokk_bifrost_core::hash::HashMap<_, Vec<_>> = Default::default();
1591 for (index, fact) in facts.declarations.iter().enumerate() {
1592 if let Some(units) = declaration_units.get(&fact.declaration) {
1593 for unit in units {
1594 by_unit.entry(unit.clone()).or_default().push(index);
1595 }
1596 }
1597 }
1598 Self {
1599 source,
1600 facts,
1601 declaration_units,
1602 by_unit,
1603 }
1604 }
1605
1606 pub fn for_unit(&self, unit: &CodeUnit) -> impl Iterator<Item = &CppDeclarationSourceFact> {
1607 self.by_unit
1608 .get(unit)
1609 .into_iter()
1610 .flatten()
1611 .map(|index| &self.facts.declarations[*index])
1612 }
1613
1614 pub fn estimated_retained_bytes(&self) -> usize {
1615 self.source.estimated_bytes()
1616 + self.facts.estimated_retained_bytes()
1617 + self
1618 .declaration_units
1619 .values()
1620 .map(|units| units.capacity() * std::mem::size_of::<CodeUnit>())
1621 .sum::<usize>()
1622 + self
1623 .by_unit
1624 .values()
1625 .map(|indices| indices.capacity() * std::mem::size_of::<usize>())
1626 .sum::<usize>()
1627 }
1628}
1629
1630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1632pub struct CppNavigationOccurrence {
1633 pub range: brokk_bifrost_core::analyzer::Range,
1634 pub role: CppOccurrenceRole,
1635 pub conditional_family: Option<(usize, usize)>,
1636}
1637
1638#[derive(Debug, Clone, PartialEq, Eq)]
1640pub struct CppDeclarationOccurrence {
1641 pub fact: CppDeclarationSourceFact,
1642 pub range: brokk_bifrost_core::analyzer::Range,
1643 pub name_range: Option<brokk_bifrost_core::analyzer::Range>,
1644 pub exhaustive_family: Option<(usize, usize)>,
1645 pub displaced_namespace_closing_brace: Option<usize>,
1646}
1647
1648impl CppFileSourceFacts {
1649 pub fn declaration_occurrences(&self, unit: &CodeUnit) -> Vec<CppDeclarationOccurrence> {
1650 self.for_unit(unit)
1651 .map(|fact| {
1652 let declaration = self.source.declaration(fact.declaration);
1653 CppDeclarationOccurrence {
1654 range: self.source.occurrence(declaration.occurrence).range,
1655 name_range: declaration.name.map(|id| self.source.occurrence(id).range),
1656 exhaustive_family: fact.exhaustive_conditional_family.map(|id| {
1657 let range = self.source.occurrence(id).range;
1658 (range.start_byte, range.end_byte)
1659 }),
1660 displaced_namespace_closing_brace: fact
1661 .displaced_namespace_closing_brace
1662 .map(|id| self.source.occurrence(id).range.start_byte),
1663 fact: fact.clone(),
1664 }
1665 })
1666 .collect()
1667 }
1668
1669 pub fn navigation_occurrence(
1670 &self,
1671 fact: &CppDeclarationSourceFact,
1672 ) -> CppNavigationOccurrence {
1673 let declaration = self.source.declaration(fact.declaration);
1674 let range = self.source.occurrence(declaration.occurrence).range;
1675 let conditional_family = fact.conditional_family.map(|id| {
1676 let range = self.source.occurrence(id).range;
1677 (range.start_byte, range.end_byte)
1678 });
1679 CppNavigationOccurrence {
1680 range,
1681 role: fact.occurrence_role,
1682 conditional_family,
1683 }
1684 }
1685}