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