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