Skip to main content

brokk_bifrost_cpp/
structural.rs

1//! C++ structural spec for `query_code`.
2
3use brokk_bifrost_core::analyzer::Language;
4use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
5    attach_positional_argument_roles, attach_role_with_derived_name, attach_terminal_callee,
6    first_named_child,
7};
8use brokk_bifrost_core::analyzer::structural::edges::{
9    INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
10};
11use brokk_bifrost_core::analyzer::structural::facts::Span;
12use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
13use brokk_bifrost_core::analyzer::structural::materialization::{
14    CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
15};
16use brokk_bifrost_core::analyzer::structural::occurrences::{
17    NO_OCCURRENCE_ROLE_SUPPORT, OccurrenceRoleSupport,
18};
19use brokk_bifrost_core::analyzer::structural::resolution::{
20    LexicalEnvironmentSupport, NO_LEXICAL_ENVIRONMENT_SUPPORT,
21};
22use brokk_bifrost_core::analyzer::structural::routes::{
23    IdentityAxis, IdentityRouteSupport, RouteHopKind,
24};
25use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
26use tree_sitter::Node;
27
28#[derive(Debug, Default)]
29pub struct CppStructuralSpec;
30
31pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
32
33pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
34    ("call_expression", NormalizedKind::Call),
35    ("new_expression", NormalizedKind::Call),
36    ("field_expression", NormalizedKind::FieldAccess),
37    ("function_definition", NormalizedKind::Function),
38    ("lambda_expression", NormalizedKind::Lambda),
39    ("class_specifier", NormalizedKind::Class),
40    ("struct_specifier", NormalizedKind::Class),
41    ("union_specifier", NormalizedKind::Class),
42    ("alias_declaration", NormalizedKind::Declaration),
43    ("assignment_expression", NormalizedKind::Assignment),
44    ("init_declarator", NormalizedKind::Assignment),
45    ("preproc_include", NormalizedKind::Import),
46    ("identifier", NormalizedKind::Identifier),
47    ("field_identifier", NormalizedKind::Identifier),
48    ("namespace_identifier", NormalizedKind::Identifier),
49    ("qualified_identifier", NormalizedKind::Identifier),
50    ("type_identifier", NormalizedKind::Identifier),
51    ("template_function", NormalizedKind::Identifier),
52    ("template_method", NormalizedKind::Identifier),
53    ("template_type", NormalizedKind::Identifier),
54    ("dependent_name", NormalizedKind::Identifier),
55    ("destructor_name", NormalizedKind::Identifier),
56    ("operator_name", NormalizedKind::Identifier),
57    ("primitive_type", NormalizedKind::Identifier),
58    ("char_literal", NormalizedKind::StringLiteral),
59    ("string_literal", NormalizedKind::StringLiteral),
60    ("raw_string_literal", NormalizedKind::StringLiteral),
61    ("number_literal", NormalizedKind::NumericLiteral),
62    ("true", NormalizedKind::BooleanLiteral),
63    ("false", NormalizedKind::BooleanLiteral),
64    ("null", NormalizedKind::NullLiteral),
65    ("return_statement", NormalizedKind::Return),
66    ("throw_statement", NormalizedKind::Throw),
67    ("catch_clause", NormalizedKind::Catch),
68    ("if_statement", NormalizedKind::If),
69    ("for_statement", NormalizedKind::Loop),
70    ("while_statement", NormalizedKind::WhileLoop),
71    ("do_statement", NormalizedKind::WhileLoop),
72];
73
74/// Whether tree-sitter recovered `.field = value` as a declaration child pair:
75/// an `ERROR` containing only the anonymous dot immediately followed by an
76/// `init_declarator` whose declarator is a real identifier.
77///
78/// This is intentionally narrower than rejecting declarators below `ERROR` in
79/// general: macro-decorated declarations and ordinary comma-separated initialized
80/// declarations also use recovery nodes and remain real declarations.
81pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
82    if node.kind() != "init_declarator" {
83        return false;
84    }
85    let Some(identifier) = node.child_by_field_name("declarator") else {
86        return false;
87    };
88    if identifier.kind() != "identifier" || identifier.is_missing() {
89        return false;
90    }
91    let Some(previous) = node.prev_named_sibling() else {
92        return false;
93    };
94    if previous.kind() != "ERROR"
95        || previous.named_child_count() != 0
96        || previous.child_count() != 1
97        || previous.end_byte() != node.start_byte()
98    {
99        return false;
100    }
101    previous.child(0).is_some_and(|child| {
102        child.kind() == "."
103            && !child.is_named()
104            && child.start_byte() == previous.start_byte()
105            && child.end_byte() == previous.end_byte()
106    })
107}
108
109fn last_named_field_child<'tree>(node: Node<'tree>, field: &str) -> Option<Node<'tree>> {
110    let mut cursor = node.walk();
111    node.children_by_field_name(field, &mut cursor)
112        .filter(|child| child.is_named())
113        .last()
114}
115
116fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
117    let mut current = declarator;
118    loop {
119        match current.kind() {
120            "identifier"
121            | "field_identifier"
122            | "namespace_identifier"
123            | "type_identifier"
124            | "destructor_name"
125            | "operator_name"
126            | "primitive_type" => return Some(current),
127            "qualified_identifier" => current = last_named_field_child(current, "name")?,
128            "dependent_name" | "template_function" | "template_method" | "template_type" => {
129                current = current.child_by_field_name("name")?;
130            }
131            "function_declarator"
132            | "pointer_declarator"
133            | "array_declarator"
134            | "init_declarator" => current = current.child_by_field_name("declarator")?,
135            "reference_declarator" | "parenthesized_declarator" => {
136                current = first_named_child(current)?;
137            }
138            _ => return None,
139        }
140    }
141}
142
143fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
144    let mut current = expression;
145    loop {
146        match current.kind() {
147            "identifier"
148            | "field_identifier"
149            | "namespace_identifier"
150            | "type_identifier"
151            | "destructor_name"
152            | "operator_name"
153            | "primitive_type"
154            | "this" => return Some(current),
155            "qualified_identifier" => current = last_named_field_child(current, "name")?,
156            "dependent_name" | "template_function" | "template_method" | "template_type" => {
157                current = current.child_by_field_name("name")?;
158            }
159            "field_expression" => current = current.child_by_field_name("field")?,
160            "call_expression" => current = current.child_by_field_name("function")?,
161            "new_expression" => current = current.child_by_field_name("type")?,
162            "parenthesized_expression" => current = first_named_child(current)?,
163            _ => return declarator_name_node(current),
164        }
165    }
166}
167
168fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
169    if function.kind() != "qualified_identifier" {
170        return;
171    }
172    if let Some(scope) = function.child_by_field_name("scope") {
173        attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
174    }
175}
176
177fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
178    loop {
179        if node.kind() == "qualified_identifier" {
180            return Some(node);
181        }
182        node = node
183            .child_by_field_name("declarator")
184            .or_else(|| node.child_by_field_name("name"))
185            .or_else(|| first_named_child(node))?;
186    }
187}
188
189fn node_text<'source>(node: Node<'_>, source: &'source str) -> Option<&'source str> {
190    node.utf8_text(source.as_bytes()).ok()
191}
192
193fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
194    node.child_by_field_name("declarator")
195        .and_then(qualified_declarator_node)
196        .and_then(|qualified| qualified.child_by_field_name("scope"))
197}
198
199fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
200    node.child_by_field_name("declarator")
201        .and_then(qualified_declarator_node)
202        .and_then(|qualified| {
203            Some((
204                expression_name_node(qualified.child_by_field_name("scope")?)?,
205                expression_name_node(last_named_field_child(qualified, "name")?)?,
206            ))
207        })
208        .is_some_and(|(scope, name)| node_text(scope, source) == node_text(name, source))
209}
210
211fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
212    if !matches!(node.kind(), "string_literal" | "system_lib_string") {
213        return None;
214    }
215    let start = node.start_byte().checked_add(1)?;
216    let end = node.end_byte().checked_sub(1)?;
217    (start <= end).then_some(Span {
218        start_byte: start,
219        end_byte: end,
220    })
221}
222
223impl StructuralSpec for CppStructuralSpec {
224    fn language(&self) -> Language {
225        Language::Cpp
226    }
227
228    fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
229        CPP_KIND_TABLE
230    }
231
232    fn refine_kind(
233        &self,
234        node: Node<'_>,
235        kind: NormalizedKind,
236        enclosing: Option<NormalizedKind>,
237        source: &str,
238    ) -> NormalizedKind {
239        if kind == NormalizedKind::Function
240            && (enclosing == Some(NormalizedKind::Class)
241                || scoped_function_definition(node).is_some())
242        {
243            if is_constructor_definition(node, source) {
244                NormalizedKind::Constructor
245            } else {
246                NormalizedKind::Method
247            }
248        } else {
249            kind
250        }
251    }
252
253    fn supports_kind(&self, kind: NormalizedKind) -> bool {
254        matches!(kind, NormalizedKind::Method | NormalizedKind::Constructor)
255            || self
256                .kind_table()
257                .iter()
258                .any(|(_, fact_kind)| fact_kind.satisfies(kind))
259    }
260
261    fn supports_role(&self, role: Role) -> bool {
262        !matches!(role, Role::Kwarg | Role::Decorator)
263    }
264
265    /// C++ has not learned occurrence-role classification yet (#1473).
266    /// The empty table is the honest answer: queries and assertions that ask
267    /// for an occurrence role here report incomplete rather than clean-empty.
268    fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
269        &NO_OCCURRENCE_ROLE_SUPPORT
270    }
271
272    fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
273        &NO_LEXICAL_ENVIRONMENT_SUPPORT
274    }
275
276    fn materialization_support(&self) -> &DeclarationMaterializationSupport {
277        &CPP_MATERIALIZATION_SUPPORT
278    }
279
280    fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
281        &INVERSE_REFERENCE_EDGE_SUPPORT
282    }
283
284    fn identity_route_support(&self) -> &IdentityRouteSupport {
285        // C++'s occurrence adapter is shallow, so it claims no path axes. Its
286        // declaration layer keeps prototype/body occurrences distinct, which
287        // the physical-grouping axis carries; the declaration-definition peer
288        // *relation* stays unclaimed until a producer emits typed peer rows
289        // rather than merged ranges (see the #1475 ExecPlan Decision Log, M6,
290        // and its follow-up issue).
291        static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
292            .supported_axis(IdentityAxis::CanonicalIdentity)
293            .supported_axis(IdentityAxis::PhysicalGrouping)
294            .supported_relation(RouteHopKind::NestedOwner);
295        &SUPPORT
296    }
297
298    fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
299        match kind {
300            NormalizedKind::Call => {
301                let function_field = if node.kind() == "new_expression" {
302                    "type"
303                } else {
304                    "function"
305                };
306                if let Some(function) = node.child_by_field_name(function_field) {
307                    attach_terminal_callee(sink, function, expression_name_node(function));
308                    if function.kind() == "field_expression"
309                        && let Some(argument) = function.child_by_field_name("argument")
310                    {
311                        attach_role_with_derived_name(
312                            sink,
313                            Role::Receiver,
314                            argument,
315                            expression_name_node,
316                        );
317                    }
318                    attach_qualified_scope_receiver(sink, function);
319                }
320                if let Some(arguments) = node.child_by_field_name("arguments") {
321                    attach_positional_argument_roles(sink, arguments, expression_name_node);
322                }
323            }
324            NormalizedKind::FieldAccess => {
325                if let Some(field) = node.child_by_field_name("field") {
326                    attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
327                    if let Some(name) = expression_name_node(field) {
328                        sink.set_name(name);
329                    }
330                }
331                if let Some(argument) = node.child_by_field_name("argument") {
332                    attach_role_with_derived_name(
333                        sink,
334                        Role::Object,
335                        argument,
336                        expression_name_node,
337                    );
338                }
339            }
340            NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
341                if let Some(name) = node
342                    .child_by_field_name("declarator")
343                    .and_then(declarator_name_node)
344                {
345                    sink.set_name(name);
346                }
347            }
348            NormalizedKind::Class | NormalizedKind::Declaration => {
349                if let Some(name) = node
350                    .child_by_field_name("name")
351                    .and_then(declarator_name_node)
352                    .or_else(|| node.child_by_field_name("name"))
353                {
354                    sink.set_name(name);
355                }
356            }
357            NormalizedKind::Assignment => match node.kind() {
358                "init_declarator" => {
359                    if let Some(declarator) = node.child_by_field_name("declarator") {
360                        attach_role_with_derived_name(
361                            sink,
362                            Role::Left,
363                            declarator,
364                            declarator_name_node,
365                        );
366                        if let Some(name) = declarator_name_node(declarator) {
367                            sink.set_name(name);
368                        }
369                    }
370                    if let Some(value) = node.child_by_field_name("value") {
371                        attach_role_with_derived_name(
372                            sink,
373                            Role::Right,
374                            value,
375                            expression_name_node,
376                        );
377                    }
378                }
379                "assignment_expression" => {
380                    if let Some(left) = node.child_by_field_name("left") {
381                        attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
382                    }
383                    if let Some(right) = node.child_by_field_name("right") {
384                        attach_role_with_derived_name(
385                            sink,
386                            Role::Right,
387                            right,
388                            expression_name_node,
389                        );
390                    }
391                }
392                _ => {}
393            },
394            NormalizedKind::Import => {
395                if let Some(path) = node.child_by_field_name("path") {
396                    if let Some(name) = unquoted_include_span(path) {
397                        sink.role_named_span(Role::Module, path, name);
398                    } else {
399                        attach_role_with_derived_name(
400                            sink,
401                            Role::Module,
402                            path,
403                            expression_name_node,
404                        );
405                    }
406                }
407            }
408            NormalizedKind::Identifier => match expression_name_node(node) {
409                Some(name) => sink.set_name(name),
410                None => sink.set_name(node),
411            },
412            _ => {}
413        }
414    }
415}