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 base = match type_node.kind() {
136        "type_descriptor" if type_node.child_by_field_name("declarator").is_none() => {
137            type_node.child_by_field_name("type")
138        }
139        "primitive_type" => Some(type_node),
140        _ => None,
141    };
142    base.is_some_and(|base| {
143        base.kind() == "primitive_type" && node_text(base, source) == Some("void")
144    })
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
498/// Declaration evidence retained only while classifying one switch.
499#[derive(Clone, Copy)]
500struct CppSwitchDeclaration<'tree> {
501    type_node: Node<'tree>,
502    declarator: Node<'tree>,
503}
504
505fn cpp_switch_scope(node: Node<'_>) -> bool {
506    matches!(
507        node.kind(),
508        "compound_statement"
509            | "for_statement"
510            | "for_range_loop"
511            | "if_statement"
512            | "switch_statement"
513            | "while_statement"
514            | "catch_clause"
515    )
516}
517
518fn cpp_switch_local_declaration<'tree>(
519    switch: Node<'tree>,
520    name: &str,
521    source: &str,
522) -> Option<CppSwitchDeclaration<'tree>> {
523    let condition = condition_expression(switch)?;
524    let function = std::iter::successors(switch.parent(), |node| node.parent())
525        .find(|node| matches!(node.kind(), "function_definition" | "lambda_expression"))?;
526    enum Step<'tree> {
527        Enter(Node<'tree>),
528        LeaveScope,
529    }
530    enum Binding<'tree> {
531        Unknown,
532        Declaration(CppSwitchDeclaration<'tree>),
533    }
534    let mut stack = vec![Step::Enter(function)];
535    // Only the requested identifier is retained at each lexical tier.
536    let mut scopes: Vec<Option<Binding<'tree>>> = vec![None];
537    while let Some(step) = stack.pop() {
538        let node = match step {
539            Step::LeaveScope => {
540                scopes.pop().expect("entered scope");
541                continue;
542            }
543            Step::Enter(node) => node,
544        };
545        if node == condition {
546            return scopes.iter().rev().find_map(Option::as_ref).and_then(
547                |binding| match binding {
548                    Binding::Unknown => None,
549                    Binding::Declaration(declaration) => Some(*declaration),
550                },
551            );
552        }
553        if node != function
554            && matches!(
555                node.kind(),
556                "function_definition"
557                    | "lambda_expression"
558                    | "class_specifier"
559                    | "struct_specifier"
560                    | "union_specifier"
561                    | "enum_specifier"
562            )
563        {
564            continue;
565        }
566        if cpp_switch_scope(node) {
567            scopes.push(None);
568            stack.push(Step::LeaveScope);
569        }
570        if node.kind() == "declaration" {
571            for declarator in node.children_by_field_name("declarator", &mut node.walk()) {
572                if declarator_name_node(declarator).and_then(|node| node_text(node, source))
573                    == Some(name)
574                {
575                    // Prototypes and unsupported declarators also hide names.
576                    *scopes.last_mut().expect("function scope") = Some(Binding::Unknown);
577                }
578            }
579        }
580        if node.kind() == "identifier"
581            && node_text(node, source) == Some(name)
582            && let Some(owner) = cpp_binder_owner(node, |node| node.parent(), field_name_in_parent)
583        {
584            let declaration = match owner {
585                CppBinderOwner::Local(declarator) => {
586                    let owner = std::iter::successors(declarator.parent(), |node| node.parent())
587                        .find(|node| node.kind() == "declaration")
588                        .expect("local binder declaration");
589                    owner
590                        .child_by_field_name("type")
591                        .map(|type_node| CppSwitchDeclaration {
592                            type_node,
593                            declarator,
594                        })
595                }
596                CppBinderOwner::Parameter => {
597                    let owner = std::iter::successors(node.parent(), |node| node.parent())
598                        .find(|node| {
599                            matches!(
600                                node.kind(),
601                                "parameter_declaration"
602                                    | "optional_parameter_declaration"
603                                    | "variadic_parameter_declaration"
604                            )
605                        })
606                        .expect("parameter binder declaration");
607                    owner
608                        .child_by_field_name("type")
609                        .zip(owner.child_by_field_name("declarator"))
610                        .map(|(type_node, declarator)| CppSwitchDeclaration {
611                            type_node,
612                            declarator,
613                        })
614                }
615                CppBinderOwner::RangeFor(owner) => owner
616                    .child_by_field_name("type")
617                    .zip(owner.child_by_field_name("declarator"))
618                    .map(|(type_node, declarator)| CppSwitchDeclaration {
619                        type_node,
620                        declarator,
621                    }),
622                _ => None,
623            };
624            // An unknown inner binding must not fall through to a parameter.
625            *scopes.last_mut().expect("function scope") =
626                Some(declaration.map_or(Binding::Unknown, Binding::Declaration));
627        }
628        for index in (0..node.named_child_count()).rev() {
629            stack.push(Step::Enter(node.named_child(index).expect("named child")));
630        }
631    }
632    None
633}
634
635/// Search one lexical type tier, descending into a named scope only when the
636/// written qualification selects it. Aliases are barriers, not enum evidence.
637fn cpp_switch_type_in_scope<'tree>(
638    scope: Node<'tree>,
639    path: &[String],
640    before: usize,
641    source: &str,
642) -> Option<Node<'tree>> {
643    use crate::declarations::cpp_structured_type_path;
644    let mut stack = vec![(scope, 0)];
645    let mut found = None;
646    let mut unresolved_import = None;
647    while let Some((node, component)) = stack.pop() {
648        if node.start_byte() >= before {
649            continue;
650        }
651        if node != scope {
652            if node.kind() == "using_declaration" {
653                let imports_namespace = (0..node.child_count())
654                    .any(|index| node.child(index).expect("child").kind() == "namespace");
655                let imported = first_named_child(node)
656                    .and_then(declarator_name_node)
657                    .and_then(|name| node_text(name, source));
658                if imports_namespace || imported == Some(path[component].as_str()) {
659                    unresolved_import = Some(node);
660                }
661                continue;
662            }
663            let named_type = matches!(
664                node.kind(),
665                "enum_specifier"
666                    | "class_specifier"
667                    | "struct_specifier"
668                    | "union_specifier"
669                    | "namespace_definition"
670                    | "namespace_alias_definition"
671                    | "alias_declaration"
672                    | "type_definition"
673                    | "type_parameter_declaration"
674                    | "variadic_type_parameter_declaration"
675            );
676            if named_type {
677                let names = if node.kind() == "type_definition" {
678                    node.children_by_field_name("declarator", &mut node.walk())
679                        .filter_map(declarator_name_node)
680                        .filter_map(|name| cpp_structured_type_path(name, source))
681                        .collect::<Vec<_>>()
682                } else if matches!(
683                    node.kind(),
684                    "type_parameter_declaration" | "variadic_type_parameter_declaration"
685                ) {
686                    first_named_child(node)
687                        .and_then(|name| cpp_structured_type_path(name, source))
688                        .into_iter()
689                        .collect()
690                } else {
691                    node.child_by_field_name("name")
692                        .and_then(|name| {
693                            // A C++17 nested namespace has a different grammar
694                            // wrapper from a qualified type name.
695                            let mut stack = vec![name];
696                            let mut path = Vec::new();
697                            while let Some(part) = stack.pop() {
698                                if part.kind() == "nested_namespace_specifier" {
699                                    for index in (0..part.named_child_count()).rev() {
700                                        stack.push(
701                                            part.named_child(index).expect("namespace component"),
702                                        );
703                                    }
704                                } else {
705                                    path.extend(cpp_structured_type_path(part, source)?);
706                                }
707                            }
708                            Some(path)
709                        })
710                        .into_iter()
711                        .collect()
712                };
713                for name in names {
714                    if path[component..].starts_with(&name) {
715                        let next = component + name.len();
716                        if next < path.len()
717                            && let Some(body) = node.child_by_field_name("body")
718                        {
719                            stack.push((body, next));
720                        } else if found.is_none_or(|previous: Node<'_>| {
721                            previous.start_byte() < node.start_byte()
722                        }) {
723                            // An alias/forward declaration cannot supply the
724                            // rest of a qualification and must block fallback.
725                            found = Some(node);
726                        }
727                    }
728                }
729                continue;
730            }
731            if cpp_switch_scope(node)
732                || matches!(node.kind(), "function_definition" | "lambda_expression")
733            {
734                continue;
735            }
736        }
737        for index in (0..node.named_child_count()).rev() {
738            stack.push((node.named_child(index).expect("named child"), component));
739        }
740    }
741    found.or(unresolved_import)
742}
743
744fn cpp_switch_named_type<'tree>(type_node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
745    use crate::declarations::cpp_structured_type_path;
746    if type_node.kind() == "enum_specifier"
747        && type_node.child_by_field_name("body").is_some()
748        && type_node.child_by_field_name("name").is_some()
749    {
750        return Some(type_node);
751    }
752    if matches!(
753        type_node.kind(),
754        "class_specifier" | "struct_specifier" | "union_specifier"
755    ) && type_node.child_by_field_name("body").is_some()
756    {
757        return Some(type_node);
758    }
759    let name = if matches!(
760        type_node.kind(),
761        "enum_specifier" | "class_specifier" | "struct_specifier" | "union_specifier"
762    ) {
763        type_node.child_by_field_name("name")?
764    } else {
765        type_node
766    };
767    // Template arguments and dependent types require semantic resolution.
768    let mut stack = vec![name];
769    while let Some(node) = stack.pop() {
770        if !matches!(
771            node.kind(),
772            "type_identifier"
773                | "identifier"
774                | "namespace_identifier"
775                | "qualified_identifier"
776                | "scoped_identifier"
777                | "scoped_type_identifier"
778        ) {
779            return None;
780        }
781        stack.extend(named_children_iter(node).filter(|node| !node.is_extra()));
782    }
783    let path = cpp_structured_type_path(name, source)?;
784    let global = name.child(0).is_some_and(|child| child.kind() == "::");
785    for scope in std::iter::successors(type_node.parent(), |node| node.parent()) {
786        if !(cpp_switch_scope(scope)
787            || matches!(
788                scope.kind(),
789                "function_definition"
790                    | "template_declaration"
791                    | "namespace_definition"
792                    | "class_specifier"
793                    | "struct_specifier"
794                    | "union_specifier"
795                    | "translation_unit"
796            ))
797            || (global && scope.kind() != "translation_unit")
798        {
799            continue;
800        }
801        if let Some(declaration) =
802            cpp_switch_type_in_scope(scope, &path, type_node.start_byte(), source)
803        {
804            return Some(declaration);
805        }
806        if !global
807            && matches!(
808                scope.kind(),
809                "class_specifier" | "struct_specifier" | "union_specifier"
810            )
811        {
812            // Inherited names and complete-class lookup can hide an outer
813            // enum even without an earlier declaration in the class body.
814            if named_children_iter(scope).any(|node| node.kind() == "base_class_clause")
815                || cpp_switch_type_in_scope(scope, &path, usize::MAX, source)
816                    .is_some_and(|declaration| declaration.start_byte() >= type_node.start_byte())
817            {
818                return None;
819            }
820        }
821        // An out-of-line definition's logical owner is not its AST parent.
822        // Resolving relative names across that boundary needs the resolver.
823        if !global
824            && (scope.kind() == "function_definition"
825                && scoped_function_definition(scope).is_some()
826                || scope
827                    .child_by_field_name("name")
828                    .is_some_and(|name| name.kind() == "qualified_identifier"))
829        {
830            return None;
831        }
832    }
833    None
834}
835
836fn cpp_switch_pointer_depth(mut declarator: Node<'_>) -> Option<usize> {
837    let mut depth = 0;
838    loop {
839        match declarator.kind() {
840            "identifier" | "field_identifier" => return Some(depth),
841            "pointer_declarator" => {
842                depth += 1;
843                declarator = declarator.child_by_field_name("declarator")?;
844            }
845            "init_declarator" => declarator = declarator.child_by_field_name("declarator")?,
846            "reference_declarator" | "parenthesized_declarator" | "attributed_declarator" => {
847                declarator = first_named_child(declarator)?
848            }
849            _ => return None,
850        }
851    }
852}
853
854fn cpp_switch_enum_type<'tree>(switch: Node<'tree>, source: &str) -> Option<Node<'tree>> {
855    let mut operand = condition_expression(switch)?;
856    let mut fields = Vec::new();
857    while operand.kind() == "field_expression" {
858        fields.push(operand);
859        operand = operand.child_by_field_name("argument")?;
860    }
861    if operand.kind() != "identifier" {
862        return None;
863    }
864    let mut declaration =
865        cpp_switch_local_declaration(switch, node_text(operand, source)?, source)?;
866    for field in fields.into_iter().rev() {
867        let operator = field.child_by_field_name("operator")?;
868        let expected_depth = match operator.kind() {
869            "." => 0,
870            "->" => 1,
871            _ => return None,
872        };
873        if cpp_switch_pointer_depth(declaration.declarator)? != expected_depth {
874            return None;
875        }
876        let record = cpp_switch_named_type(declaration.type_node, source)?;
877        if !matches!(
878            record.kind(),
879            "class_specifier" | "struct_specifier" | "union_specifier"
880        ) {
881            return None;
882        }
883        let name = node_text(field.child_by_field_name("field")?, source)?;
884        let mut member = None;
885        for field_declaration in named_children_iter(record.child_by_field_name("body")?)
886            .filter(|node| node.kind() == "field_declaration")
887        {
888            for declarator in field_declaration
889                .children_by_field_name("declarator", &mut field_declaration.walk())
890            {
891                if declarator_name_node(declarator).and_then(|name| node_text(name, source))
892                    == Some(name)
893                {
894                    member = Some(CppSwitchDeclaration {
895                        type_node: field_declaration.child_by_field_name("type")?,
896                        declarator,
897                    });
898                }
899            }
900        }
901        declaration = member?;
902    }
903    if cpp_switch_pointer_depth(declaration.declarator)? != 0 {
904        return None;
905    }
906    let enum_type = cpp_switch_named_type(declaration.type_node, source)?;
907    (enum_type.kind() == "enum_specifier").then_some(enum_type)
908}
909
910fn switch_case_names(switch: Node<'_>, source: &str) -> HashSet<String> {
911    let Some(body) = switch.child_by_field_name("body") else {
912        return HashSet::default();
913    };
914    switch_cases(body)
915        .into_iter()
916        .filter_map(|case| case.child_by_field_name("value"))
917        .filter_map(|value| {
918            expression_name_node(value)
919                .and_then(|name| node_text(name, source))
920                .map(str::to_owned)
921        })
922        .collect()
923}
924
925fn switch_has_default(switch: Node<'_>) -> bool {
926    switch
927        .child_by_field_name("body")
928        .map(switch_cases)
929        .is_some_and(|cases| {
930            cases
931                .iter()
932                .any(|case| case.child_by_field_name("value").is_none())
933        })
934}
935
936fn enum_switch_is_missing_case(switch: Node<'_>, source: &str) -> bool {
937    if switch_has_default(switch) {
938        return false;
939    }
940    let Some(enum_type) = cpp_switch_enum_type(switch, source) else {
941        // Unknown operand types carry no enum-completeness evidence.
942        return false;
943    };
944    let Some(body) = enum_type.child_by_field_name("body") else {
945        return false;
946    };
947    let declared: HashSet<_> = named_children_iter(body)
948        .filter(|node| node.kind() == "enumerator")
949        .filter_map(|node| node.child_by_field_name("name"))
950        .filter_map(|name| node_text(name, source))
951        .collect();
952    let cases = switch_case_names(switch, source);
953    !cases.is_empty()
954        && cases.iter().all(|name| declared.contains(name.as_str()))
955        && declared.iter().any(|name| !cases.contains(*name))
956}
957
958fn declarator_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
959    let mut current = node;
960    loop {
961        match current.kind() {
962            "identifier" | "field_identifier" | "statement_identifier" => return Some(current),
963            "init_declarator"
964            | "pointer_declarator"
965            | "reference_declarator"
966            | "array_declarator"
967            | "parenthesized_declarator"
968            | "attributed_declarator" => {
969                current = current.child_by_field_name("declarator")?;
970            }
971            "qualified_identifier" => current = current.child_by_field_name("name")?,
972            _ => return None,
973        }
974    }
975}
976
977fn loop_variable<'tree>(loop_node: Node<'tree>, source: &'tree str) -> Option<&'tree str> {
978    let declarator = if loop_node.kind() == "for_range_loop" {
979        loop_node.child_by_field_name("declarator")
980    } else {
981        let initializer = loop_node.child_by_field_name("initializer")?;
982        if initializer.kind() == "declaration" {
983            initializer
984                .child_by_field_name("declarator")
985                .and_then(declarator_identifier)
986        } else if initializer.kind() == "assignment_expression" {
987            initializer
988                .child_by_field_name("left")
989                .and_then(expression_name_node)
990        } else {
991            None
992        }
993    }?;
994    let name = declarator_identifier(declarator).or(Some(declarator))?;
995    node_text(name, source)
996}
997
998fn numeric_literal_value(node: Node<'_>, source: &str) -> Option<i64> {
999    if node.kind() != "number_literal" {
1000        return None;
1001    }
1002    node_text(node, source)?.parse().ok()
1003}
1004
1005fn for_loop_has_small_literal_bound(loop_node: Node<'_>, source: &str) -> bool {
1006    use brokk_bifrost_core::analyzer::tree_walk::unwrap_parenthesized_expression;
1007
1008    let Some(variable) = loop_variable(loop_node, source) else {
1009        return false;
1010    };
1011    let Some(condition) = loop_node.child_by_field_name("condition") else {
1012        return false;
1013    };
1014    let mut stack = vec![condition];
1015    while let Some(node) = stack.pop() {
1016        let node = unwrap_parenthesized_expression(node);
1017        if node.kind() != "binary_expression" {
1018            continue;
1019        }
1020        let Some(operator) = node.child_by_field_name("operator") else {
1021            continue;
1022        };
1023        // A conjunct must hold on every iteration. A comparison nested in an
1024        // OR, negation or another expression does not bound the loop.
1025        if operator.kind() == "&&" {
1026            stack.extend([
1027                node.child_by_field_name("left").expect("binary left"),
1028                node.child_by_field_name("right").expect("binary right"),
1029            ]);
1030            continue;
1031        }
1032        let operands = match operator.kind() {
1033            "<" | "<=" => Some(("left", "right")),
1034            ">" | ">=" => Some(("right", "left")),
1035            _ => None,
1036        };
1037        if let Some((value_field, bound_field)) = operands
1038            && let Some(value) = node
1039                .child_by_field_name(value_field)
1040                .map(unwrap_parenthesized_expression)
1041            && value.kind() == "identifier"
1042            && node_text(value, source) == Some(variable)
1043            && let Some(bound) = node
1044                .child_by_field_name(bound_field)
1045                .map(unwrap_parenthesized_expression)
1046                .and_then(|value| numeric_literal_value(value, source))
1047            && (0..=1).contains(&bound)
1048        {
1049            return true;
1050        }
1051    }
1052    false
1053}
1054
1055fn alloca_is_in_unbounded_loop(call: Node<'_>, source: &str) -> bool {
1056    let Some(function) = call.child_by_field_name("function") else {
1057        return false;
1058    };
1059    if !matches!(
1060        node_text(function, source),
1061        Some("alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
1062    ) {
1063        return false;
1064    }
1065    let mut current = call.parent();
1066    while let Some(parent) = current {
1067        if matches!(parent.kind(), "for_statement" | "for_range_loop") {
1068            if parent
1069                .child_by_field_name("initializer")
1070                .is_some_and(|initializer| {
1071                    initializer.start_byte() <= call.start_byte()
1072                        && call.end_byte() <= initializer.end_byte()
1073                })
1074            {
1075                return false;
1076            }
1077            return !for_loop_has_small_literal_bound(parent, source);
1078        }
1079        if matches!(parent.kind(), "while_statement" | "do_statement") {
1080            return true;
1081        }
1082        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1083            break;
1084        }
1085        current = parent.parent();
1086    }
1087    false
1088}
1089
1090fn goto_counts(function: Node<'_>, source: &str) -> (usize, usize) {
1091    let mut labels = HashMap::<String, usize>::default();
1092    let mut gotos = Vec::new();
1093    let mut stack = vec![function];
1094    while let Some(node) = stack.pop() {
1095        if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
1096            continue;
1097        }
1098        match node.kind() {
1099            "labeled_statement" => {
1100                if let Some(label) = node.child_by_field_name("label")
1101                    && let Some(text) = node_text(label, source)
1102                {
1103                    labels.insert(text.to_owned(), node.start_position().row);
1104                }
1105            }
1106            "goto_statement" => {
1107                if let Some(label) = node.child_by_field_name("label")
1108                    && let Some(text) = node_text(label, source)
1109                {
1110                    gotos.push((text.to_owned(), node.start_position().row));
1111                }
1112            }
1113            _ => {}
1114        }
1115        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
1116    }
1117    gotos
1118        .into_iter()
1119        .fold((0, 0), |(forward, backward), (name, line)| {
1120            match labels.get(&name).copied() {
1121                Some(target) if line < target => (forward + 1, backward),
1122                Some(target) if target < line => (forward, backward + 1),
1123                _ => (forward, backward),
1124            }
1125        })
1126}
1127
1128fn conditional_block_is_empty(block: Node<'_>) -> bool {
1129    body_relation_for_block(block, Language::Cpp).is_some_and(|relation| {
1130        relation.state == BodyState::Empty
1131            && matches!(
1132                relation.owner_kind,
1133                BodyOwner::IfConsequence | BodyOwner::IfAlternative
1134            )
1135    })
1136}
1137
1138/// Classify one C++ AST node for the statement-level CodeQL parity facts.
1139///
1140/// Every branch is anchored to a tree-sitter node and reads only structured
1141/// fields or values selected from those nodes. The same classifier is used by
1142/// structural construct labels and by the C++ statement relation producer.
1143pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
1144    match node.kind() {
1145        "call_expression" if alloca_is_in_unbounded_loop(node, source) => {
1146            Some(StatementFactKind::AllocaInLoop)
1147        }
1148        "compound_statement" => {
1149            if complex_statement_count(node) > 3 {
1150                Some(StatementFactKind::ComplexBlock)
1151            } else if conditional_block_is_empty(node) {
1152                Some(StatementFactKind::EmptyBlock)
1153            } else {
1154                None
1155            }
1156        }
1157        "if_statement"
1158            if condition_expression(node)
1159                .is_some_and(|condition| logical_operator_count(condition) > 5) =>
1160        {
1161            Some(StatementFactKind::ComplexCondition)
1162        }
1163        "switch_statement" => {
1164            if switch_case_is_long(node) {
1165                Some(StatementFactKind::LongSwitch)
1166            } else if enum_switch_is_missing_case(node, source) {
1167                Some(StatementFactKind::MissingCaseInSwitch)
1168            } else {
1169                None
1170            }
1171        }
1172        "function_definition" => {
1173            let (forward, backward) = goto_counts(node, source);
1174            (forward > 0 && backward > 0 && forward != 1 && backward != 1)
1175                .then_some(StatementFactKind::UseOfGoto)
1176        }
1177        _ => None,
1178    }
1179}
1180
1181/// Capture the source-owned statement rows while the parsed tree is available.
1182/// The rows retain only bounded, file-local facts; semantic CFG joins remain in
1183/// the query layer and are never rebuilt here.
1184pub(crate) fn capture_statement_fact_rows(
1185    node: Node<'_>,
1186    source: &str,
1187    rows: &mut brokk_bifrost_core::analyzer::statement_facts::CppStatementFactRows,
1188) {
1189    use brokk_bifrost_core::analyzer::statement_facts::{
1190        CppConditionFact, CppEnumFact, CppEnumeratorFact, CppGotoFact, CppLabelFact, CppLoopFact,
1191        CppStatementFact, CppSwitchCaseFact, CppSwitchFact,
1192    };
1193
1194    let range = node_range(node);
1195    if let Some(kind) = statement_fact_kind(node, source) {
1196        rows.statements.push(CppStatementFact { range, kind });
1197    }
1198    if node.kind() == "switch_statement" {
1199        let long_case = switch_case_is_long(node);
1200        let missing_case = enum_switch_is_missing_case(node, source);
1201        if long_case
1202            && !rows
1203                .statements
1204                .iter()
1205                .any(|fact| fact.range == range && fact.kind == StatementFactKind::LongSwitch)
1206        {
1207            rows.statements.push(CppStatementFact {
1208                range,
1209                kind: StatementFactKind::LongSwitch,
1210            });
1211        }
1212        if missing_case
1213            && !rows.statements.iter().any(|fact| {
1214                fact.range == range && fact.kind == StatementFactKind::MissingCaseInSwitch
1215            })
1216        {
1217            rows.statements.push(CppStatementFact {
1218                range,
1219                kind: StatementFactKind::MissingCaseInSwitch,
1220            });
1221        }
1222        let cases = node
1223            .child_by_field_name("body")
1224            .map(switch_cases)
1225            .unwrap_or_default();
1226        for case in &cases {
1227            let value = case
1228                .child_by_field_name("value")
1229                .and_then(expression_name_node)
1230                .and_then(|name| node_text(name, source))
1231                .map(str::to_owned);
1232            rows.switch_cases.push(CppSwitchCaseFact {
1233                range: node_range(*case),
1234                value,
1235            });
1236        }
1237        rows.switches.push(CppSwitchFact {
1238            range,
1239            case_count: cases.len(),
1240            default_present: switch_has_default(node),
1241            long_case,
1242            missing_enum_case: missing_case,
1243        });
1244    }
1245    if node.kind() == "if_statement"
1246        && let Some(condition) = condition_expression(node)
1247    {
1248        rows.conditions.push(CppConditionFact {
1249            range: node_range(condition),
1250            logical_operator_count: logical_operator_count(condition),
1251        });
1252    }
1253    if matches!(
1254        node.kind(),
1255        "for_statement" | "for_range_loop" | "while_statement" | "do_statement"
1256    ) {
1257        let body_statement_count = node
1258            .child_by_field_name("body")
1259            .map(direct_code_child_count)
1260            .unwrap_or_default();
1261        let variable = if matches!(node.kind(), "for_statement" | "for_range_loop") {
1262            loop_variable(node, source).map(str::to_owned)
1263        } else {
1264            None
1265        };
1266        rows.loops.push(CppLoopFact {
1267            range,
1268            variable,
1269            body_statement_count,
1270            tightly_bounded: node.kind() == "for_statement"
1271                && for_loop_has_small_literal_bound(node, source),
1272        });
1273    }
1274    if node.kind() == "labeled_statement"
1275        && let Some(label) = node.child_by_field_name("label")
1276        && let Some(name) = node_text(label, source)
1277    {
1278        rows.labels.push(CppLabelFact {
1279            range,
1280            name: name.to_owned(),
1281        });
1282    }
1283    if node.kind() == "goto_statement"
1284        && let Some(label) = node.child_by_field_name("label")
1285        && let Some(name) = node_text(label, source)
1286    {
1287        let forward = goto_target_line(node, name, source)
1288            .is_some_and(|target| node.start_position().row < target);
1289        rows.gotos.push(CppGotoFact {
1290            range,
1291            target: name.to_owned(),
1292            forward,
1293        });
1294    }
1295    if node.kind() == "enum_specifier" {
1296        let name = node
1297            .child_by_field_name("name")
1298            .and_then(|name| node_text(name, source))
1299            .map(str::to_owned);
1300        rows.enums.push(CppEnumFact { range, name });
1301        let mut stack = vec![node];
1302        while let Some(current) = stack.pop() {
1303            if current != node && current.kind() == "enum_specifier" {
1304                continue;
1305            }
1306            if current.kind() == "enumerator"
1307                && let Some(name) = current.child_by_field_name("name")
1308                && let Some(text) = node_text(name, source)
1309            {
1310                rows.enumerators.push(CppEnumeratorFact {
1311                    range: node_range(current),
1312                    enum_range: range,
1313                    name: text.to_owned(),
1314                });
1315            }
1316            stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
1317        }
1318    }
1319}
1320
1321fn goto_target_line(goto: Node<'_>, target: &str, source: &str) -> Option<usize> {
1322    let mut function = goto;
1323    while let Some(parent) = function.parent() {
1324        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1325            function = parent;
1326            break;
1327        }
1328        function = parent;
1329    }
1330    let mut stack = vec![function];
1331    while let Some(node) = stack.pop() {
1332        if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
1333            continue;
1334        }
1335        if node.kind() == "labeled_statement"
1336            && let Some(label) = node.child_by_field_name("label")
1337            && node_text(label, source) == Some(target)
1338        {
1339            return Some(node.start_position().row);
1340        }
1341        stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
1342    }
1343    None
1344}
1345
1346/// Whether tree-sitter recovered `.field = value` as a declaration child pair:
1347/// an `ERROR` containing only the anonymous dot immediately followed by an
1348/// `init_declarator` whose declarator is a real identifier.
1349///
1350/// This is intentionally narrower than rejecting declarators below `ERROR` in
1351/// general: macro-decorated declarations and ordinary comma-separated initialized
1352/// declarations also use recovery nodes and remain real declarations.
1353pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
1354    if node.kind() != "init_declarator" {
1355        return false;
1356    }
1357    let Some(identifier) = node.child_by_field_name("declarator") else {
1358        return false;
1359    };
1360    if identifier.kind() != "identifier" || identifier.is_missing() {
1361        return false;
1362    }
1363    let Some(previous) = node.prev_named_sibling() else {
1364        return false;
1365    };
1366    if previous.kind() != "ERROR"
1367        || previous.named_child_count() != 0
1368        || previous.child_count() != 1
1369        || previous.end_byte() != node.start_byte()
1370    {
1371        return false;
1372    }
1373    previous.child(0).is_some_and(|child| {
1374        child.kind() == "."
1375            && !child.is_named()
1376            && child.start_byte() == previous.start_byte()
1377            && child.end_byte() == previous.end_byte()
1378    })
1379}
1380
1381use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
1382
1383/// The declared name of a `namespace_definition`.
1384///
1385/// `namespace a::b { }` spells a `nested_namespace_specifier` that nests to
1386/// the right, so the innermost specifier's last child is the terminal segment.
1387/// An anonymous namespace has no `name` field at all and stays unnamed.
1388fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
1389    let mut current = namespace.child_by_field_name("name")?;
1390    while current.kind() == "nested_namespace_specifier" {
1391        current = current.named_child(current.named_child_count().checked_sub(1)?)?;
1392    }
1393    Some(current)
1394}
1395
1396pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
1397    let mut current = declarator;
1398    loop {
1399        match current.kind() {
1400            "identifier"
1401            | "field_identifier"
1402            | "namespace_identifier"
1403            | "type_identifier"
1404            | "destructor_name"
1405            | "operator_name"
1406            | "operator_cast"
1407            | "primitive_type" => return Some(current),
1408            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1409            "dependent_name" => current = first_named_child(current)?,
1410            "template_function" | "template_method" | "template_type" => {
1411                current = current.child_by_field_name("name")?;
1412            }
1413            "function_declarator"
1414            | "pointer_declarator"
1415            | "pointer_type_declarator"
1416            | "macro_modifier_declarator"
1417            | "variadic_declarator"
1418            | "array_declarator"
1419            | "init_declarator" => current = current.child_by_field_name("declarator")?,
1420            "attributed_declarator" => current = first_named_child(current)?,
1421            "reference_declarator" | "parenthesized_declarator" => {
1422                current = first_named_child(current)?;
1423            }
1424            _ => return None,
1425        }
1426    }
1427}
1428
1429/// Grammar steps for the shared call-callee spine. A value expression has no step.
1430pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1431    use CalleeSpineStep::{Field, FieldOrOnlyChild, Name, OnlyChild};
1432    match kind {
1433        "identifier"
1434        | "field_identifier"
1435        | "type_identifier"
1436        | "operator_name"
1437        | "operator_cast"
1438        | "destructor_name"
1439        | "literal_operator_name"
1440        | "primitive_type" => Some(Name),
1441        "qualified_identifier"
1442        | "scoped_identifier"
1443        | "scoped_type_identifier"
1444        | "template_function"
1445        | "template_method"
1446        | "template_type" => Some(Field("name")),
1447        "dependent_name" => Some(OnlyChild),
1448        "field_expression" => Some(Field("field")),
1449        "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1450        _ => None,
1451    }
1452}
1453
1454fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1455    let mut current = expression;
1456    loop {
1457        match current.kind() {
1458            "identifier"
1459            | "field_identifier"
1460            | "namespace_identifier"
1461            | "type_identifier"
1462            | "destructor_name"
1463            | "operator_name"
1464            | "primitive_type"
1465            | "this" => return Some(current),
1466            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1467            "dependent_name" => current = first_named_child(current)?,
1468            "template_function" | "template_method" | "template_type" => {
1469                current = current.child_by_field_name("name")?;
1470            }
1471            "field_expression" => current = current.child_by_field_name("field")?,
1472            "call_expression" => current = current.child_by_field_name("function")?,
1473            "new_expression" => current = current.child_by_field_name("type")?,
1474            "parenthesized_expression" => current = first_named_child(current)?,
1475            _ => return declarator_name_node(current),
1476        }
1477    }
1478}
1479
1480fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1481    if function.kind() != "qualified_identifier" {
1482        return;
1483    }
1484    if let Some(scope) = function.child_by_field_name("scope") {
1485        attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1486    }
1487}
1488
1489fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1490    loop {
1491        if node.kind() == "qualified_identifier" {
1492            return Some(node);
1493        }
1494        node = node
1495            .child_by_field_name("declarator")
1496            .or_else(|| node.child_by_field_name("name"))
1497            .or_else(|| first_named_child(node))?;
1498    }
1499}
1500
1501fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1502    node.child_by_field_name("declarator")
1503        .and_then(qualified_declarator_node)
1504        .and_then(|qualified| qualified.child_by_field_name("scope"))
1505}
1506
1507pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1508    node_text(scope, source) == node_text(name, source)
1509}
1510
1511fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1512    node.child_by_field_name("declarator")
1513        .and_then(qualified_declarator_node)
1514        .and_then(|qualified| {
1515            Some((
1516                expression_name_node(qualified.child_by_field_name("scope")?)?,
1517                expression_name_node(last_named_field_child(qualified, "name")?)?,
1518            ))
1519        })
1520        .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1521}
1522
1523fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1524    if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1525        return None;
1526    }
1527    unquoted_string_content_span(node)
1528}
1529
1530/// The names of every function-like macro this translation unit defines, read
1531/// from `preproc_function_def` name fields.
1532///
1533/// The walk is iterative and descends only through preprocessor conditional
1534/// blocks, which is where a definition can nest. A `#define` written inside a
1535/// function body is not collected; that is a known and stated boundary, not an
1536/// approximation of one.
1537fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1538    let mut names = HashSet::default();
1539    let mut stack = vec![root];
1540    while let Some(node) = stack.pop() {
1541        for child in named_children_iter(node) {
1542            match child.kind() {
1543                "preproc_function_def" => {
1544                    if let Some(name) = child.child_by_field_name("name") {
1545                        names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1546                    }
1547                }
1548                "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1549                | "preproc_elifdef" => stack.push(child),
1550                _ => {}
1551            }
1552        }
1553    }
1554    names
1555}
1556
1557/// Return the one identifier-bearing node that spells a member in a C++
1558/// `field_expression`.
1559///
1560/// The grammar permits the `field` child to be a qualified name, a dependent
1561/// name, a template method, or a destructor name. `expression_name_node`
1562/// already follows those wrappers to their terminal name. Walking upward from
1563/// the candidate and comparing that terminal node keeps receiver identifiers,
1564/// declaration names, field-designator labels, and path prefixes out of the
1565/// member role without looking at source text.
1566fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1567    if !matches!(
1568        node.kind(),
1569        "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1570    ) {
1571        return None;
1572    }
1573
1574    let mut current = node;
1575    while let Some(parent) = current.parent() {
1576        if parent.kind() == "field_expression" {
1577            let field = parent.child_by_field_name("field")?;
1578            return expression_name_node(field)
1579                .filter(|name| name.id() == node.id())
1580                .map(|_| OccurrenceRole::MemberPosition);
1581        }
1582
1583        // These are the only named wrappers that can occur between the
1584        // terminal field name and its owning field expression.
1585        if matches!(
1586            parent.kind(),
1587            "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1588        ) {
1589            current = parent;
1590        } else {
1591            return None;
1592        }
1593    }
1594    None
1595}
1596
1597/// What declares a C or C++ binder.
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599enum CppBinderOwner<'tree> {
1600    /// A parameter of a function definition, a lambda or a `catch` clause.
1601    Parameter,
1602    /// A local declaration; the node is the declarator the name completes.
1603    Local(Node<'tree>),
1604    /// A range-based `for` variable; the node is the loop.
1605    RangeFor(Node<'tree>),
1606    /// A structured binding; the node is the binding declarator.
1607    StructuredBinding(Node<'tree>),
1608    /// A lambda init-capture (`[x = y]`).
1609    InitCapture,
1610}
1611
1612/// Declarator wrappers that carry a name inward through their `declarator`
1613/// field or their single named child.
1614const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1615    "pointer_declarator",
1616    "array_declarator",
1617    "reference_declarator",
1618    "parenthesized_declarator",
1619    "attributed_declarator",
1620    "variadic_declarator",
1621];
1622
1623/// What declares `node` as a C or C++ variable, or `None` when it is not a
1624/// variable binder.
1625///
1626/// The name is found by walking up the declarator chain. A name that reaches
1627/// a function declarator without passing a pointer or reference declarator
1628/// names a function; one that does names a function pointer variable. A
1629/// parameter binds only in a function definition, a lambda or a `catch`
1630/// clause; a prototype's parameter names bind nothing. A file-scope or
1631/// member declaration names a member or a global, not a lexical binding.
1632fn cpp_binder_owner<'tree>(
1633    node: Node<'tree>,
1634    parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1635    mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1636) -> Option<CppBinderOwner<'tree>> {
1637    if node.kind() != "identifier" {
1638        return None;
1639    }
1640    let mut child = node;
1641    let mut current = parent(node)?;
1642    let mut indirect = false;
1643    loop {
1644        // This field lookup scans earlier siblings. Generated C initializer
1645        // lists can be enormous, so only ask for fields on binder-shaped nodes.
1646        match current.kind() {
1647            kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1648                let field = field_name(current, child);
1649                if field.is_some() && field != Some("declarator") {
1650                    return None;
1651                }
1652                indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1653            }
1654            "function_declarator" => {
1655                let field = field_name(current, child);
1656                if field != Some("declarator") || !indirect {
1657                    return None;
1658                }
1659            }
1660            "init_declarator" => {
1661                let field = field_name(current, child);
1662                if field != Some("declarator") {
1663                    return None;
1664                }
1665            }
1666            "declaration" => {
1667                let field = field_name(current, child);
1668                if field != Some("declarator") {
1669                    return None;
1670                }
1671                let local = std::iter::successors(parent(current), |node| parent(*node))
1672                    .take_while(|node| {
1673                        !matches!(
1674                            node.kind(),
1675                            "translation_unit"
1676                                | "field_declaration_list"
1677                                | "declaration_list"
1678                                | "namespace_definition"
1679                        )
1680                    })
1681                    .any(|node| {
1682                        matches!(
1683                            node.kind(),
1684                            "compound_statement" | "for_statement" | "condition_clause"
1685                        )
1686                    });
1687                // The name's scope starts at the end of its declarator, before
1688                // any initializer.
1689                let declarator = if child.kind() == "init_declarator" {
1690                    child.child_by_field_name("declarator")?
1691                } else {
1692                    child
1693                };
1694                return local.then_some(CppBinderOwner::Local(declarator));
1695            }
1696            "parameter_declaration"
1697            | "optional_parameter_declaration"
1698            | "variadic_parameter_declaration" => {
1699                let field = field_name(current, child);
1700                if field != Some("declarator") {
1701                    return None;
1702                }
1703                let parameter_parent = parent(current)?;
1704                let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1705                    // The explicit-object wrapper owns exactly `this` and this
1706                    // parameter; only then may its parent supply the list.
1707                    if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1708                        || parameter_parent.named_child(1) != Some(current)
1709                    {
1710                        return None;
1711                    }
1712                    parent(parameter_parent)?
1713                } else {
1714                    parameter_parent
1715                };
1716                if list.kind() != "parameter_list" {
1717                    return None;
1718                }
1719                let owner = parent(list)?;
1720                let binds = match owner.kind() {
1721                    "catch_clause" => true,
1722                    "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1723                        .is_some_and(|lambda| {
1724                            lambda.kind() == "lambda_expression"
1725                                && lambda.child_by_field_name("declarator") == Some(owner)
1726                        }),
1727                    "function_declarator" => {
1728                        let mut declarator = owner;
1729                        loop {
1730                            let Some(up) = parent(declarator) else {
1731                                break false;
1732                            };
1733                            if up.kind() == "function_definition" {
1734                                break field_name_in_parent(up, declarator) == Some("declarator");
1735                            }
1736                            if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1737                                break false;
1738                            }
1739                            declarator = up;
1740                        }
1741                    }
1742                    _ => false,
1743                };
1744                return binds.then_some(CppBinderOwner::Parameter);
1745            }
1746            "for_range_loop" => {
1747                let field = field_name(current, child);
1748                return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1749            }
1750            "structured_binding_declarator" => {
1751                return Some(CppBinderOwner::StructuredBinding(current));
1752            }
1753            "lambda_capture_initializer" => {
1754                let field = field_name(current, child);
1755                return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1756            }
1757            _ => return None,
1758        }
1759        child = current;
1760        current = parent(current)?;
1761    }
1762}
1763
1764/// Whether `node` declares a C or C++ variable, climbing through the walk's
1765/// parent index: asking `Node::parent` re-descends from the root, which is
1766/// quadratic over a large file.
1767fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1768    cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1769}
1770
1771/// The binding one C or C++ binder token introduces, and the interval it is
1772/// in effect over.
1773///
1774/// A parameter or init-capture is in effect over its whole function, lambda
1775/// or catch clause. A local is in effect from the end of its declarator: C
1776/// and C++ start a name's scope there, before its initializer, so in
1777/// `int x = x;` the right side reads the new `x`. A condition declaration is
1778/// therefore in effect over the rest of its `if`, including `else`. A
1779/// range-`for` variable is in effect over the loop body, and a structured
1780/// binding from the end of its binding declarator.
1781fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1782    let binding = |kind, hoisting, activation| {
1783        Some(BindingActivation {
1784            kind,
1785            hoisting,
1786            activation,
1787        })
1788    };
1789    let after = |node: Node<'_>| Range {
1790        start_byte: node.end_byte(),
1791        end_byte: scope.end_byte,
1792        start_line: node.end_position().row + 1,
1793        end_line: scope.end_line,
1794    };
1795    match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1796        CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1797            binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1798        }
1799        CppBinderOwner::Local(declarator) => binding(
1800            BindingKind::Local,
1801            HoistingClass::SourceOrder,
1802            after(declarator),
1803        ),
1804        CppBinderOwner::RangeFor(loop_node) => binding(
1805            BindingKind::LoopVariable,
1806            HoistingClass::DeclaredHead,
1807            node_range(loop_node.child_by_field_name("body")?),
1808        ),
1809        CppBinderOwner::StructuredBinding(declarator) => binding(
1810            BindingKind::Local,
1811            HoistingClass::SourceOrder,
1812            after(declarator),
1813        ),
1814    }
1815}
1816
1817impl StructuralSpec for CppStructuralSpec {
1818    fn language(&self) -> Language {
1819        Language::Cpp
1820    }
1821
1822    fn supports_boolean_literal_value(&self) -> bool {
1823        true
1824    }
1825
1826    fn supports_string_literal_value(&self) -> bool {
1827        true
1828    }
1829
1830    fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1831        matches!(node.kind(), "string_literal" | "raw_string_literal")
1832            && cpp_string_literal_value(node, source).is_none()
1833    }
1834
1835    fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1836        cpp_string_literal_value(node, source)
1837    }
1838
1839    fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1840        CPP_KIND_TABLE
1841    }
1842
1843    fn generator_construct(
1844        &self,
1845        node: Node<'_>,
1846        _kind: NormalizedKind,
1847        source: &str,
1848    ) -> Option<&'static str> {
1849        statement_fact_kind(node, source).map(StatementFactKind::construct)
1850    }
1851
1852    fn refine_kind(
1853        &self,
1854        node: Node<'_>,
1855        kind: NormalizedKind,
1856        enclosing: Option<NormalizedKind>,
1857        source: &str,
1858        _context: &CallSiteContext,
1859    ) -> NormalizedKind {
1860        if kind == NormalizedKind::Function
1861            && (enclosing == Some(NormalizedKind::Class)
1862                || scoped_function_definition(node).is_some())
1863        {
1864            if is_constructor_definition(node, source) {
1865                NormalizedKind::Constructor
1866            } else {
1867                NormalizedKind::Method
1868            }
1869        } else {
1870            kind
1871        }
1872    }
1873
1874    fn supports_kind(&self, kind: NormalizedKind) -> bool {
1875        matches!(
1876            kind,
1877            NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1878        ) || self
1879            .kind_table()
1880            .iter()
1881            .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1882    }
1883
1884    fn supports_role(&self, role: Role) -> bool {
1885        !matches!(
1886            role,
1887            Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1888        )
1889    }
1890
1891    fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1892        CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1893    }
1894
1895    /// A `new_expression` is a constructor call, and a call whose callee names
1896    /// a function-like macro of this translation unit has an argument list
1897    /// that belongs to the macro, not to the callable that finally runs
1898    /// (#1478). `CALL_TWICE(2)` parses as an ordinary call of two source
1899    /// arguments even when the expansion calls a three-parameter callable, so
1900    /// the honest answer is unknown coverage and no argument rows at all —
1901    /// never a fabricated list an exact-arity assertion could read as clean.
1902    ///
1903    /// The boundary is deliberate: only macros defined in this file are
1904    /// known here, because that is what this file's parse tree contains. A
1905    /// macro defined in an included header leaves the site exact, which is
1906    /// the same answer the analyzer gives today.
1907    fn call_site_facts(
1908        &self,
1909        node: Node<'_>,
1910        source: &str,
1911        context: &CallSiteContext,
1912    ) -> Option<CallSiteFacts> {
1913        if node.kind() == "new_expression" {
1914            return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1915        }
1916        let callee = node.child_by_field_name("function")?;
1917        (callee.kind() == "identifier"
1918            && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1919        .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1920    }
1921
1922    fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1923        static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1924            .supported(OccurrenceRole::MemberPosition)
1925            .supported(OccurrenceRole::ValueReference)
1926            .supported(OccurrenceRole::Binder);
1927        &SUPPORT
1928    }
1929
1930    fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1931        // C and C++ derive scopes and binding intervals; import binders and
1932        // the package clause are not derived. The call seams report
1933        // per-candidate callable applicability (#1478 M3).
1934        static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1935            .supported(EnvironmentAxis::Scopes)
1936            .supported(EnvironmentAxis::BindingIntervals)
1937            .supported(EnvironmentAxis::CallableApplicability);
1938        &SUPPORT
1939    }
1940
1941    /// An `if` statement is a scope: a C++ condition or init-statement
1942    /// declaration is in effect in both branches and nowhere after.
1943    fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1944        if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1945            ScopeFormation::BindingScope
1946        } else {
1947            default_scope_formation(kind)
1948        }
1949    }
1950
1951    fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1952        cpp_binding_activation(binder, scope)
1953    }
1954
1955    fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1956        &CPP_MATERIALIZATION_SUPPORT
1957    }
1958
1959    fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1960        &INVERSE_REFERENCE_EDGE_SUPPORT
1961    }
1962
1963    fn identity_route_support(&self) -> &IdentityRouteSupport {
1964        // C++'s occurrence adapter is shallow, so it claims no path axes. Its
1965        // declaration layer keeps prototype/body occurrences distinct, which
1966        // the physical-grouping axis carries, and the analyzer now labels each
1967        // of those occurrences a head or a body and pairs a prototype with the
1968        // out-of-line callable body that defines it, so the
1969        // declaration-definition peer relation is supplied as typed rows with
1970        // the body occurrence's own range as provenance (#1650). Classes stay
1971        // outside that relation: a same-file forward declaration is not a
1972        // distinct occurrence, and across files a class carries no linkage
1973        // evidence to pair on.
1974        static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1975            .supported_axis(IdentityAxis::CanonicalIdentity)
1976            .supported_axis(IdentityAxis::PhysicalGrouping)
1977            .supported_relation(RouteHopKind::NestedOwner)
1978            .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1979        &SUPPORT
1980    }
1981
1982    fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1983        if let Some(role) = cpp_member_position(node) {
1984            sink.occurrence_role(node, role);
1985        } else if cpp_is_binder(node, sink.parents()) {
1986            sink.occurrence_role(node, OccurrenceRole::Binder);
1987        }
1988
1989        match kind {
1990            NormalizedKind::Call => {
1991                let function_field = if node.kind() == "new_expression" {
1992                    "type"
1993                } else {
1994                    "function"
1995                };
1996                if let Some(function) = node.child_by_field_name(function_field) {
1997                    attach_terminal_callee(
1998                        sink,
1999                        function,
2000                        terminal_callee_leaf(function, callee_spine_step),
2001                    );
2002                    // The callee token of a free-function call is a plain value
2003                    // read, and it is the only occurrence the call site leaves
2004                    // behind when the function is not indexed (#3466). Member
2005                    // calls are excluded: their terminal field is already the
2006                    // member-position occurrence, and classifying it twice would
2007                    // report one call site as two. A composite callee
2008                    // expression names its own callee at an inner node
2009                    // (`f()()`), so it is excluded too.
2010                    if node.kind() == "call_expression"
2011                        && !matches!(function.kind(), "field_expression" | "call_expression")
2012                        && let Some(callee) = expression_name_node(function)
2013                        && self.kind_table().iter().any(|(node_kind, kind)| {
2014                            *node_kind == callee.kind() && self.should_extract(callee, *kind)
2015                        })
2016                    {
2017                        sink.occurrence_role(callee, OccurrenceRole::ValueReference);
2018                    }
2019                    if function.kind() == "field_expression"
2020                        && let Some(argument) = function.child_by_field_name("argument")
2021                    {
2022                        attach_role_with_derived_name(
2023                            sink,
2024                            Role::Receiver,
2025                            argument,
2026                            expression_name_node,
2027                        );
2028                    }
2029                    attach_qualified_scope_receiver(sink, function);
2030                }
2031                if let Some(arguments) = node.child_by_field_name("arguments") {
2032                    attach_positional_argument_roles(sink, arguments, expression_name_node);
2033                }
2034            }
2035            NormalizedKind::FieldAccess => {
2036                if let Some(field) = node.child_by_field_name("field") {
2037                    attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
2038                    if let Some(name) = expression_name_node(field) {
2039                        sink.set_name(name);
2040                    }
2041                }
2042                if let Some(argument) = node.child_by_field_name("argument") {
2043                    attach_role_with_derived_name(
2044                        sink,
2045                        Role::Object,
2046                        argument,
2047                        expression_name_node,
2048                    );
2049                }
2050            }
2051            NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
2052                if let Some(name) = node
2053                    .child_by_field_name("declarator")
2054                    .and_then(declarator_name_node)
2055                {
2056                    sink.set_name(name);
2057                }
2058            }
2059            NormalizedKind::Module => {
2060                if let Some(name) = namespace_name_node(node) {
2061                    sink.set_name(name);
2062                }
2063            }
2064            NormalizedKind::Class | NormalizedKind::Declaration => {
2065                if let Some(name) = node
2066                    .child_by_field_name("name")
2067                    .and_then(declarator_name_node)
2068                    .or_else(|| node.child_by_field_name("name"))
2069                {
2070                    sink.set_name(name);
2071                }
2072            }
2073            NormalizedKind::Assignment => match node.kind() {
2074                "init_declarator" => {
2075                    if let Some(declarator) = node.child_by_field_name("declarator") {
2076                        attach_role_with_derived_name(
2077                            sink,
2078                            Role::Left,
2079                            declarator,
2080                            declarator_name_node,
2081                        );
2082                        if let Some(name) = declarator_name_node(declarator) {
2083                            sink.set_name(name);
2084                        }
2085                    }
2086                    if let Some(value) = node.child_by_field_name("value") {
2087                        attach_role_with_derived_name(
2088                            sink,
2089                            Role::Right,
2090                            value,
2091                            expression_name_node,
2092                        );
2093                    }
2094                }
2095                "assignment_expression" => {
2096                    if let Some(left) = node.child_by_field_name("left") {
2097                        attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
2098                    }
2099                    if let Some(right) = node.child_by_field_name("right") {
2100                        attach_role_with_derived_name(
2101                            sink,
2102                            Role::Right,
2103                            right,
2104                            expression_name_node,
2105                        );
2106                    }
2107                }
2108                _ => {}
2109            },
2110            NormalizedKind::Import => {
2111                if let Some(path) = node.child_by_field_name("path") {
2112                    if let Some(name) = unquoted_include_span(path) {
2113                        sink.role_named_span(Role::Module, path, name);
2114                    } else {
2115                        attach_role_with_derived_name(
2116                            sink,
2117                            Role::Module,
2118                            path,
2119                            expression_name_node,
2120                        );
2121                    }
2122                }
2123            }
2124            NormalizedKind::Identifier => match expression_name_node(node) {
2125                Some(name) => sink.set_name(name),
2126                None => sink.set_name(node),
2127            },
2128            _ => {}
2129        }
2130    }
2131}
2132
2133#[cfg(test)]
2134mod tests {
2135    use super::CPP_STRUCTURAL_SPEC;
2136    use super::cpp_binder_owner;
2137    use super::cpp_member_position;
2138    use super::field_name_in_parent;
2139    use super::node_range;
2140    use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
2141    use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
2142    use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
2143    use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
2144    use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
2145    use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
2146    use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
2147    use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
2148    use brokk_bifrost_core::hash::HashMap;
2149    use brokk_bifrost_test_macros::hardwon;
2150    use tree_sitter::Parser;
2151
2152    #[test]
2153    #[hardwon(
2154        "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: unrelated comparisons suppress alloca-in-loop"
2155    )]
2156    fn alloca_bound_must_constrain_loop_variable() {
2157        for (condition, expected) in [
2158            ("i < n && enabled < 1", true),
2159            ("i < n || i < 1", true),
2160            ("i < 1", false),
2161            ("1 > i && enabled", false),
2162        ] {
2163            let source = format!(
2164                "void f(int n, int enabled) {{ for (int i=0; {condition}; ++i) {{ alloca(16); }} }}"
2165            );
2166            let mut parser = Parser::new();
2167            parser
2168                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2169                .unwrap();
2170            let tree = parser.parse(&source, None).unwrap();
2171            let mut stack = vec![tree.root_node()];
2172            let mut actual = None;
2173            while let Some(node) = stack.pop() {
2174                if node.kind() == "call_expression" {
2175                    actual = Some(super::alloca_is_in_unbounded_loop(node, &source));
2176                }
2177                stack.extend(super::named_children_iter(node));
2178            }
2179            assert_eq!(actual, Some(expected), "{source}");
2180        }
2181    }
2182
2183    #[test]
2184    #[hardwon(
2185        "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: integer switches borrow unrelated enum cases"
2186    )]
2187    fn integer_switch_is_not_an_enum_switch() {
2188        let source = "enum E { A, B }; void f(int value) { switch(value) { case A: break; } }";
2189        let mut parser = Parser::new();
2190        parser
2191            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2192            .unwrap();
2193        let tree = parser.parse(source, None).unwrap();
2194        let mut stack = vec![tree.root_node()];
2195        let mut checked = false;
2196        while let Some(node) = stack.pop() {
2197            if node.kind() == "switch_statement" {
2198                checked = true;
2199                assert!(!super::enum_switch_is_missing_case(node, source));
2200            }
2201            stack.extend(super::named_children_iter(node));
2202        }
2203        assert!(checked);
2204    }
2205
2206    #[test]
2207    #[hardwon(
2208        "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: enum coverage needs scoped local declaration evidence"
2209    )]
2210    fn enum_switch_uses_local_type_evidence() {
2211        for (source, expected) in [
2212            (
2213                "enum E { A,B }; void f(E e) { switch(e) {case A:break;} }",
2214                vec![true],
2215            ),
2216            (
2217                "enum E { A,B }; void f() { E e=A; switch(e) {case A:break;} }",
2218                vec![true],
2219            ),
2220            (
2221                "enum class E { A,B }; void f(E e) { switch(e) {case E::A:break;} }",
2222                vec![true],
2223            ),
2224            (
2225                "namespace ns { enum class E { A,B }; } void f(ns::E e) { switch(e) {case ns::E::A:break;} }",
2226                vec![true],
2227            ),
2228            (
2229                "enum E { A,B }; struct S { E state; }; void f(S s) { switch(s.state) {case A:break;} }",
2230                vec![true],
2231            ),
2232            (
2233                "enum E { A,B }; struct S { E state; }; void f() { S s; switch(s.state) {case A:break;} }",
2234                vec![true],
2235            ),
2236            (
2237                "enum E { A,B }; void f(E e) { { int e=0; switch(e) {case A:break;} } switch(e) {case A:break;} }",
2238                vec![false, true],
2239            ),
2240            (
2241                "enum E { A,B }; void f(E e) { { int e=0; } switch(e) {case A:break;} }",
2242                vec![true],
2243            ),
2244            (
2245                "enum E { A,B }; void f() { switch(e) {case A:break;} E e=A; }",
2246                vec![false],
2247            ),
2248            (
2249                "enum E { A,B }; using Alias=E; void f(Alias e) { switch(e) {case A:break;} }",
2250                vec![false],
2251            ),
2252            (
2253                "enum E { A,B }; void f(E e) { switch(e==A) {case A:break;} }",
2254                vec![false],
2255            ),
2256            (
2257                "enum E { A,B }; E e; void f() { switch(e) {case A:break;} }",
2258                vec![false],
2259            ),
2260            (
2261                "enum E { A,B }; void f(E &e) { switch(e) {case A:break;} }",
2262                vec![true],
2263            ),
2264            (
2265                "enum E { A,B }; void f(E *e) { switch(e) {case A:break;} }",
2266                vec![false],
2267            ),
2268            (
2269                "enum E { A,B }; struct S { E state; }; void f(S *s) { switch(s->state) {case A:break;} }",
2270                vec![true],
2271            ),
2272            (
2273                "enum E { A,B }; struct S { int state; }; void f(S s) { switch(s.state) {case A:break;} }",
2274                vec![false],
2275            ),
2276            (
2277                "enum E { A,B }; struct Inner { E state; }; struct Outer { Inner inner; }; void f(Outer s) { switch(s.inner.state) {case A:break;} }",
2278                vec![true],
2279            ),
2280            (
2281                "void f() { enum class E { A,B }; E e=E::A; switch(e) {case E::A:break;} }",
2282                vec![true],
2283            ),
2284            (
2285                "struct S { enum class E { A,B }; E state; }; void f(S s) { switch(s.state) {case S::E::A:break;} }",
2286                vec![true],
2287            ),
2288            (
2289                "namespace n { namespace m { enum class E { A,B }; } } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
2290                vec![true],
2291            ),
2292            (
2293                "enum E { A,B }; void f() { using E=int; E e=0; switch(e) {case A:break;} }",
2294                vec![false],
2295            ),
2296            (
2297                "enum E { A,B }; void f(E e) { { int e(); switch(e) {case A:break;} } }",
2298                vec![false],
2299            ),
2300            (
2301                "enum E { A,B }; void f(E e) { auto g=[e]() { switch(e) {case A:break;} }; }",
2302                vec![false],
2303            ),
2304            (
2305                "enum E { A,B }; void f() { auto g=[](E e) { switch(e) {case A:break;} }; }",
2306                vec![true],
2307            ),
2308            (
2309                "namespace n::m { enum class E { A,B }; } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
2310                vec![true],
2311            ),
2312            (
2313                "void f() { enum E { A,B } e=A; switch(e) {case A:break;} }",
2314                vec![true],
2315            ),
2316            (
2317                "enum E { A,B }; void f(E (&values)[2]) { for (E e : values) { switch(e) {case A:break;} } }",
2318                vec![true],
2319            ),
2320            (
2321                "enum E { A,B }; namespace n { enum E { A,C }; } void f() { using n::E; E e; switch(e) {case A:break;} }",
2322                vec![false],
2323            ),
2324            (
2325                "enum E { A,B }; namespace n { enum E { A }; void f(E); } void n::f(E e) { switch(e) {case n::A:break;} }",
2326                vec![false],
2327            ),
2328            (
2329                "enum E { A,B }; void f(E e) { switch(int e=0; e) {case A:break;} }",
2330                vec![false],
2331            ),
2332            (
2333                "enum E { A,B }; void f() { switch(E e=A; e) {case A:break;} }",
2334                vec![true],
2335            ),
2336            (
2337                "enum E { A,B }; template<typename E> void f(E e) { switch(e) {case A:break;} }",
2338                vec![false],
2339            ),
2340            (
2341                "enum E { A,B }; template<typename... T> void f(T... e) { switch(e) {case A:break;} }",
2342                vec![false],
2343            ),
2344            (
2345                "namespace n { enum E { A,B }; } namespace m { enum E { A }; } namespace r { namespace n=m; void f(n::E e) { switch(e) {case m::A:break;} } }",
2346                vec![false],
2347            ),
2348            (
2349                "struct R { enum E { A,B }; }; struct S { enum E { A }; }; void f() { using R=S; R::E e; switch(e) {case S::A:break;} }",
2350                vec![false],
2351            ),
2352            (
2353                "enum E { A,B }; struct Base { enum E { A }; }; struct S: Base { void f(E e) { switch(e) {case A:break;} } };",
2354                vec![false],
2355            ),
2356            (
2357                "enum E { A,B }; struct S { void f() { E e; switch(e) {case A:break;} } enum E { A }; };",
2358                vec![false],
2359            ),
2360        ] {
2361            let mut parser = Parser::new();
2362            parser
2363                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2364                .unwrap();
2365            let tree = parser.parse(source, None).unwrap();
2366            assert!(
2367                !tree.root_node().has_error(),
2368                "{source}: {}",
2369                tree.root_node().to_sexp()
2370            );
2371            let mut stack = vec![tree.root_node()];
2372            let mut results = Vec::new();
2373            while let Some(node) = stack.pop() {
2374                if node.kind() == "switch_statement" {
2375                    results.push((
2376                        node.start_byte(),
2377                        super::enum_switch_is_missing_case(node, source),
2378                    ));
2379                }
2380                stack.extend(super::named_children_iter(node));
2381            }
2382            results.sort_unstable_by_key(|(start, _)| *start);
2383            assert_eq!(
2384                results.into_iter().map(|(_, v)| v).collect::<Vec<_>>(),
2385                expected,
2386                "{source}"
2387            );
2388        }
2389    }
2390
2391    fn collect_cpp_structural_facts(source: &str) {
2392        collect_structural_rows(source);
2393    }
2394
2395    fn collect_structural_rows(source: &str) -> StructuralFactRows {
2396        let mut parser = Parser::new();
2397        parser
2398            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2399            .expect("C++ grammar");
2400        let tree = parser.parse(source, None).expect("C++ parse");
2401        let root = tree.root_node();
2402        let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
2403        let compiled = CompiledKinds::compile(
2404            &tree_sitter_cpp::LANGUAGE.into(),
2405            super::CPP_STRUCTURAL_SPEC.kind_table(),
2406        );
2407        let mut structural = StructuralFactCollector::new(
2408            &super::CPP_STRUCTURAL_SPEC,
2409            source,
2410            &context,
2411            ParentIndex::new(root),
2412            usize::MAX,
2413            None,
2414        );
2415        let mut source_facts = PrimarySourceFactCollector::new(source);
2416        let mut pending = vec![(root, None)];
2417        while let Some((node, parent)) = pending.pop() {
2418            let mut child_parent = parent;
2419            if node.is_named()
2420                && let Some(raw_kind) = compiled.kind_of(&node)
2421                && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
2422            {
2423                let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
2424                    node,
2425                    raw_kind,
2426                    parent.map(|id| structural.normalized_kind(id)),
2427                    source,
2428                    &context,
2429                );
2430                let id = structural
2431                    .enter(node, kind, parent, &mut source_facts)
2432                    .expect("unbounded C++ structural collector");
2433                let mut sink = structural.role_sink(&mut source_facts);
2434                super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2435                structural
2436                    .accept_roles(id, sink.into_parts())
2437                    .expect("unbounded C++ structural roles");
2438                child_parent = Some(id);
2439            }
2440            for index in (0..node.named_child_count()).rev() {
2441                if let Some(child) = node.named_child(index) {
2442                    pending.push((child, child_parent));
2443                }
2444            }
2445        }
2446        structural
2447            .finish(root)
2448            .expect("C++ structural collection finishes")
2449    }
2450
2451    fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
2452        let mut parser = Parser::new();
2453        parser
2454            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2455            .expect("C++ grammar");
2456        let tree = parser.parse(source, None).expect("C++ parse");
2457        assert!(
2458            !tree.root_node().has_error(),
2459            "{}",
2460            tree.root_node().to_sexp()
2461        );
2462        let mut found = Vec::new();
2463        let mut pending = vec![tree.root_node()];
2464        while let Some(node) = pending.pop() {
2465            if let Some(role) = cpp_member_position(node) {
2466                found.push((
2467                    node.start_byte(),
2468                    &source[node.start_byte()..node.end_byte()],
2469                    role,
2470                ));
2471            }
2472            for index in (0..node.named_child_count()).rev() {
2473                if let Some(child) = node.named_child(index) {
2474                    pending.push(child);
2475                }
2476            }
2477        }
2478        found
2479    }
2480
2481    #[test]
2482    #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
2483    fn binder_classification_scans_large_initializer_lists_linearly() {
2484        use std::fmt::Write as _;
2485
2486        let mut source = String::from("int values[] = {");
2487        for index in 0..4096 {
2488            write!(source, "item_{index},").expect("append generated initializer item");
2489        }
2490        source.push_str("};\n");
2491
2492        let mut parser = Parser::new();
2493        parser
2494            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2495            .expect("C++ grammar");
2496        let tree = parser.parse(&source, None).expect("C++ parse");
2497        assert!(
2498            !tree.root_node().has_error(),
2499            "{}",
2500            tree.root_node().to_sexp()
2501        );
2502        let parents = ParentIndex::new(tree.root_node());
2503        let mut pending = vec![tree.root_node()];
2504        let mut identifiers = 0usize;
2505        let mut sibling_steps = 0usize;
2506        while let Some(node) = pending.pop() {
2507            if node.kind() == "identifier" {
2508                identifiers += 1;
2509                let _ = cpp_binder_owner(
2510                    node,
2511                    |node| parents.parent(node),
2512                    |parent, child| {
2513                        let mut cursor = parent.walk();
2514                        if !cursor.goto_first_child() {
2515                            return None;
2516                        }
2517                        loop {
2518                            sibling_steps += 1;
2519                            if cursor.node() == child {
2520                                return cursor.field_name();
2521                            }
2522                            if !cursor.goto_next_sibling() {
2523                                return None;
2524                            }
2525                        }
2526                    },
2527                );
2528            }
2529            let mut cursor = node.walk();
2530            let children = node.children(&mut cursor).collect::<Vec<_>>();
2531            pending.extend(children.into_iter().rev());
2532        }
2533
2534        assert!(
2535            identifiers >= 4096,
2536            "parsed {identifiers} initializer identifiers"
2537        );
2538        assert!(
2539            sibling_steps <= identifiers * 4,
2540            "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
2541        );
2542    }
2543
2544    #[test]
2545    #[brokk_bifrost_test_macros::hardwon(
2546        "#4407: ABI 15 lambda declarators lost parameter binder identity"
2547    )]
2548    fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
2549        let source = concat!(
2550            "int enclosing(int outer) {\n",
2551            "    return [outer](int lambda) {\n",
2552            "        return [lambda](int nested) {\n",
2553            "            return outer + lambda + nested;\n",
2554            "        }(lambda);\n",
2555            "    }(outer);\n",
2556            "}\n",
2557        );
2558        let mut parser = Parser::new();
2559        parser
2560            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2561            .expect("C++ grammar");
2562        let tree = parser.parse(source, None).expect("C++ parse");
2563        assert!(
2564            !tree.root_node().has_error(),
2565            "{}",
2566            tree.root_node().to_sexp()
2567        );
2568
2569        let identifier_at = |start_byte| {
2570            let mut pending = vec![tree.root_node()];
2571            while let Some(node) = pending.pop() {
2572                if node.kind() == "identifier" && node.start_byte() == start_byte {
2573                    return Some(node);
2574                }
2575                for index in (0..node.named_child_count()).rev() {
2576                    if let Some(child) = node.named_child(index) {
2577                        pending.push(child);
2578                    }
2579                }
2580            }
2581            None
2582        };
2583        let outer_parameter =
2584            identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
2585                .expect("outer parameter");
2586        let lambda_parameter =
2587            identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
2588                .expect("lambda parameter");
2589        let nested_parameter =
2590            identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
2591                .expect("nested parameter");
2592
2593        for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
2594            assert_eq!(
2595                cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
2596                Some(super::CppBinderOwner::Parameter)
2597            );
2598            assert_eq!(
2599                super::cpp_binding_activation(parameter, node_range(tree.root_node()))
2600                    .map(|activation| activation.kind),
2601                Some(BindingKind::Parameter)
2602            );
2603        }
2604
2605        for (start, name) in [
2606            (
2607                source
2608                    .find("return outer + lambda")
2609                    .expect("outer reference")
2610                    + "return ".len(),
2611                "outer",
2612            ),
2613            (
2614                source
2615                    .find("outer + lambda + nested")
2616                    .expect("lambda reference")
2617                    + "outer + ".len(),
2618                "lambda",
2619            ),
2620            (
2621                source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
2622                "nested",
2623            ),
2624        ] {
2625            let reference = identifier_at(start).expect("body reference identifier");
2626            assert_eq!(
2627                cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
2628                None,
2629                "body reference {name:?} was classified as a binder"
2630            );
2631        }
2632    }
2633
2634    #[test]
2635    #[brokk_bifrost_test_macros::hardwon(
2636        "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
2637    )]
2638    fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
2639        let source = concat!(
2640            "struct Visitor {\n",
2641            "    int visit(this Visitor const& self, int ordinary) {\n",
2642            "        return self.value + ordinary;\n",
2643            "    }\n",
2644            "};\n",
2645            "using Callback = void (*)(int nested);\n",
2646        );
2647        let mut parser = Parser::new();
2648        parser
2649            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2650            .expect("C++ grammar");
2651        let tree = parser.parse(source, None).expect("C++ parse");
2652        assert!(
2653            !tree.root_node().has_error(),
2654            "{}",
2655            tree.root_node().to_sexp()
2656        );
2657
2658        let identifier_at = |start: usize, len: usize| {
2659            tree.root_node()
2660                .descendant_for_byte_range(start, start + len)
2661                .filter(|node| node.kind() == "identifier")
2662                .expect("identifier at source span")
2663        };
2664        let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2665        let receiver_body = identifier_at(
2666            source.find("return self").expect("receiver body reference") + "return ".len(),
2667            4,
2668        );
2669        let function_type_parameter =
2670            identifier_at(source.find("nested").expect("function type"), 6);
2671        let parents = ParentIndex::new(tree.root_node());
2672
2673        assert_eq!(receiver.kind(), "identifier");
2674        assert_eq!(
2675            cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2676            Some(super::CppBinderOwner::Parameter)
2677        );
2678        assert!(super::cpp_is_binder(receiver, &parents));
2679        assert_eq!(
2680            super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2681                .map(|activation| activation.kind),
2682            Some(BindingKind::Parameter)
2683        );
2684        assert_eq!(
2685            cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2686            None,
2687            "the receiver read in the body is not another binder"
2688        );
2689        assert!(!super::cpp_is_binder(receiver_body, &parents));
2690        assert_eq!(
2691            cpp_binder_owner(
2692                function_type_parameter,
2693                |node| node.parent(),
2694                field_name_in_parent
2695            ),
2696            None,
2697            "a function-type parameter remains outside callable binding ownership"
2698        );
2699    }
2700
2701    #[test]
2702    fn attributed_declarator_resolves_its_declared_name() {
2703        let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2704        let mut parser = Parser::new();
2705        parser
2706            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2707            .expect("C++ grammar");
2708        let tree = parser.parse(source, None).expect("C++ parse");
2709        assert!(
2710            !tree.root_node().has_error(),
2711            "{}",
2712            tree.root_node().to_sexp()
2713        );
2714
2715        let mut pending = vec![tree.root_node()];
2716        let mut attributed = None;
2717        let mut conversion = None;
2718        while let Some(node) = pending.pop() {
2719            match node.kind() {
2720                "attributed_declarator" => attributed = Some(node),
2721                "operator_cast" => conversion = Some(node),
2722                _ => {}
2723            }
2724            for index in (0..node.named_child_count()).rev() {
2725                if let Some(child) = node.named_child(index) {
2726                    pending.push(child);
2727                }
2728            }
2729        }
2730        let attributed = attributed.expect("attributed declarator");
2731        let name = super::declarator_name_node(attributed).expect("declarator name");
2732
2733        assert_eq!(&source[name.byte_range()], "x");
2734        let conversion = conversion.expect("conversion operator");
2735        assert_eq!(
2736            super::declarator_name_node(conversion).map(|node| node.kind()),
2737            Some("operator_cast")
2738        );
2739    }
2740
2741    #[test]
2742    fn cpp_member_position_is_limited_to_member_access_and_calls() {
2743        let source = concat!(
2744            "struct Widget {\n",
2745            "    int value;\n",
2746            "    int method(int label) {\n",
2747            "        return this->value + label.value + label.method()\n",
2748            "            + label.template convert<int>() + label.ns::member;\n",
2749            "    }\n",
2750            "};\n",
2751            "int build(Widget widget) {\n",
2752            "    Widget result{.value = widget.value};\n",
2753            "    return widget.method(result.value);\n",
2754            "}\n",
2755        );
2756        let found = member_occurrences(source);
2757        let member_texts = found
2758            .iter()
2759            .map(|(_, text, role)| (*text, *role))
2760            .collect::<Vec<_>>();
2761
2762        assert_eq!(
2763            member_texts,
2764            vec![
2765                ("value", OccurrenceRole::MemberPosition),
2766                ("value", OccurrenceRole::MemberPosition),
2767                ("method", OccurrenceRole::MemberPosition),
2768                ("convert", OccurrenceRole::MemberPosition),
2769                ("member", OccurrenceRole::MemberPosition),
2770                ("value", OccurrenceRole::MemberPosition),
2771                ("method", OccurrenceRole::MemberPosition),
2772                ("value", OccurrenceRole::MemberPosition),
2773            ]
2774        );
2775
2776        let at = |needle: &str| source.find(needle).expect("fixture token");
2777        for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2778            let receiver_start = at(receiver);
2779            assert!(
2780                found.iter().all(|(offset, _, _)| *offset != receiver_start),
2781                "receiver was classified: {receiver:?}"
2782            );
2783        }
2784        let non_members = [
2785            at("Widget {"),
2786            at("int value") + "int ".len(),
2787            at("int method") + "int ".len(),
2788            at(".value =") + 1,
2789        ];
2790        for unrelated_start in non_members {
2791            assert!(
2792                found
2793                    .iter()
2794                    .all(|(offset, _, _)| *offset != unrelated_start),
2795                "non-member identifier at byte {unrelated_start} was classified"
2796            );
2797        }
2798    }
2799
2800    #[test]
2801    #[brokk_bifrost_test_macros::hardwon(
2802        "#3466: C and C++ free-function calls published no resolver occurrence"
2803    )]
2804    fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2805        let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2806        assert!(support.is_supported(OccurrenceRole::MemberPosition));
2807        assert!(support.is_supported(OccurrenceRole::ValueReference));
2808        for role in [
2809            OccurrenceRole::ReceiverPosition,
2810            OccurrenceRole::LabelOrKey,
2811            OccurrenceRole::DeclarationName,
2812        ] {
2813            assert!(
2814                !support.is_supported(role),
2815                "unexpected C++ support for {role:?}"
2816            );
2817        }
2818    }
2819
2820    #[test]
2821    fn cpp_string_literals_publish_decoded_values() {
2822        let source = r#"
2823const char *ordinary = "http://example.test/a\n";
2824const char *raw = R"(https://example.test/path)";
2825const char *wide = L"wide";
2826const char character = 'x';
2827const char *mixed = R"(\u0041)" "\u0042";
2828"#;
2829        let rows = collect_structural_rows(source);
2830        let values = rows
2831            .nodes()
2832            .iter()
2833            .filter(|node| node.kind == NormalizedKind::StringLiteral)
2834            .map(|node| node.string_value.clone())
2835            .collect::<Vec<_>>();
2836
2837        assert_eq!(
2838            values,
2839            vec![
2840                Some("http://example.test/a\n".to_owned()),
2841                Some("https://example.test/path".to_owned()),
2842                None,
2843                None,
2844                Some(r"\u0041".to_owned()),
2845                Some("B".to_owned()),
2846            ]
2847        );
2848    }
2849
2850    #[test]
2851    fn cpp_numeric_string_escapes_follow_scalar_and_ascii_boundaries() {
2852        // The grammar selects escape_sequence nodes; Unicode scalar validity
2853        // and the portable ASCII byte policy independently decide their values.
2854        let mut cases = vec![
2855            (
2856                r"\101\x42\u0043\U00000044".to_owned(),
2857                Some("ABCD".to_owned()),
2858            ),
2859            (r"\177".to_owned(), Some("\u{7f}".to_owned())),
2860            (r"\200".to_owned(), None),
2861            (r"\377".to_owned(), None),
2862            (r"\x7f".to_owned(), Some("\u{7f}".to_owned())),
2863            (r"\x80".to_owned(), None),
2864            (r"\x0041".to_owned(), Some("A".to_owned())),
2865            (r"\x007f".to_owned(), Some("\u{7f}".to_owned())),
2866            (r"\x0080".to_owned(), None),
2867            (r"\xfffffffff".to_owned(), None),
2868        ];
2869        for scalar in [
2870            0,
2871            0x7f,
2872            0x80,
2873            0xd7ff,
2874            0xd800,
2875            0xdfff,
2876            0xe000,
2877            0x10ffff,
2878            0x110000,
2879            u32::MAX,
2880        ] {
2881            let expected = char::from_u32(scalar).map(|character| character.to_string());
2882            cases.push((format!("\\U{scalar:08x}"), expected.clone()));
2883            if scalar <= 0xffff {
2884                cases.push((format!("\\u{scalar:04x}"), expected));
2885            }
2886        }
2887        for (escape, expected) in cases {
2888            let source = format!("const char *value = \"{escape}\";");
2889            let mut parser = Parser::new();
2890            parser
2891                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2892                .unwrap();
2893            let tree = parser.parse(&source, None).unwrap();
2894            assert!(!tree.root_node().has_error(), "{source}");
2895            let mut stack = vec![tree.root_node()];
2896            let mut escapes = Vec::new();
2897            while let Some(node) = stack.pop() {
2898                if node.kind() == "escape_sequence" {
2899                    escapes.push(node.byte_range());
2900                }
2901                stack.extend(node.named_children(&mut node.walk()));
2902            }
2903            assert!(!escapes.is_empty(), "grammar did not select {source}");
2904            if escape.starts_with(r"\x00") {
2905                let expected_span = 21..21 + escape.len();
2906                assert_eq!(
2907                    escapes.as_slice(),
2908                    std::slice::from_ref(&expected_span),
2909                    "{source}"
2910                );
2911            }
2912            let rows = collect_structural_rows(&source);
2913            let values: Vec<_> = rows
2914                .nodes()
2915                .iter()
2916                .filter(|node| node.kind == NormalizedKind::StringLiteral)
2917                .map(|node| node.string_value.clone())
2918                .collect();
2919            assert_eq!(values, vec![expected], "{source}, escapes={escapes:?}");
2920        }
2921    }
2922
2923    /// The callee token of a free-function call is the only occurrence a call
2924    /// site leaves behind when the function is not indexed (#3466). Member
2925    /// calls stay member-position-only, and a composite callee expression
2926    /// names its own callee at an inner node.
2927    #[test]
2928    #[brokk_bifrost_test_macros::hardwon(
2929        "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2930    )]
2931    fn cpp_free_function_callees_are_value_references() {
2932        let source = concat!(
2933            "int bound(void) { return 1; }\n",
2934            "struct Widget { int method(void) { return 0; } };\n",
2935            "int go(struct Widget *widget) {\n",
2936            "    return getenv(\"PATH\") != 0\n",
2937            "        + ns::getenv(\"USER\") != 0\n",
2938            "        + bound()\n",
2939            "        + widget->method()\n",
2940            "        + bound()();\n",
2941            "}\n",
2942        );
2943        let mut parser = Parser::new();
2944        parser
2945            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2946            .expect("C++ grammar");
2947        let tree = parser.parse(source, None).expect("C++ parse");
2948        assert!(
2949            !tree.root_node().has_error(),
2950            "{}",
2951            tree.root_node().to_sexp()
2952        );
2953
2954        // Drive the same extraction entry the file walk drives: every fact
2955        // node admitted by the compiled kind table, through a real RoleSink.
2956        // The sink addresses occurrences by fact id, so the test keeps the
2957        // fact id to start byte mapping to name the tokens.
2958        let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2959        let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2960        let mut start_by_fact = HashMap::default();
2961        let mut extractions = Vec::new();
2962        let mut pending = vec![tree.root_node()];
2963        while let Some(node) = pending.pop() {
2964            if node.is_named()
2965                && let Some(kind) = compiled.kind_of(&node)
2966                && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2967            {
2968                start_by_fact.insert(node.id(), node.start_byte());
2969                extractions.push((node.start_byte(), node, kind));
2970            }
2971            for index in (0..node.named_child_count()).rev() {
2972                if let Some(child) = node.named_child(index) {
2973                    pending.push(child);
2974                }
2975            }
2976        }
2977        extractions.sort_by_key(|(start, _, _)| *start);
2978        let mut source_facts =
2979            brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2980        let parents = ParentIndex::new(tree.root_node());
2981        let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2982        for (_, node, kind) in extractions {
2983            CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2984        }
2985        let (_, _, occurrences, stop) = sink.into_parts();
2986        assert_eq!(stop, None);
2987
2988        let roles_at = |needle: &str| {
2989            let start = source.find(needle).expect("fixture token");
2990            occurrences
2991                .iter()
2992                .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2993                .map(|occurrence| occurrence.role)
2994                .collect::<Vec<_>>()
2995        };
2996        // Free-function callees are value reads, including a qualified callee
2997        // whose terminal name is the identifier and the callee of the inner
2998        // call in `bound()()`.
2999        assert_eq!(
3000            roles_at("getenv(\"PATH\")"),
3001            vec![OccurrenceRole::ValueReference]
3002        );
3003        assert_eq!(
3004            roles_at("getenv(\"USER\")"),
3005            vec![OccurrenceRole::ValueReference],
3006            "the terminal name of a qualified callee is the value read"
3007        );
3008        assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
3009        // A member call's terminal field stays one member-position occurrence;
3010        // the call itself adds no second row for the same token.
3011        assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
3012        // The receiver of a member call is neither a value read of the callee
3013        // nor a member position.
3014        assert!(roles_at("widget->").is_empty());
3015    }
3016
3017    /// Tree-sitter recovers the valid `this->operator[](...)` expression as a
3018    /// call whose function is the unfactored `this` node. That node must not
3019    /// receive a value-reference occurrence role.
3020    #[test]
3021    fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
3022        collect_cpp_structural_facts(
3023            "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
3024        );
3025    }
3026}
3027
3028#[cfg(test)]
3029mod grammar_boundary_tests {
3030    use super::*;
3031
3032    #[test]
3033    fn expression_and_pointer_declarator_name_fields() {
3034        let source = "void run() { auto p = new ns::Widget(); receiver.method<int>(); int (*callback)(int) = nullptr; int (ordinary)(int); }";
3035        let tree = crate::diagnostics::parse_cpp_tree(source);
3036        assert!(
3037            !tree.root_node().has_error(),
3038            "{}",
3039            tree.root_node().to_sexp()
3040        );
3041        let mut stack = vec![tree.root_node()];
3042        let mut names = Vec::new();
3043        let mut binders = Vec::new();
3044        while let Some(node) = stack.pop() {
3045            if matches!(node.kind(), "new_expression" | "field_expression") {
3046                let field = if node.kind() == "new_expression" {
3047                    "type"
3048                } else {
3049                    "field"
3050                };
3051                let child = node.child_by_field_name(field).unwrap();
3052                names.push((
3053                    node.kind(),
3054                    child.kind(),
3055                    expression_name_node(node).map(|n| &source[n.byte_range()]),
3056                ));
3057            }
3058            if node.kind() == "identifier"
3059                && let Some(name @ ("callback" | "ordinary")) = node_text(node, source)
3060            {
3061                let wrapper = if name == "callback" {
3062                    "pointer_declarator"
3063                } else {
3064                    "parenthesized_declarator"
3065                };
3066                assert_eq!(node.parent().unwrap().kind(), wrapper);
3067                binders.push((
3068                    name,
3069                    cpp_binder_owner(node, |n| n.parent(), field_name_in_parent)
3070                        .is_some_and(|owner| matches!(owner, CppBinderOwner::Local(_))),
3071                ));
3072            }
3073            stack.extend(
3074                node.named_children(&mut node.walk())
3075                    .collect::<Vec<_>>()
3076                    .into_iter()
3077                    .rev(),
3078            );
3079        }
3080        assert_eq!(
3081            names,
3082            [
3083                ("new_expression", "qualified_identifier", Some("Widget")),
3084                ("field_expression", "template_method", Some("method"))
3085            ]
3086        );
3087        assert_eq!(binders, [("callback", true), ("ordinary", false)]);
3088    }
3089
3090    #[test]
3091    #[brokk_bifrost_test_macros::hardwon(
3092        "6ec52f232835: quicksilver round 4 survivor review: dependent member names lost their terminal token"
3093    )]
3094    fn dependent_member_name_grammar_boundary() {
3095        let source = "template<class T> void f(T receiver) { receiver.template method<int>(); }";
3096        let tree = crate::diagnostics::parse_cpp_tree(source);
3097        assert!(!tree.root_node().has_error());
3098        let mut stack = vec![tree.root_node()];
3099        let mut names = Vec::new();
3100        while let Some(node) = stack.pop() {
3101            if node.kind() == "dependent_name" {
3102                assert!(node.child_by_field_name("name").is_none());
3103                assert_eq!(node.named_child(0).unwrap().kind(), "template_method");
3104                assert_eq!(
3105                    declarator_name_node(node).map(|name| &source[name.byte_range()]),
3106                    Some("method")
3107                );
3108                assert_eq!(
3109                    terminal_callee_leaf(node.parent().unwrap(), callee_spine_step)
3110                        .map(|name| &source[name.byte_range()]),
3111                    Some("method")
3112                );
3113                names.push(expression_name_node(node).map(|n| &source[n.byte_range()]));
3114            }
3115            stack.extend(node.named_children(&mut node.walk()));
3116        }
3117        assert_eq!(names, [Some("method")]);
3118    }
3119
3120    #[test]
3121    fn conditional_macro_name_fields() {
3122        let source = "#if ON\n#define FIRST(x) x\n#elif ALT\n#define SECOND(x) x\n#else\n#define THIRD(x) x\n#endif\nvoid f() {}\n";
3123        let tree = crate::diagnostics::parse_cpp_tree(source);
3124        assert!(!tree.root_node().has_error());
3125        let mut stack = vec![tree.root_node()];
3126        let mut definitions = Vec::new();
3127        while let Some(node) = stack.pop() {
3128            if node.kind() == "preproc_function_def" {
3129                let name = node.child_by_field_name("name").unwrap();
3130                definitions.push((
3131                    node_text(name, source).unwrap(),
3132                    node.parent().unwrap().kind(),
3133                ));
3134            }
3135            stack.extend(
3136                node.named_children(&mut node.walk())
3137                    .collect::<Vec<_>>()
3138                    .into_iter()
3139                    .rev(),
3140            );
3141        }
3142        assert_eq!(
3143            definitions,
3144            [
3145                ("FIRST", "preproc_if"),
3146                ("SECOND", "preproc_elif"),
3147                ("THIRD", "preproc_else"),
3148            ]
3149        );
3150        let mut names: Vec<_> = function_like_macro_names(tree.root_node(), source)
3151            .into_iter()
3152            .collect();
3153        names.sort();
3154        assert_eq!(names, ["FIRST", "SECOND", "THIRD"]);
3155    }
3156}
3157
3158#[cfg(test)]
3159mod result_use_tests {
3160    use super::{CppCallResultUseIndex, cpp_call_result_use};
3161    use brokk_bifrost_core::analyzer::structural::result_use::{
3162        CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
3163    };
3164    use tree_sitter::Parser;
3165
3166    fn tree(source: &str) -> tree_sitter::Tree {
3167        let mut parser = Parser::new();
3168        parser
3169            .set_language(&tree_sitter_cpp::LANGUAGE.into())
3170            .expect("C++ grammar");
3171        parser.parse(source, None).expect("C++ tree")
3172    }
3173
3174    fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
3175        let mut found = Vec::new();
3176        let mut stack = vec![tree.root_node()];
3177        while let Some(node) = stack.pop() {
3178            if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
3179                found.push(node);
3180            }
3181            for index in (0..node.named_child_count()).rev() {
3182                stack.push(node.named_child(index).expect("named child"));
3183            }
3184        }
3185        found.sort_by_key(|node| node.start_byte());
3186        found
3187    }
3188
3189    #[test]
3190    fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
3191        let source = concat!(
3192            "int f();\n",
3193            "void sink(int);\n",
3194            "void run() {\n",
3195            "    f();\n",
3196            "    (void)f();\n",
3197            "    static_cast<void>(f());\n",
3198            "    sink(f());\n",
3199            "    return;\n",
3200            "}\n",
3201        );
3202        let tree = tree(source);
3203        assert!(
3204            !tree.root_node().has_error(),
3205            "{}",
3206            tree.root_node().to_sexp()
3207        );
3208        let calls = calls(&tree, source);
3209        assert_eq!(calls.len(), 5);
3210        assert_eq!(
3211            cpp_call_result_use(calls[0], source).for_port(0),
3212            CallResultUse::Discarded
3213        );
3214        assert!(matches!(
3215            cpp_call_result_use(calls[1], source).for_port(0),
3216            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
3217        ));
3218        assert!(matches!(
3219            cpp_call_result_use(calls[2], source).for_port(0),
3220            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
3221        ));
3222        assert_eq!(
3223            cpp_call_result_use(calls[3], source).for_port(0),
3224            CallResultUse::Discarded
3225        );
3226        assert_eq!(
3227            cpp_call_result_use(calls[4], source).for_port(0),
3228            CallResultUse::Consumed
3229        );
3230    }
3231
3232    #[test]
3233    fn lambda_tail_and_recovered_syntax_are_open() {
3234        // The macro-declaration grammar splits a bare `f(,)` into an
3235        // identifier and a separate ERROR with no call expression at all, so
3236        // the recovered call is spelled `f(+)` here: it still produces a
3237        // call_expression whose argument list carries the ERROR that marks
3238        // the syntax as recovered.
3239        let source = "int f(); void run() { auto value = [] { return f(); }; f(+); }";
3240        let tree = tree(source);
3241        assert!(tree.root_node().has_error());
3242        let calls = calls(&tree, source);
3243        assert!(matches!(
3244            cpp_call_result_use(calls[0], source).for_port(0),
3245            CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
3246        ));
3247        assert_eq!(
3248            cpp_call_result_use(calls[1], source).for_port(0),
3249            CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
3250        );
3251    }
3252
3253    #[test]
3254    fn exact_index_keeps_call_spans_separate() {
3255        let source = "int f(); void run() { f(); }";
3256        let tree = tree(source);
3257        let calls = calls(&tree, source);
3258        let index = CppCallResultUseIndex::new(tree.root_node(), source);
3259        assert_eq!(
3260            index
3261                .at_span(calls[0].start_byte(), calls[0].end_byte())
3262                .expect("indexed call")
3263                .for_port(0),
3264            CallResultUse::Discarded
3265        );
3266        assert!(index.at_span(source.len(), source.len()).is_err());
3267    }
3268
3269    #[test]
3270    fn result_use_grammar_boundary_sites_and_recovery() {
3271        // Spellings are the independent site oracle: casts are syntax wrappers,
3272        // allocations are expressions, and an ERROR ancestor invalidates a site.
3273        for (source, expected) in [
3274            (
3275                "void run() { dynamic_cast<T*>(a()); static_cast<T*>(b()); reinterpret_cast<T*>(c()); const_cast<T*>(d()); new W(); auto p = new X(); sink(new Y()); }",
3276                vec![
3277                    ("a()", CallResultUse::Consumed),
3278                    ("b()", CallResultUse::Consumed),
3279                    ("c()", CallResultUse::Consumed),
3280                    ("d()", CallResultUse::Consumed),
3281                    ("new W()", CallResultUse::Discarded),
3282                    ("new X()", CallResultUse::Consumed),
3283                    ("sink(new Y())", CallResultUse::Discarded),
3284                    ("new Y()", CallResultUse::Consumed),
3285                ],
3286            ),
3287            (
3288                "void run() { try { f(); } }",
3289                vec![(
3290                    "f()",
3291                    CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax),
3292                )],
3293            ),
3294        ] {
3295            let tree = tree(source);
3296            let mut stack = vec![tree.root_node()];
3297            let mut grammar_sites = Vec::new();
3298            let mut recovery_witness = false;
3299            while let Some(node) = stack.pop() {
3300                if matches!(node.kind(), "call_expression" | "new_expression") {
3301                    grammar_sites.push(node.byte_range());
3302                    if source[node.byte_range()] == *"f()" {
3303                        assert!(!node.has_error());
3304                        assert!(!node.parent().unwrap().is_error());
3305                        let mut parent = node.parent();
3306                        while let Some(ancestor) = parent {
3307                            recovery_witness |= ancestor.is_error();
3308                            parent = ancestor.parent();
3309                        }
3310                    }
3311                }
3312                stack.extend(node.named_children(&mut node.walk()));
3313            }
3314            if source.contains("try") {
3315                assert!(recovery_witness, "{}", tree.root_node().to_sexp());
3316            } else {
3317                assert!(!tree.root_node().has_error());
3318                assert_eq!(
3319                    grammar_sites.len(),
3320                    12,
3321                    "four cast wrappers plus eight sites"
3322                );
3323            }
3324            let index = CppCallResultUseIndex::new(tree.root_node(), source);
3325            let actual: Vec<_> = index
3326                .sites()
3327                .iter()
3328                .map(|site| (&source[site.start_byte..site.end_byte], site.for_port(0)))
3329                .collect();
3330            assert_eq!(actual, expected, "{source}");
3331        }
3332    }
3333
3334    #[test]
3335    #[brokk_bifrost_test_macros::hardwon(
3336        "6ec52f232835: quicksilver round 4 survivor review: void pointer casts falsely discarded source values"
3337    )]
3338    fn pointer_to_void_casts_consume_the_source_value() {
3339        let source = "int* make(); void run() { auto p = (void*)make(); auto q = static_cast<void*>(make()); }";
3340        let tree = tree(source);
3341        assert!(!tree.root_node().has_error());
3342        let mut stack = vec![tree.root_node()];
3343        let mut type_declarators = Vec::new();
3344        while let Some(node) = stack.pop() {
3345            if node.kind() == "type_descriptor" {
3346                type_declarators.push(node.child_by_field_name("declarator").map(|n| n.kind()));
3347            }
3348            stack.extend(node.named_children(&mut node.walk()));
3349        }
3350        assert_eq!(
3351            type_declarators,
3352            [
3353                Some("abstract_pointer_declarator"),
3354                Some("abstract_pointer_declarator")
3355            ]
3356        );
3357        let index = CppCallResultUseIndex::new(tree.root_node(), source);
3358        // Each source result feeds a pointer conversion and then an initializer.
3359        // Neither conversion discards a value by casting it to scalar void.
3360        let uses: Vec<_> = index.sites().iter().map(|site| site.for_port(0)).collect();
3361        assert_eq!(uses, [CallResultUse::Consumed, CallResultUse::Consumed]);
3362    }
3363}