1use 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::callable::{
9 CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
10};
11use brokk_bifrost_core::analyzer::structural::edges::{
12 INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
13};
14use brokk_bifrost_core::analyzer::structural::facts::Span;
15use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
16use brokk_bifrost_core::analyzer::structural::materialization::{
17 CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
18};
19use brokk_bifrost_core::analyzer::structural::occurrences::{
20 OccurrenceRole, OccurrenceRoleSupport,
21};
22use brokk_bifrost_core::analyzer::structural::resolution::{
23 CALLABLE_APPLICABILITY_ONLY_SUPPORT, LexicalEnvironmentSupport,
24};
25use brokk_bifrost_core::analyzer::structural::routes::{
26 IdentityAxis, IdentityRouteSupport, RouteHopKind,
27};
28use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
29use brokk_bifrost_core::analyzer::tree_walk::named_children_iter;
30use brokk_bifrost_core::hash::HashSet;
31use tree_sitter::Node;
32
33#[derive(Debug, Default)]
34pub struct CppStructuralSpec;
35
36pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
37
38pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
39 ("call_expression", NormalizedKind::Call),
40 ("new_expression", NormalizedKind::Call),
41 ("field_expression", NormalizedKind::FieldAccess),
42 ("function_definition", NormalizedKind::Function),
43 ("lambda_expression", NormalizedKind::Lambda),
44 ("class_specifier", NormalizedKind::Class),
45 ("struct_specifier", NormalizedKind::Class),
46 ("union_specifier", NormalizedKind::Class),
47 ("alias_declaration", NormalizedKind::Declaration),
48 ("namespace_definition", NormalizedKind::Module),
49 ("assignment_expression", NormalizedKind::Assignment),
50 ("init_declarator", NormalizedKind::Assignment),
51 ("preproc_include", NormalizedKind::Import),
52 ("identifier", NormalizedKind::Identifier),
53 ("field_identifier", NormalizedKind::Identifier),
54 ("namespace_identifier", NormalizedKind::Identifier),
55 ("qualified_identifier", NormalizedKind::Identifier),
56 ("type_identifier", NormalizedKind::Identifier),
57 ("template_function", NormalizedKind::Identifier),
58 ("template_method", NormalizedKind::Identifier),
59 ("template_type", NormalizedKind::Identifier),
60 ("dependent_name", NormalizedKind::Identifier),
61 ("destructor_name", NormalizedKind::Identifier),
62 ("operator_name", NormalizedKind::Identifier),
63 ("primitive_type", NormalizedKind::Identifier),
64 ("char_literal", NormalizedKind::StringLiteral),
65 ("string_literal", NormalizedKind::StringLiteral),
66 ("raw_string_literal", NormalizedKind::StringLiteral),
67 ("number_literal", NormalizedKind::NumericLiteral),
68 ("true", NormalizedKind::BooleanLiteral),
69 ("false", NormalizedKind::BooleanLiteral),
70 ("null", NormalizedKind::NullLiteral),
71 ("return_statement", NormalizedKind::Return),
72 ("throw_statement", NormalizedKind::Throw),
73 ("catch_clause", NormalizedKind::Catch),
74 ("if_statement", NormalizedKind::If),
75 ("for_statement", NormalizedKind::Loop),
76 ("while_statement", NormalizedKind::WhileLoop),
77 ("do_statement", NormalizedKind::WhileLoop),
78];
79
80pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
88 if node.kind() != "init_declarator" {
89 return false;
90 }
91 let Some(identifier) = node.child_by_field_name("declarator") else {
92 return false;
93 };
94 if identifier.kind() != "identifier" || identifier.is_missing() {
95 return false;
96 }
97 let Some(previous) = node.prev_named_sibling() else {
98 return false;
99 };
100 if previous.kind() != "ERROR"
101 || previous.named_child_count() != 0
102 || previous.child_count() != 1
103 || previous.end_byte() != node.start_byte()
104 {
105 return false;
106 }
107 previous.child(0).is_some_and(|child| {
108 child.kind() == "."
109 && !child.is_named()
110 && child.start_byte() == previous.start_byte()
111 && child.end_byte() == previous.end_byte()
112 })
113}
114
115fn last_named_field_child<'tree>(node: Node<'tree>, field: &str) -> Option<Node<'tree>> {
116 let mut cursor = node.walk();
117 node.children_by_field_name(field, &mut cursor)
118 .filter(|child| child.is_named())
119 .last()
120}
121
122fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
128 let mut current = namespace.child_by_field_name("name")?;
129 while current.kind() == "nested_namespace_specifier" {
130 current = current.named_child(current.named_child_count().checked_sub(1)?)?;
131 }
132 Some(current)
133}
134
135fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
136 let mut current = declarator;
137 loop {
138 match current.kind() {
139 "identifier"
140 | "field_identifier"
141 | "namespace_identifier"
142 | "type_identifier"
143 | "destructor_name"
144 | "operator_name"
145 | "primitive_type" => return Some(current),
146 "qualified_identifier" => current = last_named_field_child(current, "name")?,
147 "dependent_name" | "template_function" | "template_method" | "template_type" => {
148 current = current.child_by_field_name("name")?;
149 }
150 "function_declarator"
151 | "pointer_declarator"
152 | "array_declarator"
153 | "init_declarator" => current = current.child_by_field_name("declarator")?,
154 "reference_declarator" | "parenthesized_declarator" => {
155 current = first_named_child(current)?;
156 }
157 _ => return None,
158 }
159 }
160}
161
162fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
163 let mut current = expression;
164 loop {
165 match current.kind() {
166 "identifier"
167 | "field_identifier"
168 | "namespace_identifier"
169 | "type_identifier"
170 | "destructor_name"
171 | "operator_name"
172 | "primitive_type"
173 | "this" => return Some(current),
174 "qualified_identifier" => current = last_named_field_child(current, "name")?,
175 "dependent_name" | "template_function" | "template_method" | "template_type" => {
176 current = current.child_by_field_name("name")?;
177 }
178 "field_expression" => current = current.child_by_field_name("field")?,
179 "call_expression" => current = current.child_by_field_name("function")?,
180 "new_expression" => current = current.child_by_field_name("type")?,
181 "parenthesized_expression" => current = first_named_child(current)?,
182 _ => return declarator_name_node(current),
183 }
184 }
185}
186
187fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
188 if function.kind() != "qualified_identifier" {
189 return;
190 }
191 if let Some(scope) = function.child_by_field_name("scope") {
192 attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
193 }
194}
195
196fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
197 loop {
198 if node.kind() == "qualified_identifier" {
199 return Some(node);
200 }
201 node = node
202 .child_by_field_name("declarator")
203 .or_else(|| node.child_by_field_name("name"))
204 .or_else(|| first_named_child(node))?;
205 }
206}
207
208fn node_text<'source>(node: Node<'_>, source: &'source str) -> Option<&'source str> {
209 node.utf8_text(source.as_bytes()).ok()
210}
211
212fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
213 node.child_by_field_name("declarator")
214 .and_then(qualified_declarator_node)
215 .and_then(|qualified| qualified.child_by_field_name("scope"))
216}
217
218fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
219 node.child_by_field_name("declarator")
220 .and_then(qualified_declarator_node)
221 .and_then(|qualified| {
222 Some((
223 expression_name_node(qualified.child_by_field_name("scope")?)?,
224 expression_name_node(last_named_field_child(qualified, "name")?)?,
225 ))
226 })
227 .is_some_and(|(scope, name)| node_text(scope, source) == node_text(name, source))
228}
229
230fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
231 if !matches!(node.kind(), "string_literal" | "system_lib_string") {
232 return None;
233 }
234 let start = node.start_byte().checked_add(1)?;
235 let end = node.end_byte().checked_sub(1)?;
236 (start <= end).then_some(Span {
237 start_byte: start,
238 end_byte: end,
239 })
240}
241
242fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
250 let mut names = HashSet::default();
251 let mut stack = vec![root];
252 while let Some(node) = stack.pop() {
253 for child in named_children_iter(node) {
254 match child.kind() {
255 "preproc_function_def" => {
256 if let Some(name) = child.child_by_field_name("name") {
257 names.insert(source[name.start_byte()..name.end_byte()].to_owned());
258 }
259 }
260 "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
261 | "preproc_elifdef" => stack.push(child),
262 _ => {}
263 }
264 }
265 }
266 names
267}
268
269fn cpp_member_terminal_name<'tree>(field: Node<'tree>) -> Option<Node<'tree>> {
270 let field = if field.kind() == "dependent_name" {
271 first_named_child(field)?
272 } else {
273 field
274 };
275 expression_name_node(field)
276}
277
278fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
288 if !matches!(
289 node.kind(),
290 "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
291 ) {
292 return None;
293 }
294
295 let mut current = node;
296 while let Some(parent) = current.parent() {
297 if parent.kind() == "field_expression" {
298 let field = parent.child_by_field_name("field")?;
299 return cpp_member_terminal_name(field)
300 .filter(|name| name.id() == node.id())
301 .map(|_| OccurrenceRole::MemberPosition);
302 }
303
304 if matches!(
307 parent.kind(),
308 "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
309 ) {
310 current = parent;
311 } else {
312 return None;
313 }
314 }
315 None
316}
317
318impl StructuralSpec for CppStructuralSpec {
319 fn language(&self) -> Language {
320 Language::Cpp
321 }
322
323 fn supports_boolean_literal_value(&self) -> bool {
324 true
325 }
326
327 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
328 CPP_KIND_TABLE
329 }
330
331 fn refine_kind(
332 &self,
333 node: Node<'_>,
334 kind: NormalizedKind,
335 enclosing: Option<NormalizedKind>,
336 source: &str,
337 _context: &CallSiteContext,
338 ) -> NormalizedKind {
339 if kind == NormalizedKind::Function
340 && (enclosing == Some(NormalizedKind::Class)
341 || scoped_function_definition(node).is_some())
342 {
343 if is_constructor_definition(node, source) {
344 NormalizedKind::Constructor
345 } else {
346 NormalizedKind::Method
347 }
348 } else {
349 kind
350 }
351 }
352
353 fn supports_kind(&self, kind: NormalizedKind) -> bool {
354 matches!(kind, NormalizedKind::Method | NormalizedKind::Constructor)
355 || self
356 .kind_table()
357 .iter()
358 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
359 }
360
361 fn supports_role(&self, role: Role) -> bool {
362 !matches!(
363 role,
364 Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
365 )
366 }
367
368 fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
369 CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
370 }
371
372 fn call_site_facts(
385 &self,
386 node: Node<'_>,
387 source: &str,
388 context: &CallSiteContext,
389 ) -> Option<CallSiteFacts> {
390 if node.kind() == "new_expression" {
391 return Some(CallSiteFacts::of_kind(CallKind::Constructor));
392 }
393 let callee = node.child_by_field_name("function")?;
394 (callee.kind() == "identifier"
395 && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
396 .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
397 }
398
399 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
400 static SUPPORT: OccurrenceRoleSupport =
401 OccurrenceRoleSupport::NONE.supported(OccurrenceRole::MemberPosition);
402 &SUPPORT
403 }
404
405 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
406 &CALLABLE_APPLICABILITY_ONLY_SUPPORT
410 }
411
412 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
413 &CPP_MATERIALIZATION_SUPPORT
414 }
415
416 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
417 &INVERSE_REFERENCE_EDGE_SUPPORT
418 }
419
420 fn identity_route_support(&self) -> &IdentityRouteSupport {
421 static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
432 .supported_axis(IdentityAxis::CanonicalIdentity)
433 .supported_axis(IdentityAxis::PhysicalGrouping)
434 .supported_relation(RouteHopKind::NestedOwner)
435 .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
436 &SUPPORT
437 }
438
439 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
440 if let Some(role) = cpp_member_position(node) {
441 sink.occurrence_role(node, role);
442 }
443
444 match kind {
445 NormalizedKind::Call => {
446 let function_field = if node.kind() == "new_expression" {
447 "type"
448 } else {
449 "function"
450 };
451 if let Some(function) = node.child_by_field_name(function_field) {
452 attach_terminal_callee(sink, function, expression_name_node(function));
453 if function.kind() == "field_expression"
454 && let Some(argument) = function.child_by_field_name("argument")
455 {
456 attach_role_with_derived_name(
457 sink,
458 Role::Receiver,
459 argument,
460 expression_name_node,
461 );
462 }
463 attach_qualified_scope_receiver(sink, function);
464 }
465 if let Some(arguments) = node.child_by_field_name("arguments") {
466 attach_positional_argument_roles(sink, arguments, expression_name_node);
467 }
468 }
469 NormalizedKind::FieldAccess => {
470 if let Some(field) = node.child_by_field_name("field") {
471 attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
472 if let Some(name) = expression_name_node(field) {
473 sink.set_name(name);
474 }
475 }
476 if let Some(argument) = node.child_by_field_name("argument") {
477 attach_role_with_derived_name(
478 sink,
479 Role::Object,
480 argument,
481 expression_name_node,
482 );
483 }
484 }
485 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
486 if let Some(name) = node
487 .child_by_field_name("declarator")
488 .and_then(declarator_name_node)
489 {
490 sink.set_name(name);
491 }
492 }
493 NormalizedKind::Module => {
494 if let Some(name) = namespace_name_node(node) {
495 sink.set_name(name);
496 }
497 }
498 NormalizedKind::Class | NormalizedKind::Declaration => {
499 if let Some(name) = node
500 .child_by_field_name("name")
501 .and_then(declarator_name_node)
502 .or_else(|| node.child_by_field_name("name"))
503 {
504 sink.set_name(name);
505 }
506 }
507 NormalizedKind::Assignment => match node.kind() {
508 "init_declarator" => {
509 if let Some(declarator) = node.child_by_field_name("declarator") {
510 attach_role_with_derived_name(
511 sink,
512 Role::Left,
513 declarator,
514 declarator_name_node,
515 );
516 if let Some(name) = declarator_name_node(declarator) {
517 sink.set_name(name);
518 }
519 }
520 if let Some(value) = node.child_by_field_name("value") {
521 attach_role_with_derived_name(
522 sink,
523 Role::Right,
524 value,
525 expression_name_node,
526 );
527 }
528 }
529 "assignment_expression" => {
530 if let Some(left) = node.child_by_field_name("left") {
531 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
532 }
533 if let Some(right) = node.child_by_field_name("right") {
534 attach_role_with_derived_name(
535 sink,
536 Role::Right,
537 right,
538 expression_name_node,
539 );
540 }
541 }
542 _ => {}
543 },
544 NormalizedKind::Import => {
545 if let Some(path) = node.child_by_field_name("path") {
546 if let Some(name) = unquoted_include_span(path) {
547 sink.role_named_span(Role::Module, path, name);
548 } else {
549 attach_role_with_derived_name(
550 sink,
551 Role::Module,
552 path,
553 expression_name_node,
554 );
555 }
556 }
557 }
558 NormalizedKind::Identifier => match expression_name_node(node) {
559 Some(name) => sink.set_name(name),
560 None => sink.set_name(node),
561 },
562 _ => {}
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::cpp_member_position;
570 use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
571 use brokk_bifrost_core::analyzer::structural::spec::StructuralSpec;
572 use tree_sitter::Parser;
573
574 fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
575 let mut parser = Parser::new();
576 parser
577 .set_language(&tree_sitter_cpp::LANGUAGE.into())
578 .expect("C++ grammar");
579 let tree = parser.parse(source, None).expect("C++ parse");
580 assert!(
581 !tree.root_node().has_error(),
582 "{}",
583 tree.root_node().to_sexp()
584 );
585 let mut found = Vec::new();
586 let mut pending = vec![tree.root_node()];
587 while let Some(node) = pending.pop() {
588 if let Some(role) = cpp_member_position(node) {
589 found.push((
590 node.start_byte(),
591 &source[node.start_byte()..node.end_byte()],
592 role,
593 ));
594 }
595 for index in (0..node.named_child_count()).rev() {
596 if let Some(child) = node.named_child(index) {
597 pending.push(child);
598 }
599 }
600 }
601 found
602 }
603
604 #[test]
605 fn cpp_member_position_is_limited_to_member_access_and_calls() {
606 let source = concat!(
607 "struct Widget {\n",
608 " int value;\n",
609 " int method(int label) {\n",
610 " return this->value + label.value + label.method()\n",
611 " + label.template convert<int>() + label.ns::member;\n",
612 " }\n",
613 "};\n",
614 "int build(Widget widget) {\n",
615 " Widget result{.value = widget.value};\n",
616 " return widget.method(result.value);\n",
617 "}\n",
618 );
619 let found = member_occurrences(source);
620 let member_texts = found
621 .iter()
622 .map(|(_, text, role)| (*text, *role))
623 .collect::<Vec<_>>();
624
625 assert_eq!(
626 member_texts,
627 vec![
628 ("value", OccurrenceRole::MemberPosition),
629 ("value", OccurrenceRole::MemberPosition),
630 ("method", OccurrenceRole::MemberPosition),
631 ("convert", OccurrenceRole::MemberPosition),
632 ("member", OccurrenceRole::MemberPosition),
633 ("value", OccurrenceRole::MemberPosition),
634 ("method", OccurrenceRole::MemberPosition),
635 ("value", OccurrenceRole::MemberPosition),
636 ]
637 );
638
639 let at = |needle: &str| source.find(needle).expect("fixture token");
640 for receiver in ["this->", "label.value", "widget.value", "result.value"] {
641 let receiver_start = at(receiver);
642 assert!(
643 found.iter().all(|(offset, _, _)| *offset != receiver_start),
644 "receiver was classified: {receiver:?}"
645 );
646 }
647 let non_members = [
648 at("Widget {"),
649 at("int value") + "int ".len(),
650 at("int method") + "int ".len(),
651 at(".value =") + 1,
652 ];
653 for unrelated_start in non_members {
654 assert!(
655 found
656 .iter()
657 .all(|(offset, _, _)| *offset != unrelated_start),
658 "non-member identifier at byte {unrelated_start} was classified"
659 );
660 }
661 }
662
663 #[test]
664 fn cpp_member_position_support_declares_only_member_position() {
665 let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
666 assert!(support.is_supported(OccurrenceRole::MemberPosition));
667 for role in [
668 OccurrenceRole::ReceiverPosition,
669 OccurrenceRole::LabelOrKey,
670 OccurrenceRole::DeclarationName,
671 OccurrenceRole::ValueReference,
672 ] {
673 assert!(
674 !support.is_supported(role),
675 "unexpected C++ support for {role:?}"
676 );
677 }
678 }
679}