Skip to main content

brokk_bifrost_cpp/
structural.rs

1//! C++ structural spec for `query_code`.
2
3use brokk_bifrost_core::analyzer::common::try_node_source_text as node_text;
4use brokk_bifrost_core::analyzer::statement_facts::StatementFactKind;
5use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
6    CalleeSpineStep, attach_positional_argument_roles, attach_role_with_derived_name,
7    attach_terminal_callee, field_name_in_parent, first_named_child, terminal_callee_leaf,
8    unquoted_string_content_span,
9};
10use brokk_bifrost_core::analyzer::structural::body_relations::{
11    BodyOwner, BodyState, body_relation_for_block,
12};
13use brokk_bifrost_core::analyzer::structural::callable::{
14    CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
15};
16use brokk_bifrost_core::analyzer::structural::edges::{
17    INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
18};
19use brokk_bifrost_core::analyzer::structural::facts::Span;
20use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
21use brokk_bifrost_core::analyzer::structural::materialization::{
22    CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
23};
24use brokk_bifrost_core::analyzer::structural::occurrences::{
25    OccurrenceRole, OccurrenceRoleSupport,
26};
27use brokk_bifrost_core::analyzer::structural::resolution::{
28    BindingActivation, BindingKind, EnvironmentAxis, HoistingClass, LexicalEnvironmentSupport,
29    ScopeFormation, default_scope_formation,
30};
31use brokk_bifrost_core::analyzer::structural::result_use::{
32    CallResultUse, CallResultUseIndex, CallResultUseOpenReason, CallResultUseSite,
33    IntentionalDiscardKind,
34};
35use brokk_bifrost_core::analyzer::structural::routes::{
36    IdentityAxis, IdentityRouteSupport, RouteHopKind,
37};
38use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
39use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, named_children_iter, node_range};
40use brokk_bifrost_core::analyzer::{Language, Range};
41use brokk_bifrost_core::hash::{HashMap, HashSet};
42use tree_sitter::Node;
43
44#[derive(Debug, Default)]
45pub struct CppStructuralSpec;
46
47pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
48
49/// Whether this is a C++ preprocessor conditional node.
50///
51/// The linked tree-sitter-cpp grammar uses `preproc_ifdef` for both `#ifdef`
52/// and `#ifndef`; `preproc_ifndef` remains accepted for trees that expose it
53/// as a distinct node kind.
54pub(crate) fn is_cpp_preprocessor_conditional(node: Node<'_>) -> bool {
55    matches!(
56        node.kind(),
57        "preproc_if"
58            | "preproc_ifdef"
59            | "preproc_ifndef"
60            | "preproc_elif"
61            | "preproc_elifdef"
62            | "preproc_else"
63    )
64}
65
66/// Classify one exact C or C++ call expression.  The C grammar is the same
67/// parser route as C++, so the syntax classifier deliberately does not use a
68/// source-file extension or a text-search fallback.
69pub fn cpp_call_result_use(call: Node<'_>, source: &str) -> CallResultUseSite {
70    let default = if !matches!(call.kind(), "call_expression" | "new_expression")
71        || call.has_error()
72        || has_recovery_ancestor(call)
73    {
74        CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
75    } else if has_deferred_ancestor(call) {
76        CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
77    } else {
78        let mut expression = call;
79        loop {
80            let Some(parent) = expression.parent() else {
81                break CallResultUse::Open(CallResultUseOpenReason::UnclassifiedSyntax);
82            };
83            if parent.is_error() || parent.is_missing() {
84                break CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax);
85            }
86            match parent.kind() {
87                "parenthesized_expression" | "comma_expression"
88                    if parent
89                        .named_children(&mut parent.walk())
90                        .any(|child| child.id() == expression.id()) =>
91                {
92                    expression = parent;
93                }
94                "expression_statement"
95                    if parent
96                        .named_children(&mut parent.walk())
97                        .any(|child| child.id() == expression.id()) =>
98                {
99                    break CallResultUse::Discarded;
100                }
101                "for_statement"
102                    if matches!(
103                        field_name_in_parent(parent, expression),
104                        Some("initializer" | "update")
105                    ) =>
106                {
107                    break CallResultUse::Discarded;
108                }
109                "cast_expression"
110                    if field_name_in_parent(parent, expression) == Some("value")
111                        && parent
112                            .child_by_field_name("type")
113                            .is_some_and(|type_node| cpp_type_is_void(type_node, source)) =>
114                {
115                    break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
116                }
117                "argument_list"
118                    if parent
119                        .parent()
120                        .is_some_and(|outer_call| cpp_static_cast_to_void(outer_call, source)) =>
121                {
122                    break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
123                }
124                "lambda_expression" => {
125                    break CallResultUse::Open(CallResultUseOpenReason::DeferredContext);
126                }
127                _ => break CallResultUse::Consumed,
128            }
129        }
130    };
131    CallResultUseSite::new(call.start_byte(), call.end_byte(), default)
132}
133
134fn cpp_type_is_void(type_node: Node<'_>, source: &str) -> bool {
135    let mut stack = vec![type_node];
136    while let Some(node) = stack.pop() {
137        if node.kind() == "primitive_type" && node_text(node, source) == Some("void") {
138            return true;
139        }
140        for index in (0..node.named_child_count()).rev() {
141            stack.push(node.named_child(index).expect("named child exists"));
142        }
143    }
144    false
145}
146
147fn has_recovery_ancestor(mut node: Node<'_>) -> bool {
148    while let Some(parent) = node.parent() {
149        if parent.is_error() || parent.is_missing() {
150            return true;
151        }
152        node = parent;
153    }
154    false
155}
156
157fn has_deferred_ancestor(mut node: Node<'_>) -> bool {
158    while let Some(parent) = node.parent() {
159        if parent.kind() == "lambda_expression" {
160            return true;
161        }
162        node = parent;
163    }
164    false
165}
166
167fn cpp_static_cast_to_void(call: Node<'_>, source: &str) -> bool {
168    if call.kind() != "call_expression" {
169        return false;
170    }
171    let Some(function) = call.child_by_field_name("function") else {
172        return false;
173    };
174    if function.kind() != "template_function"
175        || function
176            .child_by_field_name("name")
177            .and_then(|name| node_text(name, source))
178            != Some("static_cast")
179    {
180        return false;
181    }
182    function
183        .child_by_field_name("arguments")
184        .and_then(|arguments| arguments.named_child(0))
185        .is_some_and(|type_node| cpp_type_is_void(type_node, source))
186}
187
188fn cpp_cast_call(call: Node<'_>, source: &str) -> bool {
189    let Some(function) = call.child_by_field_name("function") else {
190        return false;
191    };
192    if function.kind() != "template_function" {
193        return false;
194    }
195    matches!(
196        function
197            .child_by_field_name("name")
198            .and_then(|name| node_text(name, source)),
199        Some("static_cast" | "reinterpret_cast" | "const_cast" | "dynamic_cast")
200    )
201}
202
203/// One source-snapshot index of exact C/C++ call spans.
204#[derive(Debug, Clone)]
205pub struct CppCallResultUseIndex {
206    inner: CallResultUseIndex,
207}
208
209impl CppCallResultUseIndex {
210    pub fn new(root: Node<'_>, source: &str) -> Self {
211        let mut sites = Vec::new();
212        let mut stack = vec![root];
213        while let Some(node) = stack.pop() {
214            if matches!(node.kind(), "call_expression" | "new_expression")
215                && !cpp_cast_call(node, source)
216            {
217                sites.push(cpp_call_result_use(node, source));
218            }
219            for index in (0..node.named_child_count()).rev() {
220                stack.push(node.named_child(index).expect("named child exists"));
221            }
222        }
223        Self {
224            inner: CallResultUseIndex::new(sites),
225        }
226    }
227
228    pub fn at_span(
229        &self,
230        start_byte: usize,
231        end_byte: usize,
232    ) -> Result<&CallResultUseSite, CallResultUseOpenReason> {
233        self.inner.at_span(start_byte, end_byte)
234    }
235
236    pub fn sites(&self) -> &[CallResultUseSite] {
237        self.inner.sites()
238    }
239}
240
241pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
242    ("comment", NormalizedKind::Comment),
243    ("call_expression", NormalizedKind::Call),
244    ("new_expression", NormalizedKind::Call),
245    ("field_expression", NormalizedKind::FieldAccess),
246    ("function_definition", NormalizedKind::Function),
247    ("lambda_expression", NormalizedKind::Lambda),
248    ("class_specifier", NormalizedKind::Class),
249    ("struct_specifier", NormalizedKind::Class),
250    ("union_specifier", NormalizedKind::Class),
251    ("alias_declaration", NormalizedKind::Declaration),
252    ("namespace_definition", NormalizedKind::Module),
253    ("assignment_expression", NormalizedKind::Assignment),
254    ("init_declarator", NormalizedKind::Assignment),
255    ("preproc_include", NormalizedKind::Import),
256    ("identifier", NormalizedKind::Identifier),
257    ("field_identifier", NormalizedKind::Identifier),
258    ("namespace_identifier", NormalizedKind::Identifier),
259    ("qualified_identifier", NormalizedKind::Identifier),
260    ("type_identifier", NormalizedKind::Identifier),
261    ("template_function", NormalizedKind::Identifier),
262    ("template_method", NormalizedKind::Identifier),
263    ("template_type", NormalizedKind::Identifier),
264    ("dependent_name", NormalizedKind::Identifier),
265    ("destructor_name", NormalizedKind::Identifier),
266    ("operator_name", NormalizedKind::Identifier),
267    ("primitive_type", NormalizedKind::Identifier),
268    ("char_literal", NormalizedKind::StringLiteral),
269    ("string_literal", NormalizedKind::StringLiteral),
270    ("raw_string_literal", NormalizedKind::StringLiteral),
271    ("number_literal", NormalizedKind::NumericLiteral),
272    ("true", NormalizedKind::BooleanLiteral),
273    ("false", NormalizedKind::BooleanLiteral),
274    ("null", NormalizedKind::NullLiteral),
275    ("return_statement", NormalizedKind::Return),
276    ("throw_statement", NormalizedKind::Throw),
277    ("catch_clause", NormalizedKind::Catch),
278    ("if_statement", NormalizedKind::If),
279    ("for_statement", NormalizedKind::Loop),
280    ("for_range_loop", NormalizedKind::Loop),
281    ("compound_statement", NormalizedKind::Block),
282    ("switch_statement", NormalizedKind::Switch),
283    ("case_statement", NormalizedKind::Case),
284    ("labeled_statement", NormalizedKind::Label),
285    ("goto_statement", NormalizedKind::Goto),
286    ("break_statement", NormalizedKind::Break),
287    ("continue_statement", NormalizedKind::Continue),
288    ("while_statement", NormalizedKind::WhileLoop),
289    ("do_statement", NormalizedKind::WhileLoop),
290];
291
292fn cpp_escape_codepoint(digits: &str, radix: u32, byte_escape: bool) -> Option<char> {
293    let value = u32::from_str_radix(digits, radix).ok()?;
294    if byte_escape && value > 0x7f {
295        return None;
296    }
297    char::from_u32(value)
298}
299
300fn cpp_cooked_string_escape(escape: &str) -> Option<Option<char>> {
301    let escaped = escape.strip_prefix('\\')?;
302    if matches!(escaped, "\n" | "\r\n") {
303        return Some(None);
304    }
305    let character = match escaped {
306        "a" => '\x07',
307        "b" => '\x08',
308        "f" => '\x0c',
309        "n" => '\n',
310        "r" => '\r',
311        "t" => '\t',
312        "v" => '\x0b',
313        "0" => '\0',
314        "\\" => '\\',
315        "'" => '\'',
316        "\"" => '\"',
317        "?" => '?',
318        _ if escaped.starts_with('x') => {
319            let digits = escaped.get(1..)?;
320            (!digits.is_empty()
321                && digits.len() <= 4
322                && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
323            .then_some(())?;
324            cpp_escape_codepoint(digits, 16, true)?
325        }
326        _ if escaped.starts_with('u') => {
327            let digits = escaped.get(1..)?;
328            (digits.len() == 4 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
329                .then_some(())?;
330            cpp_escape_codepoint(digits, 16, false)?
331        }
332        _ if escaped.starts_with('U') => {
333            let digits = escaped.get(1..)?;
334            (digits.len() == 8 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
335                .then_some(())?;
336            cpp_escape_codepoint(digits, 16, false)?
337        }
338        _ if escaped.len() <= 3 && escaped.bytes().all(|byte| (b'0'..=b'7').contains(&byte)) => {
339            cpp_escape_codepoint(escaped, 8, true)?
340        }
341        _ => return None,
342    };
343    Some(Some(character))
344}
345
346fn cpp_string_literal_value(node: Node<'_>, source: &str) -> Option<String> {
347    let spelling = node_text(node, source)?;
348    match node.kind() {
349        "string_literal" => {
350            // Wide and UTF-prefixed literals have implementation-specific code
351            // unit encodings. Only the ordinary narrow form has a portable
352            // decoded UTF-8 value for this fact axis.
353            if !spelling.starts_with('"') {
354                return None;
355            }
356            let mut decoded = String::new();
357            let mut cursor = node.walk();
358            for child in node.named_children(&mut cursor) {
359                let text = node_text(child, source)?;
360                match child.kind() {
361                    "string_content" => decoded.push_str(text),
362                    "escape_sequence" => {
363                        if let Some(character) = cpp_cooked_string_escape(text)? {
364                            decoded.push(character);
365                        }
366                    }
367                    _ => return None,
368                }
369            }
370            Some(decoded)
371        }
372        "raw_string_literal" => {
373            if !spelling.starts_with("R\"") {
374                return None;
375            }
376            let mut content = None;
377            let mut cursor = node.walk();
378            for child in node.named_children(&mut cursor) {
379                match child.kind() {
380                    "raw_string_content" => {
381                        if content.is_some() {
382                            return None;
383                        }
384                        content = Some(node_text(child, source)?);
385                    }
386                    "raw_string_delimiter" => {}
387                    _ => return None,
388                }
389            }
390            content.map(str::to_owned)
391        }
392        _ => None,
393    }
394}
395
396fn direct_code_children(node: Node<'_>) -> impl Iterator<Item = Node<'_>> {
397    named_children_iter(node).filter(|child| {
398        !child.is_extra() && !child.kind().starts_with("preproc_") && child.kind() != "comment"
399    })
400}
401
402fn direct_code_child_count(node: Node<'_>) -> usize {
403    direct_code_children(node).count()
404}
405
406fn loop_body_is_complex(loop_node: Node<'_>) -> bool {
407    loop_node
408        .child_by_field_name("body")
409        .is_some_and(|body| direct_code_child_count(body) > 6)
410}
411
412fn complex_statement_count(block: Node<'_>) -> usize {
413    direct_code_children(block)
414        .filter(|child| {
415            matches!(
416                child.kind(),
417                "for_statement"
418                    | "for_range_loop"
419                    | "while_statement"
420                    | "do_statement"
421                    | "switch_statement"
422            )
423        })
424        .filter(|child| child.kind() == "switch_statement" || loop_body_is_complex(*child))
425        .count()
426}
427
428fn operator_kind(node: Node<'_>) -> Option<&'static str> {
429    let operator = node.child_by_field_name("operator")?;
430    match operator.kind() {
431        "&&" => Some("&&"),
432        "||" => Some("||"),
433        _ => None,
434    }
435}
436
437fn logical_operator_count(expression: Node<'_>) -> usize {
438    let mut count = 0usize;
439    let mut stack = vec![expression];
440    while let Some(node) = stack.pop() {
441        if let Some(operator) = operator_kind(node) {
442            let parent_operator = node.parent().and_then(operator_kind);
443            if parent_operator != Some(operator) {
444                count = count.saturating_add(1);
445            }
446        }
447        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
448    }
449    count
450}
451
452fn condition_expression(node: Node<'_>) -> Option<Node<'_>> {
453    let condition = node.child_by_field_name("condition")?;
454    if condition.kind() == "condition_clause" {
455        condition
456            .child_by_field_name("value")
457            .or_else(|| condition.named_child(0))
458    } else {
459        Some(condition)
460    }
461}
462
463fn switch_cases(body: Node<'_>) -> Vec<Node<'_>> {
464    let mut cases = Vec::new();
465    let mut stack = vec![body];
466    while let Some(node) = stack.pop() {
467        if node != body
468            && matches!(
469                node.kind(),
470                "switch_statement" | "function_definition" | "lambda_expression"
471            )
472        {
473            continue;
474        }
475        if node != body && node.kind() == "case_statement" {
476            cases.push(node);
477        }
478        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
479    }
480    cases.sort_unstable_by_key(Node::start_byte);
481    cases
482}
483
484fn switch_case_is_long(switch: Node<'_>) -> bool {
485    let Some(body) = switch.child_by_field_name("body") else {
486        return false;
487    };
488    let cases = switch_cases(body);
489    cases.iter().enumerate().any(|(index, case)| {
490        let end_line = cases.get(index + 1).map_or_else(
491            || switch.end_position().row + 1,
492            |next| next.start_position().row + 1,
493        );
494        end_line.saturating_sub(case.start_position().row + 1) > 30
495    })
496}
497
498fn enum_name_sets(root: Node<'_>, source: &str) -> Vec<HashSet<String>> {
499    let mut names = Vec::new();
500    let mut stack = vec![root];
501    while let Some(node) = stack.pop() {
502        if node.kind() == "enum_specifier" {
503            let mut declared = HashSet::default();
504            let mut enum_stack = vec![node];
505            while let Some(enum_node) = enum_stack.pop() {
506                if enum_node.kind() == "enumerator"
507                    && let Some(name) = enum_node.child_by_field_name("name")
508                    && let Some(text) = node_text(name, source)
509                {
510                    declared.insert(text.to_owned());
511                }
512                enum_stack.extend(
513                    named_children_iter(enum_node)
514                        .filter(|child| !child.is_extra() && child.kind() != "enum_specifier"),
515                );
516            }
517            if !declared.is_empty() {
518                names.push(declared);
519            }
520            continue;
521        }
522        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
523    }
524    names
525}
526
527fn switch_case_names(switch: Node<'_>, source: &str) -> HashSet<String> {
528    let Some(body) = switch.child_by_field_name("body") else {
529        return HashSet::default();
530    };
531    switch_cases(body)
532        .into_iter()
533        .filter_map(|case| case.child_by_field_name("value"))
534        .filter_map(|value| {
535            expression_name_node(value)
536                .and_then(|name| node_text(name, source))
537                .map(str::to_owned)
538        })
539        .collect()
540}
541
542fn switch_has_default(switch: Node<'_>) -> bool {
543    switch
544        .child_by_field_name("body")
545        .map(switch_cases)
546        .is_some_and(|cases| {
547            cases
548                .iter()
549                .any(|case| case.child_by_field_name("value").is_none())
550        })
551}
552
553fn enum_switch_is_missing_case(switch: Node<'_>, source: &str) -> bool {
554    if switch_has_default(switch) {
555        return false;
556    }
557    let mut root = switch;
558    while let Some(parent) = root.parent() {
559        root = parent;
560    }
561    let cases = switch_case_names(switch, source);
562    if cases.is_empty() {
563        return false;
564    }
565    enum_name_sets(root, source)
566        .into_iter()
567        .filter(|declared| cases.iter().all(|name| declared.contains(name)))
568        .min_by_key(HashSet::len)
569        .is_some_and(|declared| declared.iter().any(|name| !cases.contains(name)))
570}
571
572fn declarator_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
573    let mut current = node;
574    loop {
575        match current.kind() {
576            "identifier" | "field_identifier" | "statement_identifier" => return Some(current),
577            "init_declarator"
578            | "pointer_declarator"
579            | "reference_declarator"
580            | "array_declarator"
581            | "parenthesized_declarator"
582            | "attributed_declarator" => {
583                current = current.child_by_field_name("declarator")?;
584            }
585            "qualified_identifier" => current = current.child_by_field_name("name")?,
586            _ => return None,
587        }
588    }
589}
590
591fn loop_variable<'tree>(loop_node: Node<'tree>, source: &'tree str) -> Option<&'tree str> {
592    let declarator = if loop_node.kind() == "for_range_loop" {
593        loop_node.child_by_field_name("declarator")
594    } else {
595        let initializer = loop_node.child_by_field_name("initializer")?;
596        if initializer.kind() == "declaration" {
597            initializer
598                .child_by_field_name("declarator")
599                .and_then(declarator_identifier)
600        } else if initializer.kind() == "assignment_expression" {
601            initializer
602                .child_by_field_name("left")
603                .and_then(expression_name_node)
604        } else {
605            None
606        }
607    }?;
608    let name = declarator_identifier(declarator).or(Some(declarator))?;
609    node_text(name, source)
610}
611
612fn numeric_literal_value(node: Node<'_>, source: &str) -> Option<i64> {
613    if node.kind() != "number_literal" {
614        return None;
615    }
616    node_text(node, source)?.parse().ok()
617}
618
619fn for_loop_has_small_literal_bound(loop_node: Node<'_>, source: &str) -> bool {
620    let Some(condition) = loop_node.child_by_field_name("condition") else {
621        return false;
622    };
623    let mut stack = vec![condition];
624    while let Some(node) = stack.pop() {
625        if node.kind() == "binary_expression"
626            && let Some(operator) = node.child_by_field_name("operator")
627        {
628            let bound = match operator.kind() {
629                "<" | "<=" => node
630                    .child_by_field_name("right")
631                    .and_then(|value| numeric_literal_value(value, source)),
632                ">" | ">=" => node
633                    .child_by_field_name("left")
634                    .and_then(|value| numeric_literal_value(value, source)),
635                _ => None,
636            };
637            if bound.is_some_and(|value| (0..=1).contains(&value)) {
638                return true;
639            }
640        }
641        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
642    }
643    false
644}
645
646fn alloca_is_in_unbounded_loop(call: Node<'_>, source: &str) -> bool {
647    let Some(function) = call.child_by_field_name("function") else {
648        return false;
649    };
650    if !matches!(
651        node_text(function, source),
652        Some("alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
653    ) {
654        return false;
655    }
656    let mut current = call.parent();
657    while let Some(parent) = current {
658        if matches!(parent.kind(), "for_statement" | "for_range_loop") {
659            if parent
660                .child_by_field_name("initializer")
661                .is_some_and(|initializer| {
662                    initializer.start_byte() <= call.start_byte()
663                        && call.end_byte() <= initializer.end_byte()
664                })
665            {
666                return false;
667            }
668            return !for_loop_has_small_literal_bound(parent, source);
669        }
670        if matches!(parent.kind(), "while_statement" | "do_statement") {
671            return true;
672        }
673        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
674            break;
675        }
676        current = parent.parent();
677    }
678    false
679}
680
681fn goto_counts(function: Node<'_>, source: &str) -> (usize, usize) {
682    let mut labels = HashMap::<String, usize>::default();
683    let mut gotos = Vec::new();
684    let mut stack = vec![function];
685    while let Some(node) = stack.pop() {
686        if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
687            continue;
688        }
689        match node.kind() {
690            "labeled_statement" => {
691                if let Some(label) = node.child_by_field_name("label")
692                    && let Some(text) = node_text(label, source)
693                {
694                    labels.insert(text.to_owned(), node.start_position().row);
695                }
696            }
697            "goto_statement" => {
698                if let Some(label) = node.child_by_field_name("label")
699                    && let Some(text) = node_text(label, source)
700                {
701                    gotos.push((text.to_owned(), node.start_position().row));
702                }
703            }
704            _ => {}
705        }
706        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
707    }
708    gotos
709        .into_iter()
710        .fold((0, 0), |(forward, backward), (name, line)| {
711            match labels.get(&name).copied() {
712                Some(target) if line < target => (forward + 1, backward),
713                Some(target) if target < line => (forward, backward + 1),
714                _ => (forward, backward),
715            }
716        })
717}
718
719fn conditional_block_is_empty(block: Node<'_>) -> bool {
720    body_relation_for_block(block, Language::Cpp).is_some_and(|relation| {
721        relation.state == BodyState::Empty
722            && matches!(
723                relation.owner_kind,
724                BodyOwner::IfConsequence | BodyOwner::IfAlternative
725            )
726    })
727}
728
729/// Classify one C++ AST node for the statement-level CodeQL parity facts.
730///
731/// Every branch is anchored to a tree-sitter node and reads only structured
732/// fields or values selected from those nodes. The same classifier is used by
733/// structural construct labels and by the C++ statement relation producer.
734pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
735    match node.kind() {
736        "call_expression" if alloca_is_in_unbounded_loop(node, source) => {
737            Some(StatementFactKind::AllocaInLoop)
738        }
739        "compound_statement" => {
740            if complex_statement_count(node) > 3 {
741                Some(StatementFactKind::ComplexBlock)
742            } else if conditional_block_is_empty(node) {
743                Some(StatementFactKind::EmptyBlock)
744            } else {
745                None
746            }
747        }
748        "if_statement"
749            if condition_expression(node)
750                .is_some_and(|condition| logical_operator_count(condition) > 5) =>
751        {
752            Some(StatementFactKind::ComplexCondition)
753        }
754        "switch_statement" => {
755            if switch_case_is_long(node) {
756                Some(StatementFactKind::LongSwitch)
757            } else if enum_switch_is_missing_case(node, source) {
758                Some(StatementFactKind::MissingCaseInSwitch)
759            } else {
760                None
761            }
762        }
763        "function_definition" => {
764            let (forward, backward) = goto_counts(node, source);
765            (forward > 0 && backward > 0 && forward != 1 && backward != 1)
766                .then_some(StatementFactKind::UseOfGoto)
767        }
768        _ => None,
769    }
770}
771
772/// Capture the source-owned statement rows while the parsed tree is available.
773/// The rows retain only bounded, file-local facts; semantic CFG joins remain in
774/// the query layer and are never rebuilt here.
775pub(crate) fn capture_statement_fact_rows(
776    node: Node<'_>,
777    source: &str,
778    rows: &mut brokk_bifrost_core::analyzer::statement_facts::CppStatementFactRows,
779) {
780    use brokk_bifrost_core::analyzer::statement_facts::{
781        CppConditionFact, CppEnumFact, CppEnumeratorFact, CppGotoFact, CppLabelFact, CppLoopFact,
782        CppStatementFact, CppSwitchCaseFact, CppSwitchFact,
783    };
784
785    let range = node_range(node);
786    if let Some(kind) = statement_fact_kind(node, source) {
787        rows.statements.push(CppStatementFact { range, kind });
788    }
789    if node.kind() == "switch_statement" {
790        let long_case = switch_case_is_long(node);
791        let missing_case = enum_switch_is_missing_case(node, source);
792        if long_case
793            && !rows
794                .statements
795                .iter()
796                .any(|fact| fact.range == range && fact.kind == StatementFactKind::LongSwitch)
797        {
798            rows.statements.push(CppStatementFact {
799                range,
800                kind: StatementFactKind::LongSwitch,
801            });
802        }
803        if missing_case
804            && !rows.statements.iter().any(|fact| {
805                fact.range == range && fact.kind == StatementFactKind::MissingCaseInSwitch
806            })
807        {
808            rows.statements.push(CppStatementFact {
809                range,
810                kind: StatementFactKind::MissingCaseInSwitch,
811            });
812        }
813        let cases = node
814            .child_by_field_name("body")
815            .map(switch_cases)
816            .unwrap_or_default();
817        for case in &cases {
818            let value = case
819                .child_by_field_name("value")
820                .and_then(expression_name_node)
821                .and_then(|name| node_text(name, source))
822                .map(str::to_owned);
823            rows.switch_cases.push(CppSwitchCaseFact {
824                range: node_range(*case),
825                value,
826            });
827        }
828        rows.switches.push(CppSwitchFact {
829            range,
830            case_count: cases.len(),
831            default_present: switch_has_default(node),
832            long_case,
833            missing_enum_case: missing_case,
834        });
835    }
836    if node.kind() == "if_statement"
837        && let Some(condition) = condition_expression(node)
838    {
839        rows.conditions.push(CppConditionFact {
840            range: node_range(condition),
841            logical_operator_count: logical_operator_count(condition),
842        });
843    }
844    if matches!(
845        node.kind(),
846        "for_statement" | "for_range_loop" | "while_statement" | "do_statement"
847    ) {
848        let body_statement_count = node
849            .child_by_field_name("body")
850            .map(direct_code_child_count)
851            .unwrap_or_default();
852        let variable = if matches!(node.kind(), "for_statement" | "for_range_loop") {
853            loop_variable(node, source).map(str::to_owned)
854        } else {
855            None
856        };
857        rows.loops.push(CppLoopFact {
858            range,
859            variable,
860            body_statement_count,
861            tightly_bounded: node.kind() == "for_statement"
862                && for_loop_has_small_literal_bound(node, source),
863        });
864    }
865    if node.kind() == "labeled_statement"
866        && let Some(label) = node.child_by_field_name("label")
867        && let Some(name) = node_text(label, source)
868    {
869        rows.labels.push(CppLabelFact {
870            range,
871            name: name.to_owned(),
872        });
873    }
874    if node.kind() == "goto_statement"
875        && let Some(label) = node.child_by_field_name("label")
876        && let Some(name) = node_text(label, source)
877    {
878        let forward = goto_target_line(node, name, source)
879            .is_some_and(|target| node.start_position().row < target);
880        rows.gotos.push(CppGotoFact {
881            range,
882            target: name.to_owned(),
883            forward,
884        });
885    }
886    if node.kind() == "enum_specifier" {
887        let name = node
888            .child_by_field_name("name")
889            .and_then(|name| node_text(name, source))
890            .map(str::to_owned);
891        rows.enums.push(CppEnumFact { range, name });
892        let mut stack = vec![node];
893        while let Some(current) = stack.pop() {
894            if current != node && current.kind() == "enum_specifier" {
895                continue;
896            }
897            if current.kind() == "enumerator"
898                && let Some(name) = current.child_by_field_name("name")
899                && let Some(text) = node_text(name, source)
900            {
901                rows.enumerators.push(CppEnumeratorFact {
902                    range: node_range(current),
903                    enum_range: range,
904                    name: text.to_owned(),
905                });
906            }
907            stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
908        }
909    }
910}
911
912fn goto_target_line(goto: Node<'_>, target: &str, source: &str) -> Option<usize> {
913    let mut function = goto;
914    while let Some(parent) = function.parent() {
915        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
916            function = parent;
917            break;
918        }
919        function = parent;
920    }
921    let mut stack = vec![function];
922    while let Some(node) = stack.pop() {
923        if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
924            continue;
925        }
926        if node.kind() == "labeled_statement"
927            && let Some(label) = node.child_by_field_name("label")
928            && node_text(label, source) == Some(target)
929        {
930            return Some(node.start_position().row);
931        }
932        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
933    }
934    None
935}
936
937/// Whether tree-sitter recovered `.field = value` as a declaration child pair:
938/// an `ERROR` containing only the anonymous dot immediately followed by an
939/// `init_declarator` whose declarator is a real identifier.
940///
941/// This is intentionally narrower than rejecting declarators below `ERROR` in
942/// general: macro-decorated declarations and ordinary comma-separated initialized
943/// declarations also use recovery nodes and remain real declarations.
944pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
945    if node.kind() != "init_declarator" {
946        return false;
947    }
948    let Some(identifier) = node.child_by_field_name("declarator") else {
949        return false;
950    };
951    if identifier.kind() != "identifier" || identifier.is_missing() {
952        return false;
953    }
954    let Some(previous) = node.prev_named_sibling() else {
955        return false;
956    };
957    if previous.kind() != "ERROR"
958        || previous.named_child_count() != 0
959        || previous.child_count() != 1
960        || previous.end_byte() != node.start_byte()
961    {
962        return false;
963    }
964    previous.child(0).is_some_and(|child| {
965        child.kind() == "."
966            && !child.is_named()
967            && child.start_byte() == previous.start_byte()
968            && child.end_byte() == previous.end_byte()
969    })
970}
971
972use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
973
974/// The declared name of a `namespace_definition`.
975///
976/// `namespace a::b { }` spells a `nested_namespace_specifier` that nests to
977/// the right, so the innermost specifier's last child is the terminal segment.
978/// An anonymous namespace has no `name` field at all and stays unnamed.
979fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
980    let mut current = namespace.child_by_field_name("name")?;
981    while current.kind() == "nested_namespace_specifier" {
982        current = current.named_child(current.named_child_count().checked_sub(1)?)?;
983    }
984    Some(current)
985}
986
987pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
988    let mut current = declarator;
989    loop {
990        match current.kind() {
991            "identifier"
992            | "field_identifier"
993            | "namespace_identifier"
994            | "type_identifier"
995            | "destructor_name"
996            | "operator_name"
997            | "operator_cast"
998            | "primitive_type" => return Some(current),
999            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1000            "dependent_name" | "template_function" | "template_method" | "template_type" => {
1001                current = current.child_by_field_name("name")?;
1002            }
1003            "function_declarator"
1004            | "pointer_declarator"
1005            | "array_declarator"
1006            | "init_declarator" => current = current.child_by_field_name("declarator")?,
1007            "attributed_declarator" => current = first_named_child(current)?,
1008            "reference_declarator" | "parenthesized_declarator" => {
1009                current = first_named_child(current)?;
1010            }
1011            _ => return None,
1012        }
1013    }
1014}
1015
1016/// Grammar steps for the shared call-callee spine. A value expression has no step.
1017pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1018    use CalleeSpineStep::{Field, FieldOrOnlyChild, Name};
1019    match kind {
1020        "identifier"
1021        | "field_identifier"
1022        | "type_identifier"
1023        | "operator_name"
1024        | "operator_cast"
1025        | "destructor_name"
1026        | "literal_operator_name"
1027        | "primitive_type" => Some(Name),
1028        "qualified_identifier"
1029        | "scoped_identifier"
1030        | "scoped_type_identifier"
1031        | "dependent_name"
1032        | "template_function"
1033        | "template_method"
1034        | "template_type" => Some(Field("name")),
1035        "field_expression" => Some(Field("field")),
1036        "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1037        _ => None,
1038    }
1039}
1040
1041fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1042    let mut current = expression;
1043    loop {
1044        match current.kind() {
1045            "identifier"
1046            | "field_identifier"
1047            | "namespace_identifier"
1048            | "type_identifier"
1049            | "destructor_name"
1050            | "operator_name"
1051            | "primitive_type"
1052            | "this" => return Some(current),
1053            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1054            "dependent_name" | "template_function" | "template_method" | "template_type" => {
1055                current = current.child_by_field_name("name")?;
1056            }
1057            "field_expression" => current = current.child_by_field_name("field")?,
1058            "call_expression" => current = current.child_by_field_name("function")?,
1059            "new_expression" => current = current.child_by_field_name("type")?,
1060            "parenthesized_expression" => current = first_named_child(current)?,
1061            _ => return declarator_name_node(current),
1062        }
1063    }
1064}
1065
1066fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1067    if function.kind() != "qualified_identifier" {
1068        return;
1069    }
1070    if let Some(scope) = function.child_by_field_name("scope") {
1071        attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1072    }
1073}
1074
1075fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1076    loop {
1077        if node.kind() == "qualified_identifier" {
1078            return Some(node);
1079        }
1080        node = node
1081            .child_by_field_name("declarator")
1082            .or_else(|| node.child_by_field_name("name"))
1083            .or_else(|| first_named_child(node))?;
1084    }
1085}
1086
1087fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1088    node.child_by_field_name("declarator")
1089        .and_then(qualified_declarator_node)
1090        .and_then(|qualified| qualified.child_by_field_name("scope"))
1091}
1092
1093pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1094    node_text(scope, source) == node_text(name, source)
1095}
1096
1097fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1098    node.child_by_field_name("declarator")
1099        .and_then(qualified_declarator_node)
1100        .and_then(|qualified| {
1101            Some((
1102                expression_name_node(qualified.child_by_field_name("scope")?)?,
1103                expression_name_node(last_named_field_child(qualified, "name")?)?,
1104            ))
1105        })
1106        .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1107}
1108
1109fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1110    if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1111        return None;
1112    }
1113    unquoted_string_content_span(node)
1114}
1115
1116/// The names of every function-like macro this translation unit defines, read
1117/// from `preproc_function_def` name fields.
1118///
1119/// The walk is iterative and descends only through preprocessor conditional
1120/// blocks, which is where a definition can nest. A `#define` written inside a
1121/// function body is not collected; that is a known and stated boundary, not an
1122/// approximation of one.
1123fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1124    let mut names = HashSet::default();
1125    let mut stack = vec![root];
1126    while let Some(node) = stack.pop() {
1127        for child in named_children_iter(node) {
1128            match child.kind() {
1129                "preproc_function_def" => {
1130                    if let Some(name) = child.child_by_field_name("name") {
1131                        names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1132                    }
1133                }
1134                "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1135                | "preproc_elifdef" => stack.push(child),
1136                _ => {}
1137            }
1138        }
1139    }
1140    names
1141}
1142
1143fn cpp_member_terminal_name<'tree>(field: Node<'tree>) -> Option<Node<'tree>> {
1144    let field = if field.kind() == "dependent_name" {
1145        first_named_child(field)?
1146    } else {
1147        field
1148    };
1149    expression_name_node(field)
1150}
1151
1152/// Return the one identifier-bearing node that spells a member in a C++
1153/// `field_expression`.
1154///
1155/// The grammar permits the `field` child to be a qualified name, a dependent
1156/// name, a template method, or a destructor name. `expression_name_node`
1157/// already follows those wrappers to their terminal name. Walking upward from
1158/// the candidate and comparing that terminal node keeps receiver identifiers,
1159/// declaration names, field-designator labels, and path prefixes out of the
1160/// member role without looking at source text.
1161fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1162    if !matches!(
1163        node.kind(),
1164        "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1165    ) {
1166        return None;
1167    }
1168
1169    let mut current = node;
1170    while let Some(parent) = current.parent() {
1171        if parent.kind() == "field_expression" {
1172            let field = parent.child_by_field_name("field")?;
1173            return cpp_member_terminal_name(field)
1174                .filter(|name| name.id() == node.id())
1175                .map(|_| OccurrenceRole::MemberPosition);
1176        }
1177
1178        // These are the only named wrappers that can occur between the
1179        // terminal field name and its owning field expression.
1180        if matches!(
1181            parent.kind(),
1182            "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1183        ) {
1184            current = parent;
1185        } else {
1186            return None;
1187        }
1188    }
1189    None
1190}
1191
1192/// What declares a C or C++ binder.
1193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1194enum CppBinderOwner<'tree> {
1195    /// A parameter of a function definition, a lambda or a `catch` clause.
1196    Parameter,
1197    /// A local declaration; the node is the declarator the name completes.
1198    Local(Node<'tree>),
1199    /// A range-based `for` variable; the node is the loop.
1200    RangeFor(Node<'tree>),
1201    /// A structured binding; the node is the binding declarator.
1202    StructuredBinding(Node<'tree>),
1203    /// A lambda init-capture (`[x = y]`).
1204    InitCapture,
1205}
1206
1207/// Declarator wrappers that carry a name inward through their `declarator`
1208/// field or their single named child.
1209const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1210    "pointer_declarator",
1211    "array_declarator",
1212    "reference_declarator",
1213    "parenthesized_declarator",
1214    "attributed_declarator",
1215    "variadic_declarator",
1216];
1217
1218/// What declares `node` as a C or C++ variable, or `None` when it is not a
1219/// variable binder.
1220///
1221/// The name is found by walking up the declarator chain. A name that reaches
1222/// a function declarator without passing a pointer or reference declarator
1223/// names a function; one that does names a function pointer variable. A
1224/// parameter binds only in a function definition, a lambda or a `catch`
1225/// clause; a prototype's parameter names bind nothing. A file-scope or
1226/// member declaration names a member or a global, not a lexical binding.
1227fn cpp_binder_owner<'tree>(
1228    node: Node<'tree>,
1229    parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1230    mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1231) -> Option<CppBinderOwner<'tree>> {
1232    if node.kind() != "identifier" {
1233        return None;
1234    }
1235    let mut child = node;
1236    let mut current = parent(node)?;
1237    let mut indirect = false;
1238    loop {
1239        // This field lookup scans earlier siblings. Generated C initializer
1240        // lists can be enormous, so only ask for fields on binder-shaped nodes.
1241        match current.kind() {
1242            kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1243                let field = field_name(current, child);
1244                if field.is_some() && field != Some("declarator") {
1245                    return None;
1246                }
1247                indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1248            }
1249            "function_declarator" => {
1250                let field = field_name(current, child);
1251                if field != Some("declarator") || !indirect {
1252                    return None;
1253                }
1254            }
1255            "init_declarator" => {
1256                let field = field_name(current, child);
1257                if field != Some("declarator") {
1258                    return None;
1259                }
1260            }
1261            "declaration" => {
1262                let field = field_name(current, child);
1263                if field != Some("declarator") {
1264                    return None;
1265                }
1266                let local = std::iter::successors(parent(current), |node| parent(*node))
1267                    .take_while(|node| {
1268                        !matches!(
1269                            node.kind(),
1270                            "translation_unit"
1271                                | "field_declaration_list"
1272                                | "declaration_list"
1273                                | "namespace_definition"
1274                        )
1275                    })
1276                    .any(|node| {
1277                        matches!(
1278                            node.kind(),
1279                            "compound_statement" | "for_statement" | "condition_clause"
1280                        )
1281                    });
1282                // The name's scope starts at the end of its declarator, before
1283                // any initializer.
1284                let declarator = if child.kind() == "init_declarator" {
1285                    child.child_by_field_name("declarator")?
1286                } else {
1287                    child
1288                };
1289                return local.then_some(CppBinderOwner::Local(declarator));
1290            }
1291            "parameter_declaration"
1292            | "optional_parameter_declaration"
1293            | "variadic_parameter_declaration" => {
1294                let field = field_name(current, child);
1295                if field != Some("declarator") {
1296                    return None;
1297                }
1298                let parameter_parent = parent(current)?;
1299                let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1300                    // The explicit-object wrapper owns exactly `this` and this
1301                    // parameter; only then may its parent supply the list.
1302                    if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1303                        || parameter_parent.named_child(1) != Some(current)
1304                    {
1305                        return None;
1306                    }
1307                    parent(parameter_parent)?
1308                } else {
1309                    parameter_parent
1310                };
1311                if list.kind() != "parameter_list" {
1312                    return None;
1313                }
1314                let owner = parent(list)?;
1315                let binds = match owner.kind() {
1316                    "catch_clause" => true,
1317                    "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1318                        .is_some_and(|lambda| {
1319                            lambda.kind() == "lambda_expression"
1320                                && lambda.child_by_field_name("declarator") == Some(owner)
1321                        }),
1322                    "function_declarator" => {
1323                        let mut declarator = owner;
1324                        loop {
1325                            let Some(up) = parent(declarator) else {
1326                                break false;
1327                            };
1328                            if up.kind() == "function_definition" {
1329                                break field_name_in_parent(up, declarator) == Some("declarator");
1330                            }
1331                            if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1332                                break false;
1333                            }
1334                            declarator = up;
1335                        }
1336                    }
1337                    _ => false,
1338                };
1339                return binds.then_some(CppBinderOwner::Parameter);
1340            }
1341            "for_range_loop" => {
1342                let field = field_name(current, child);
1343                return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1344            }
1345            "structured_binding_declarator" => {
1346                return Some(CppBinderOwner::StructuredBinding(current));
1347            }
1348            "lambda_capture_initializer" => {
1349                let field = field_name(current, child);
1350                return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1351            }
1352            _ => return None,
1353        }
1354        child = current;
1355        current = parent(current)?;
1356    }
1357}
1358
1359/// Whether `node` declares a C or C++ variable, climbing through the walk's
1360/// parent index: asking `Node::parent` re-descends from the root, which is
1361/// quadratic over a large file.
1362fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1363    cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1364}
1365
1366/// The binding one C or C++ binder token introduces, and the interval it is
1367/// in effect over.
1368///
1369/// A parameter or init-capture is in effect over its whole function, lambda
1370/// or catch clause. A local is in effect from the end of its declarator: C
1371/// and C++ start a name's scope there, before its initializer, so in
1372/// `int x = x;` the right side reads the new `x`. A condition declaration is
1373/// therefore in effect over the rest of its `if`, including `else`. A
1374/// range-`for` variable is in effect over the loop body, and a structured
1375/// binding from the end of its binding declarator.
1376fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1377    let binding = |kind, hoisting, activation| {
1378        Some(BindingActivation {
1379            kind,
1380            hoisting,
1381            activation,
1382        })
1383    };
1384    let after = |node: Node<'_>| Range {
1385        start_byte: node.end_byte(),
1386        end_byte: scope.end_byte,
1387        start_line: node.end_position().row + 1,
1388        end_line: scope.end_line,
1389    };
1390    match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1391        CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1392            binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1393        }
1394        CppBinderOwner::Local(declarator) => binding(
1395            BindingKind::Local,
1396            HoistingClass::SourceOrder,
1397            after(declarator),
1398        ),
1399        CppBinderOwner::RangeFor(loop_node) => binding(
1400            BindingKind::LoopVariable,
1401            HoistingClass::DeclaredHead,
1402            node_range(loop_node.child_by_field_name("body")?),
1403        ),
1404        CppBinderOwner::StructuredBinding(declarator) => binding(
1405            BindingKind::Local,
1406            HoistingClass::SourceOrder,
1407            after(declarator),
1408        ),
1409    }
1410}
1411
1412impl StructuralSpec for CppStructuralSpec {
1413    fn language(&self) -> Language {
1414        Language::Cpp
1415    }
1416
1417    fn supports_boolean_literal_value(&self) -> bool {
1418        true
1419    }
1420
1421    fn supports_string_literal_value(&self) -> bool {
1422        true
1423    }
1424
1425    fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1426        matches!(node.kind(), "string_literal" | "raw_string_literal")
1427            && cpp_string_literal_value(node, source).is_none()
1428    }
1429
1430    fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1431        cpp_string_literal_value(node, source)
1432    }
1433
1434    fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1435        CPP_KIND_TABLE
1436    }
1437
1438    fn generator_construct(
1439        &self,
1440        node: Node<'_>,
1441        _kind: NormalizedKind,
1442        source: &str,
1443    ) -> Option<&'static str> {
1444        statement_fact_kind(node, source).map(StatementFactKind::construct)
1445    }
1446
1447    fn refine_kind(
1448        &self,
1449        node: Node<'_>,
1450        kind: NormalizedKind,
1451        enclosing: Option<NormalizedKind>,
1452        source: &str,
1453        _context: &CallSiteContext,
1454    ) -> NormalizedKind {
1455        if kind == NormalizedKind::Function
1456            && (enclosing == Some(NormalizedKind::Class)
1457                || scoped_function_definition(node).is_some())
1458        {
1459            if is_constructor_definition(node, source) {
1460                NormalizedKind::Constructor
1461            } else {
1462                NormalizedKind::Method
1463            }
1464        } else {
1465            kind
1466        }
1467    }
1468
1469    fn supports_kind(&self, kind: NormalizedKind) -> bool {
1470        matches!(
1471            kind,
1472            NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1473        ) || self
1474            .kind_table()
1475            .iter()
1476            .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1477    }
1478
1479    fn supports_role(&self, role: Role) -> bool {
1480        !matches!(
1481            role,
1482            Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1483        )
1484    }
1485
1486    fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1487        CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1488    }
1489
1490    /// A `new_expression` is a constructor call, and a call whose callee names
1491    /// a function-like macro of this translation unit has an argument list
1492    /// that belongs to the macro, not to the callable that finally runs
1493    /// (#1478). `CALL_TWICE(2)` parses as an ordinary call of two source
1494    /// arguments even when the expansion calls a three-parameter callable, so
1495    /// the honest answer is unknown coverage and no argument rows at all —
1496    /// never a fabricated list an exact-arity assertion could read as clean.
1497    ///
1498    /// The boundary is deliberate: only macros defined in this file are
1499    /// known here, because that is what this file's parse tree contains. A
1500    /// macro defined in an included header leaves the site exact, which is
1501    /// the same answer the analyzer gives today.
1502    fn call_site_facts(
1503        &self,
1504        node: Node<'_>,
1505        source: &str,
1506        context: &CallSiteContext,
1507    ) -> Option<CallSiteFacts> {
1508        if node.kind() == "new_expression" {
1509            return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1510        }
1511        let callee = node.child_by_field_name("function")?;
1512        (callee.kind() == "identifier"
1513            && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1514        .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1515    }
1516
1517    fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1518        static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1519            .supported(OccurrenceRole::MemberPosition)
1520            .supported(OccurrenceRole::ValueReference)
1521            .supported(OccurrenceRole::Binder);
1522        &SUPPORT
1523    }
1524
1525    fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1526        // C and C++ derive scopes and binding intervals; import binders and
1527        // the package clause are not derived. The call seams report
1528        // per-candidate callable applicability (#1478 M3).
1529        static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1530            .supported(EnvironmentAxis::Scopes)
1531            .supported(EnvironmentAxis::BindingIntervals)
1532            .supported(EnvironmentAxis::CallableApplicability);
1533        &SUPPORT
1534    }
1535
1536    /// An `if` statement is a scope: a C++ condition or init-statement
1537    /// declaration is in effect in both branches and nowhere after.
1538    fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1539        if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1540            ScopeFormation::BindingScope
1541        } else {
1542            default_scope_formation(kind)
1543        }
1544    }
1545
1546    fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1547        cpp_binding_activation(binder, scope)
1548    }
1549
1550    fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1551        &CPP_MATERIALIZATION_SUPPORT
1552    }
1553
1554    fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1555        &INVERSE_REFERENCE_EDGE_SUPPORT
1556    }
1557
1558    fn identity_route_support(&self) -> &IdentityRouteSupport {
1559        // C++'s occurrence adapter is shallow, so it claims no path axes. Its
1560        // declaration layer keeps prototype/body occurrences distinct, which
1561        // the physical-grouping axis carries, and the analyzer now labels each
1562        // of those occurrences a head or a body and pairs a prototype with the
1563        // out-of-line callable body that defines it, so the
1564        // declaration-definition peer relation is supplied as typed rows with
1565        // the body occurrence's own range as provenance (#1650). Classes stay
1566        // outside that relation: a same-file forward declaration is not a
1567        // distinct occurrence, and across files a class carries no linkage
1568        // evidence to pair on.
1569        static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1570            .supported_axis(IdentityAxis::CanonicalIdentity)
1571            .supported_axis(IdentityAxis::PhysicalGrouping)
1572            .supported_relation(RouteHopKind::NestedOwner)
1573            .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1574        &SUPPORT
1575    }
1576
1577    fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1578        if let Some(role) = cpp_member_position(node) {
1579            sink.occurrence_role(node, role);
1580        } else if cpp_is_binder(node, sink.parents()) {
1581            sink.occurrence_role(node, OccurrenceRole::Binder);
1582        }
1583
1584        match kind {
1585            NormalizedKind::Call => {
1586                let function_field = if node.kind() == "new_expression" {
1587                    "type"
1588                } else {
1589                    "function"
1590                };
1591                if let Some(function) = node.child_by_field_name(function_field) {
1592                    attach_terminal_callee(
1593                        sink,
1594                        function,
1595                        terminal_callee_leaf(function, callee_spine_step),
1596                    );
1597                    // The callee token of a free-function call is a plain value
1598                    // read, and it is the only occurrence the call site leaves
1599                    // behind when the function is not indexed (#3466). Member
1600                    // calls are excluded: their terminal field is already the
1601                    // member-position occurrence, and classifying it twice would
1602                    // report one call site as two. A composite callee
1603                    // expression names its own callee at an inner node
1604                    // (`f()()`), so it is excluded too.
1605                    if node.kind() == "call_expression"
1606                        && !matches!(function.kind(), "field_expression" | "call_expression")
1607                        && let Some(callee) = expression_name_node(function)
1608                        && self.kind_table().iter().any(|(node_kind, kind)| {
1609                            *node_kind == callee.kind() && self.should_extract(callee, *kind)
1610                        })
1611                    {
1612                        sink.occurrence_role(callee, OccurrenceRole::ValueReference);
1613                    }
1614                    if function.kind() == "field_expression"
1615                        && let Some(argument) = function.child_by_field_name("argument")
1616                    {
1617                        attach_role_with_derived_name(
1618                            sink,
1619                            Role::Receiver,
1620                            argument,
1621                            expression_name_node,
1622                        );
1623                    }
1624                    attach_qualified_scope_receiver(sink, function);
1625                }
1626                if let Some(arguments) = node.child_by_field_name("arguments") {
1627                    attach_positional_argument_roles(sink, arguments, expression_name_node);
1628                }
1629            }
1630            NormalizedKind::FieldAccess => {
1631                if let Some(field) = node.child_by_field_name("field") {
1632                    attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
1633                    if let Some(name) = expression_name_node(field) {
1634                        sink.set_name(name);
1635                    }
1636                }
1637                if let Some(argument) = node.child_by_field_name("argument") {
1638                    attach_role_with_derived_name(
1639                        sink,
1640                        Role::Object,
1641                        argument,
1642                        expression_name_node,
1643                    );
1644                }
1645            }
1646            NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
1647                if let Some(name) = node
1648                    .child_by_field_name("declarator")
1649                    .and_then(declarator_name_node)
1650                {
1651                    sink.set_name(name);
1652                }
1653            }
1654            NormalizedKind::Module => {
1655                if let Some(name) = namespace_name_node(node) {
1656                    sink.set_name(name);
1657                }
1658            }
1659            NormalizedKind::Class | NormalizedKind::Declaration => {
1660                if let Some(name) = node
1661                    .child_by_field_name("name")
1662                    .and_then(declarator_name_node)
1663                    .or_else(|| node.child_by_field_name("name"))
1664                {
1665                    sink.set_name(name);
1666                }
1667            }
1668            NormalizedKind::Assignment => match node.kind() {
1669                "init_declarator" => {
1670                    if let Some(declarator) = node.child_by_field_name("declarator") {
1671                        attach_role_with_derived_name(
1672                            sink,
1673                            Role::Left,
1674                            declarator,
1675                            declarator_name_node,
1676                        );
1677                        if let Some(name) = declarator_name_node(declarator) {
1678                            sink.set_name(name);
1679                        }
1680                    }
1681                    if let Some(value) = node.child_by_field_name("value") {
1682                        attach_role_with_derived_name(
1683                            sink,
1684                            Role::Right,
1685                            value,
1686                            expression_name_node,
1687                        );
1688                    }
1689                }
1690                "assignment_expression" => {
1691                    if let Some(left) = node.child_by_field_name("left") {
1692                        attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1693                    }
1694                    if let Some(right) = node.child_by_field_name("right") {
1695                        attach_role_with_derived_name(
1696                            sink,
1697                            Role::Right,
1698                            right,
1699                            expression_name_node,
1700                        );
1701                    }
1702                }
1703                _ => {}
1704            },
1705            NormalizedKind::Import => {
1706                if let Some(path) = node.child_by_field_name("path") {
1707                    if let Some(name) = unquoted_include_span(path) {
1708                        sink.role_named_span(Role::Module, path, name);
1709                    } else {
1710                        attach_role_with_derived_name(
1711                            sink,
1712                            Role::Module,
1713                            path,
1714                            expression_name_node,
1715                        );
1716                    }
1717                }
1718            }
1719            NormalizedKind::Identifier => match expression_name_node(node) {
1720                Some(name) => sink.set_name(name),
1721                None => sink.set_name(node),
1722            },
1723            _ => {}
1724        }
1725    }
1726}
1727
1728#[cfg(test)]
1729mod tests {
1730    use super::CPP_STRUCTURAL_SPEC;
1731    use super::cpp_binder_owner;
1732    use super::cpp_member_position;
1733    use super::field_name_in_parent;
1734    use super::node_range;
1735    use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
1736    use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
1737    use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
1738    use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
1739    use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
1740    use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
1741    use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
1742    use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
1743    use brokk_bifrost_core::hash::HashMap;
1744    use brokk_bifrost_test_macros::hardwon;
1745    use tree_sitter::Parser;
1746
1747    fn collect_cpp_structural_facts(source: &str) {
1748        collect_structural_rows(source);
1749    }
1750
1751    fn collect_structural_rows(source: &str) -> StructuralFactRows {
1752        let mut parser = Parser::new();
1753        parser
1754            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1755            .expect("C++ grammar");
1756        let tree = parser.parse(source, None).expect("C++ parse");
1757        let root = tree.root_node();
1758        let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
1759        let compiled = CompiledKinds::compile(
1760            &tree_sitter_cpp::LANGUAGE.into(),
1761            super::CPP_STRUCTURAL_SPEC.kind_table(),
1762        );
1763        let mut structural = StructuralFactCollector::new(
1764            &super::CPP_STRUCTURAL_SPEC,
1765            source,
1766            &context,
1767            ParentIndex::new(root),
1768            usize::MAX,
1769            None,
1770        );
1771        let mut source_facts = PrimarySourceFactCollector::new(source);
1772        let mut pending = vec![(root, None)];
1773        while let Some((node, parent)) = pending.pop() {
1774            let mut child_parent = parent;
1775            if node.is_named()
1776                && let Some(raw_kind) = compiled.kind_of(&node)
1777                && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
1778            {
1779                let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
1780                    node,
1781                    raw_kind,
1782                    parent.map(|id| structural.normalized_kind(id)),
1783                    source,
1784                    &context,
1785                );
1786                let id = structural
1787                    .enter(node, kind, parent, &mut source_facts)
1788                    .expect("unbounded C++ structural collector");
1789                let mut sink = structural.role_sink(&mut source_facts);
1790                super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
1791                structural
1792                    .accept_roles(id, sink.into_parts())
1793                    .expect("unbounded C++ structural roles");
1794                child_parent = Some(id);
1795            }
1796            for index in (0..node.named_child_count()).rev() {
1797                if let Some(child) = node.named_child(index) {
1798                    pending.push((child, child_parent));
1799                }
1800            }
1801        }
1802        structural
1803            .finish(root)
1804            .expect("C++ structural collection finishes")
1805    }
1806
1807    fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
1808        let mut parser = Parser::new();
1809        parser
1810            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1811            .expect("C++ grammar");
1812        let tree = parser.parse(source, None).expect("C++ parse");
1813        assert!(
1814            !tree.root_node().has_error(),
1815            "{}",
1816            tree.root_node().to_sexp()
1817        );
1818        let mut found = Vec::new();
1819        let mut pending = vec![tree.root_node()];
1820        while let Some(node) = pending.pop() {
1821            if let Some(role) = cpp_member_position(node) {
1822                found.push((
1823                    node.start_byte(),
1824                    &source[node.start_byte()..node.end_byte()],
1825                    role,
1826                ));
1827            }
1828            for index in (0..node.named_child_count()).rev() {
1829                if let Some(child) = node.named_child(index) {
1830                    pending.push(child);
1831                }
1832            }
1833        }
1834        found
1835    }
1836
1837    #[test]
1838    #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
1839    fn binder_classification_scans_large_initializer_lists_linearly() {
1840        use std::fmt::Write as _;
1841
1842        let mut source = String::from("int values[] = {");
1843        for index in 0..4096 {
1844            write!(source, "item_{index},").expect("append generated initializer item");
1845        }
1846        source.push_str("};\n");
1847
1848        let mut parser = Parser::new();
1849        parser
1850            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1851            .expect("C++ grammar");
1852        let tree = parser.parse(&source, None).expect("C++ parse");
1853        assert!(
1854            !tree.root_node().has_error(),
1855            "{}",
1856            tree.root_node().to_sexp()
1857        );
1858        let parents = ParentIndex::new(tree.root_node());
1859        let mut pending = vec![tree.root_node()];
1860        let mut identifiers = 0usize;
1861        let mut sibling_steps = 0usize;
1862        while let Some(node) = pending.pop() {
1863            if node.kind() == "identifier" {
1864                identifiers += 1;
1865                let _ = cpp_binder_owner(
1866                    node,
1867                    |node| parents.parent(node),
1868                    |parent, child| {
1869                        let mut cursor = parent.walk();
1870                        if !cursor.goto_first_child() {
1871                            return None;
1872                        }
1873                        loop {
1874                            sibling_steps += 1;
1875                            if cursor.node() == child {
1876                                return cursor.field_name();
1877                            }
1878                            if !cursor.goto_next_sibling() {
1879                                return None;
1880                            }
1881                        }
1882                    },
1883                );
1884            }
1885            let mut cursor = node.walk();
1886            let children = node.children(&mut cursor).collect::<Vec<_>>();
1887            pending.extend(children.into_iter().rev());
1888        }
1889
1890        assert!(
1891            identifiers >= 4096,
1892            "parsed {identifiers} initializer identifiers"
1893        );
1894        assert!(
1895            sibling_steps <= identifiers * 4,
1896            "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
1897        );
1898    }
1899
1900    #[test]
1901    #[brokk_bifrost_test_macros::hardwon(
1902        "#4407: ABI 15 lambda declarators lost parameter binder identity"
1903    )]
1904    fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
1905        let source = concat!(
1906            "int enclosing(int outer) {\n",
1907            "    return [outer](int lambda) {\n",
1908            "        return [lambda](int nested) {\n",
1909            "            return outer + lambda + nested;\n",
1910            "        }(lambda);\n",
1911            "    }(outer);\n",
1912            "}\n",
1913        );
1914        let mut parser = Parser::new();
1915        parser
1916            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1917            .expect("C++ grammar");
1918        let tree = parser.parse(source, None).expect("C++ parse");
1919        assert!(
1920            !tree.root_node().has_error(),
1921            "{}",
1922            tree.root_node().to_sexp()
1923        );
1924
1925        let identifier_at = |start_byte| {
1926            let mut pending = vec![tree.root_node()];
1927            while let Some(node) = pending.pop() {
1928                if node.kind() == "identifier" && node.start_byte() == start_byte {
1929                    return Some(node);
1930                }
1931                for index in (0..node.named_child_count()).rev() {
1932                    if let Some(child) = node.named_child(index) {
1933                        pending.push(child);
1934                    }
1935                }
1936            }
1937            None
1938        };
1939        let outer_parameter =
1940            identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
1941                .expect("outer parameter");
1942        let lambda_parameter =
1943            identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
1944                .expect("lambda parameter");
1945        let nested_parameter =
1946            identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
1947                .expect("nested parameter");
1948
1949        for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
1950            assert_eq!(
1951                cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
1952                Some(super::CppBinderOwner::Parameter)
1953            );
1954            assert_eq!(
1955                super::cpp_binding_activation(parameter, node_range(tree.root_node()))
1956                    .map(|activation| activation.kind),
1957                Some(BindingKind::Parameter)
1958            );
1959        }
1960
1961        for (start, name) in [
1962            (
1963                source
1964                    .find("return outer + lambda")
1965                    .expect("outer reference")
1966                    + "return ".len(),
1967                "outer",
1968            ),
1969            (
1970                source
1971                    .find("outer + lambda + nested")
1972                    .expect("lambda reference")
1973                    + "outer + ".len(),
1974                "lambda",
1975            ),
1976            (
1977                source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
1978                "nested",
1979            ),
1980        ] {
1981            let reference = identifier_at(start).expect("body reference identifier");
1982            assert_eq!(
1983                cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
1984                None,
1985                "body reference {name:?} was classified as a binder"
1986            );
1987        }
1988    }
1989
1990    #[test]
1991    #[brokk_bifrost_test_macros::hardwon(
1992        "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
1993    )]
1994    fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
1995        let source = concat!(
1996            "struct Visitor {\n",
1997            "    int visit(this Visitor const& self, int ordinary) {\n",
1998            "        return self.value + ordinary;\n",
1999            "    }\n",
2000            "};\n",
2001            "using Callback = void (*)(int nested);\n",
2002        );
2003        let mut parser = Parser::new();
2004        parser
2005            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2006            .expect("C++ grammar");
2007        let tree = parser.parse(source, None).expect("C++ parse");
2008        assert!(
2009            !tree.root_node().has_error(),
2010            "{}",
2011            tree.root_node().to_sexp()
2012        );
2013
2014        let identifier_at = |start: usize, len: usize| {
2015            tree.root_node()
2016                .descendant_for_byte_range(start, start + len)
2017                .filter(|node| node.kind() == "identifier")
2018                .expect("identifier at source span")
2019        };
2020        let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2021        let receiver_body = identifier_at(
2022            source.find("return self").expect("receiver body reference") + "return ".len(),
2023            4,
2024        );
2025        let function_type_parameter =
2026            identifier_at(source.find("nested").expect("function type"), 6);
2027        let parents = ParentIndex::new(tree.root_node());
2028
2029        assert_eq!(receiver.kind(), "identifier");
2030        assert_eq!(
2031            cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2032            Some(super::CppBinderOwner::Parameter)
2033        );
2034        assert!(super::cpp_is_binder(receiver, &parents));
2035        assert_eq!(
2036            super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2037                .map(|activation| activation.kind),
2038            Some(BindingKind::Parameter)
2039        );
2040        assert_eq!(
2041            cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2042            None,
2043            "the receiver read in the body is not another binder"
2044        );
2045        assert!(!super::cpp_is_binder(receiver_body, &parents));
2046        assert_eq!(
2047            cpp_binder_owner(
2048                function_type_parameter,
2049                |node| node.parent(),
2050                field_name_in_parent
2051            ),
2052            None,
2053            "a function-type parameter remains outside callable binding ownership"
2054        );
2055    }
2056
2057    #[test]
2058    fn attributed_declarator_resolves_its_declared_name() {
2059        let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2060        let mut parser = Parser::new();
2061        parser
2062            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2063            .expect("C++ grammar");
2064        let tree = parser.parse(source, None).expect("C++ parse");
2065        assert!(
2066            !tree.root_node().has_error(),
2067            "{}",
2068            tree.root_node().to_sexp()
2069        );
2070
2071        let mut pending = vec![tree.root_node()];
2072        let mut attributed = None;
2073        let mut conversion = None;
2074        while let Some(node) = pending.pop() {
2075            match node.kind() {
2076                "attributed_declarator" => attributed = Some(node),
2077                "operator_cast" => conversion = Some(node),
2078                _ => {}
2079            }
2080            for index in (0..node.named_child_count()).rev() {
2081                if let Some(child) = node.named_child(index) {
2082                    pending.push(child);
2083                }
2084            }
2085        }
2086        let attributed = attributed.expect("attributed declarator");
2087        let name = super::declarator_name_node(attributed).expect("declarator name");
2088
2089        assert_eq!(&source[name.byte_range()], "x");
2090        let conversion = conversion.expect("conversion operator");
2091        assert_eq!(
2092            super::declarator_name_node(conversion).map(|node| node.kind()),
2093            Some("operator_cast")
2094        );
2095    }
2096
2097    #[test]
2098    fn cpp_member_position_is_limited_to_member_access_and_calls() {
2099        let source = concat!(
2100            "struct Widget {\n",
2101            "    int value;\n",
2102            "    int method(int label) {\n",
2103            "        return this->value + label.value + label.method()\n",
2104            "            + label.template convert<int>() + label.ns::member;\n",
2105            "    }\n",
2106            "};\n",
2107            "int build(Widget widget) {\n",
2108            "    Widget result{.value = widget.value};\n",
2109            "    return widget.method(result.value);\n",
2110            "}\n",
2111        );
2112        let found = member_occurrences(source);
2113        let member_texts = found
2114            .iter()
2115            .map(|(_, text, role)| (*text, *role))
2116            .collect::<Vec<_>>();
2117
2118        assert_eq!(
2119            member_texts,
2120            vec![
2121                ("value", OccurrenceRole::MemberPosition),
2122                ("value", OccurrenceRole::MemberPosition),
2123                ("method", OccurrenceRole::MemberPosition),
2124                ("convert", OccurrenceRole::MemberPosition),
2125                ("member", OccurrenceRole::MemberPosition),
2126                ("value", OccurrenceRole::MemberPosition),
2127                ("method", OccurrenceRole::MemberPosition),
2128                ("value", OccurrenceRole::MemberPosition),
2129            ]
2130        );
2131
2132        let at = |needle: &str| source.find(needle).expect("fixture token");
2133        for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2134            let receiver_start = at(receiver);
2135            assert!(
2136                found.iter().all(|(offset, _, _)| *offset != receiver_start),
2137                "receiver was classified: {receiver:?}"
2138            );
2139        }
2140        let non_members = [
2141            at("Widget {"),
2142            at("int value") + "int ".len(),
2143            at("int method") + "int ".len(),
2144            at(".value =") + 1,
2145        ];
2146        for unrelated_start in non_members {
2147            assert!(
2148                found
2149                    .iter()
2150                    .all(|(offset, _, _)| *offset != unrelated_start),
2151                "non-member identifier at byte {unrelated_start} was classified"
2152            );
2153        }
2154    }
2155
2156    #[test]
2157    #[brokk_bifrost_test_macros::hardwon(
2158        "#3466: C and C++ free-function calls published no resolver occurrence"
2159    )]
2160    fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2161        let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2162        assert!(support.is_supported(OccurrenceRole::MemberPosition));
2163        assert!(support.is_supported(OccurrenceRole::ValueReference));
2164        for role in [
2165            OccurrenceRole::ReceiverPosition,
2166            OccurrenceRole::LabelOrKey,
2167            OccurrenceRole::DeclarationName,
2168        ] {
2169            assert!(
2170                !support.is_supported(role),
2171                "unexpected C++ support for {role:?}"
2172            );
2173        }
2174    }
2175
2176    #[test]
2177    fn cpp_string_literals_publish_decoded_values() {
2178        let source = r#"
2179const char *ordinary = "http://example.test/a\n";
2180const char *raw = R"(https://example.test/path)";
2181const char *wide = L"wide";
2182const char character = 'x';
2183"#;
2184        let rows = collect_structural_rows(source);
2185        let values = rows
2186            .nodes()
2187            .iter()
2188            .filter(|node| node.kind == NormalizedKind::StringLiteral)
2189            .map(|node| node.string_value.clone())
2190            .collect::<Vec<_>>();
2191
2192        assert_eq!(
2193            values,
2194            vec![
2195                Some("http://example.test/a\n".to_owned()),
2196                Some("https://example.test/path".to_owned()),
2197                None,
2198                None,
2199            ]
2200        );
2201    }
2202
2203    /// The callee token of a free-function call is the only occurrence a call
2204    /// site leaves behind when the function is not indexed (#3466). Member
2205    /// calls stay member-position-only, and a composite callee expression
2206    /// names its own callee at an inner node.
2207    #[test]
2208    #[brokk_bifrost_test_macros::hardwon(
2209        "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2210    )]
2211    fn cpp_free_function_callees_are_value_references() {
2212        let source = concat!(
2213            "int bound(void) { return 1; }\n",
2214            "struct Widget { int method(void) { return 0; } };\n",
2215            "int go(struct Widget *widget) {\n",
2216            "    return getenv(\"PATH\") != 0\n",
2217            "        + ns::getenv(\"USER\") != 0\n",
2218            "        + bound()\n",
2219            "        + widget->method()\n",
2220            "        + bound()();\n",
2221            "}\n",
2222        );
2223        let mut parser = Parser::new();
2224        parser
2225            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2226            .expect("C++ grammar");
2227        let tree = parser.parse(source, None).expect("C++ parse");
2228        assert!(
2229            !tree.root_node().has_error(),
2230            "{}",
2231            tree.root_node().to_sexp()
2232        );
2233
2234        // Drive the same extraction entry the file walk drives: every fact
2235        // node admitted by the compiled kind table, through a real RoleSink.
2236        // The sink addresses occurrences by fact id, so the test keeps the
2237        // fact id to start byte mapping to name the tokens.
2238        let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2239        let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2240        let mut start_by_fact = HashMap::default();
2241        let mut extractions = Vec::new();
2242        let mut pending = vec![tree.root_node()];
2243        while let Some(node) = pending.pop() {
2244            if node.is_named()
2245                && let Some(kind) = compiled.kind_of(&node)
2246                && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2247            {
2248                start_by_fact.insert(node.id(), node.start_byte());
2249                extractions.push((node.start_byte(), node, kind));
2250            }
2251            for index in (0..node.named_child_count()).rev() {
2252                if let Some(child) = node.named_child(index) {
2253                    pending.push(child);
2254                }
2255            }
2256        }
2257        extractions.sort_by_key(|(start, _, _)| *start);
2258        let mut source_facts =
2259            brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2260        let parents = ParentIndex::new(tree.root_node());
2261        let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2262        for (_, node, kind) in extractions {
2263            CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2264        }
2265        let (_, _, occurrences, stop) = sink.into_parts();
2266        assert_eq!(stop, None);
2267
2268        let roles_at = |needle: &str| {
2269            let start = source.find(needle).expect("fixture token");
2270            occurrences
2271                .iter()
2272                .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2273                .map(|occurrence| occurrence.role)
2274                .collect::<Vec<_>>()
2275        };
2276        // Free-function callees are value reads, including a qualified callee
2277        // whose terminal name is the identifier and the callee of the inner
2278        // call in `bound()()`.
2279        assert_eq!(
2280            roles_at("getenv(\"PATH\")"),
2281            vec![OccurrenceRole::ValueReference]
2282        );
2283        assert_eq!(
2284            roles_at("getenv(\"USER\")"),
2285            vec![OccurrenceRole::ValueReference],
2286            "the terminal name of a qualified callee is the value read"
2287        );
2288        assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
2289        // A member call's terminal field stays one member-position occurrence;
2290        // the call itself adds no second row for the same token.
2291        assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
2292        // The receiver of a member call is neither a value read of the callee
2293        // nor a member position.
2294        assert!(roles_at("widget->").is_empty());
2295    }
2296
2297    /// Tree-sitter recovers the valid `this->operator[](...)` expression as a
2298    /// call whose function is the unfactored `this` node. That node must not
2299    /// receive a value-reference occurrence role.
2300    #[test]
2301    fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
2302        collect_cpp_structural_facts(
2303            "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
2304        );
2305    }
2306}
2307
2308#[cfg(test)]
2309mod result_use_tests {
2310    use super::{CppCallResultUseIndex, cpp_call_result_use};
2311    use brokk_bifrost_core::analyzer::structural::result_use::{
2312        CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
2313    };
2314    use tree_sitter::Parser;
2315
2316    fn tree(source: &str) -> tree_sitter::Tree {
2317        let mut parser = Parser::new();
2318        parser
2319            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2320            .expect("C++ grammar");
2321        parser.parse(source, None).expect("C++ tree")
2322    }
2323
2324    fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
2325        let mut found = Vec::new();
2326        let mut stack = vec![tree.root_node()];
2327        while let Some(node) = stack.pop() {
2328            if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
2329                found.push(node);
2330            }
2331            for index in (0..node.named_child_count()).rev() {
2332                stack.push(node.named_child(index).expect("named child"));
2333            }
2334        }
2335        found.sort_by_key(|node| node.start_byte());
2336        found
2337    }
2338
2339    #[test]
2340    fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
2341        let source = concat!(
2342            "int f();\n",
2343            "void sink(int);\n",
2344            "void run() {\n",
2345            "    f();\n",
2346            "    (void)f();\n",
2347            "    static_cast<void>(f());\n",
2348            "    sink(f());\n",
2349            "    return;\n",
2350            "}\n",
2351        );
2352        let tree = tree(source);
2353        assert!(
2354            !tree.root_node().has_error(),
2355            "{}",
2356            tree.root_node().to_sexp()
2357        );
2358        let calls = calls(&tree, source);
2359        assert_eq!(calls.len(), 5);
2360        assert_eq!(
2361            cpp_call_result_use(calls[0], source).for_port(0),
2362            CallResultUse::Discarded
2363        );
2364        assert!(matches!(
2365            cpp_call_result_use(calls[1], source).for_port(0),
2366            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2367        ));
2368        assert!(matches!(
2369            cpp_call_result_use(calls[2], source).for_port(0),
2370            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2371        ));
2372        assert_eq!(
2373            cpp_call_result_use(calls[3], source).for_port(0),
2374            CallResultUse::Discarded
2375        );
2376        assert_eq!(
2377            cpp_call_result_use(calls[4], source).for_port(0),
2378            CallResultUse::Consumed
2379        );
2380    }
2381
2382    #[test]
2383    fn lambda_tail_and_recovered_syntax_are_open() {
2384        // The macro-declaration grammar splits a bare `f(,)` into an
2385        // identifier and a separate ERROR with no call expression at all, so
2386        // the recovered call is spelled `f(+)` here: it still produces a
2387        // call_expression whose argument list carries the ERROR that marks
2388        // the syntax as recovered.
2389        let source = "int f(); void run() { auto value = [] { return f(); }; f(+); }";
2390        let tree = tree(source);
2391        assert!(tree.root_node().has_error());
2392        let calls = calls(&tree, source);
2393        assert!(matches!(
2394            cpp_call_result_use(calls[0], source).for_port(0),
2395            CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
2396        ));
2397        assert_eq!(
2398            cpp_call_result_use(calls[1], source).for_port(0),
2399            CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
2400        );
2401    }
2402
2403    #[test]
2404    fn exact_index_keeps_call_spans_separate() {
2405        let source = "int f(); void run() { f(); }";
2406        let tree = tree(source);
2407        let calls = calls(&tree, source);
2408        let index = CppCallResultUseIndex::new(tree.root_node(), source);
2409        assert_eq!(
2410            index
2411                .at_span(calls[0].start_byte(), calls[0].end_byte())
2412                .expect("indexed call")
2413                .for_port(0),
2414            CallResultUse::Discarded
2415        );
2416        assert!(index.at_span(source.len(), source.len()).is_err());
2417    }
2418}