Skip to main content

brokk_bifrost_cpp/
source_facts.rs

1//! Declaration properties interpreted while the coordinated producer owns syntax.
2
3use crate::declarations::{
4    cpp_callable_identity_suffix, cpp_callable_is_structural_constructor,
5    cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection, extract_function_declarator,
6    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
21/// Parse one source snapshot into the expression-only projection used by RQL.
22/// This helper is intentionally query-scoped: it owns no cross-file cache and
23/// retains only the source arena and rows for this one file.
24pub 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
55/// Capture one expression-shaped row while the primary tree is live. The
56/// caller has already selected the occurrence; this function only interprets
57/// the selected AST node and never searches source text for syntax.
58pub(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
761/// Parse the bounded C++ statement rows used by the statement-fact RQL
762/// relation. This is query-lifetime data; it owns no cross-file state.
763pub 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
782/// Parse one source snapshot into the class and exception projection used by
783/// RQL. The rows are query-lifetime and own no cross-file state.
784pub 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
845/// Capture the structured type properties of one field declaration while its
846/// declaration AST is still available.
847pub(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                    // Only identity qualifiers participate in overload hiding.
1009                    // The declarator can also contain override/final specifiers.
1010                    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
1060/// Add the class and exception rows whose meaning lives below a declaration
1061/// node. This is called by the primary iterative walk, while the tree and its
1062/// parent index are still owned by the producer.
1063pub(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, 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(node: Node<'_>, ancestry: &ParentIndex<'_>) -> CppAccessSpecifier {
1443    let mut current = ancestry.parent(node);
1444    while let Some(parent) = current {
1445        if matches!(
1446            parent.kind(),
1447            "class_specifier" | "struct_specifier" | "union_specifier"
1448        ) {
1449            let default = if parent.kind() == "class_specifier" {
1450                CppAccessSpecifier::Private
1451            } else {
1452                CppAccessSpecifier::Public
1453            };
1454            let Some(body) = parent.child_by_field_name("body") else {
1455                return CppAccessSpecifier::Unknown;
1456            };
1457            let mut access = default;
1458            let mut cursor = body.walk();
1459            for child in body.named_children(&mut cursor) {
1460                if child.start_byte() >= node.start_byte() {
1461                    break;
1462                }
1463                if child.kind() == "access_specifier" {
1464                    access = match child.child(0).map(|keyword| keyword.kind()) {
1465                        Some("public") => CppAccessSpecifier::Public,
1466                        Some("protected") => CppAccessSpecifier::Protected,
1467                        Some("private") => CppAccessSpecifier::Private,
1468                        _ => CppAccessSpecifier::Unknown,
1469                    };
1470                }
1471            }
1472            return access;
1473        }
1474        current = ancestry.parent(parent);
1475    }
1476    CppAccessSpecifier::Unknown
1477}
1478
1479pub(crate) fn base_access_specifier(
1480    clause: Node<'_>,
1481    base: Node<'_>,
1482    class_kind: &str,
1483) -> CppAccessSpecifier {
1484    let default = if matches!(class_kind, "struct_specifier" | "union_specifier") {
1485        CppAccessSpecifier::Public
1486    } else {
1487        CppAccessSpecifier::Private
1488    };
1489    let mut access = default;
1490    let mut cursor = clause.walk();
1491    for child in clause.children(&mut cursor) {
1492        if child.start_byte() >= base.start_byte() {
1493            break;
1494        }
1495        if child.kind() == "access_specifier" {
1496            access = match child.child(0).map(|keyword| keyword.kind()) {
1497                Some("public") => CppAccessSpecifier::Public,
1498                Some("protected") => CppAccessSpecifier::Protected,
1499                Some("private") => CppAccessSpecifier::Private,
1500                _ => CppAccessSpecifier::Unknown,
1501            };
1502        }
1503    }
1504    access
1505}
1506
1507fn tree_contains_kind(node: Node<'_>, kind: &str) -> bool {
1508    let mut stack = vec![node];
1509    while let Some(current) = stack.pop() {
1510        if current.kind() == kind {
1511            return true;
1512        }
1513        let mut cursor = current.walk();
1514        stack.extend(current.named_children(&mut cursor));
1515    }
1516    false
1517}
1518
1519fn tree_contains_pure_virtual(node: Node<'_>, source: &str) -> bool {
1520    let mut stack = vec![node];
1521    while let Some(current) = stack.pop() {
1522        if current.kind() == "field_declaration"
1523            && current
1524                .child_by_field_name("default_value")
1525                .is_some_and(|value| {
1526                    value.kind() == "number_literal" && node_text(value, source).trim() == "0"
1527                })
1528            && current
1529                .child_by_field_name("declarator")
1530                .and_then(extract_function_declarator)
1531                .is_some()
1532        {
1533            return true;
1534        }
1535        let mut cursor = current.walk();
1536        stack.extend(current.named_children(&mut cursor));
1537    }
1538    tree_contains_kind(node, "pure_virtual_clause")
1539}
1540
1541fn cpp_callable_is_deduction_guide(
1542    declaration: Node<'_>,
1543    function_declarator: Node<'_>,
1544    unit: &CodeUnit,
1545    source: &str,
1546) -> bool {
1547    if declaration.kind() != "declaration"
1548        || declaration.child_by_field_name("type").is_some()
1549        || function_declarator.kind() != "function_declarator"
1550    {
1551        return false;
1552    }
1553    let mut cursor = function_declarator.walk();
1554    function_declarator
1555        .named_children(&mut cursor)
1556        .any(|child| child.kind() == "trailing_return_type")
1557        && declarator_name_node(function_declarator)
1558            .is_some_and(|name| node_text(name, source) == unit.identifier())
1559}
1560
1561/// One mounted, generation-selected canonical declaration family.
1562#[derive(Debug, Clone)]
1563pub struct CppFileSourceFacts {
1564    pub source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
1565    pub facts: CppSourceFacts,
1566    pub declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
1567    by_unit: brokk_bifrost_core::hash::HashMap<CodeUnit, Vec<usize>>,
1568}
1569
1570impl CppFileSourceFacts {
1571    pub fn new(
1572        source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
1573        facts: CppSourceFacts,
1574        declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
1575    ) -> Self {
1576        assert!(
1577            facts.valid_links(&source),
1578            "invalid C++ canonical source links"
1579        );
1580        let mut by_unit: brokk_bifrost_core::hash::HashMap<_, Vec<_>> = Default::default();
1581        for (index, fact) in facts.declarations.iter().enumerate() {
1582            if let Some(units) = declaration_units.get(&fact.declaration) {
1583                for unit in units {
1584                    by_unit.entry(unit.clone()).or_default().push(index);
1585                }
1586            }
1587        }
1588        Self {
1589            source,
1590            facts,
1591            declaration_units,
1592            by_unit,
1593        }
1594    }
1595
1596    pub fn for_unit(&self, unit: &CodeUnit) -> impl Iterator<Item = &CppDeclarationSourceFact> {
1597        self.by_unit
1598            .get(unit)
1599            .into_iter()
1600            .flatten()
1601            .map(|index| &self.facts.declarations[*index])
1602    }
1603
1604    pub fn estimated_retained_bytes(&self) -> usize {
1605        self.source.estimated_bytes()
1606            + self.facts.estimated_retained_bytes()
1607            + self
1608                .declaration_units
1609                .values()
1610                .map(|units| units.capacity() * std::mem::size_of::<CodeUnit>())
1611                .sum::<usize>()
1612            + self
1613                .by_unit
1614                .values()
1615                .map(|indices| indices.capacity() * std::mem::size_of::<usize>())
1616                .sum::<usize>()
1617    }
1618}
1619
1620/// One source-backed navigation alternative for a mounted declaration.
1621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1622pub struct CppNavigationOccurrence {
1623    pub range: brokk_bifrost_core::analyzer::Range,
1624    pub role: CppOccurrenceRole,
1625    pub conditional_family: Option<(usize, usize)>,
1626}
1627
1628/// Canonical declaration context with IDs resolved in its own dialect arena.
1629#[derive(Debug, Clone, PartialEq, Eq)]
1630pub struct CppDeclarationOccurrence {
1631    pub fact: CppDeclarationSourceFact,
1632    pub range: brokk_bifrost_core::analyzer::Range,
1633    pub name_range: Option<brokk_bifrost_core::analyzer::Range>,
1634    pub exhaustive_family: Option<(usize, usize)>,
1635    pub displaced_namespace_closing_brace: Option<usize>,
1636}
1637
1638impl CppFileSourceFacts {
1639    pub fn declaration_occurrences(&self, unit: &CodeUnit) -> Vec<CppDeclarationOccurrence> {
1640        self.for_unit(unit)
1641            .map(|fact| {
1642                let declaration = self.source.declaration(fact.declaration);
1643                CppDeclarationOccurrence {
1644                    range: self.source.occurrence(declaration.occurrence).range,
1645                    name_range: declaration.name.map(|id| self.source.occurrence(id).range),
1646                    exhaustive_family: fact.exhaustive_conditional_family.map(|id| {
1647                        let range = self.source.occurrence(id).range;
1648                        (range.start_byte, range.end_byte)
1649                    }),
1650                    displaced_namespace_closing_brace: fact
1651                        .displaced_namespace_closing_brace
1652                        .map(|id| self.source.occurrence(id).range.start_byte),
1653                    fact: fact.clone(),
1654                }
1655            })
1656            .collect()
1657    }
1658
1659    pub fn navigation_occurrence(
1660        &self,
1661        fact: &CppDeclarationSourceFact,
1662    ) -> CppNavigationOccurrence {
1663        let declaration = self.source.declaration(fact.declaration);
1664        let range = self.source.occurrence(declaration.occurrence).range;
1665        let conditional_family = fact.conditional_family.map(|id| {
1666            let range = self.source.occurrence(id).range;
1667            (range.start_byte, range.end_byte)
1668        });
1669        CppNavigationOccurrence {
1670            range,
1671            role: fact.occurrence_role,
1672            conditional_family,
1673        }
1674    }
1675}