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