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 std::ops::Range;
7use tree_sitter::{Node, Parser};
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct MacroReplacementTypeReference {
11    pub components: Vec<String>,
12    pub component_ranges: Vec<Range<usize>>,
13    pub global: bool,
14}
15
16/// One direct field declaration recovered from an object-like macro
17/// replacement. The replacement is parsed as the body of a synthetic struct,
18/// so the name and declaration text come from C/C++ grammar nodes rather than
19/// from a textual macro expansion.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct MacroReplacementField {
22    pub name: String,
23    pub declaration: String,
24}
25
26/// Recover direct fields hidden in an object-like macro replacement.
27///
28/// A field-list macro is valid in more than one owner, so this helper returns
29/// only the declaration-shaped children of the synthetic field list. Nested
30/// aggregate promotion remains the owner's normal structured aggregate logic;
31/// treating nested members as direct fields here would leak them across owners.
32pub fn object_macro_replacement_fields(replacement: &str) -> Vec<MacroReplacementField> {
33    if replacement.trim().is_empty() {
34        return Vec::new();
35    }
36    let normalized_replacement = normalize_macro_continuations(replacement);
37    const PREFIX: &str = "struct __bifrost_macro_fields { ";
38    let synthetic = format!("{PREFIX}{normalized_replacement} }};");
39    let mut parser = Parser::new();
40    if parser
41        .set_language(&tree_sitter_cpp::LANGUAGE.into())
42        .is_err()
43    {
44        return Vec::new();
45    }
46    let Some(tree) = parser.parse(&synthetic, None) else {
47        return Vec::new();
48    };
49    if tree.root_node().has_error() {
50        return Vec::new();
51    }
52    let mut stack = vec![tree.root_node()];
53    let body = loop {
54        let Some(current) = stack.pop() else {
55            return Vec::new();
56        };
57        if current.kind() == "struct_specifier"
58            && let Some(body) = current.child_by_field_name("body")
59        {
60            break body;
61        }
62        let mut cursor = current.walk();
63        for child in current.named_children(&mut cursor) {
64            stack.push(child);
65        }
66    };
67    let mut fields = Vec::new();
68    let mut cursor = body.walk();
69    for declaration in body.named_children(&mut cursor) {
70        if !matches!(declaration.kind(), "declaration" | "field_declaration") {
71            continue;
72        }
73        let Some(declarator) = declaration
74            .child_by_field_name("declarator")
75            .or_else(|| declaration.named_child(1))
76        else {
77            continue;
78        };
79        let Some(name) = macro_replacement_declarator_name(declarator, &synthetic) else {
80            continue;
81        };
82        let Some(declaration_text) =
83            declaration_text_without_synthetic_prefix(declaration, replacement, PREFIX.len())
84        else {
85            continue;
86        };
87        fields.push(MacroReplacementField {
88            name,
89            declaration: declaration_text,
90        });
91    }
92    fields
93}
94
95/// Keep preprocessor line continuations as byte-preserving whitespace before
96/// reparsing an opaque replacement. The parser's replacement node includes
97/// the backslash/newline pair, while C's preprocessing phase treats it as one
98/// logical line. Replacing both bytes (and CRLF's three bytes) keeps every
99/// tree-sitter byte range mapped directly to the original replacement.
100fn normalize_macro_continuations(replacement: &str) -> String {
101    let source = replacement.as_bytes();
102    let mut normalized = source.to_vec();
103    let mut index = 0;
104    while index + 1 < source.len() {
105        if source[index] == b'\\' && source[index + 1] == b'\n' {
106            normalized[index] = b' ';
107            normalized[index + 1] = b' ';
108            index += 2;
109        } else if index + 2 < source.len()
110            && source[index] == b'\\'
111            && source[index + 1] == b'\r'
112            && source[index + 2] == b'\n'
113        {
114            normalized[index] = b' ';
115            normalized[index + 1] = b' ';
116            normalized[index + 2] = b' ';
117            index += 3;
118        } else {
119            index += 1;
120        }
121    }
122    String::from_utf8(normalized).expect("source text must remain valid UTF-8")
123}
124
125fn macro_replacement_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
126    match node.kind() {
127        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
128            let name = node_text(node, source).trim();
129            (!name.is_empty()).then(|| name.to_string())
130        }
131        "function_declarator" => None,
132        _ => node
133            .child_by_field_name("declarator")
134            .or_else(|| node.child_by_field_name("name"))
135            .and_then(|child| macro_replacement_declarator_name(child, source)),
136    }
137}
138
139fn declaration_text_without_synthetic_prefix(
140    node: Node<'_>,
141    source: &str,
142    prefix_len: usize,
143) -> Option<String> {
144    let start = node.start_byte().checked_sub(prefix_len)?;
145    let end = node.end_byte().checked_sub(prefix_len)?;
146    (end <= source.len()).then(|| source[start..end].to_string())
147}
148
149/// Recover type-bearing syntax hidden inside an object-like macro replacement.
150///
151/// Tree-sitter deliberately keeps the replacement of `#define NAME value` as
152/// one opaque `preproc_arg`. Reparse that exact byte slice as a C++ expression
153/// and return only references proven by the resulting tree: ordinary type
154/// nodes and the owner prefixes of qualified values such as `Owner::member`.
155/// Every returned range is mapped back to the original file.
156pub fn object_macro_replacement_type_references(
157    node: Node<'_>,
158    source: &str,
159) -> Vec<MacroReplacementTypeReference> {
160    if node.kind() != "preproc_arg"
161        || !node.parent().is_some_and(|parent| {
162            parent.kind() == "preproc_def"
163                && parent
164                    .child_by_field_name("value")
165                    .is_some_and(|value| value == node)
166        })
167    {
168        return Vec::new();
169    }
170    let Some(replacement) = source.get(node.start_byte()..node.end_byte()) else {
171        return Vec::new();
172    };
173    const PREFIX: &str = "void __bifrost_macro_reference() { ";
174    let synthetic = format!("{PREFIX}{replacement}; }}");
175    let mut parser = Parser::new();
176    if parser
177        .set_language(&tree_sitter_cpp::LANGUAGE.into())
178        .is_err()
179    {
180        return Vec::new();
181    }
182    let Some(tree) = parser.parse(&synthetic, None) else {
183        return Vec::new();
184    };
185    if tree.root_node().has_error() {
186        return Vec::new();
187    }
188
189    let mut references = Vec::new();
190    let mut stack = vec![tree.root_node()];
191    while let Some(current) = stack.pop() {
192        let structured = if matches!(
193            current.kind(),
194            "type_identifier" | "scoped_type_identifier" | "template_type"
195        ) && !is_nested_type_node(current)
196        {
197            cpp_type_name_components(current, &synthetic)
198                .zip(cpp_name_component_nodes(current))
199                .map(|(components, nodes)| {
200                    (components, nodes, is_globally_qualified_cpp_name(current))
201                })
202        } else if current.kind() == "qualified_identifier"
203            && !current.parent().is_some_and(|parent| {
204                matches!(
205                    parent.kind(),
206                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
207                )
208            })
209        {
210            qualified_owner_components(current, &synthetic)
211                .map(|owner| (owner.names, owner.nodes, owner.global))
212        } else {
213            None
214        };
215        if let Some((components, component_nodes, global)) = structured {
216            let component_ranges = component_nodes
217                .into_iter()
218                .map(|component| {
219                    let start = component.start_byte().checked_sub(PREFIX.len())?;
220                    let end = component.end_byte().checked_sub(PREFIX.len())?;
221                    (end <= replacement.len())
222                        .then_some(node.start_byte() + start..node.start_byte() + end)
223                })
224                .collect::<Option<Vec<_>>>();
225            if let Some(component_ranges) = component_ranges
226                && component_ranges.len() == components.len()
227            {
228                let reference = MacroReplacementTypeReference {
229                    components,
230                    component_ranges,
231                    global,
232                };
233                if !references.contains(&reference) {
234                    references.push(reference);
235                }
236            }
237        }
238        for index in (0..current.named_child_count()).rev() {
239            if let Some(child) = current.named_child(index) {
240                stack.push(child);
241            }
242        }
243    }
244    references
245}
246
247#[derive(Clone)]
248pub struct QualifiedCallableValue<'tree> {
249    pub qualified: Node<'tree>,
250    pub global: bool,
251    pub owner_components: Vec<Node<'tree>>,
252    pub member: Node<'tree>,
253}
254
255/// Recognize an explicit address-of qualified callable value such as
256/// `&Owner::method` or `&namespace::Owner::method`.
257///
258/// The returned nodes come exclusively from the C++ grammar's named fields. In
259/// particular, a nested namespace/type owner remains a structured subtree rather
260/// than being reconstructed from source text.
261pub fn explicit_qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
262    if node.kind() != "pointer_expression" || node.child_by_field_name("operator")?.kind() != "&" {
263        return None;
264    }
265    let qualified = node.child_by_field_name("argument")?;
266    qualified_callable_value_from_node(qualified)
267}
268
269/// Recognize a qualified callable used as an expression value.
270///
271/// Calls use their own arity-aware path. Address-of expressions use the
272/// explicit path above. This arm covers structured values such as
273/// `bind(Owner::method)` and `callback = namespace::function`.
274pub fn qualified_callable_value(node: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
275    if let Some(value) = explicit_qualified_callable_value(node) {
276        return Some(value);
277    }
278    if node.kind() != "qualified_identifier" {
279        return None;
280    }
281    if crate::graph::resolver::is_declaration_name(node) {
282        return None;
283    }
284    if node.parent().is_some_and(|parent| {
285        parent.child_by_field_name("type") == Some(node)
286            || (parent.kind() == "call_expression"
287                && parent.child_by_field_name("function") == Some(node))
288            || (parent.kind() == "pointer_expression"
289                && parent.child_by_field_name("argument") == Some(node))
290            || matches!(
291                parent.kind(),
292                "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
293            )
294    }) {
295        return None;
296    }
297    qualified_callable_value_from_node(node)
298}
299
300fn qualified_callable_value_from_node(qualified: Node<'_>) -> Option<QualifiedCallableValue<'_>> {
301    if qualified.kind() != "qualified_identifier" {
302        return None;
303    }
304    let mut components = Vec::new();
305    let global = qualified.child_by_field_name("scope").is_none()
306        && qualified.child(0).is_some_and(|child| child.kind() == "::");
307    append_qualified_components(qualified, &mut components)?;
308    let member = components.pop()?;
309    if components.is_empty() {
310        return None;
311    }
312    Some(QualifiedCallableValue {
313        qualified,
314        global,
315        owner_components: components,
316        member,
317    })
318}
319
320fn append_qualified_components<'tree>(node: Node<'tree>, out: &mut Vec<Node<'tree>>) -> Option<()> {
321    let mut stack = vec![node];
322    while let Some(current) = stack.pop() {
323        match current.kind() {
324            "identifier" | "namespace_identifier" | "type_identifier" | "operator_name" => {
325                out.push(current)
326            }
327            "qualified_identifier" | "scoped_identifier" => {
328                stack.push(current.child_by_field_name("name")?);
329                if let Some(scope) = current.child_by_field_name("scope") {
330                    stack.push(scope);
331                } else if current.child(0).is_none_or(|child| child.kind() != "::") {
332                    return None;
333                }
334            }
335            "template_type" | "template_function" => {
336                stack.push(current.child_by_field_name("name")?);
337            }
338            "nested_namespace_specifier" => {
339                for index in (0..current.named_child_count()).rev() {
340                    stack.push(current.named_child(index)?);
341                }
342            }
343            _ => return None,
344        }
345    }
346    Some(())
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    fn references(source: &str) -> Vec<MacroReplacementTypeReference> {
354        let mut parser = Parser::new();
355        parser
356            .set_language(&tree_sitter_cpp::LANGUAGE.into())
357            .expect("C++ grammar");
358        let tree = parser.parse(source, None).expect("macro fixture tree");
359        let value = tree
360            .root_node()
361            .named_child(0)
362            .and_then(|definition| definition.child_by_field_name("value"))
363            .expect("macro replacement");
364        object_macro_replacement_type_references(value, source)
365    }
366
367    #[test]
368    fn object_macro_replacement_reparse_preserves_type_ranges() {
369        let source = "#define SETTINGS (*api::SettingsImpl::GetInstance())\n";
370        let references = references(source);
371        let reference = references
372            .iter()
373            .find(|reference| reference.components == ["api", "SettingsImpl"])
374            .expect("qualified callable owner");
375        let rendered = reference
376            .component_ranges
377            .iter()
378            .map(|range| &source[range.clone()])
379            .collect::<Vec<_>>();
380        assert_eq!(rendered, ["api", "SettingsImpl"]);
381    }
382
383    #[test]
384    fn object_macro_replacement_fields_are_structured_and_direct_only() {
385        let fields = object_macro_replacement_fields(
386            r#"int public_value; \
387             union { int nested_value; }; \
388             unsigned private_value;"#,
389        );
390        assert_eq!(
391            fields,
392            vec![
393                MacroReplacementField {
394                    name: "public_value".to_string(),
395                    declaration: "int public_value;".to_string(),
396                },
397                MacroReplacementField {
398                    name: "private_value".to_string(),
399                    declaration: "unsigned private_value;".to_string(),
400                },
401            ]
402        );
403        assert!(object_macro_replacement_fields("not a declaration").is_empty());
404    }
405
406    #[test]
407    fn macro_reparse_ignores_function_like_and_non_code_text() {
408        let function_like = "#define SETTINGS(Type) (*Type::GetInstance())\n";
409        assert!(references(function_like).is_empty());
410
411        let text = "#define SETTINGS \"SettingsImpl::GetInstance()\"\n";
412        assert!(references(text).is_empty());
413    }
414}