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