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::callable::{
11    CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
12};
13use brokk_bifrost_core::analyzer::structural::edges::{
14    INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
15};
16use brokk_bifrost_core::analyzer::structural::facts::Span;
17use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
18use brokk_bifrost_core::analyzer::structural::materialization::{
19    CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
20};
21use brokk_bifrost_core::analyzer::structural::occurrences::{
22    OccurrenceRole, OccurrenceRoleSupport,
23};
24use brokk_bifrost_core::analyzer::structural::resolution::{
25    BindingActivation, BindingKind, EnvironmentAxis, HoistingClass, LexicalEnvironmentSupport,
26    ScopeFormation, default_scope_formation,
27};
28use brokk_bifrost_core::analyzer::structural::result_use::{
29    CallResultUse, CallResultUseIndex, CallResultUseOpenReason, CallResultUseSite,
30    IntentionalDiscardKind,
31};
32use brokk_bifrost_core::analyzer::structural::routes::{
33    IdentityAxis, IdentityRouteSupport, RouteHopKind,
34};
35use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
36use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, named_children_iter, node_range};
37use brokk_bifrost_core::analyzer::{Language, Range};
38use brokk_bifrost_core::hash::HashSet;
39use tree_sitter::Node;
40
41#[derive(Debug, Default)]
42pub struct CppStructuralSpec;
43
44pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
45
46/// Whether this is a C++ preprocessor conditional node.
47///
48/// The linked tree-sitter-cpp grammar uses `preproc_ifdef` for both `#ifdef`
49/// and `#ifndef`; `preproc_ifndef` remains accepted for trees that expose it
50/// as a distinct node kind.
51pub(crate) fn is_cpp_preprocessor_conditional(node: Node<'_>) -> bool {
52    matches!(
53        node.kind(),
54        "preproc_if"
55            | "preproc_ifdef"
56            | "preproc_ifndef"
57            | "preproc_elif"
58            | "preproc_elifdef"
59            | "preproc_else"
60    )
61}
62
63/// Classify one exact C or C++ call expression.  The C grammar is the same
64/// parser route as C++, so the syntax classifier deliberately does not use a
65/// source-file extension or a text-search fallback.
66pub fn cpp_call_result_use(call: Node<'_>, source: &str) -> CallResultUseSite {
67    let default = if !matches!(call.kind(), "call_expression" | "new_expression")
68        || call.has_error()
69        || has_recovery_ancestor(call)
70    {
71        CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
72    } else if has_deferred_ancestor(call) {
73        CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
74    } else {
75        let mut expression = call;
76        loop {
77            let Some(parent) = expression.parent() else {
78                break CallResultUse::Open(CallResultUseOpenReason::UnclassifiedSyntax);
79            };
80            if parent.is_error() || parent.is_missing() {
81                break CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax);
82            }
83            match parent.kind() {
84                "parenthesized_expression" | "comma_expression"
85                    if parent
86                        .named_children(&mut parent.walk())
87                        .any(|child| child.id() == expression.id()) =>
88                {
89                    expression = parent;
90                }
91                "expression_statement"
92                    if parent
93                        .named_children(&mut parent.walk())
94                        .any(|child| child.id() == expression.id()) =>
95                {
96                    break CallResultUse::Discarded;
97                }
98                "for_statement"
99                    if matches!(
100                        field_name_in_parent(parent, expression),
101                        Some("initializer" | "update")
102                    ) =>
103                {
104                    break CallResultUse::Discarded;
105                }
106                "cast_expression"
107                    if field_name_in_parent(parent, expression) == Some("value")
108                        && parent
109                            .child_by_field_name("type")
110                            .is_some_and(|type_node| cpp_type_is_void(type_node, source)) =>
111                {
112                    break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
113                }
114                "argument_list"
115                    if parent
116                        .parent()
117                        .is_some_and(|outer_call| cpp_static_cast_to_void(outer_call, source)) =>
118                {
119                    break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
120                }
121                "lambda_expression" => {
122                    break CallResultUse::Open(CallResultUseOpenReason::DeferredContext);
123                }
124                _ => break CallResultUse::Consumed,
125            }
126        }
127    };
128    CallResultUseSite::new(call.start_byte(), call.end_byte(), default)
129}
130
131fn cpp_type_is_void(type_node: Node<'_>, source: &str) -> bool {
132    let base = match type_node.kind() {
133        "type_descriptor" if type_node.child_by_field_name("declarator").is_none() => {
134            type_node.child_by_field_name("type")
135        }
136        "primitive_type" => Some(type_node),
137        _ => None,
138    };
139    base.is_some_and(|base| {
140        base.kind() == "primitive_type" && node_text(base, source) == Some("void")
141    })
142}
143
144fn has_recovery_ancestor(mut node: Node<'_>) -> bool {
145    while let Some(parent) = node.parent() {
146        if parent.is_error() || parent.is_missing() {
147            return true;
148        }
149        node = parent;
150    }
151    false
152}
153
154fn has_deferred_ancestor(mut node: Node<'_>) -> bool {
155    while let Some(parent) = node.parent() {
156        if parent.kind() == "lambda_expression" {
157            return true;
158        }
159        node = parent;
160    }
161    false
162}
163
164fn cpp_static_cast_to_void(call: Node<'_>, source: &str) -> bool {
165    if call.kind() != "call_expression" {
166        return false;
167    }
168    let Some(function) = call.child_by_field_name("function") else {
169        return false;
170    };
171    if function.kind() != "template_function"
172        || function
173            .child_by_field_name("name")
174            .and_then(|name| node_text(name, source))
175            != Some("static_cast")
176    {
177        return false;
178    }
179    function
180        .child_by_field_name("arguments")
181        .and_then(|arguments| arguments.named_child(0))
182        .is_some_and(|type_node| cpp_type_is_void(type_node, source))
183}
184
185fn cpp_cast_call(call: Node<'_>, source: &str) -> bool {
186    let Some(function) = call.child_by_field_name("function") else {
187        return false;
188    };
189    if function.kind() != "template_function" {
190        return false;
191    }
192    matches!(
193        function
194            .child_by_field_name("name")
195            .and_then(|name| node_text(name, source)),
196        Some("static_cast" | "reinterpret_cast" | "const_cast" | "dynamic_cast")
197    )
198}
199
200/// One source-snapshot index of exact C/C++ call spans.
201#[derive(Debug, Clone)]
202pub struct CppCallResultUseIndex {
203    inner: CallResultUseIndex,
204}
205
206impl CppCallResultUseIndex {
207    pub fn new(root: Node<'_>, source: &str) -> Self {
208        let mut sites = Vec::new();
209        let mut stack = vec![root];
210        while let Some(node) = stack.pop() {
211            if matches!(node.kind(), "call_expression" | "new_expression")
212                && !cpp_cast_call(node, source)
213            {
214                sites.push(cpp_call_result_use(node, source));
215            }
216            for index in (0..node.named_child_count()).rev() {
217                stack.push(node.named_child(index).expect("named child exists"));
218            }
219        }
220        Self {
221            inner: CallResultUseIndex::new(sites),
222        }
223    }
224
225    pub fn at_span(
226        &self,
227        start_byte: usize,
228        end_byte: usize,
229    ) -> Result<&CallResultUseSite, CallResultUseOpenReason> {
230        self.inner.at_span(start_byte, end_byte)
231    }
232
233    pub fn sites(&self) -> &[CallResultUseSite] {
234        self.inner.sites()
235    }
236}
237
238pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
239    ("comment", NormalizedKind::Comment),
240    ("call_expression", NormalizedKind::Call),
241    ("new_expression", NormalizedKind::Call),
242    ("field_expression", NormalizedKind::FieldAccess),
243    ("function_definition", NormalizedKind::Function),
244    ("lambda_expression", NormalizedKind::Lambda),
245    ("class_specifier", NormalizedKind::Class),
246    ("struct_specifier", NormalizedKind::Class),
247    ("union_specifier", NormalizedKind::Class),
248    ("alias_declaration", NormalizedKind::Declaration),
249    ("namespace_definition", NormalizedKind::Module),
250    ("assignment_expression", NormalizedKind::Assignment),
251    ("init_declarator", NormalizedKind::Assignment),
252    ("preproc_include", NormalizedKind::Import),
253    ("identifier", NormalizedKind::Identifier),
254    ("field_identifier", NormalizedKind::Identifier),
255    ("namespace_identifier", NormalizedKind::Identifier),
256    ("qualified_identifier", NormalizedKind::Identifier),
257    ("type_identifier", NormalizedKind::Identifier),
258    ("template_function", NormalizedKind::Identifier),
259    ("template_method", NormalizedKind::Identifier),
260    ("template_type", NormalizedKind::Identifier),
261    ("dependent_name", NormalizedKind::Identifier),
262    ("destructor_name", NormalizedKind::Identifier),
263    ("operator_name", NormalizedKind::Identifier),
264    ("primitive_type", NormalizedKind::Identifier),
265    ("char_literal", NormalizedKind::StringLiteral),
266    ("string_literal", NormalizedKind::StringLiteral),
267    ("raw_string_literal", NormalizedKind::StringLiteral),
268    ("number_literal", NormalizedKind::NumericLiteral),
269    ("true", NormalizedKind::BooleanLiteral),
270    ("false", NormalizedKind::BooleanLiteral),
271    ("null", NormalizedKind::NullLiteral),
272    ("return_statement", NormalizedKind::Return),
273    ("throw_statement", NormalizedKind::Throw),
274    ("catch_clause", NormalizedKind::Catch),
275    ("if_statement", NormalizedKind::If),
276    ("for_statement", NormalizedKind::Loop),
277    ("for_range_loop", NormalizedKind::Loop),
278    ("compound_statement", NormalizedKind::Block),
279    ("switch_statement", NormalizedKind::Switch),
280    ("case_statement", NormalizedKind::Case),
281    ("labeled_statement", NormalizedKind::Label),
282    ("goto_statement", NormalizedKind::Goto),
283    ("break_statement", NormalizedKind::Break),
284    ("continue_statement", NormalizedKind::Continue),
285    ("while_statement", NormalizedKind::WhileLoop),
286    ("do_statement", NormalizedKind::WhileLoop),
287];
288
289fn cpp_escape_codepoint(digits: &str, radix: u32, byte_escape: bool) -> Option<char> {
290    let value = u32::from_str_radix(digits, radix).ok()?;
291    if byte_escape && value > 0x7f {
292        return None;
293    }
294    char::from_u32(value)
295}
296
297fn cpp_cooked_string_escape(escape: &str) -> Option<Option<char>> {
298    let escaped = escape.strip_prefix('\\')?;
299    if matches!(escaped, "\n" | "\r\n") {
300        return Some(None);
301    }
302    let character = match escaped {
303        "a" => '\x07',
304        "b" => '\x08',
305        "f" => '\x0c',
306        "n" => '\n',
307        "r" => '\r',
308        "t" => '\t',
309        "v" => '\x0b',
310        "0" => '\0',
311        "\\" => '\\',
312        "'" => '\'',
313        "\"" => '\"',
314        "?" => '?',
315        _ if escaped.starts_with('x') => {
316            let digits = escaped.get(1..)?;
317            (!digits.is_empty()
318                && digits.len() <= 4
319                && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
320            .then_some(())?;
321            cpp_escape_codepoint(digits, 16, true)?
322        }
323        _ if escaped.starts_with('u') => {
324            let digits = escaped.get(1..)?;
325            (digits.len() == 4 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
326                .then_some(())?;
327            cpp_escape_codepoint(digits, 16, false)?
328        }
329        _ if escaped.starts_with('U') => {
330            let digits = escaped.get(1..)?;
331            (digits.len() == 8 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
332                .then_some(())?;
333            cpp_escape_codepoint(digits, 16, false)?
334        }
335        _ if escaped.len() <= 3 && escaped.bytes().all(|byte| (b'0'..=b'7').contains(&byte)) => {
336            cpp_escape_codepoint(escaped, 8, true)?
337        }
338        _ => return None,
339    };
340    Some(Some(character))
341}
342
343fn cpp_string_literal_value(node: Node<'_>, source: &str) -> Option<String> {
344    let spelling = node_text(node, source)?;
345    match node.kind() {
346        "string_literal" => {
347            // Wide and UTF-prefixed literals have implementation-specific code
348            // unit encodings. Only the ordinary narrow form has a portable
349            // decoded UTF-8 value for this fact axis.
350            if !spelling.starts_with('"') {
351                return None;
352            }
353            let mut decoded = String::new();
354            let mut cursor = node.walk();
355            for child in node.named_children(&mut cursor) {
356                let text = node_text(child, source)?;
357                match child.kind() {
358                    "string_content" => decoded.push_str(text),
359                    "escape_sequence" => {
360                        if let Some(character) = cpp_cooked_string_escape(text)? {
361                            decoded.push(character);
362                        }
363                    }
364                    _ => return None,
365                }
366            }
367            Some(decoded)
368        }
369        "raw_string_literal" => {
370            if !spelling.starts_with("R\"") {
371                return None;
372            }
373            let mut content = None;
374            let mut cursor = node.walk();
375            for child in node.named_children(&mut cursor) {
376                match child.kind() {
377                    "raw_string_content" => {
378                        if content.is_some() {
379                            return None;
380                        }
381                        content = Some(node_text(child, source)?);
382                    }
383                    "raw_string_delimiter" => {}
384                    _ => return None,
385                }
386            }
387            content.map(str::to_owned)
388        }
389        _ => None,
390    }
391}
392
393fn condition_expression(node: Node<'_>) -> Option<Node<'_>> {
394    let condition = node.child_by_field_name("condition")?;
395    if condition.kind() == "condition_clause" {
396        condition
397            .child_by_field_name("value")
398            .or_else(|| condition.named_child(0))
399    } else {
400        Some(condition)
401    }
402}
403
404/// Declaration evidence retained only while classifying one switch.
405#[derive(Clone, Copy)]
406struct CppSwitchDeclaration<'tree> {
407    type_node: Node<'tree>,
408    declarator: Node<'tree>,
409}
410
411fn cpp_switch_scope(node: Node<'_>) -> bool {
412    matches!(
413        node.kind(),
414        "compound_statement"
415            | "for_statement"
416            | "for_range_loop"
417            | "if_statement"
418            | "switch_statement"
419            | "while_statement"
420            | "catch_clause"
421    )
422}
423
424fn cpp_switch_local_declaration<'tree>(
425    switch: Node<'tree>,
426    name: &str,
427    source: &str,
428) -> Option<CppSwitchDeclaration<'tree>> {
429    let condition = condition_expression(switch)?;
430    let function = std::iter::successors(switch.parent(), |node| node.parent())
431        .find(|node| matches!(node.kind(), "function_definition" | "lambda_expression"))?;
432    enum Step<'tree> {
433        Enter(Node<'tree>),
434        LeaveScope,
435    }
436    enum Binding<'tree> {
437        Unknown,
438        Declaration(CppSwitchDeclaration<'tree>),
439    }
440    let mut stack = vec![Step::Enter(function)];
441    // Only the requested identifier is retained at each lexical tier.
442    let mut scopes: Vec<Option<Binding<'tree>>> = vec![None];
443    while let Some(step) = stack.pop() {
444        let node = match step {
445            Step::LeaveScope => {
446                scopes.pop().expect("entered scope");
447                continue;
448            }
449            Step::Enter(node) => node,
450        };
451        if node == condition {
452            return scopes.iter().rev().find_map(Option::as_ref).and_then(
453                |binding| match binding {
454                    Binding::Unknown => None,
455                    Binding::Declaration(declaration) => Some(*declaration),
456                },
457            );
458        }
459        if node != function
460            && matches!(
461                node.kind(),
462                "function_definition"
463                    | "lambda_expression"
464                    | "class_specifier"
465                    | "struct_specifier"
466                    | "union_specifier"
467                    | "enum_specifier"
468            )
469        {
470            continue;
471        }
472        if cpp_switch_scope(node) {
473            scopes.push(None);
474            stack.push(Step::LeaveScope);
475        }
476        if node.kind() == "declaration" {
477            for declarator in node.children_by_field_name("declarator", &mut node.walk()) {
478                if declarator_name_node(declarator).and_then(|node| node_text(node, source))
479                    == Some(name)
480                {
481                    // Prototypes and unsupported declarators also hide names.
482                    *scopes.last_mut().expect("function scope") = Some(Binding::Unknown);
483                }
484            }
485        }
486        if node.kind() == "identifier"
487            && node_text(node, source) == Some(name)
488            && let Some(owner) = cpp_binder_owner(node, |node| node.parent(), field_name_in_parent)
489        {
490            let declaration = match owner {
491                CppBinderOwner::Local(declarator) => {
492                    let owner = std::iter::successors(declarator.parent(), |node| node.parent())
493                        .find(|node| node.kind() == "declaration")
494                        .expect("local binder declaration");
495                    owner
496                        .child_by_field_name("type")
497                        .map(|type_node| CppSwitchDeclaration {
498                            type_node,
499                            declarator,
500                        })
501                }
502                CppBinderOwner::Parameter => {
503                    let owner = std::iter::successors(node.parent(), |node| node.parent())
504                        .find(|node| {
505                            matches!(
506                                node.kind(),
507                                "parameter_declaration"
508                                    | "optional_parameter_declaration"
509                                    | "variadic_parameter_declaration"
510                            )
511                        })
512                        .expect("parameter binder declaration");
513                    owner
514                        .child_by_field_name("type")
515                        .zip(owner.child_by_field_name("declarator"))
516                        .map(|(type_node, declarator)| CppSwitchDeclaration {
517                            type_node,
518                            declarator,
519                        })
520                }
521                CppBinderOwner::RangeFor(owner) => owner
522                    .child_by_field_name("type")
523                    .zip(owner.child_by_field_name("declarator"))
524                    .map(|(type_node, declarator)| CppSwitchDeclaration {
525                        type_node,
526                        declarator,
527                    }),
528                _ => None,
529            };
530            // An unknown inner binding must not fall through to a parameter.
531            *scopes.last_mut().expect("function scope") =
532                Some(declaration.map_or(Binding::Unknown, Binding::Declaration));
533        }
534        for index in (0..node.named_child_count()).rev() {
535            stack.push(Step::Enter(node.named_child(index).expect("named child")));
536        }
537    }
538    None
539}
540
541/// Search one lexical type tier, descending into a named scope only when the
542/// written qualification selects it. Aliases are barriers, not enum evidence.
543fn cpp_switch_type_in_scope<'tree>(
544    scope: Node<'tree>,
545    path: &[String],
546    before: usize,
547    source: &str,
548) -> Option<Node<'tree>> {
549    use crate::declarations::cpp_structured_type_path;
550    let mut stack = vec![(scope, 0)];
551    let mut found = None;
552    let mut unresolved_import = None;
553    while let Some((node, component)) = stack.pop() {
554        if node.start_byte() >= before {
555            continue;
556        }
557        if node != scope {
558            if node.kind() == "using_declaration" {
559                let imports_namespace = (0..node.child_count())
560                    .any(|index| node.child(index).expect("child").kind() == "namespace");
561                let imported = first_named_child(node)
562                    .and_then(declarator_name_node)
563                    .and_then(|name| node_text(name, source));
564                if imports_namespace || imported == Some(path[component].as_str()) {
565                    unresolved_import = Some(node);
566                }
567                continue;
568            }
569            let named_type = matches!(
570                node.kind(),
571                "enum_specifier"
572                    | "class_specifier"
573                    | "struct_specifier"
574                    | "union_specifier"
575                    | "namespace_definition"
576                    | "namespace_alias_definition"
577                    | "alias_declaration"
578                    | "type_definition"
579                    | "type_parameter_declaration"
580                    | "variadic_type_parameter_declaration"
581            );
582            if named_type {
583                let names = if node.kind() == "type_definition" {
584                    node.children_by_field_name("declarator", &mut node.walk())
585                        .filter_map(declarator_name_node)
586                        .filter_map(|name| cpp_structured_type_path(name, source))
587                        .collect::<Vec<_>>()
588                } else if matches!(
589                    node.kind(),
590                    "type_parameter_declaration" | "variadic_type_parameter_declaration"
591                ) {
592                    first_named_child(node)
593                        .and_then(|name| cpp_structured_type_path(name, source))
594                        .into_iter()
595                        .collect()
596                } else {
597                    node.child_by_field_name("name")
598                        .and_then(|name| {
599                            // A C++17 nested namespace has a different grammar
600                            // wrapper from a qualified type name.
601                            let mut stack = vec![name];
602                            let mut path = Vec::new();
603                            while let Some(part) = stack.pop() {
604                                if part.kind() == "nested_namespace_specifier" {
605                                    for index in (0..part.named_child_count()).rev() {
606                                        stack.push(
607                                            part.named_child(index).expect("namespace component"),
608                                        );
609                                    }
610                                } else {
611                                    path.extend(cpp_structured_type_path(part, source)?);
612                                }
613                            }
614                            Some(path)
615                        })
616                        .into_iter()
617                        .collect()
618                };
619                for name in names {
620                    if path[component..].starts_with(&name) {
621                        let next = component + name.len();
622                        if next < path.len()
623                            && let Some(body) = node.child_by_field_name("body")
624                        {
625                            stack.push((body, next));
626                        } else if found.is_none_or(|previous: Node<'_>| {
627                            previous.start_byte() < node.start_byte()
628                        }) {
629                            // An alias/forward declaration cannot supply the
630                            // rest of a qualification and must block fallback.
631                            found = Some(node);
632                        }
633                    }
634                }
635                continue;
636            }
637            if cpp_switch_scope(node)
638                || matches!(node.kind(), "function_definition" | "lambda_expression")
639            {
640                continue;
641            }
642        }
643        for index in (0..node.named_child_count()).rev() {
644            stack.push((node.named_child(index).expect("named child"), component));
645        }
646    }
647    found.or(unresolved_import)
648}
649
650fn cpp_switch_named_type<'tree>(type_node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
651    use crate::declarations::cpp_structured_type_path;
652    if type_node.kind() == "enum_specifier"
653        && type_node.child_by_field_name("body").is_some()
654        && type_node.child_by_field_name("name").is_some()
655    {
656        return Some(type_node);
657    }
658    if matches!(
659        type_node.kind(),
660        "class_specifier" | "struct_specifier" | "union_specifier"
661    ) && type_node.child_by_field_name("body").is_some()
662    {
663        return Some(type_node);
664    }
665    let name = if matches!(
666        type_node.kind(),
667        "enum_specifier" | "class_specifier" | "struct_specifier" | "union_specifier"
668    ) {
669        type_node.child_by_field_name("name")?
670    } else {
671        type_node
672    };
673    // Template arguments and dependent types require semantic resolution.
674    let mut stack = vec![name];
675    while let Some(node) = stack.pop() {
676        if !matches!(
677            node.kind(),
678            "type_identifier"
679                | "identifier"
680                | "namespace_identifier"
681                | "qualified_identifier"
682                | "scoped_identifier"
683                | "scoped_type_identifier"
684        ) {
685            return None;
686        }
687        stack.extend(named_children_iter(node).filter(|node| !node.is_extra()));
688    }
689    let path = cpp_structured_type_path(name, source)?;
690    let global = name.child(0).is_some_and(|child| child.kind() == "::");
691    for scope in std::iter::successors(type_node.parent(), |node| node.parent()) {
692        if !(cpp_switch_scope(scope)
693            || matches!(
694                scope.kind(),
695                "function_definition"
696                    | "template_declaration"
697                    | "namespace_definition"
698                    | "class_specifier"
699                    | "struct_specifier"
700                    | "union_specifier"
701                    | "translation_unit"
702            ))
703            || (global && scope.kind() != "translation_unit")
704        {
705            continue;
706        }
707        if let Some(declaration) =
708            cpp_switch_type_in_scope(scope, &path, type_node.start_byte(), source)
709        {
710            return Some(declaration);
711        }
712        if !global
713            && matches!(
714                scope.kind(),
715                "class_specifier" | "struct_specifier" | "union_specifier"
716            )
717        {
718            // Inherited names and complete-class lookup can hide an outer
719            // enum even without an earlier declaration in the class body.
720            if named_children_iter(scope).any(|node| node.kind() == "base_class_clause")
721                || cpp_switch_type_in_scope(scope, &path, usize::MAX, source)
722                    .is_some_and(|declaration| declaration.start_byte() >= type_node.start_byte())
723            {
724                return None;
725            }
726        }
727        // An out-of-line definition's logical owner is not its AST parent.
728        // Resolving relative names across that boundary needs the resolver.
729        if !global
730            && (scope.kind() == "function_definition"
731                && scoped_function_definition(scope).is_some()
732                || scope
733                    .child_by_field_name("name")
734                    .is_some_and(|name| name.kind() == "qualified_identifier"))
735        {
736            return None;
737        }
738    }
739    None
740}
741
742fn cpp_switch_pointer_depth(mut declarator: Node<'_>) -> Option<usize> {
743    let mut depth = 0;
744    loop {
745        match declarator.kind() {
746            "identifier" | "field_identifier" => return Some(depth),
747            "pointer_declarator" => {
748                depth += 1;
749                declarator = declarator.child_by_field_name("declarator")?;
750            }
751            "init_declarator" => declarator = declarator.child_by_field_name("declarator")?,
752            "reference_declarator" | "parenthesized_declarator" | "attributed_declarator" => {
753                declarator = first_named_child(declarator)?
754            }
755            _ => return None,
756        }
757    }
758}
759
760fn cpp_switch_enum_type<'tree>(switch: Node<'tree>, source: &str) -> Option<Node<'tree>> {
761    let mut operand = condition_expression(switch)?;
762    let mut fields = Vec::new();
763    while operand.kind() == "field_expression" {
764        fields.push(operand);
765        operand = operand.child_by_field_name("argument")?;
766    }
767    if operand.kind() != "identifier" {
768        return None;
769    }
770    let mut declaration =
771        cpp_switch_local_declaration(switch, node_text(operand, source)?, source)?;
772    for field in fields.into_iter().rev() {
773        let operator = field.child_by_field_name("operator")?;
774        let expected_depth = match operator.kind() {
775            "." => 0,
776            "->" => 1,
777            _ => return None,
778        };
779        if cpp_switch_pointer_depth(declaration.declarator)? != expected_depth {
780            return None;
781        }
782        let record = cpp_switch_named_type(declaration.type_node, source)?;
783        if !matches!(
784            record.kind(),
785            "class_specifier" | "struct_specifier" | "union_specifier"
786        ) {
787            return None;
788        }
789        let name = node_text(field.child_by_field_name("field")?, source)?;
790        let mut member = None;
791        for field_declaration in named_children_iter(record.child_by_field_name("body")?)
792            .filter(|node| node.kind() == "field_declaration")
793        {
794            for declarator in field_declaration
795                .children_by_field_name("declarator", &mut field_declaration.walk())
796            {
797                if declarator_name_node(declarator).and_then(|name| node_text(name, source))
798                    == Some(name)
799                {
800                    member = Some(CppSwitchDeclaration {
801                        type_node: field_declaration.child_by_field_name("type")?,
802                        declarator,
803                    });
804                }
805            }
806        }
807        declaration = member?;
808    }
809    if cpp_switch_pointer_depth(declaration.declarator)? != 0 {
810        return None;
811    }
812    let enum_type = cpp_switch_named_type(declaration.type_node, source)?;
813    (enum_type.kind() == "enum_specifier").then_some(enum_type)
814}
815
816fn declarator_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
817    let mut current = node;
818    loop {
819        match current.kind() {
820            "identifier" | "field_identifier" | "statement_identifier" => return Some(current),
821            "init_declarator"
822            | "pointer_declarator"
823            | "reference_declarator"
824            | "array_declarator"
825            | "parenthesized_declarator"
826            | "attributed_declarator" => {
827                current = current.child_by_field_name("declarator")?;
828            }
829            "qualified_identifier" => current = current.child_by_field_name("name")?,
830            _ => return None,
831        }
832    }
833}
834
835fn loop_variable<'tree>(loop_node: Node<'tree>, source: &'tree str) -> Option<&'tree str> {
836    let declarator = if loop_node.kind() == "for_range_loop" {
837        loop_node.child_by_field_name("declarator")
838    } else {
839        let initializer = loop_node.child_by_field_name("initializer")?;
840        if initializer.kind() == "declaration" {
841            initializer
842                .child_by_field_name("declarator")
843                .and_then(declarator_identifier)
844        } else if initializer.kind() == "assignment_expression" {
845            initializer
846                .child_by_field_name("left")
847                .and_then(expression_name_node)
848        } else {
849            None
850        }
851    }?;
852    let name = declarator_identifier(declarator).or(Some(declarator))?;
853    node_text(name, source)
854}
855
856impl brokk_bifrost_core::analyzer::statement_facts::producer::StatementFactSyntax
857    for CppStructuralSpec
858{
859    fn supports_kind(
860        &self,
861        kind: brokk_bifrost_core::analyzer::statement_facts::StatementFactKind,
862    ) -> bool {
863        use brokk_bifrost_core::analyzer::statement_facts::StatementFactKind;
864        !matches!(
865            kind,
866            StatementFactKind::NestedIfStatements | StatementFactKind::MisleadingIndentation
867        )
868    }
869
870    fn roles<'tree>(&self, node: Node<'tree>, role: Role) -> Vec<Node<'tree>> {
871        let target = match role {
872            Role::Test if node.kind() == "case_statement" => node.child_by_field_name("value"),
873            Role::Test => condition_expression(node),
874            Role::Consequence => node.child_by_field_name("consequence"),
875            Role::Alternative => {
876                brokk_bifrost_core::analyzer::structural::body_relations::if_alternative_body(
877                    node,
878                    brokk_bifrost_core::analyzer::structural::body_relations::syntax_for(
879                        Language::Cpp,
880                    )
881                    .expect("C++ body syntax"),
882                )
883            }
884            Role::Body => node.child_by_field_name("body"),
885            Role::Operator => node.child_by_field_name("operator"),
886            Role::Left => node.child_by_field_name("left"),
887            Role::Right => node.child_by_field_name("right"),
888            Role::Label => node.child_by_field_name("label"),
889            _ => None,
890        };
891        target.into_iter().collect()
892    }
893
894    fn is_code_child(&self, node: Node<'_>) -> bool {
895        !node.is_extra() && !node.kind().starts_with("preproc_") && node.kind() != "comment"
896    }
897
898    fn loop_variable(&self, node: Node<'_>, source: &str) -> Option<String> {
899        matches!(node.kind(), "for_statement" | "for_range_loop")
900            .then(|| loop_variable(node, source).map(str::to_owned))
901            .flatten()
902    }
903
904    fn is_counted_loop(&self, node: Node<'_>) -> bool {
905        node.kind() == "for_statement"
906    }
907
908    fn loop_initializers<'tree>(&self, node: Node<'tree>) -> Vec<Node<'tree>> {
909        node.child_by_field_name("initializer")
910            .into_iter()
911            .collect()
912    }
913
914    fn is_alloca(&self, node: Node<'_>, source: &str) -> bool {
915        node.kind() == "call_expression"
916            && node
917                .child_by_field_name("function")
918                .and_then(|function| node_text(function, source))
919                .is_some_and(|name| {
920                    matches!(name, "alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
921                })
922    }
923
924    fn switch_enum<'tree>(&self, node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
925        cpp_switch_enum_type(node, source)
926    }
927
928    fn enumeration(
929        &self,
930        node: Node<'_>,
931        source: &str,
932    ) -> Option<(
933        brokk_bifrost_core::analyzer::statement_facts::EnumFact,
934        Vec<brokk_bifrost_core::analyzer::statement_facts::EnumeratorFact>,
935    )> {
936        use brokk_bifrost_core::analyzer::statement_facts::{EnumFact, EnumeratorFact};
937        if node.kind() != "enum_specifier" {
938            return None;
939        }
940        let range = node_range(node);
941        let name = node
942            .child_by_field_name("name")
943            .and_then(|name| node_text(name, source))
944            .map(str::to_owned);
945        let mut enumerators = Vec::new();
946        let mut stack = vec![node];
947        while let Some(current) = stack.pop() {
948            if current != node && current.kind() == "enum_specifier" {
949                continue;
950            }
951            if current.kind() == "enumerator"
952                && let Some(name) = current.child_by_field_name("name")
953                && let Some(name) = node_text(name, source)
954            {
955                enumerators.push(EnumeratorFact {
956                    range: node_range(current),
957                    enum_range: range,
958                    name: name.to_owned(),
959                });
960            }
961            stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
962        }
963        Some((EnumFact { range, name }, enumerators))
964    }
965
966    fn name<'tree>(&self, node: Node<'tree>) -> Option<Node<'tree>> {
967        expression_name_node(node)
968    }
969}
970
971/// The persisted C++ generator label and the query-time rows share one classifier.
972pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
973    brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
974        &CPP_STRUCTURAL_SPEC,
975    )
976    .expect("C++ statement syntax")
977    .classify(node, source, &Default::default())
978}
979
980pub(crate) fn capture_statement_fact_rows(
981    node: Node<'_>,
982    source: &str,
983    rows: &mut brokk_bifrost_core::analyzer::statement_facts::StatementFactRows,
984) {
985    brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
986        &CPP_STRUCTURAL_SPEC,
987    )
988    .expect("C++ statement syntax")
989    .capture(node, source, &Default::default(), rows);
990}
991
992#[cfg(test)]
993fn alloca_is_in_unbounded_loop(node: Node<'_>, source: &str) -> bool {
994    statement_fact_kind(node, source) == Some(StatementFactKind::AllocaInLoop)
995}
996
997#[cfg(test)]
998fn enum_switch_is_missing_case(node: Node<'_>, source: &str) -> bool {
999    let producer = brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
1000        &CPP_STRUCTURAL_SPEC,
1001    )
1002    .unwrap();
1003    let mut rows = brokk_bifrost_core::analyzer::statement_facts::StatementFactRows::default();
1004    producer.capture(node, source, &Default::default(), &mut rows);
1005    rows.switches.iter().any(|fact| fact.missing_enum_case)
1006}
1007
1008/// Whether tree-sitter recovered `.field = value` as a declaration child pair:
1009/// an `ERROR` containing only the anonymous dot immediately followed by an
1010/// `init_declarator` whose declarator is a real identifier.
1011///
1012/// This is intentionally narrower than rejecting declarators below `ERROR` in
1013/// general: macro-decorated declarations and ordinary comma-separated initialized
1014/// declarations also use recovery nodes and remain real declarations.
1015pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
1016    if node.kind() != "init_declarator" {
1017        return false;
1018    }
1019    let Some(identifier) = node.child_by_field_name("declarator") else {
1020        return false;
1021    };
1022    if identifier.kind() != "identifier" || identifier.is_missing() {
1023        return false;
1024    }
1025    let Some(previous) = node.prev_named_sibling() else {
1026        return false;
1027    };
1028    if previous.kind() != "ERROR"
1029        || previous.named_child_count() != 0
1030        || previous.child_count() != 1
1031        || previous.end_byte() != node.start_byte()
1032    {
1033        return false;
1034    }
1035    previous.child(0).is_some_and(|child| {
1036        child.kind() == "."
1037            && !child.is_named()
1038            && child.start_byte() == previous.start_byte()
1039            && child.end_byte() == previous.end_byte()
1040    })
1041}
1042
1043use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
1044
1045/// The declared name of a `namespace_definition`.
1046///
1047/// `namespace a::b { }` spells a `nested_namespace_specifier` that nests to
1048/// the right, so the innermost specifier's last child is the terminal segment.
1049/// An anonymous namespace has no `name` field at all and stays unnamed.
1050fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
1051    let mut current = namespace.child_by_field_name("name")?;
1052    while current.kind() == "nested_namespace_specifier" {
1053        current = current.named_child(current.named_child_count().checked_sub(1)?)?;
1054    }
1055    Some(current)
1056}
1057
1058pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
1059    let mut current = declarator;
1060    loop {
1061        match current.kind() {
1062            "identifier"
1063            | "field_identifier"
1064            | "namespace_identifier"
1065            | "type_identifier"
1066            | "destructor_name"
1067            | "operator_name"
1068            | "operator_cast"
1069            | "primitive_type" => return Some(current),
1070            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1071            "dependent_name" => current = first_named_child(current)?,
1072            "template_function" | "template_method" | "template_type" => {
1073                current = current.child_by_field_name("name")?;
1074            }
1075            "function_declarator"
1076            | "pointer_declarator"
1077            | "pointer_type_declarator"
1078            | "macro_modifier_declarator"
1079            | "variadic_declarator"
1080            | "array_declarator"
1081            | "init_declarator" => current = current.child_by_field_name("declarator")?,
1082            "attributed_declarator" => current = first_named_child(current)?,
1083            "reference_declarator" | "parenthesized_declarator" => {
1084                current = first_named_child(current)?;
1085            }
1086            _ => return None,
1087        }
1088    }
1089}
1090
1091/// Grammar steps for the shared call-callee spine. A value expression has no step.
1092pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1093    use CalleeSpineStep::{Field, FieldOrOnlyChild, Name, OnlyChild};
1094    match kind {
1095        "identifier"
1096        | "field_identifier"
1097        | "type_identifier"
1098        | "operator_name"
1099        | "operator_cast"
1100        | "destructor_name"
1101        | "literal_operator_name"
1102        | "primitive_type" => Some(Name),
1103        "qualified_identifier"
1104        | "scoped_identifier"
1105        | "scoped_type_identifier"
1106        | "template_function"
1107        | "template_method"
1108        | "template_type" => Some(Field("name")),
1109        "dependent_name" => Some(OnlyChild),
1110        "field_expression" => Some(Field("field")),
1111        "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1112        _ => None,
1113    }
1114}
1115
1116fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1117    let mut current = expression;
1118    loop {
1119        match current.kind() {
1120            "identifier"
1121            | "field_identifier"
1122            | "namespace_identifier"
1123            | "type_identifier"
1124            | "destructor_name"
1125            | "operator_name"
1126            | "primitive_type"
1127            | "this" => return Some(current),
1128            "qualified_identifier" => current = last_named_field_child(current, "name")?,
1129            "dependent_name" => current = first_named_child(current)?,
1130            "template_function" | "template_method" | "template_type" => {
1131                current = current.child_by_field_name("name")?;
1132            }
1133            "field_expression" => current = current.child_by_field_name("field")?,
1134            "call_expression" => current = current.child_by_field_name("function")?,
1135            "new_expression" => current = current.child_by_field_name("type")?,
1136            "parenthesized_expression" => current = first_named_child(current)?,
1137            _ => return declarator_name_node(current),
1138        }
1139    }
1140}
1141
1142fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1143    if function.kind() != "qualified_identifier" {
1144        return;
1145    }
1146    if let Some(scope) = function.child_by_field_name("scope") {
1147        attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1148    }
1149}
1150
1151fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1152    loop {
1153        if node.kind() == "qualified_identifier" {
1154            return Some(node);
1155        }
1156        node = node
1157            .child_by_field_name("declarator")
1158            .or_else(|| node.child_by_field_name("name"))
1159            .or_else(|| first_named_child(node))?;
1160    }
1161}
1162
1163fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1164    node.child_by_field_name("declarator")
1165        .and_then(qualified_declarator_node)
1166        .and_then(|qualified| qualified.child_by_field_name("scope"))
1167}
1168
1169pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1170    node_text(scope, source) == node_text(name, source)
1171}
1172
1173fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1174    node.child_by_field_name("declarator")
1175        .and_then(qualified_declarator_node)
1176        .and_then(|qualified| {
1177            Some((
1178                expression_name_node(qualified.child_by_field_name("scope")?)?,
1179                expression_name_node(last_named_field_child(qualified, "name")?)?,
1180            ))
1181        })
1182        .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1183}
1184
1185fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1186    if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1187        return None;
1188    }
1189    unquoted_string_content_span(node)
1190}
1191
1192/// The names of every function-like macro this translation unit defines, read
1193/// from `preproc_function_def` name fields.
1194///
1195/// The walk is iterative and descends only through preprocessor conditional
1196/// blocks, which is where a definition can nest. A `#define` written inside a
1197/// function body is not collected; that is a known and stated boundary, not an
1198/// approximation of one.
1199fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1200    let mut names = HashSet::default();
1201    let mut stack = vec![root];
1202    while let Some(node) = stack.pop() {
1203        for child in named_children_iter(node) {
1204            match child.kind() {
1205                "preproc_function_def" => {
1206                    if let Some(name) = child.child_by_field_name("name") {
1207                        names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1208                    }
1209                }
1210                "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1211                | "preproc_elifdef" => stack.push(child),
1212                _ => {}
1213            }
1214        }
1215    }
1216    names
1217}
1218
1219/// Return the one identifier-bearing node that spells a member in a C++
1220/// `field_expression`.
1221///
1222/// The grammar permits the `field` child to be a qualified name, a dependent
1223/// name, a template method, or a destructor name. `expression_name_node`
1224/// already follows those wrappers to their terminal name. Walking upward from
1225/// the candidate and comparing that terminal node keeps receiver identifiers,
1226/// declaration names, field-designator labels, and path prefixes out of the
1227/// member role without looking at source text.
1228fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1229    if !matches!(
1230        node.kind(),
1231        "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1232    ) {
1233        return None;
1234    }
1235
1236    let mut current = node;
1237    while let Some(parent) = current.parent() {
1238        if parent.kind() == "field_expression" {
1239            let field = parent.child_by_field_name("field")?;
1240            return expression_name_node(field)
1241                .filter(|name| name.id() == node.id())
1242                .map(|_| OccurrenceRole::MemberPosition);
1243        }
1244
1245        // These are the only named wrappers that can occur between the
1246        // terminal field name and its owning field expression.
1247        if matches!(
1248            parent.kind(),
1249            "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1250        ) {
1251            current = parent;
1252        } else {
1253            return None;
1254        }
1255    }
1256    None
1257}
1258
1259/// What declares a C or C++ binder.
1260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1261enum CppBinderOwner<'tree> {
1262    /// A parameter of a function definition, a lambda or a `catch` clause.
1263    Parameter,
1264    /// A local declaration; the node is the declarator the name completes.
1265    Local(Node<'tree>),
1266    /// A range-based `for` variable; the node is the loop.
1267    RangeFor(Node<'tree>),
1268    /// A structured binding; the node is the binding declarator.
1269    StructuredBinding(Node<'tree>),
1270    /// A lambda init-capture (`[x = y]`).
1271    InitCapture,
1272}
1273
1274/// Declarator wrappers that carry a name inward through their `declarator`
1275/// field or their single named child.
1276const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1277    "pointer_declarator",
1278    "array_declarator",
1279    "reference_declarator",
1280    "parenthesized_declarator",
1281    "attributed_declarator",
1282    "variadic_declarator",
1283];
1284
1285/// What declares `node` as a C or C++ variable, or `None` when it is not a
1286/// variable binder.
1287///
1288/// The name is found by walking up the declarator chain. A name that reaches
1289/// a function declarator without passing a pointer or reference declarator
1290/// names a function; one that does names a function pointer variable. A
1291/// parameter binds only in a function definition, a lambda or a `catch`
1292/// clause; a prototype's parameter names bind nothing. A file-scope or
1293/// member declaration names a member or a global, not a lexical binding.
1294fn cpp_binder_owner<'tree>(
1295    node: Node<'tree>,
1296    parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1297    mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1298) -> Option<CppBinderOwner<'tree>> {
1299    if node.kind() != "identifier" {
1300        return None;
1301    }
1302    let mut child = node;
1303    let mut current = parent(node)?;
1304    let mut indirect = false;
1305    loop {
1306        // This field lookup scans earlier siblings. Generated C initializer
1307        // lists can be enormous, so only ask for fields on binder-shaped nodes.
1308        match current.kind() {
1309            kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1310                let field = field_name(current, child);
1311                if field.is_some() && field != Some("declarator") {
1312                    return None;
1313                }
1314                indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1315            }
1316            "function_declarator" => {
1317                let field = field_name(current, child);
1318                if field != Some("declarator") || !indirect {
1319                    return None;
1320                }
1321            }
1322            "init_declarator" => {
1323                let field = field_name(current, child);
1324                if field != Some("declarator") {
1325                    return None;
1326                }
1327            }
1328            "declaration" => {
1329                let field = field_name(current, child);
1330                if field != Some("declarator") {
1331                    return None;
1332                }
1333                let local = std::iter::successors(parent(current), |node| parent(*node))
1334                    .take_while(|node| {
1335                        !matches!(
1336                            node.kind(),
1337                            "translation_unit"
1338                                | "field_declaration_list"
1339                                | "declaration_list"
1340                                | "namespace_definition"
1341                        )
1342                    })
1343                    .any(|node| {
1344                        matches!(
1345                            node.kind(),
1346                            "compound_statement" | "for_statement" | "condition_clause"
1347                        )
1348                    });
1349                // The name's scope starts at the end of its declarator, before
1350                // any initializer.
1351                let declarator = if child.kind() == "init_declarator" {
1352                    child.child_by_field_name("declarator")?
1353                } else {
1354                    child
1355                };
1356                return local.then_some(CppBinderOwner::Local(declarator));
1357            }
1358            "parameter_declaration"
1359            | "optional_parameter_declaration"
1360            | "variadic_parameter_declaration" => {
1361                let field = field_name(current, child);
1362                if field != Some("declarator") {
1363                    return None;
1364                }
1365                let parameter_parent = parent(current)?;
1366                let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1367                    // The explicit-object wrapper owns exactly `this` and this
1368                    // parameter; only then may its parent supply the list.
1369                    if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1370                        || parameter_parent.named_child(1) != Some(current)
1371                    {
1372                        return None;
1373                    }
1374                    parent(parameter_parent)?
1375                } else {
1376                    parameter_parent
1377                };
1378                if list.kind() != "parameter_list" {
1379                    return None;
1380                }
1381                let owner = parent(list)?;
1382                let binds = match owner.kind() {
1383                    "catch_clause" => true,
1384                    "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1385                        .is_some_and(|lambda| {
1386                            lambda.kind() == "lambda_expression"
1387                                && lambda.child_by_field_name("declarator") == Some(owner)
1388                        }),
1389                    "function_declarator" => {
1390                        let mut declarator = owner;
1391                        loop {
1392                            let Some(up) = parent(declarator) else {
1393                                break false;
1394                            };
1395                            if up.kind() == "function_definition" {
1396                                break field_name_in_parent(up, declarator) == Some("declarator");
1397                            }
1398                            if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1399                                break false;
1400                            }
1401                            declarator = up;
1402                        }
1403                    }
1404                    _ => false,
1405                };
1406                return binds.then_some(CppBinderOwner::Parameter);
1407            }
1408            "for_range_loop" => {
1409                let field = field_name(current, child);
1410                return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1411            }
1412            "structured_binding_declarator" => {
1413                return Some(CppBinderOwner::StructuredBinding(current));
1414            }
1415            "lambda_capture_initializer" => {
1416                let field = field_name(current, child);
1417                return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1418            }
1419            _ => return None,
1420        }
1421        child = current;
1422        current = parent(current)?;
1423    }
1424}
1425
1426/// Whether `node` declares a C or C++ variable, climbing through the walk's
1427/// parent index: asking `Node::parent` re-descends from the root, which is
1428/// quadratic over a large file.
1429fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1430    cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1431}
1432
1433/// The binding one C or C++ binder token introduces, and the interval it is
1434/// in effect over.
1435///
1436/// A parameter or init-capture is in effect over its whole function, lambda
1437/// or catch clause. A local is in effect from the end of its declarator: C
1438/// and C++ start a name's scope there, before its initializer, so in
1439/// `int x = x;` the right side reads the new `x`. A condition declaration is
1440/// therefore in effect over the rest of its `if`, including `else`. A
1441/// range-`for` variable is in effect over the loop body, and a structured
1442/// binding from the end of its binding declarator.
1443fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1444    let binding = |kind, hoisting, activation| {
1445        Some(BindingActivation {
1446            kind,
1447            hoisting,
1448            activation,
1449        })
1450    };
1451    let after = |node: Node<'_>| Range {
1452        start_byte: node.end_byte(),
1453        end_byte: scope.end_byte,
1454        start_line: node.end_position().row + 1,
1455        end_line: scope.end_line,
1456    };
1457    match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1458        CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1459            binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1460        }
1461        CppBinderOwner::Local(declarator) => binding(
1462            BindingKind::Local,
1463            HoistingClass::SourceOrder,
1464            after(declarator),
1465        ),
1466        CppBinderOwner::RangeFor(loop_node) => binding(
1467            BindingKind::LoopVariable,
1468            HoistingClass::DeclaredHead,
1469            node_range(loop_node.child_by_field_name("body")?),
1470        ),
1471        CppBinderOwner::StructuredBinding(declarator) => binding(
1472            BindingKind::Local,
1473            HoistingClass::SourceOrder,
1474            after(declarator),
1475        ),
1476    }
1477}
1478
1479impl StructuralSpec for CppStructuralSpec {
1480    fn language(&self) -> Language {
1481        Language::Cpp
1482    }
1483
1484    fn supports_boolean_literal_value(&self) -> bool {
1485        true
1486    }
1487
1488    fn supports_string_literal_value(&self) -> bool {
1489        true
1490    }
1491
1492    fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1493        matches!(node.kind(), "string_literal" | "raw_string_literal")
1494            && cpp_string_literal_value(node, source).is_none()
1495    }
1496
1497    fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1498        cpp_string_literal_value(node, source)
1499    }
1500
1501    fn extra_emitted_kinds(&self) -> &'static [NormalizedKind] {
1502        // Refinement and embedded-leaf outputs beyond the grammar kind table.
1503        &[NormalizedKind::Constructor, NormalizedKind::Method]
1504    }
1505
1506    fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1507        CPP_KIND_TABLE
1508    }
1509
1510    fn statement_fact_syntax(
1511        &self,
1512    ) -> Option<&dyn brokk_bifrost_core::analyzer::statement_facts::producer::StatementFactSyntax>
1513    {
1514        Some(self)
1515    }
1516
1517    fn generator_construct(
1518        &self,
1519        node: Node<'_>,
1520        _kind: NormalizedKind,
1521        source: &str,
1522    ) -> Option<&'static str> {
1523        statement_fact_kind(node, source).map(StatementFactKind::construct)
1524    }
1525
1526    fn refine_kind(
1527        &self,
1528        node: Node<'_>,
1529        kind: NormalizedKind,
1530        enclosing: Option<NormalizedKind>,
1531        source: &str,
1532        _context: &CallSiteContext,
1533    ) -> NormalizedKind {
1534        if kind == NormalizedKind::Function
1535            && (enclosing == Some(NormalizedKind::Class)
1536                || scoped_function_definition(node).is_some())
1537        {
1538            if is_constructor_definition(node, source) {
1539                NormalizedKind::Constructor
1540            } else {
1541                NormalizedKind::Method
1542            }
1543        } else {
1544            kind
1545        }
1546    }
1547
1548    fn supports_kind(&self, kind: NormalizedKind) -> bool {
1549        matches!(
1550            kind,
1551            NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1552        ) || self
1553            .kind_table()
1554            .iter()
1555            .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1556    }
1557
1558    fn supports_role(&self, role: Role) -> bool {
1559        !matches!(
1560            role,
1561            Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1562        )
1563    }
1564
1565    fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1566        CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1567    }
1568
1569    /// A `new_expression` is a constructor call, and a call whose callee names
1570    /// a function-like macro of this translation unit has an argument list
1571    /// that belongs to the macro, not to the callable that finally runs
1572    /// (#1478). `CALL_TWICE(2)` parses as an ordinary call of two source
1573    /// arguments even when the expansion calls a three-parameter callable, so
1574    /// the honest answer is unknown coverage and no argument rows at all —
1575    /// never a fabricated list an exact-arity assertion could read as clean.
1576    ///
1577    /// The boundary is deliberate: only macros defined in this file are
1578    /// known here, because that is what this file's parse tree contains. A
1579    /// macro defined in an included header leaves the site exact, which is
1580    /// the same answer the analyzer gives today.
1581    fn call_site_facts(
1582        &self,
1583        node: Node<'_>,
1584        source: &str,
1585        context: &CallSiteContext,
1586    ) -> Option<CallSiteFacts> {
1587        if node.kind() == "new_expression" {
1588            return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1589        }
1590        let callee = node.child_by_field_name("function")?;
1591        (callee.kind() == "identifier"
1592            && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1593        .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1594    }
1595
1596    fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1597        static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1598            .supported(OccurrenceRole::MemberPosition)
1599            .supported(OccurrenceRole::ValueReference)
1600            .supported(OccurrenceRole::Binder);
1601        &SUPPORT
1602    }
1603
1604    fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1605        // C and C++ derive scopes and binding intervals; import binders and
1606        // the package clause are not derived. The call seams report
1607        // per-candidate callable applicability (#1478 M3).
1608        static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1609            .supported(EnvironmentAxis::Scopes)
1610            .supported(EnvironmentAxis::BindingIntervals)
1611            .supported(EnvironmentAxis::CallableApplicability);
1612        &SUPPORT
1613    }
1614
1615    /// An `if` statement is a scope: a C++ condition or init-statement
1616    /// declaration is in effect in both branches and nowhere after.
1617    fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1618        if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1619            ScopeFormation::BindingScope
1620        } else {
1621            default_scope_formation(kind)
1622        }
1623    }
1624
1625    fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1626        cpp_binding_activation(binder, scope)
1627    }
1628
1629    fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1630        &CPP_MATERIALIZATION_SUPPORT
1631    }
1632
1633    fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1634        &INVERSE_REFERENCE_EDGE_SUPPORT
1635    }
1636
1637    fn identity_route_support(&self) -> &IdentityRouteSupport {
1638        // C++'s occurrence adapter is shallow, so it claims no path axes. Its
1639        // declaration layer keeps prototype/body occurrences distinct, which
1640        // the physical-grouping axis carries, and the analyzer now labels each
1641        // of those occurrences a head or a body and pairs a prototype with the
1642        // out-of-line callable body that defines it, so the
1643        // declaration-definition peer relation is supplied as typed rows with
1644        // the body occurrence's own range as provenance (#1650). Classes stay
1645        // outside that relation: a same-file forward declaration is not a
1646        // distinct occurrence, and across files a class carries no linkage
1647        // evidence to pair on.
1648        static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1649            .supported_axis(IdentityAxis::CanonicalIdentity)
1650            .supported_axis(IdentityAxis::PhysicalGrouping)
1651            .supported_relation(RouteHopKind::NestedOwner)
1652            .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1653        &SUPPORT
1654    }
1655
1656    fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1657        if let Some(role) = cpp_member_position(node) {
1658            sink.occurrence_role(node, role);
1659        } else if cpp_is_binder(node, sink.parents()) {
1660            sink.occurrence_role(node, OccurrenceRole::Binder);
1661        }
1662
1663        match kind {
1664            NormalizedKind::Call => {
1665                let function_field = if node.kind() == "new_expression" {
1666                    "type"
1667                } else {
1668                    "function"
1669                };
1670                if let Some(function) = node.child_by_field_name(function_field) {
1671                    attach_terminal_callee(
1672                        sink,
1673                        function,
1674                        terminal_callee_leaf(function, callee_spine_step),
1675                    );
1676                    // The callee token of a free-function call is a plain value
1677                    // read, and it is the only occurrence the call site leaves
1678                    // behind when the function is not indexed (#3466). Member
1679                    // calls are excluded: their terminal field is already the
1680                    // member-position occurrence, and classifying it twice would
1681                    // report one call site as two. A composite callee
1682                    // expression names its own callee at an inner node
1683                    // (`f()()`), so it is excluded too.
1684                    if node.kind() == "call_expression"
1685                        && !matches!(function.kind(), "field_expression" | "call_expression")
1686                        && let Some(callee) = expression_name_node(function)
1687                        && self.kind_table().iter().any(|(node_kind, kind)| {
1688                            *node_kind == callee.kind() && self.should_extract(callee, *kind)
1689                        })
1690                    {
1691                        sink.occurrence_role(callee, OccurrenceRole::ValueReference);
1692                    }
1693                    if function.kind() == "field_expression"
1694                        && let Some(argument) = function.child_by_field_name("argument")
1695                    {
1696                        attach_role_with_derived_name(
1697                            sink,
1698                            Role::Receiver,
1699                            argument,
1700                            expression_name_node,
1701                        );
1702                    }
1703                    attach_qualified_scope_receiver(sink, function);
1704                }
1705                if let Some(arguments) = node.child_by_field_name("arguments") {
1706                    attach_positional_argument_roles(sink, arguments, expression_name_node);
1707                }
1708            }
1709            NormalizedKind::FieldAccess => {
1710                if let Some(field) = node.child_by_field_name("field") {
1711                    attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
1712                    if let Some(name) = expression_name_node(field) {
1713                        sink.set_name(name);
1714                    }
1715                }
1716                if let Some(argument) = node.child_by_field_name("argument") {
1717                    attach_role_with_derived_name(
1718                        sink,
1719                        Role::Object,
1720                        argument,
1721                        expression_name_node,
1722                    );
1723                }
1724            }
1725            NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
1726                if let Some(name) = node
1727                    .child_by_field_name("declarator")
1728                    .and_then(declarator_name_node)
1729                {
1730                    sink.set_name(name);
1731                }
1732            }
1733            NormalizedKind::Module => {
1734                if let Some(name) = namespace_name_node(node) {
1735                    sink.set_name(name);
1736                }
1737            }
1738            NormalizedKind::Class | NormalizedKind::Declaration => {
1739                if let Some(name) = node
1740                    .child_by_field_name("name")
1741                    .and_then(declarator_name_node)
1742                    .or_else(|| node.child_by_field_name("name"))
1743                {
1744                    sink.set_name(name);
1745                }
1746            }
1747            NormalizedKind::Assignment => match node.kind() {
1748                "init_declarator" => {
1749                    if let Some(declarator) = node.child_by_field_name("declarator") {
1750                        attach_role_with_derived_name(
1751                            sink,
1752                            Role::Left,
1753                            declarator,
1754                            declarator_name_node,
1755                        );
1756                        if let Some(name) = declarator_name_node(declarator) {
1757                            sink.set_name(name);
1758                        }
1759                    }
1760                    if let Some(value) = node.child_by_field_name("value") {
1761                        attach_role_with_derived_name(
1762                            sink,
1763                            Role::Right,
1764                            value,
1765                            expression_name_node,
1766                        );
1767                    }
1768                }
1769                "assignment_expression" => {
1770                    if let Some(left) = node.child_by_field_name("left") {
1771                        attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1772                    }
1773                    if let Some(right) = node.child_by_field_name("right") {
1774                        attach_role_with_derived_name(
1775                            sink,
1776                            Role::Right,
1777                            right,
1778                            expression_name_node,
1779                        );
1780                    }
1781                }
1782                _ => {}
1783            },
1784            NormalizedKind::Import => {
1785                if let Some(path) = node.child_by_field_name("path") {
1786                    if let Some(name) = unquoted_include_span(path) {
1787                        sink.role_named_span(Role::Module, path, name);
1788                    } else {
1789                        attach_role_with_derived_name(
1790                            sink,
1791                            Role::Module,
1792                            path,
1793                            expression_name_node,
1794                        );
1795                    }
1796                }
1797            }
1798            NormalizedKind::Identifier => match expression_name_node(node) {
1799                Some(name) => sink.set_name(name),
1800                None => sink.set_name(node),
1801            },
1802            _ => {}
1803        }
1804    }
1805}
1806
1807#[cfg(test)]
1808mod tests {
1809    use super::CPP_STRUCTURAL_SPEC;
1810    use super::cpp_binder_owner;
1811    use super::cpp_member_position;
1812    use super::field_name_in_parent;
1813    use super::node_range;
1814    use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
1815    use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
1816    use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
1817    use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
1818    use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
1819    use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
1820    use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
1821    use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
1822    use brokk_bifrost_core::hash::HashMap;
1823    use brokk_bifrost_test_macros::hardwon;
1824    use tree_sitter::Parser;
1825
1826    #[test]
1827    #[hardwon(
1828        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: unrelated comparisons suppress alloca-in-loop"
1829    )]
1830    fn alloca_bound_must_constrain_loop_variable() {
1831        for (condition, expected) in [
1832            ("i < n && enabled < 1", true),
1833            ("i < n || i < 1", true),
1834            ("i < 1", false),
1835            ("1 > i && enabled", false),
1836        ] {
1837            let source = format!(
1838                "void f(int n, int enabled) {{ for (int i=0; {condition}; ++i) {{ alloca(16); }} }}"
1839            );
1840            let mut parser = Parser::new();
1841            parser
1842                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1843                .unwrap();
1844            let tree = parser.parse(&source, None).unwrap();
1845            let mut stack = vec![tree.root_node()];
1846            let mut actual = None;
1847            while let Some(node) = stack.pop() {
1848                if node.kind() == "call_expression" {
1849                    actual = Some(super::alloca_is_in_unbounded_loop(node, &source));
1850                }
1851                stack.extend(super::named_children_iter(node));
1852            }
1853            assert_eq!(actual, Some(expected), "{source}");
1854        }
1855    }
1856
1857    #[test]
1858    #[hardwon(
1859        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: integer switches borrow unrelated enum cases"
1860    )]
1861    fn integer_switch_is_not_an_enum_switch() {
1862        let source = "enum E { A, B }; void f(int value) { switch(value) { case A: break; } }";
1863        let mut parser = Parser::new();
1864        parser
1865            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1866            .unwrap();
1867        let tree = parser.parse(source, None).unwrap();
1868        let mut stack = vec![tree.root_node()];
1869        let mut checked = false;
1870        while let Some(node) = stack.pop() {
1871            if node.kind() == "switch_statement" {
1872                checked = true;
1873                assert!(!super::enum_switch_is_missing_case(node, source));
1874            }
1875            stack.extend(super::named_children_iter(node));
1876        }
1877        assert!(checked);
1878    }
1879
1880    #[test]
1881    #[hardwon(
1882        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: enum coverage needs scoped local declaration evidence"
1883    )]
1884    fn enum_switch_uses_local_type_evidence() {
1885        for (source, expected) in [
1886            (
1887                "enum E { A,B }; void f(E e) { switch(e) {case A:break;} }",
1888                vec![true],
1889            ),
1890            (
1891                "enum E { A,B }; void f() { E e=A; switch(e) {case A:break;} }",
1892                vec![true],
1893            ),
1894            (
1895                "enum class E { A,B }; void f(E e) { switch(e) {case E::A:break;} }",
1896                vec![true],
1897            ),
1898            (
1899                "namespace ns { enum class E { A,B }; } void f(ns::E e) { switch(e) {case ns::E::A:break;} }",
1900                vec![true],
1901            ),
1902            (
1903                "enum E { A,B }; struct S { E state; }; void f(S s) { switch(s.state) {case A:break;} }",
1904                vec![true],
1905            ),
1906            (
1907                "enum E { A,B }; struct S { E state; }; void f() { S s; switch(s.state) {case A:break;} }",
1908                vec![true],
1909            ),
1910            (
1911                "enum E { A,B }; void f(E e) { { int e=0; switch(e) {case A:break;} } switch(e) {case A:break;} }",
1912                vec![false, true],
1913            ),
1914            (
1915                "enum E { A,B }; void f(E e) { { int e=0; } switch(e) {case A:break;} }",
1916                vec![true],
1917            ),
1918            (
1919                "enum E { A,B }; void f() { switch(e) {case A:break;} E e=A; }",
1920                vec![false],
1921            ),
1922            (
1923                "enum E { A,B }; using Alias=E; void f(Alias e) { switch(e) {case A:break;} }",
1924                vec![false],
1925            ),
1926            (
1927                "enum E { A,B }; void f(E e) { switch(e==A) {case A:break;} }",
1928                vec![false],
1929            ),
1930            (
1931                "enum E { A,B }; E e; void f() { switch(e) {case A:break;} }",
1932                vec![false],
1933            ),
1934            (
1935                "enum E { A,B }; void f(E &e) { switch(e) {case A:break;} }",
1936                vec![true],
1937            ),
1938            (
1939                "enum E { A,B }; void f(E *e) { switch(e) {case A:break;} }",
1940                vec![false],
1941            ),
1942            (
1943                "enum E { A,B }; struct S { E state; }; void f(S *s) { switch(s->state) {case A:break;} }",
1944                vec![true],
1945            ),
1946            (
1947                "enum E { A,B }; struct S { int state; }; void f(S s) { switch(s.state) {case A:break;} }",
1948                vec![false],
1949            ),
1950            (
1951                "enum E { A,B }; struct Inner { E state; }; struct Outer { Inner inner; }; void f(Outer s) { switch(s.inner.state) {case A:break;} }",
1952                vec![true],
1953            ),
1954            (
1955                "void f() { enum class E { A,B }; E e=E::A; switch(e) {case E::A:break;} }",
1956                vec![true],
1957            ),
1958            (
1959                "struct S { enum class E { A,B }; E state; }; void f(S s) { switch(s.state) {case S::E::A:break;} }",
1960                vec![true],
1961            ),
1962            (
1963                "namespace n { namespace m { enum class E { A,B }; } } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1964                vec![true],
1965            ),
1966            (
1967                "enum E { A,B }; void f() { using E=int; E e=0; switch(e) {case A:break;} }",
1968                vec![false],
1969            ),
1970            (
1971                "enum E { A,B }; void f(E e) { { int e(); switch(e) {case A:break;} } }",
1972                vec![false],
1973            ),
1974            (
1975                "enum E { A,B }; void f(E e) { auto g=[e]() { switch(e) {case A:break;} }; }",
1976                vec![false],
1977            ),
1978            (
1979                "enum E { A,B }; void f() { auto g=[](E e) { switch(e) {case A:break;} }; }",
1980                vec![true],
1981            ),
1982            (
1983                "namespace n::m { enum class E { A,B }; } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1984                vec![true],
1985            ),
1986            (
1987                "void f() { enum E { A,B } e=A; switch(e) {case A:break;} }",
1988                vec![true],
1989            ),
1990            (
1991                "enum E { A,B }; void f(E (&values)[2]) { for (E e : values) { switch(e) {case A:break;} } }",
1992                vec![true],
1993            ),
1994            (
1995                "enum E { A,B }; namespace n { enum E { A,C }; } void f() { using n::E; E e; switch(e) {case A:break;} }",
1996                vec![false],
1997            ),
1998            (
1999                "enum E { A,B }; namespace n { enum E { A }; void f(E); } void n::f(E e) { switch(e) {case n::A:break;} }",
2000                vec![false],
2001            ),
2002            (
2003                "enum E { A,B }; void f(E e) { switch(int e=0; e) {case A:break;} }",
2004                vec![false],
2005            ),
2006            (
2007                "enum E { A,B }; void f() { switch(E e=A; e) {case A:break;} }",
2008                vec![true],
2009            ),
2010            (
2011                "enum E { A,B }; template<typename E> void f(E e) { switch(e) {case A:break;} }",
2012                vec![false],
2013            ),
2014            (
2015                "enum E { A,B }; template<typename... T> void f(T... e) { switch(e) {case A:break;} }",
2016                vec![false],
2017            ),
2018            (
2019                "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;} } }",
2020                vec![false],
2021            ),
2022            (
2023                "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;} }",
2024                vec![false],
2025            ),
2026            (
2027                "enum E { A,B }; struct Base { enum E { A }; }; struct S: Base { void f(E e) { switch(e) {case A:break;} } };",
2028                vec![false],
2029            ),
2030            (
2031                "enum E { A,B }; struct S { void f() { E e; switch(e) {case A:break;} } enum E { A }; };",
2032                vec![false],
2033            ),
2034        ] {
2035            let mut parser = Parser::new();
2036            parser
2037                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2038                .unwrap();
2039            let tree = parser.parse(source, None).unwrap();
2040            assert!(
2041                !tree.root_node().has_error(),
2042                "{source}: {}",
2043                tree.root_node().to_sexp()
2044            );
2045            let mut stack = vec![tree.root_node()];
2046            let mut results = Vec::new();
2047            while let Some(node) = stack.pop() {
2048                if node.kind() == "switch_statement" {
2049                    results.push((
2050                        node.start_byte(),
2051                        super::enum_switch_is_missing_case(node, source),
2052                    ));
2053                }
2054                stack.extend(super::named_children_iter(node));
2055            }
2056            results.sort_unstable_by_key(|(start, _)| *start);
2057            assert_eq!(
2058                results.into_iter().map(|(_, v)| v).collect::<Vec<_>>(),
2059                expected,
2060                "{source}"
2061            );
2062        }
2063    }
2064
2065    fn collect_cpp_structural_facts(source: &str) {
2066        collect_structural_rows(source);
2067    }
2068
2069    fn collect_structural_rows(source: &str) -> StructuralFactRows {
2070        let mut parser = Parser::new();
2071        parser
2072            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2073            .expect("C++ grammar");
2074        let tree = parser.parse(source, None).expect("C++ parse");
2075        let root = tree.root_node();
2076        let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
2077        let compiled = CompiledKinds::compile(
2078            &tree_sitter_cpp::LANGUAGE.into(),
2079            super::CPP_STRUCTURAL_SPEC.kind_table(),
2080        );
2081        let mut structural = StructuralFactCollector::new(
2082            &super::CPP_STRUCTURAL_SPEC,
2083            source,
2084            &context,
2085            ParentIndex::new(root),
2086            usize::MAX,
2087            None,
2088        );
2089        let mut source_facts = PrimarySourceFactCollector::new(source);
2090        let mut pending = vec![(root, None)];
2091        while let Some((node, parent)) = pending.pop() {
2092            let mut child_parent = parent;
2093            if node.is_named()
2094                && let Some(raw_kind) = compiled.kind_of(&node)
2095                && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
2096            {
2097                let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
2098                    node,
2099                    raw_kind,
2100                    parent.map(|id| structural.normalized_kind(id)),
2101                    source,
2102                    &context,
2103                );
2104                let id = structural
2105                    .enter(node, kind, parent, &mut source_facts)
2106                    .expect("unbounded C++ structural collector");
2107                let mut sink = structural.role_sink(&mut source_facts);
2108                super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2109                structural
2110                    .accept_roles(id, sink.into_parts())
2111                    .expect("unbounded C++ structural roles");
2112                child_parent = Some(id);
2113            }
2114            for index in (0..node.named_child_count()).rev() {
2115                if let Some(child) = node.named_child(index) {
2116                    pending.push((child, child_parent));
2117                }
2118            }
2119        }
2120        structural
2121            .finish(root)
2122            .expect("C++ structural collection finishes")
2123    }
2124
2125    fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
2126        let mut parser = Parser::new();
2127        parser
2128            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2129            .expect("C++ grammar");
2130        let tree = parser.parse(source, None).expect("C++ parse");
2131        assert!(
2132            !tree.root_node().has_error(),
2133            "{}",
2134            tree.root_node().to_sexp()
2135        );
2136        let mut found = Vec::new();
2137        let mut pending = vec![tree.root_node()];
2138        while let Some(node) = pending.pop() {
2139            if let Some(role) = cpp_member_position(node) {
2140                found.push((
2141                    node.start_byte(),
2142                    &source[node.start_byte()..node.end_byte()],
2143                    role,
2144                ));
2145            }
2146            for index in (0..node.named_child_count()).rev() {
2147                if let Some(child) = node.named_child(index) {
2148                    pending.push(child);
2149                }
2150            }
2151        }
2152        found
2153    }
2154
2155    #[test]
2156    #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
2157    fn binder_classification_scans_large_initializer_lists_linearly() {
2158        use std::fmt::Write as _;
2159
2160        let mut source = String::from("int values[] = {");
2161        for index in 0..4096 {
2162            write!(source, "item_{index},").expect("append generated initializer item");
2163        }
2164        source.push_str("};\n");
2165
2166        let mut parser = Parser::new();
2167        parser
2168            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2169            .expect("C++ grammar");
2170        let tree = parser.parse(&source, None).expect("C++ parse");
2171        assert!(
2172            !tree.root_node().has_error(),
2173            "{}",
2174            tree.root_node().to_sexp()
2175        );
2176        let parents = ParentIndex::new(tree.root_node());
2177        let mut pending = vec![tree.root_node()];
2178        let mut identifiers = 0usize;
2179        let mut sibling_steps = 0usize;
2180        while let Some(node) = pending.pop() {
2181            if node.kind() == "identifier" {
2182                identifiers += 1;
2183                let _ = cpp_binder_owner(
2184                    node,
2185                    |node| parents.parent(node),
2186                    |parent, child| {
2187                        let mut cursor = parent.walk();
2188                        if !cursor.goto_first_child() {
2189                            return None;
2190                        }
2191                        loop {
2192                            sibling_steps += 1;
2193                            if cursor.node() == child {
2194                                return cursor.field_name();
2195                            }
2196                            if !cursor.goto_next_sibling() {
2197                                return None;
2198                            }
2199                        }
2200                    },
2201                );
2202            }
2203            let mut cursor = node.walk();
2204            let children = node.children(&mut cursor).collect::<Vec<_>>();
2205            pending.extend(children.into_iter().rev());
2206        }
2207
2208        assert!(
2209            identifiers >= 4096,
2210            "parsed {identifiers} initializer identifiers"
2211        );
2212        assert!(
2213            sibling_steps <= identifiers * 4,
2214            "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
2215        );
2216    }
2217
2218    #[test]
2219    #[brokk_bifrost_test_macros::hardwon(
2220        "#4407: ABI 15 lambda declarators lost parameter binder identity"
2221    )]
2222    fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
2223        let source = concat!(
2224            "int enclosing(int outer) {\n",
2225            "    return [outer](int lambda) {\n",
2226            "        return [lambda](int nested) {\n",
2227            "            return outer + lambda + nested;\n",
2228            "        }(lambda);\n",
2229            "    }(outer);\n",
2230            "}\n",
2231        );
2232        let mut parser = Parser::new();
2233        parser
2234            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2235            .expect("C++ grammar");
2236        let tree = parser.parse(source, None).expect("C++ parse");
2237        assert!(
2238            !tree.root_node().has_error(),
2239            "{}",
2240            tree.root_node().to_sexp()
2241        );
2242
2243        let identifier_at = |start_byte| {
2244            let mut pending = vec![tree.root_node()];
2245            while let Some(node) = pending.pop() {
2246                if node.kind() == "identifier" && node.start_byte() == start_byte {
2247                    return Some(node);
2248                }
2249                for index in (0..node.named_child_count()).rev() {
2250                    if let Some(child) = node.named_child(index) {
2251                        pending.push(child);
2252                    }
2253                }
2254            }
2255            None
2256        };
2257        let outer_parameter =
2258            identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
2259                .expect("outer parameter");
2260        let lambda_parameter =
2261            identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
2262                .expect("lambda parameter");
2263        let nested_parameter =
2264            identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
2265                .expect("nested parameter");
2266
2267        for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
2268            assert_eq!(
2269                cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
2270                Some(super::CppBinderOwner::Parameter)
2271            );
2272            assert_eq!(
2273                super::cpp_binding_activation(parameter, node_range(tree.root_node()))
2274                    .map(|activation| activation.kind),
2275                Some(BindingKind::Parameter)
2276            );
2277        }
2278
2279        for (start, name) in [
2280            (
2281                source
2282                    .find("return outer + lambda")
2283                    .expect("outer reference")
2284                    + "return ".len(),
2285                "outer",
2286            ),
2287            (
2288                source
2289                    .find("outer + lambda + nested")
2290                    .expect("lambda reference")
2291                    + "outer + ".len(),
2292                "lambda",
2293            ),
2294            (
2295                source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
2296                "nested",
2297            ),
2298        ] {
2299            let reference = identifier_at(start).expect("body reference identifier");
2300            assert_eq!(
2301                cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
2302                None,
2303                "body reference {name:?} was classified as a binder"
2304            );
2305        }
2306    }
2307
2308    #[test]
2309    #[brokk_bifrost_test_macros::hardwon(
2310        "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
2311    )]
2312    fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
2313        let source = concat!(
2314            "struct Visitor {\n",
2315            "    int visit(this Visitor const& self, int ordinary) {\n",
2316            "        return self.value + ordinary;\n",
2317            "    }\n",
2318            "};\n",
2319            "using Callback = void (*)(int nested);\n",
2320        );
2321        let mut parser = Parser::new();
2322        parser
2323            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2324            .expect("C++ grammar");
2325        let tree = parser.parse(source, None).expect("C++ parse");
2326        assert!(
2327            !tree.root_node().has_error(),
2328            "{}",
2329            tree.root_node().to_sexp()
2330        );
2331
2332        let identifier_at = |start: usize, len: usize| {
2333            tree.root_node()
2334                .descendant_for_byte_range(start, start + len)
2335                .filter(|node| node.kind() == "identifier")
2336                .expect("identifier at source span")
2337        };
2338        let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2339        let receiver_body = identifier_at(
2340            source.find("return self").expect("receiver body reference") + "return ".len(),
2341            4,
2342        );
2343        let function_type_parameter =
2344            identifier_at(source.find("nested").expect("function type"), 6);
2345        let parents = ParentIndex::new(tree.root_node());
2346
2347        assert_eq!(receiver.kind(), "identifier");
2348        assert_eq!(
2349            cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2350            Some(super::CppBinderOwner::Parameter)
2351        );
2352        assert!(super::cpp_is_binder(receiver, &parents));
2353        assert_eq!(
2354            super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2355                .map(|activation| activation.kind),
2356            Some(BindingKind::Parameter)
2357        );
2358        assert_eq!(
2359            cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2360            None,
2361            "the receiver read in the body is not another binder"
2362        );
2363        assert!(!super::cpp_is_binder(receiver_body, &parents));
2364        assert_eq!(
2365            cpp_binder_owner(
2366                function_type_parameter,
2367                |node| node.parent(),
2368                field_name_in_parent
2369            ),
2370            None,
2371            "a function-type parameter remains outside callable binding ownership"
2372        );
2373    }
2374
2375    #[test]
2376    fn attributed_declarator_resolves_its_declared_name() {
2377        let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2378        let mut parser = Parser::new();
2379        parser
2380            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2381            .expect("C++ grammar");
2382        let tree = parser.parse(source, None).expect("C++ parse");
2383        assert!(
2384            !tree.root_node().has_error(),
2385            "{}",
2386            tree.root_node().to_sexp()
2387        );
2388
2389        let mut pending = vec![tree.root_node()];
2390        let mut attributed = None;
2391        let mut conversion = None;
2392        while let Some(node) = pending.pop() {
2393            match node.kind() {
2394                "attributed_declarator" => attributed = Some(node),
2395                "operator_cast" => conversion = Some(node),
2396                _ => {}
2397            }
2398            for index in (0..node.named_child_count()).rev() {
2399                if let Some(child) = node.named_child(index) {
2400                    pending.push(child);
2401                }
2402            }
2403        }
2404        let attributed = attributed.expect("attributed declarator");
2405        let name = super::declarator_name_node(attributed).expect("declarator name");
2406
2407        assert_eq!(&source[name.byte_range()], "x");
2408        let conversion = conversion.expect("conversion operator");
2409        assert_eq!(
2410            super::declarator_name_node(conversion).map(|node| node.kind()),
2411            Some("operator_cast")
2412        );
2413    }
2414
2415    #[test]
2416    fn cpp_member_position_is_limited_to_member_access_and_calls() {
2417        let source = concat!(
2418            "struct Widget {\n",
2419            "    int value;\n",
2420            "    int method(int label) {\n",
2421            "        return this->value + label.value + label.method()\n",
2422            "            + label.template convert<int>() + label.ns::member;\n",
2423            "    }\n",
2424            "};\n",
2425            "int build(Widget widget) {\n",
2426            "    Widget result{.value = widget.value};\n",
2427            "    return widget.method(result.value);\n",
2428            "}\n",
2429        );
2430        let found = member_occurrences(source);
2431        let member_texts = found
2432            .iter()
2433            .map(|(_, text, role)| (*text, *role))
2434            .collect::<Vec<_>>();
2435
2436        assert_eq!(
2437            member_texts,
2438            vec![
2439                ("value", OccurrenceRole::MemberPosition),
2440                ("value", OccurrenceRole::MemberPosition),
2441                ("method", OccurrenceRole::MemberPosition),
2442                ("convert", OccurrenceRole::MemberPosition),
2443                ("member", OccurrenceRole::MemberPosition),
2444                ("value", OccurrenceRole::MemberPosition),
2445                ("method", OccurrenceRole::MemberPosition),
2446                ("value", OccurrenceRole::MemberPosition),
2447            ]
2448        );
2449
2450        let at = |needle: &str| source.find(needle).expect("fixture token");
2451        for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2452            let receiver_start = at(receiver);
2453            assert!(
2454                found.iter().all(|(offset, _, _)| *offset != receiver_start),
2455                "receiver was classified: {receiver:?}"
2456            );
2457        }
2458        let non_members = [
2459            at("Widget {"),
2460            at("int value") + "int ".len(),
2461            at("int method") + "int ".len(),
2462            at(".value =") + 1,
2463        ];
2464        for unrelated_start in non_members {
2465            assert!(
2466                found
2467                    .iter()
2468                    .all(|(offset, _, _)| *offset != unrelated_start),
2469                "non-member identifier at byte {unrelated_start} was classified"
2470            );
2471        }
2472    }
2473
2474    #[test]
2475    #[brokk_bifrost_test_macros::hardwon(
2476        "#3466: C and C++ free-function calls published no resolver occurrence"
2477    )]
2478    fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2479        let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2480        assert!(support.is_supported(OccurrenceRole::MemberPosition));
2481        assert!(support.is_supported(OccurrenceRole::ValueReference));
2482        for role in [
2483            OccurrenceRole::ReceiverPosition,
2484            OccurrenceRole::LabelOrKey,
2485            OccurrenceRole::DeclarationName,
2486        ] {
2487            assert!(
2488                !support.is_supported(role),
2489                "unexpected C++ support for {role:?}"
2490            );
2491        }
2492    }
2493
2494    #[test]
2495    fn cpp_string_literals_publish_decoded_values() {
2496        let source = r#"
2497const char *ordinary = "http://example.test/a\n";
2498const char *raw = R"(https://example.test/path)";
2499const char *wide = L"wide";
2500const char character = 'x';
2501const char *mixed = R"(\u0041)" "\u0042";
2502"#;
2503        let rows = collect_structural_rows(source);
2504        let values = rows
2505            .nodes()
2506            .iter()
2507            .filter(|node| node.kind == NormalizedKind::StringLiteral)
2508            .map(|node| node.string_value.clone())
2509            .collect::<Vec<_>>();
2510
2511        assert_eq!(
2512            values,
2513            vec![
2514                Some("http://example.test/a\n".to_owned()),
2515                Some("https://example.test/path".to_owned()),
2516                None,
2517                None,
2518                Some(r"\u0041".to_owned()),
2519                Some("B".to_owned()),
2520            ]
2521        );
2522    }
2523
2524    #[test]
2525    fn cpp_numeric_string_escapes_follow_scalar_and_ascii_boundaries() {
2526        // The grammar selects escape_sequence nodes; Unicode scalar validity
2527        // and the portable ASCII byte policy independently decide their values.
2528        let mut cases = vec![
2529            (
2530                r"\101\x42\u0043\U00000044".to_owned(),
2531                Some("ABCD".to_owned()),
2532            ),
2533            (r"\177".to_owned(), Some("\u{7f}".to_owned())),
2534            (r"\200".to_owned(), None),
2535            (r"\377".to_owned(), None),
2536            (r"\x7f".to_owned(), Some("\u{7f}".to_owned())),
2537            (r"\x80".to_owned(), None),
2538            (r"\x0041".to_owned(), Some("A".to_owned())),
2539            (r"\x007f".to_owned(), Some("\u{7f}".to_owned())),
2540            (r"\x0080".to_owned(), None),
2541            (r"\xfffffffff".to_owned(), None),
2542        ];
2543        for scalar in [
2544            0,
2545            0x7f,
2546            0x80,
2547            0xd7ff,
2548            0xd800,
2549            0xdfff,
2550            0xe000,
2551            0x10ffff,
2552            0x110000,
2553            u32::MAX,
2554        ] {
2555            let expected = char::from_u32(scalar).map(|character| character.to_string());
2556            cases.push((format!("\\U{scalar:08x}"), expected.clone()));
2557            if scalar <= 0xffff {
2558                cases.push((format!("\\u{scalar:04x}"), expected));
2559            }
2560        }
2561        for (escape, expected) in cases {
2562            let source = format!("const char *value = \"{escape}\";");
2563            let mut parser = Parser::new();
2564            parser
2565                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2566                .unwrap();
2567            let tree = parser.parse(&source, None).unwrap();
2568            assert!(!tree.root_node().has_error(), "{source}");
2569            let mut stack = vec![tree.root_node()];
2570            let mut escapes = Vec::new();
2571            while let Some(node) = stack.pop() {
2572                if node.kind() == "escape_sequence" {
2573                    escapes.push(node.byte_range());
2574                }
2575                stack.extend(node.named_children(&mut node.walk()));
2576            }
2577            assert!(!escapes.is_empty(), "grammar did not select {source}");
2578            if escape.starts_with(r"\x00") {
2579                let expected_span = 21..21 + escape.len();
2580                assert_eq!(
2581                    escapes.as_slice(),
2582                    std::slice::from_ref(&expected_span),
2583                    "{source}"
2584                );
2585            }
2586            let rows = collect_structural_rows(&source);
2587            let values: Vec<_> = rows
2588                .nodes()
2589                .iter()
2590                .filter(|node| node.kind == NormalizedKind::StringLiteral)
2591                .map(|node| node.string_value.clone())
2592                .collect();
2593            assert_eq!(values, vec![expected], "{source}, escapes={escapes:?}");
2594        }
2595    }
2596
2597    /// The callee token of a free-function call is the only occurrence a call
2598    /// site leaves behind when the function is not indexed (#3466). Member
2599    /// calls stay member-position-only, and a composite callee expression
2600    /// names its own callee at an inner node.
2601    #[test]
2602    #[brokk_bifrost_test_macros::hardwon(
2603        "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2604    )]
2605    fn cpp_free_function_callees_are_value_references() {
2606        let source = concat!(
2607            "int bound(void) { return 1; }\n",
2608            "struct Widget { int method(void) { return 0; } };\n",
2609            "int go(struct Widget *widget) {\n",
2610            "    return getenv(\"PATH\") != 0\n",
2611            "        + ns::getenv(\"USER\") != 0\n",
2612            "        + bound()\n",
2613            "        + widget->method()\n",
2614            "        + bound()();\n",
2615            "}\n",
2616        );
2617        let mut parser = Parser::new();
2618        parser
2619            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2620            .expect("C++ grammar");
2621        let tree = parser.parse(source, None).expect("C++ parse");
2622        assert!(
2623            !tree.root_node().has_error(),
2624            "{}",
2625            tree.root_node().to_sexp()
2626        );
2627
2628        // Drive the same extraction entry the file walk drives: every fact
2629        // node admitted by the compiled kind table, through a real RoleSink.
2630        // The sink addresses occurrences by fact id, so the test keeps the
2631        // fact id to start byte mapping to name the tokens.
2632        let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2633        let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2634        let mut start_by_fact = HashMap::default();
2635        let mut extractions = Vec::new();
2636        let mut pending = vec![tree.root_node()];
2637        while let Some(node) = pending.pop() {
2638            if node.is_named()
2639                && let Some(kind) = compiled.kind_of(&node)
2640                && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2641            {
2642                start_by_fact.insert(node.id(), node.start_byte());
2643                extractions.push((node.start_byte(), node, kind));
2644            }
2645            for index in (0..node.named_child_count()).rev() {
2646                if let Some(child) = node.named_child(index) {
2647                    pending.push(child);
2648                }
2649            }
2650        }
2651        extractions.sort_by_key(|(start, _, _)| *start);
2652        let mut source_facts =
2653            brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2654        let parents = ParentIndex::new(tree.root_node());
2655        let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2656        for (_, node, kind) in extractions {
2657            CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2658        }
2659        let (_, _, occurrences, _, stop) = sink.into_parts();
2660        assert_eq!(stop, None);
2661
2662        let roles_at = |needle: &str| {
2663            let start = source.find(needle).expect("fixture token");
2664            occurrences
2665                .iter()
2666                .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2667                .map(|occurrence| occurrence.role)
2668                .collect::<Vec<_>>()
2669        };
2670        // Free-function callees are value reads, including a qualified callee
2671        // whose terminal name is the identifier and the callee of the inner
2672        // call in `bound()()`.
2673        assert_eq!(
2674            roles_at("getenv(\"PATH\")"),
2675            vec![OccurrenceRole::ValueReference]
2676        );
2677        assert_eq!(
2678            roles_at("getenv(\"USER\")"),
2679            vec![OccurrenceRole::ValueReference],
2680            "the terminal name of a qualified callee is the value read"
2681        );
2682        assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
2683        // A member call's terminal field stays one member-position occurrence;
2684        // the call itself adds no second row for the same token.
2685        assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
2686        // The receiver of a member call is neither a value read of the callee
2687        // nor a member position.
2688        assert!(roles_at("widget->").is_empty());
2689    }
2690
2691    /// Tree-sitter recovers the valid `this->operator[](...)` expression as a
2692    /// call whose function is the unfactored `this` node. That node must not
2693    /// receive a value-reference occurrence role.
2694    #[test]
2695    fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
2696        collect_cpp_structural_facts(
2697            "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
2698        );
2699    }
2700}
2701
2702#[cfg(test)]
2703mod grammar_boundary_tests {
2704    use super::*;
2705
2706    #[test]
2707    fn expression_and_pointer_declarator_name_fields() {
2708        let source = "void run() { auto p = new ns::Widget(); receiver.method<int>(); int (*callback)(int) = nullptr; int (ordinary)(int); }";
2709        let tree = crate::diagnostics::parse_cpp_tree(source);
2710        assert!(
2711            !tree.root_node().has_error(),
2712            "{}",
2713            tree.root_node().to_sexp()
2714        );
2715        let mut stack = vec![tree.root_node()];
2716        let mut names = Vec::new();
2717        let mut binders = Vec::new();
2718        while let Some(node) = stack.pop() {
2719            if matches!(node.kind(), "new_expression" | "field_expression") {
2720                let field = if node.kind() == "new_expression" {
2721                    "type"
2722                } else {
2723                    "field"
2724                };
2725                let child = node.child_by_field_name(field).unwrap();
2726                names.push((
2727                    node.kind(),
2728                    child.kind(),
2729                    expression_name_node(node).map(|n| &source[n.byte_range()]),
2730                ));
2731            }
2732            if node.kind() == "identifier"
2733                && let Some(name @ ("callback" | "ordinary")) = node_text(node, source)
2734            {
2735                let wrapper = if name == "callback" {
2736                    "pointer_declarator"
2737                } else {
2738                    "parenthesized_declarator"
2739                };
2740                assert_eq!(node.parent().unwrap().kind(), wrapper);
2741                binders.push((
2742                    name,
2743                    cpp_binder_owner(node, |n| n.parent(), field_name_in_parent)
2744                        .is_some_and(|owner| matches!(owner, CppBinderOwner::Local(_))),
2745                ));
2746            }
2747            stack.extend(
2748                node.named_children(&mut node.walk())
2749                    .collect::<Vec<_>>()
2750                    .into_iter()
2751                    .rev(),
2752            );
2753        }
2754        assert_eq!(
2755            names,
2756            [
2757                ("new_expression", "qualified_identifier", Some("Widget")),
2758                ("field_expression", "template_method", Some("method"))
2759            ]
2760        );
2761        assert_eq!(binders, [("callback", true), ("ordinary", false)]);
2762    }
2763
2764    #[test]
2765    #[brokk_bifrost_test_macros::hardwon(
2766        "6ec52f232835: quicksilver round 4 survivor review: dependent member names lost their terminal token"
2767    )]
2768    fn dependent_member_name_grammar_boundary() {
2769        let source = "template<class T> void f(T receiver) { receiver.template method<int>(); }";
2770        let tree = crate::diagnostics::parse_cpp_tree(source);
2771        assert!(!tree.root_node().has_error());
2772        let mut stack = vec![tree.root_node()];
2773        let mut names = Vec::new();
2774        while let Some(node) = stack.pop() {
2775            if node.kind() == "dependent_name" {
2776                assert!(node.child_by_field_name("name").is_none());
2777                assert_eq!(node.named_child(0).unwrap().kind(), "template_method");
2778                assert_eq!(
2779                    declarator_name_node(node).map(|name| &source[name.byte_range()]),
2780                    Some("method")
2781                );
2782                assert_eq!(
2783                    terminal_callee_leaf(node.parent().unwrap(), callee_spine_step)
2784                        .map(|name| &source[name.byte_range()]),
2785                    Some("method")
2786                );
2787                names.push(expression_name_node(node).map(|n| &source[n.byte_range()]));
2788            }
2789            stack.extend(node.named_children(&mut node.walk()));
2790        }
2791        assert_eq!(names, [Some("method")]);
2792    }
2793
2794    #[test]
2795    fn conditional_macro_name_fields() {
2796        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";
2797        let tree = crate::diagnostics::parse_cpp_tree(source);
2798        assert!(!tree.root_node().has_error());
2799        let mut stack = vec![tree.root_node()];
2800        let mut definitions = Vec::new();
2801        while let Some(node) = stack.pop() {
2802            if node.kind() == "preproc_function_def" {
2803                let name = node.child_by_field_name("name").unwrap();
2804                definitions.push((
2805                    node_text(name, source).unwrap(),
2806                    node.parent().unwrap().kind(),
2807                ));
2808            }
2809            stack.extend(
2810                node.named_children(&mut node.walk())
2811                    .collect::<Vec<_>>()
2812                    .into_iter()
2813                    .rev(),
2814            );
2815        }
2816        assert_eq!(
2817            definitions,
2818            [
2819                ("FIRST", "preproc_if"),
2820                ("SECOND", "preproc_elif"),
2821                ("THIRD", "preproc_else"),
2822            ]
2823        );
2824        let mut names: Vec<_> = function_like_macro_names(tree.root_node(), source)
2825            .into_iter()
2826            .collect();
2827        names.sort();
2828        assert_eq!(names, ["FIRST", "SECOND", "THIRD"]);
2829    }
2830}
2831
2832#[cfg(test)]
2833mod result_use_tests {
2834    use super::{CppCallResultUseIndex, cpp_call_result_use};
2835    use brokk_bifrost_core::analyzer::structural::result_use::{
2836        CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
2837    };
2838    use tree_sitter::Parser;
2839
2840    fn tree(source: &str) -> tree_sitter::Tree {
2841        let mut parser = Parser::new();
2842        parser
2843            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2844            .expect("C++ grammar");
2845        parser.parse(source, None).expect("C++ tree")
2846    }
2847
2848    fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
2849        let mut found = Vec::new();
2850        let mut stack = vec![tree.root_node()];
2851        while let Some(node) = stack.pop() {
2852            if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
2853                found.push(node);
2854            }
2855            for index in (0..node.named_child_count()).rev() {
2856                stack.push(node.named_child(index).expect("named child"));
2857            }
2858        }
2859        found.sort_by_key(|node| node.start_byte());
2860        found
2861    }
2862
2863    #[test]
2864    fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
2865        let source = concat!(
2866            "int f();\n",
2867            "void sink(int);\n",
2868            "void run() {\n",
2869            "    f();\n",
2870            "    (void)f();\n",
2871            "    static_cast<void>(f());\n",
2872            "    sink(f());\n",
2873            "    return;\n",
2874            "}\n",
2875        );
2876        let tree = tree(source);
2877        assert!(
2878            !tree.root_node().has_error(),
2879            "{}",
2880            tree.root_node().to_sexp()
2881        );
2882        let calls = calls(&tree, source);
2883        assert_eq!(calls.len(), 5);
2884        assert_eq!(
2885            cpp_call_result_use(calls[0], source).for_port(0),
2886            CallResultUse::Discarded
2887        );
2888        assert!(matches!(
2889            cpp_call_result_use(calls[1], source).for_port(0),
2890            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2891        ));
2892        assert!(matches!(
2893            cpp_call_result_use(calls[2], source).for_port(0),
2894            CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2895        ));
2896        assert_eq!(
2897            cpp_call_result_use(calls[3], source).for_port(0),
2898            CallResultUse::Discarded
2899        );
2900        assert_eq!(
2901            cpp_call_result_use(calls[4], source).for_port(0),
2902            CallResultUse::Consumed
2903        );
2904    }
2905
2906    #[test]
2907    fn lambda_tail_and_recovered_syntax_are_open() {
2908        // The macro-declaration grammar splits a bare `f(,)` into an
2909        // identifier and a separate ERROR with no call expression at all, so
2910        // the recovered call is spelled `f(+)` here: it still produces a
2911        // call_expression whose argument list carries the ERROR that marks
2912        // the syntax as recovered.
2913        let source = "int f(); void run() { auto value = [] { return f(); }; f(+); }";
2914        let tree = tree(source);
2915        assert!(tree.root_node().has_error());
2916        let calls = calls(&tree, source);
2917        assert!(matches!(
2918            cpp_call_result_use(calls[0], source).for_port(0),
2919            CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
2920        ));
2921        assert_eq!(
2922            cpp_call_result_use(calls[1], source).for_port(0),
2923            CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
2924        );
2925    }
2926
2927    #[test]
2928    fn exact_index_keeps_call_spans_separate() {
2929        let source = "int f(); void run() { f(); }";
2930        let tree = tree(source);
2931        let calls = calls(&tree, source);
2932        let index = CppCallResultUseIndex::new(tree.root_node(), source);
2933        assert_eq!(
2934            index
2935                .at_span(calls[0].start_byte(), calls[0].end_byte())
2936                .expect("indexed call")
2937                .for_port(0),
2938            CallResultUse::Discarded
2939        );
2940        assert!(index.at_span(source.len(), source.len()).is_err());
2941    }
2942
2943    #[test]
2944    fn result_use_grammar_boundary_sites_and_recovery() {
2945        // Spellings are the independent site oracle: casts are syntax wrappers,
2946        // allocations are expressions, and an ERROR ancestor invalidates a site.
2947        for (source, expected) in [
2948            (
2949                "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()); }",
2950                vec![
2951                    ("a()", CallResultUse::Consumed),
2952                    ("b()", CallResultUse::Consumed),
2953                    ("c()", CallResultUse::Consumed),
2954                    ("d()", CallResultUse::Consumed),
2955                    ("new W()", CallResultUse::Discarded),
2956                    ("new X()", CallResultUse::Consumed),
2957                    ("sink(new Y())", CallResultUse::Discarded),
2958                    ("new Y()", CallResultUse::Consumed),
2959                ],
2960            ),
2961            (
2962                "void run() { try { f(); } }",
2963                vec![(
2964                    "f()",
2965                    CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax),
2966                )],
2967            ),
2968        ] {
2969            let tree = tree(source);
2970            let mut stack = vec![tree.root_node()];
2971            let mut grammar_sites = Vec::new();
2972            let mut recovery_witness = false;
2973            while let Some(node) = stack.pop() {
2974                if matches!(node.kind(), "call_expression" | "new_expression") {
2975                    grammar_sites.push(node.byte_range());
2976                    if source[node.byte_range()] == *"f()" {
2977                        assert!(!node.has_error());
2978                        assert!(!node.parent().unwrap().is_error());
2979                        let mut parent = node.parent();
2980                        while let Some(ancestor) = parent {
2981                            recovery_witness |= ancestor.is_error();
2982                            parent = ancestor.parent();
2983                        }
2984                    }
2985                }
2986                stack.extend(node.named_children(&mut node.walk()));
2987            }
2988            if source.contains("try") {
2989                assert!(recovery_witness, "{}", tree.root_node().to_sexp());
2990            } else {
2991                assert!(!tree.root_node().has_error());
2992                assert_eq!(
2993                    grammar_sites.len(),
2994                    12,
2995                    "four cast wrappers plus eight sites"
2996                );
2997            }
2998            let index = CppCallResultUseIndex::new(tree.root_node(), source);
2999            let actual: Vec<_> = index
3000                .sites()
3001                .iter()
3002                .map(|site| (&source[site.start_byte..site.end_byte], site.for_port(0)))
3003                .collect();
3004            assert_eq!(actual, expected, "{source}");
3005        }
3006    }
3007
3008    #[test]
3009    #[brokk_bifrost_test_macros::hardwon(
3010        "6ec52f232835: quicksilver round 4 survivor review: void pointer casts falsely discarded source values"
3011    )]
3012    fn pointer_to_void_casts_consume_the_source_value() {
3013        let source = "int* make(); void run() { auto p = (void*)make(); auto q = static_cast<void*>(make()); }";
3014        let tree = tree(source);
3015        assert!(!tree.root_node().has_error());
3016        let mut stack = vec![tree.root_node()];
3017        let mut type_declarators = Vec::new();
3018        while let Some(node) = stack.pop() {
3019            if node.kind() == "type_descriptor" {
3020                type_declarators.push(node.child_by_field_name("declarator").map(|n| n.kind()));
3021            }
3022            stack.extend(node.named_children(&mut node.walk()));
3023        }
3024        assert_eq!(
3025            type_declarators,
3026            [
3027                Some("abstract_pointer_declarator"),
3028                Some("abstract_pointer_declarator")
3029            ]
3030        );
3031        let index = CppCallResultUseIndex::new(tree.root_node(), source);
3032        // Each source result feeds a pointer conversion and then an initializer.
3033        // Neither conversion discards a value by casting it to scalar void.
3034        let uses: Vec<_> = index.sites().iter().map(|site| site.for_port(0)).collect();
3035        assert_eq!(uses, [CallResultUse::Consumed, CallResultUse::Consumed]);
3036    }
3037}