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