Skip to main content

brokk_bifrost_cpp/graph/
syntax.rs

1use crate::declarations::node_text;
2use crate::graph::resolver::{
3    cpp_name_component_nodes, cpp_type_name_components, is_globally_qualified_cpp_name,
4    is_nested_type_node, qualified_owner_components,
5};
6use brokk_bifrost_core::analyzer::cpp_facts::CppDeclaredFieldTypeFact;
7use brokk_bifrost_core::analyzer::tree_walk::push_named_children_reversed;
8use std::ops::{ControlFlow, Range};
9use tree_sitter::{Node, Parser};
10
11/// A grammar context around embedded source with physical byte origins.
12/// Consumers of a sentinel tree count non-extra named children.
13/// Error nodes are preserved: recovery callers inspect them, while callers
14/// requiring a complete construct reject `tree.root_node().has_error()`.
15pub(crate) struct SentinelParse {
16    pub source: String,
17    pub tree: tree_sitter::Tree,
18    /// Byte origins relative to the embedded source, excluding the prefix.
19    /// Synthetic suffix bytes and the final boundary map to its original end.
20    pub original_offsets: Vec<Range<usize>>,
21    prefix_len: usize,
22}
23
24impl SentinelParse {
25    pub fn original_range(&self, node: Node<'_>) -> Option<Range<usize>> {
26        let start = node.start_byte().checked_sub(self.prefix_len)?;
27        let end = node.end_byte().checked_sub(self.prefix_len)?;
28        Some(
29            self.original_offsets.get(start)?.start
30                ..self.original_offsets.get(end.checked_sub(1)?)?.end,
31        )
32    }
33}
34
35/// Embedded bytes and their physical origins. Ordinary source regions retain
36/// their exact spelling; opaque preprocessing replacements need phase-2 splicing.
37pub(crate) struct SentinelInput {
38    pub source: String,
39    pub original_offsets: Vec<Range<usize>>,
40}
41
42impl SentinelInput {
43    pub fn replacement(source: &str) -> Self {
44        let (source, original_offsets) = normalize_macro_continuations(source);
45        Self {
46            source,
47            original_offsets,
48        }
49    }
50
51    pub fn region(source: &str) -> Self {
52        let mut original_offsets = (0..source.len()).map(|i| i..i + 1).collect::<Vec<_>>();
53        original_offsets.push(source.len()..source.len());
54        Self {
55            source: source.to_owned(),
56            original_offsets,
57        }
58    }
59}
60
61pub(crate) fn parse_sentinel(prefix: &str, embedded: SentinelInput, suffix: &str) -> SentinelParse {
62    let SentinelInput {
63        source: replacement,
64        mut original_offsets,
65    } = embedded;
66    let original_end = original_offsets
67        .last()
68        .expect("embedded source includes its final boundary")
69        .end;
70    debug_assert_eq!(original_offsets.len(), replacement.len() + 1);
71    // A preproc_arg can include a trailing line comment. End it before any
72    // synthetic punctuation, including an expression's terminating semicolon.
73    let source = format!("{prefix}{replacement}\n{suffix}");
74    original_offsets.extend(std::iter::repeat_n(
75        original_end..original_end,
76        1 + suffix.len(),
77    ));
78    let mut parser = Parser::new();
79    parser
80        .set_language(&tree_sitter_cpp::LANGUAGE.into())
81        .expect("C++ grammar matches the parser ABI");
82    let tree = parser
83        .parse(&source, None)
84        .expect("sentinel parse has no cancellation or timeout");
85    SentinelParse {
86        source,
87        tree,
88        original_offsets,
89        prefix_len: prefix.len(),
90    }
91}
92
93/// Whether an expression denotes C++'s implicit object receiver.
94///
95/// `this` is the implicit object only where C++ compiles the source; it is an
96/// ordinary identifier in a `.c` file or a header compiled as C. Parentheses
97/// and pointer expressions transparently wrap the receiver.
98pub(crate) fn receiver_is_self_like(mut node: Node<'_>, reference_is_c: bool) -> bool {
99    loop {
100        match node.kind() {
101            "this" => return !reference_is_c,
102            "pointer_expression" | "parenthesized_expression" => {
103                let Some(inner) = node
104                    .child_by_field_name("argument")
105                    .or_else(|| node.named_child(0))
106                else {
107                    return false;
108                };
109                node = inner;
110            }
111            _ => return false,
112        }
113    }
114}
115
116pub(crate) fn has_function_scope_ancestor(mut node: Node<'_>) -> bool {
117    while let Some(parent) = node.parent() {
118        if matches!(parent.kind(), "function_definition" | "lambda_expression") {
119            return true;
120        }
121        node = parent;
122    }
123    false
124}
125
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct MacroReplacementTypeReference {
128    pub components: Vec<String>,
129    pub component_ranges: Vec<Range<usize>>,
130    pub global: bool,
131}
132
133/// Whether an identifier is a callable declaration name retained beneath C++
134/// error recovery.
135///
136/// A C prototype using the traditional `__P((...))` wrapper can be parsed as a
137/// pointer declarator whose first child is `ERROR(identifier)` and whose
138/// declarator is a function declarator for the macro invocation. The identifier
139/// is still a real declaration reference to the callable's later definition,
140/// even though the ordinary census intentionally excludes the whole ERROR
141/// subtree. Keep this predicate limited to that declaration-shaped CST so
142/// arbitrary recovery leaves do not enter inverse membership.
143pub fn is_cpp_recovered_callable_declaration_reference(node: Node<'_>) -> bool {
144    if !matches!(node.kind(), "identifier" | "field_identifier") {
145        return false;
146    }
147    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
148        return false;
149    };
150    let Some(pointer) = error.parent().filter(|parent| {
151        parent.kind() == "pointer_declarator"
152            && parent.named_child(0) == Some(error)
153            && parent
154                .child_by_field_name("declarator")
155                .is_some_and(|declarator| declarator.kind() == "function_declarator")
156    }) else {
157        return false;
158    };
159    pointer.parent().is_some_and(|declaration| {
160        declaration.kind() == "declaration"
161            && declaration.child_by_field_name("declarator") == Some(pointer)
162    })
163}
164
165/// One direct field declaration recovered from an object-like macro
166/// replacement. The replacement is parsed as the body of a synthetic struct,
167/// so the name and declaration text come from C/C++ grammar nodes rather than
168/// from a textual macro expansion.
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub struct MacroReplacementField {
171    pub name: String,
172    pub declaration: String,
173    pub field_type: Option<CppDeclaredFieldTypeFact>,
174    pub names_function_type: bool,
175}
176
177/// The structured content of one object-like field-list macro replacement:
178/// the members it declares itself, and the names of the field-list macros it
179/// composes in turn. Composition is kept as names rather than resolved here
180/// because a nested name's active replacement is a property of the
181/// preprocessor environment at the invocation, not of this replacement.
182#[derive(Clone, Debug, Default, PartialEq, Eq)]
183pub struct ObjectMacroReplacement {
184    pub fields: Vec<MacroReplacementField>,
185    pub nested: Vec<String>,
186}
187
188impl ObjectMacroReplacement {
189    pub fn is_empty(&self) -> bool {
190        self.fields.is_empty() && self.nested.is_empty()
191    }
192
193    /// The members and nested names both replacements declare identically.
194    ///
195    /// An include closure can carry mutually exclusive definitions of one
196    /// field-list macro (libuv defines `UV_HANDLE_PRIVATE_FIELDS` in both
197    /// `uv/unix.h` and `uv/win.h`). Whichever header the compilation actually
198    /// took, every member in this intersection is present, and every member
199    /// that depends on the branch is left unproven.
200    pub fn intersect(&self, other: &Self) -> Self {
201        Self {
202            fields: self
203                .fields
204                .iter()
205                .filter(|field| other.fields.contains(field))
206                .cloned()
207                .collect(),
208            nested: self
209                .nested
210                .iter()
211                .filter(|name| other.nested.contains(name))
212                .cloned()
213                .collect(),
214        }
215    }
216}
217
218/// Recover direct fields hidden in an object-like macro replacement.
219///
220/// A field-list macro is valid in more than one owner, so this helper returns
221/// only the declaration-shaped children of the synthetic field list. Nested
222/// aggregate promotion remains the owner's normal structured aggregate logic;
223/// treating nested members as direct fields here would leak them across owners.
224///
225/// A replacement that ends in another field-list macro's name is reported as
226/// composition rather than refused: the grammar has no member rule for a bare
227/// identifier, so tree-sitter marks it as one `type_identifier` with a MISSING
228/// `;`. Every other malformed region still refuses the whole replacement, so an
229/// unsupported spelling stays unproven instead of donating partial members.
230pub fn object_macro_replacement(replacement: &str) -> ObjectMacroReplacement {
231    if replacement.trim().is_empty() {
232        return ObjectMacroReplacement::default();
233    }
234    const PREFIX: &str = "struct __bifrost_macro_fields { ";
235    let parsed = parse_sentinel(PREFIX, SentinelInput::replacement(replacement), " };");
236    let synthetic = parsed.source.as_str();
237    let tree = &parsed.tree;
238    let original_offsets = &parsed.original_offsets;
239    let mut stack = vec![tree.root_node()];
240    let body = loop {
241        let Some(current) = stack.pop() else {
242            return ObjectMacroReplacement::default();
243        };
244        if current.kind() == "struct_specifier"
245            && let Some(body) = current.child_by_field_name("body")
246        {
247            break body;
248        }
249        let mut cursor = current.walk();
250        for child in current.named_children(&mut cursor) {
251            stack.push(child);
252        }
253    };
254    let mut recovered = ObjectMacroReplacement::default();
255    let mut composed_terminators = Vec::new();
256    let mut cursor = body.walk();
257    for declaration in body.named_children(&mut cursor) {
258        if !matches!(declaration.kind(), "declaration" | "field_declaration") {
259            continue;
260        }
261        if let Some((name, terminator)) = nested_object_macro_invocation(declaration, synthetic) {
262            recovered.nested.push(name);
263            composed_terminators.push(terminator.id());
264            continue;
265        }
266        let Some(declarator) = declaration.child_by_field_name("declarator").or_else(|| {
267            let mut cursor = declaration.walk();
268            declaration
269                .named_children(&mut cursor)
270                .filter(|child| !child.is_extra())
271                .nth(1)
272        }) else {
273            continue;
274        };
275        let Some(name) = macro_replacement_declarator_name(declarator, synthetic) else {
276            continue;
277        };
278        let start = declaration
279            .start_byte()
280            .checked_sub(PREFIX.len())
281            .and_then(|start| original_offsets.get(start));
282        let end = declaration
283            .end_byte()
284            .checked_sub(PREFIX.len())
285            .and_then(|end| original_offsets.get(end.checked_sub(1)?));
286        let Some((start, end)) = start.zip(end) else {
287            continue;
288        };
289        let declaration_text = replacement[start.start..end.end].to_string();
290        let (field_type, names_function_type) =
291            crate::source_facts::capture_field_type(declaration, &name, synthetic);
292        recovered.fields.push(MacroReplacementField {
293            name,
294            declaration: declaration_text,
295            field_type,
296            names_function_type,
297        });
298    }
299    if !malformed_regions_are_composition(tree.root_node(), &composed_terminators) {
300        return ObjectMacroReplacement::default();
301    }
302    recovered
303}
304
305/// The name a nested field-list macro invocation contributes, with the MISSING
306/// `;` tree-sitter inserted for it. A member list holding only another macro's
307/// name has no grammar rule, so the invocation reaches the tree as exactly one
308/// `type_identifier` followed by that missing terminator.
309fn nested_object_macro_invocation<'tree>(
310    declaration: Node<'tree>,
311    source: &str,
312) -> Option<(String, Node<'tree>)> {
313    if declaration.kind() != "field_declaration" {
314        return None;
315    }
316    let mut cursor = declaration.walk();
317    let children = declaration.children(&mut cursor).collect::<Vec<_>>();
318    let [name, terminator] = children.as_slice() else {
319        return None;
320    };
321    if name.kind() != "type_identifier" || terminator.kind() != ";" || !terminator.is_missing() {
322        return None;
323    }
324    let text = node_text(*name, source).trim();
325    (!text.is_empty()).then(|| (text.to_string(), *terminator))
326}
327
328/// Whether every malformed region of a reparsed replacement is one of the
329/// MISSING terminators nested composition already accounts for. Any other
330/// error means the replacement is not a structured member list, and the whole
331/// replacement is refused rather than donating the members that did parse.
332fn malformed_regions_are_composition(root: Node<'_>, composed_terminators: &[usize]) -> bool {
333    let mut stack = vec![root];
334    while let Some(node) = stack.pop() {
335        if !node.has_error() && !node.is_missing() {
336            continue;
337        }
338        if (node.is_error() || node.is_missing()) && !composed_terminators.contains(&node.id()) {
339            return false;
340        }
341        let mut cursor = node.walk();
342        let children = node.children(&mut cursor).collect::<Vec<_>>();
343        stack.extend(children);
344    }
345    true
346}
347
348/// One member declaration recovered from an aggregate region tree-sitter could
349/// not place. `range` is the member's own byte range in the original source.
350#[derive(Clone, Debug, PartialEq, Eq)]
351pub struct RecoveredAggregateField {
352    pub name: String,
353    pub declaration: String,
354    pub range: Range<usize>,
355    pub field_type: Option<CppDeclaredFieldTypeFact>,
356    pub names_function_type: bool,
357}
358
359/// Recover the direct members of an aggregate body region the ordinary parse
360/// could not place.
361///
362/// An object-like field-list macro invocation inside a member list has no
363/// grammar rule, so tree-sitter collapses the aggregate's head and body into
364/// one `ERROR` container and the members after the invocation lose their
365/// declaration shape. Reparsing the exact byte slice that follows the
366/// invocation as a synthetic aggregate body restores that shape from the
367/// grammar, and every returned range maps back to the original source.
368pub fn recovered_aggregate_fields(
369    source: &str,
370    span: Range<usize>,
371) -> Vec<RecoveredAggregateField> {
372    let Some(region) = source.get(span.clone()) else {
373        return Vec::new();
374    };
375    if region.trim().is_empty() {
376        return Vec::new();
377    }
378    const PREFIX: &str = "struct __bifrost_recovered_members { ";
379    let parsed = parse_sentinel(PREFIX, SentinelInput::region(region), " };");
380    let synthetic = parsed.source.as_str();
381    let tree = &parsed.tree;
382    if tree.root_node().has_error() {
383        return Vec::new();
384    }
385    let mut stack = vec![tree.root_node()];
386    let body = loop {
387        let Some(current) = stack.pop() else {
388            return Vec::new();
389        };
390        if current.kind() == "struct_specifier"
391            && let Some(body) = current.child_by_field_name("body")
392        {
393            break body;
394        }
395        let mut cursor = current.walk();
396        for child in current.named_children(&mut cursor) {
397            stack.push(child);
398        }
399    };
400    let mut fields = Vec::new();
401    let mut cursor = body.walk();
402    for declaration in body.named_children(&mut cursor) {
403        if !matches!(declaration.kind(), "declaration" | "field_declaration") {
404            continue;
405        }
406        let mut declarator_cursor = declaration.walk();
407        for declarator in declaration.children_by_field_name("declarator", &mut declarator_cursor) {
408            let Some(name) = macro_replacement_declarator_name(declarator, synthetic) else {
409                continue;
410            };
411            let Some(range) = parsed.original_range(declaration) else {
412                continue;
413            };
414            let Some(declaration_text) = region.get(range.clone()).map(str::to_owned) else {
415                continue;
416            };
417            let (field_type, names_function_type) =
418                crate::source_facts::capture_field_type(declaration, &name, synthetic);
419            let start = span.start + range.start;
420            let end = span.start + range.end;
421            fields.push(RecoveredAggregateField {
422                name,
423                declaration: declaration_text,
424                range: start..end,
425                field_type,
426                names_function_type,
427            });
428        }
429    }
430    fields
431}
432
433/// The complete replacement list of an object-like `#define`.
434///
435/// tree-sitter-cpp lexes a comment inside a replacement as an extra, which
436/// ends the `preproc_arg` token and the `preproc_def` node with it: libuv's
437/// `UV_HANDLE_FIELDS` reports `void* data;` as its whole replacement and loses
438/// the eight members that follow the next comment. A replacement list is one
439/// preprocessing logical line (C17 5.1.1.2), so its extent is the directive's
440/// own line-continuation run, which the grammar's token boundaries do not
441/// describe. The AST supplies the start; the returned slice is then parsed by
442/// tree-sitter like any other replacement.
443pub fn object_macro_replacement_span(node: Node<'_>, source: &str) -> Option<Range<usize>> {
444    if node.kind() != "preproc_def" {
445        return None;
446    }
447    let start = node.child_by_field_name("name")?.end_byte();
448    macro_replacement_span(source, start)
449}
450
451/// The complete logical-line replacement list of a function-like `#define`.
452///
453/// Comments are tokenized as extras by tree-sitter-cpp and can truncate the
454/// `preproc_arg` value. Start immediately after the parameter list so a
455/// replacement that begins with a comment remains source backed even when it
456/// has no value node. Follow the preprocessing continuation run to recover the
457/// bytes that belong to the replacement. The returned span includes the
458/// original backslash/newline bytes so callers can retain source coordinates.
459pub fn function_macro_replacement_span(node: Node<'_>, source: &str) -> Option<Range<usize>> {
460    let parameters = match node.kind() {
461        "preproc_function_def" => node.child_by_field_name("parameters"),
462        "preproc_def" => {
463            // A comment-truncated function macro can recover as an object
464            // directive whose ERROR child still owns the parameter list.
465            let mut stack = vec![node];
466            let mut parameters = None;
467            while let Some(part) = stack.pop() {
468                if part.kind() == "preproc_params" {
469                    parameters = Some(part);
470                    break;
471                }
472                if part == node || part.is_error() {
473                    push_named_children_reversed(part, &mut stack);
474                }
475            }
476            parameters
477        }
478        _ => None,
479    }?;
480    macro_replacement_span(source, parameters.end_byte())
481}
482
483fn macro_replacement_span(source: &str, start: usize) -> Option<Range<usize>> {
484    (start <= source.len()).then(|| start..logical_line_end(start, source))
485}
486
487/// Return the end of the preprocessing logical line beginning at `start`.
488/// A physical newline belongs to the replacement while it is preceded by a
489/// continuation backslash (with an optional CR before the newline).
490fn logical_line_end(start: usize, source: &str) -> usize {
491    let bytes = source.as_bytes();
492    let mut index = start;
493    while index < bytes.len() {
494        if bytes[index] != b'\n' {
495            index += 1;
496            continue;
497        }
498        let mut previous = index;
499        if previous > start && bytes[previous - 1] == b'\r' {
500            previous -= 1;
501        }
502        if previous > start && bytes[previous - 1] == b'\\' {
503            index += 1;
504            continue;
505        }
506        break;
507    }
508    index
509}
510
511/// Apply translation phase 2 (C 5.1.1.2): delete a backslash-newline pair,
512/// allowing the surrounding bytes to form a single preprocessing token.
513/// Each normalized byte maps back to its physical source byte;
514/// consumers parse the joined text and report ranges in the physical source.
515fn normalize_macro_continuations(replacement: &str) -> (String, Vec<Range<usize>>) {
516    let source = replacement.as_bytes();
517    let mut normalized = Vec::with_capacity(source.len());
518    let mut original_offsets = Vec::with_capacity(source.len() + 1);
519    let mut index = 0;
520    while index < source.len() {
521        if source[index..].starts_with(b"\\\n") {
522            index += 2;
523        } else if source[index..].starts_with(b"\\\r\n") {
524            index += 3;
525        } else {
526            original_offsets.push(index..index + 1);
527            normalized.push(source[index]);
528            index += 1;
529        }
530    }
531    original_offsets.push(source.len()..source.len());
532    (
533        String::from_utf8(normalized).expect("source text remains valid UTF-8"),
534        original_offsets,
535    )
536}
537
538fn macro_replacement_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
539    match node.kind() {
540        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
541            let name = node_text(node, source).trim();
542            (!name.is_empty()).then(|| name.to_string())
543        }
544        "function_declarator" => None,
545        _ => node
546            .child_by_field_name("declarator")
547            .or_else(|| node.child_by_field_name("name"))
548            .and_then(|child| macro_replacement_declarator_name(child, source)),
549    }
550}
551
552/// Recover type-bearing syntax hidden inside an object-like macro replacement.
553///
554/// Tree-sitter deliberately keeps the replacement of `#define NAME value` as
555/// one opaque `preproc_arg`. Reparse that exact byte slice as a C++ expression
556/// and return only references proven by the resulting tree: ordinary type
557/// nodes and the owner prefixes of qualified values such as `Owner::member`.
558/// Every returned range is mapped back to the original file.
559pub fn object_macro_replacement_type_references(
560    node: Node<'_>,
561    source: &str,
562) -> Vec<MacroReplacementTypeReference> {
563    if node.kind() != "preproc_arg"
564        || !node.parent().is_some_and(|parent| {
565            parent.kind() == "preproc_def"
566                && parent
567                    .child_by_field_name("value")
568                    .is_some_and(|value| value == node)
569        })
570    {
571        return Vec::new();
572    }
573    let Some(replacement) = source.get(node.start_byte()..node.end_byte()) else {
574        return Vec::new();
575    };
576    const PREFIX: &str = "void __bifrost_macro_reference() { ";
577    let parsed = parse_sentinel(PREFIX, SentinelInput::replacement(replacement), "; }");
578    let synthetic = parsed.source.as_str();
579    let tree = &parsed.tree;
580    if tree.root_node().has_error() {
581        return Vec::new();
582    }
583
584    let mut references = Vec::new();
585    let mut stack = vec![tree.root_node()];
586    while let Some(current) = stack.pop() {
587        let structured = if matches!(
588            current.kind(),
589            "type_identifier" | "scoped_type_identifier" | "template_type"
590        ) && !is_nested_type_node(current)
591        {
592            cpp_type_name_components(current, synthetic)
593                .zip(cpp_name_component_nodes(current))
594                .map(|(components, nodes)| {
595                    (components, nodes, is_globally_qualified_cpp_name(current))
596                })
597        } else if current.kind() == "qualified_identifier"
598            && !current.parent().is_some_and(|parent| {
599                matches!(
600                    parent.kind(),
601                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
602                )
603            })
604        {
605            qualified_owner_components(current, synthetic)
606                .map(|owner| (owner.names, owner.nodes, owner.global))
607        } else {
608            None
609        };
610        if let Some((components, component_nodes, global)) = structured {
611            let component_ranges = component_nodes
612                .into_iter()
613                .map(|component| {
614                    let range = parsed.original_range(component)?;
615                    (range.end <= replacement.len())
616                        .then_some(node.start_byte() + range.start..node.start_byte() + range.end)
617                })
618                .collect::<Option<Vec<_>>>();
619            if let Some(component_ranges) = component_ranges
620                && component_ranges.len() == components.len()
621            {
622                let reference = MacroReplacementTypeReference {
623                    components,
624                    component_ranges,
625                    global,
626                };
627                if !references.contains(&reference) {
628                    references.push(reference);
629                }
630            }
631        }
632        push_named_children_reversed(current, &mut stack);
633    }
634    references
635}
636
637#[derive(Clone)]
638pub struct QualifiedCallableValue<'tree> {
639    pub qualified: Node<'tree>,
640    pub global: bool,
641    pub owner_components: Vec<Node<'tree>>,
642    pub member: Node<'tree>,
643}
644
645/// Recognize an explicit address-of qualified callable value such as
646/// `&Owner::method` or `&namespace::Owner::method`.
647///
648/// The returned nodes come exclusively from the C++ grammar's named fields. In
649/// particular, a nested namespace/type owner remains a structured subtree rather
650/// than being reconstructed from source text.
651pub fn explicit_qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
652    if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
653        return None;
654    }
655    let qualified = node.child_by_field_name("argument")?;
656    qualified_callable_value_from_node(qualified)
657}
658
659/// Recognize a qualified callable used as an expression value.
660///
661/// Calls use their own arity-aware path. Address-of expressions use the
662/// explicit path above. This arm covers structured values such as
663/// `bind(Owner::method)` and `callback = namespace::function`.
664pub fn qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
665    if let Some(value) = explicit_qualified_callable_value(node) {
666        return Some(value);
667    }
668    if node.kind() != "qualified_identifier" {
669        return None;
670    }
671    if crate::graph::resolver::is_declaration_name(node) {
672        return None;
673    }
674    if node.parent().is_some_and(|parent| {
675        parent.child_by_field_name("type") == Some(node)
676            || (parent.kind() == "call_expression"
677                && parent.child_by_field_name("function") == Some(node))
678            || (parent.kind() == "pointer_expression"
679                && parent.child_by_field_name("argument") == Some(node))
680            || matches!(
681                parent.kind(),
682                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
683            )
684    }) {
685        return None;
686    }
687    qualified_callable_value_from_node(node)
688}
689
690fn qualified_callable_value_from_node(qualified: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
691    if qualified.kind() != "qualified_identifier" {
692        return None;
693    }
694    let mut components = Vec::new();
695    let global = qualified.child_by_field_name("scope").is_none()
696        && qualified.child(0).is_some_and(|child| child.kind() == "::");
697    append_qualified_components(qualified, &mut components)?;
698    let member = components.pop()?;
699    if components.is_empty() {
700        return None;
701    }
702    Some(QualifiedCallableValue {
703        qualified,
704        global,
705        owner_components: components,
706        member,
707    })
708}
709
710fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
711    let mut stack = vec![node];
712    while let Some(current) = stack.pop() {
713        match current.kind() {
714            "identifier" | "namespace_identifier" | "type_identifier" | "operator_name" => {
715                out.push(current)
716            }
717            "qualified_identifier" | "scoped_identifier" => {
718                let (scope, name) = qualified_name_parts(current)?;
719                stack.push(name);
720                if let Some(scope) = scope {
721                    stack.push(scope);
722                } else if current.child(0).is_none_or(|child| child.kind() != "::") {
723                    return None;
724                }
725            }
726            "template_type" | "template_function" => {
727                stack.push(current.child_by_field_name("name")?);
728            }
729            "nested_namespace_specifier" => {
730                for index in (0..current.named_child_count()).rev() {
731                    stack.push(current.named_child(index)?);
732                }
733            }
734            _ => return None,
735        }
736    }
737    Some(())
738}
739
740pub(super) fn qualified_name_parts<'tree>(
741    node: Node<'tree>,
742) -> Option<(Option<Node<'tree>>, Node<'tree>)> {
743    Some((
744        node.child_by_field_name("scope"),
745        node.child_by_field_name("name")?,
746    ))
747}
748
749/// Hide grammar-owned comments inside function-like replacements from the
750/// primary parser. Otherwise tree-sitter can terminate `preproc_arg` at the
751/// comment and read the rest of the directive as surrounding C/C++ code.
752/// Replacement analysis still reads the original source and reparses the full
753/// logical span; included ranges preserve every original byte coordinate.
754/// `tree` must be the unmasked C++ grammar tree for these exact source bytes.
755/// A caller can retain that tree only after complete discovery returns `None`.
756/// Interrupted discovery never offers a partial set of included ranges.
757pub fn function_macro_included_ranges(
758    source: &str,
759    tree: &tree_sitter::Tree,
760    mut should_stop: impl FnMut() -> bool,
761) -> ControlFlow<(), Option<Vec<tree_sitter::Range>>> {
762    let mut replacements = Vec::new();
763    let mut comments = Vec::new();
764    let mut stack = vec![tree.root_node()];
765    while let Some(node) = stack.pop() {
766        if should_stop() {
767            return ControlFlow::Break(());
768        }
769        if let Some(span) = function_macro_replacement_span(node, source) {
770            replacements.push(span);
771        }
772        if node.kind() == "comment" {
773            comments.push(node.range());
774        }
775        push_named_children_reversed(node, &mut stack);
776    }
777    let mut masked_comments = Vec::new();
778    for comment in comments {
779        if should_stop() {
780            return ControlFlow::Break(());
781        }
782        for span in &replacements {
783            if should_stop() {
784                return ControlFlow::Break(());
785            }
786            if span.start <= comment.start_byte && span.end >= comment.end_byte {
787                masked_comments.push(comment);
788                break;
789            }
790        }
791    }
792    let mut comments = masked_comments;
793    if comments.is_empty() {
794        return ControlFlow::Continue(None);
795    }
796    if should_stop() {
797        return ControlFlow::Break(());
798    }
799    comments.sort_by_key(|range| range.start_byte);
800    if should_stop() {
801        return ControlFlow::Break(());
802    }
803    let mut included = Vec::new();
804    let mut start_byte = 0;
805    let mut start_point = tree_sitter::Point::new(0, 0);
806    for comment in comments {
807        if should_stop() {
808            return ControlFlow::Break(());
809        }
810        if start_byte < comment.start_byte {
811            included.push(tree_sitter::Range {
812                start_byte,
813                end_byte: comment.start_byte,
814                start_point,
815                end_point: comment.start_point,
816            });
817        }
818        start_byte = comment.end_byte;
819        start_point = comment.end_point;
820    }
821    if start_byte < source.len() {
822        included.push(tree_sitter::Range {
823            start_byte,
824            end_byte: source.len(),
825            start_point,
826            end_point: tree.root_node().end_position(),
827        });
828    }
829    ControlFlow::Continue(Some(included))
830}
831
832#[cfg(test)]
833mod tests {
834    use brokk_bifrost_test_macros::hardwon;
835    #[test]
836    #[hardwon(
837        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: line splicing split macro field tokens"
838    )]
839    fn macro_splices_join_tokens() {
840        for newline in ["\n", "\r\n"] {
841            let source = format!("in\\{newline}t val\\{newline}ue;\\{newline}int after;");
842            let replacement = object_macro_replacement(&source);
843            assert_eq!(
844                replacement
845                    .fields
846                    .iter()
847                    .map(|field| field.name.as_str())
848                    .collect::<Vec<_>>(),
849                ["value", "after"]
850            );
851            assert_eq!(
852                replacement.fields[0].declaration,
853                source[..source.find(';').unwrap() + 1]
854            );
855            assert_eq!(replacement.fields[1].declaration, "int after;");
856            assert_eq!(
857                replacement.fields[0].field_type.as_ref().unwrap().type_text,
858                "int"
859            );
860        }
861    }
862
863    #[test]
864    #[hardwon(
865        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: recovered aggregate lost repeated declarators"
866    )]
867    fn quicksilver_recovered_aggregate_multi_declarators() {
868        let source = "int left, right;";
869        let fields = recovered_aggregate_fields(source, 0..source.len());
870        assert_eq!(
871            fields
872                .iter()
873                .map(|field| field.name.as_str())
874                .collect::<Vec<_>>(),
875            ["left", "right"]
876        );
877        for field in fields {
878            assert_eq!(field.declaration, source);
879            assert_eq!(&source[field.range], source);
880            assert_eq!(field.field_type.unwrap().type_text, "int");
881        }
882    }
883
884    #[test]
885    #[hardwon(
886        "05fbd05c6bd7: C/C++ macro recovery previously lost or invented structured declarations"
887    )]
888    fn issue_3089_macro_comments_do_not_consume_caller_function() {
889        let source = "#define PROCESS(handle, block) \\\ndo { /* comment */ \\\n  int event; \\\n  if (handle) block \\\n} while (0)\nstatic void caller(int handle) { PROCESS(handle, { event; }); }\n";
890        let mut parser = tree_sitter::Parser::new();
891        parser
892            .set_language(&tree_sitter_cpp::LANGUAGE.into())
893            .unwrap();
894        let tree = parser.parse(source, None).unwrap();
895        let ranges = super::function_macro_included_ranges(source, &tree, || false)
896            .continue_value()
897            .expect("complete discovery")
898            .expect("macro comments");
899        let mut parser = tree_sitter::Parser::new();
900        parser
901            .set_language(&tree_sitter_cpp::LANGUAGE.into())
902            .unwrap();
903        parser.set_included_ranges(&ranges).unwrap();
904        let tree = parser.parse(source, None).unwrap();
905        let mut stack = vec![tree.root_node()];
906        let mut functions = Vec::new();
907        while let Some(node) = stack.pop() {
908            if node.kind() == "function_definition" {
909                functions.push(node);
910            }
911            super::push_named_children_reversed(node, &mut stack);
912        }
913        assert_eq!(functions.len(), 1, "{}", tree.root_node().to_sexp());
914        assert_eq!(
915            super::node_text(
916                functions[0]
917                    .child_by_field_name("declarator")
918                    .unwrap()
919                    .child_by_field_name("declarator")
920                    .unwrap(),
921                source
922            ),
923            "caller"
924        );
925    }
926    use super::*;
927
928    fn references(source: &str) -> Vec<MacroReplacementTypeReference> {
929        let mut parser = Parser::new();
930        parser
931            .set_language(&tree_sitter_cpp::LANGUAGE.into())
932            .expect("C++ grammar");
933        let tree = parser.parse(source, None).expect("macro fixture tree");
934        let value = tree
935            .root_node()
936            .named_child(0)
937            .and_then(|definition| definition.child_by_field_name("value"))
938            .expect("macro replacement");
939        object_macro_replacement_type_references(value, source)
940    }
941
942    #[test]
943    #[hardwon(
944        "3d330d262275: C/C++ macro recovery previously lost or invented structured declarations"
945    )]
946    fn object_macro_replacement_reparse_preserves_type_ranges() {
947        let source = "#define SETTINGS (*api::SettingsImpl::GetInstance())\n";
948        let references = references(source);
949        let reference = references
950            .iter()
951            .find(|reference| reference.components == ["api", "SettingsImpl"])
952            .expect("qualified callable owner");
953        let rendered = reference
954            .component_ranges
955            .iter()
956            .map(|range| &source[range.clone()])
957            .collect::<Vec<_>>();
958        assert_eq!(rendered, ["api", "SettingsImpl"]);
959    }
960
961    #[test]
962    #[hardwon(
963        "3d77aac19607: C/C++ macro recovery previously lost or invented structured declarations"
964    )]
965    fn object_macro_replacement_fields_are_structured_and_direct_only() {
966        let replacement = object_macro_replacement(
967            r#"int public_value; \
968             union { int nested_value; }; \
969             unsigned private_value;"#,
970        );
971        assert_eq!(
972            replacement.fields,
973            vec![
974                MacroReplacementField {
975                    name: "public_value".to_string(),
976                    declaration: "int public_value;".to_string(),
977                    field_type: Some(CppDeclaredFieldTypeFact {
978                        type_text: "int".to_string(),
979                        indirection: 0,
980                        binds_indirectly: false,
981                        template_arguments: None,
982                    }),
983                    names_function_type: false,
984                },
985                MacroReplacementField {
986                    name: "private_value".to_string(),
987                    declaration: "unsigned private_value;".to_string(),
988                    field_type: Some(CppDeclaredFieldTypeFact {
989                        type_text: "unsigned".to_string(),
990                        indirection: 0,
991                        binds_indirectly: false,
992                        template_arguments: None,
993                    }),
994                    names_function_type: false,
995                },
996            ]
997        );
998        assert!(replacement.nested.is_empty());
999        assert!(object_macro_replacement("not a declaration").is_empty());
1000    }
1001
1002    /// libuv's `UV_HANDLE_FIELDS` interleaves `/* public */`-style comments
1003    /// with its members and ends by composing `UV_HANDLE_PRIVATE_FIELDS`
1004    /// (issue #2985). tree-sitter ends the `preproc_arg` token, and the
1005    /// `preproc_def` node with it, at the first of those comments.
1006    #[test]
1007    #[hardwon(
1008        "5e7361a53510: C/C++ macro recovery previously lost or invented structured declarations"
1009    )]
1010    fn comment_split_replacement_keeps_every_member_and_its_composition() {
1011        let source = "#define UV_HANDLE_FIELDS                    \\\n\
1012                      \x20 /* public */                            \\\n\
1013                      \x20 void* data;                             \\\n\
1014                      \x20 /* read-only */                         \\\n\
1015                      \x20 uv_loop_t* loop;                        \\\n\
1016                      \x20 UV_HANDLE_PRIVATE_FIELDS                \\\n\
1017                      \nstruct uv_handle_s { UV_HANDLE_FIELDS };\n";
1018        let mut parser = Parser::new();
1019        parser
1020            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1021            .expect("C++ grammar");
1022        let tree = parser.parse(source, None).expect("macro fixture tree");
1023        let mut stack = vec![tree.root_node()];
1024        let definition = loop {
1025            let current = stack.pop().expect("the fixture defines one object macro");
1026            if current.kind() == "preproc_def" {
1027                break current;
1028            }
1029            let mut cursor = current.walk();
1030            for child in current.named_children(&mut cursor) {
1031                stack.push(child);
1032            }
1033        };
1034        let value = definition
1035            .child_by_field_name("value")
1036            .expect("truncated replacement token");
1037        assert_eq!(
1038            source[value.byte_range()]
1039                .trim_end()
1040                .trim_end_matches('\\')
1041                .trim_end(),
1042            "void* data;"
1043        );
1044
1045        let span = object_macro_replacement_span(definition, source).expect("replacement span");
1046        let replacement = object_macro_replacement(&source[span]);
1047        assert_eq!(
1048            replacement
1049                .fields
1050                .iter()
1051                .map(|field| field.name.as_str())
1052                .collect::<Vec<_>>(),
1053            ["data", "loop"]
1054        );
1055        assert_eq!(replacement.nested, ["UV_HANDLE_PRIVATE_FIELDS"]);
1056    }
1057
1058    #[test]
1059    #[hardwon(
1060        "5e7361a53510: C/C++ macro recovery previously lost or invented structured declarations"
1061    )]
1062    fn malformed_replacement_regions_still_refuse_the_whole_replacement() {
1063        assert!(object_macro_replacement("int ok; struct {").is_empty());
1064    }
1065
1066    #[test]
1067    #[hardwon(
1068        "5e7361a53510: C/C++ macro recovery previously lost or invented structured declarations"
1069    )]
1070    fn conflicting_replacements_keep_only_the_members_both_declare() {
1071        let unix = object_macro_replacement("uv_handle_t* next_closing; unsigned int flags;");
1072        let windows = object_macro_replacement("uv_handle_t* endgame_next; unsigned int flags;");
1073        assert_eq!(
1074            unix.intersect(&windows).fields,
1075            vec![MacroReplacementField {
1076                name: "flags".to_string(),
1077                declaration: "unsigned int flags;".to_string(),
1078                field_type: Some(CppDeclaredFieldTypeFact {
1079                    type_text: "unsigned int".to_string(),
1080                    indirection: 0,
1081                    binds_indirectly: false,
1082                    template_arguments: None,
1083                }),
1084                names_function_type: false,
1085            }]
1086        );
1087    }
1088
1089    #[test]
1090    #[hardwon(
1091        "5e7361a53510: C/C++ macro recovery previously lost or invented structured declarations"
1092    )]
1093    fn collapsed_aggregate_members_recover_their_names_and_ranges() {
1094        let source = "struct uv_signal_s {\n  UV_HANDLE_FIELDS\n  uv_signal_cb signal_cb;\n};";
1095        let span = source.find("uv_signal_cb").expect("member start")
1096            ..source.find("signal_cb;").expect("member end") + "signal_cb;".len();
1097        let fields = recovered_aggregate_fields(source, span);
1098        assert_eq!(
1099            fields
1100                .iter()
1101                .map(|field| (field.name.as_str(), field.declaration.as_str()))
1102                .collect::<Vec<_>>(),
1103            [("signal_cb", "uv_signal_cb signal_cb;")]
1104        );
1105        assert_eq!(&source[fields[0].range.clone()], "uv_signal_cb signal_cb;");
1106    }
1107
1108    #[test]
1109    fn function_macro_comment_boundary_grammar() {
1110        // Comments touching either replacement boundary must be masked just
1111        // like interior comments; replacement declarations cannot escape into
1112        // the containing translation unit.
1113        for replacement in [
1114            "/*head*/ int phantom;",
1115            "int phantom; /*tail*/",
1116            "int /*middle*/ phantom;",
1117        ] {
1118            let source = format!("#define F(){replacement}\nvoid caller() {{ F(); }}\n");
1119            let mut parser = tree_sitter::Parser::new();
1120            parser
1121                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1122                .unwrap();
1123            let raw = parser.parse(&source, None).unwrap();
1124            let ranges = super::function_macro_included_ranges(&source, &raw, || false)
1125                .continue_value()
1126                .unwrap()
1127                .expect("comment masking is required");
1128            parser.set_included_ranges(&ranges).unwrap();
1129            let tree = parser.parse(&source, None).unwrap();
1130            let declarations: Vec<_> = tree
1131                .root_node()
1132                .named_children(&mut tree.root_node().walk())
1133                .map(|n| n.kind())
1134                .collect();
1135            assert_eq!(
1136                declarations,
1137                ["preproc_function_def", "function_definition"],
1138                "{source}: {}",
1139                tree.root_node().to_sexp()
1140            );
1141            let caller = tree.root_node().named_child(1).unwrap();
1142            assert_eq!(&source[caller.byte_range()], "void caller() { F(); }");
1143        }
1144    }
1145}