1use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
4 attach_positional_argument_roles, attach_role_with_derived_name, attach_terminal_callee,
5 field_name_in_parent, first_named_child,
6};
7use brokk_bifrost_core::analyzer::structural::callable::{
8 CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
9};
10use brokk_bifrost_core::analyzer::structural::edges::{
11 INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
12};
13use brokk_bifrost_core::analyzer::structural::facts::Span;
14use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
15use brokk_bifrost_core::analyzer::structural::materialization::{
16 CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
17};
18use brokk_bifrost_core::analyzer::structural::occurrences::{
19 OccurrenceRole, OccurrenceRoleSupport,
20};
21use brokk_bifrost_core::analyzer::structural::resolution::{
22 BindingActivation, BindingKind, EnvironmentAxis, HoistingClass, LexicalEnvironmentSupport,
23 ScopeFormation, default_scope_formation,
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::{ParentIndex, named_children_iter, node_range};
30use brokk_bifrost_core::analyzer::{Language, Range};
31use brokk_bifrost_core::hash::HashSet;
32use tree_sitter::Node;
33
34#[derive(Debug, Default)]
35pub struct CppStructuralSpec;
36
37pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
38
39pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
40 ("call_expression", NormalizedKind::Call),
41 ("new_expression", NormalizedKind::Call),
42 ("field_expression", NormalizedKind::FieldAccess),
43 ("function_definition", NormalizedKind::Function),
44 ("lambda_expression", NormalizedKind::Lambda),
45 ("class_specifier", NormalizedKind::Class),
46 ("struct_specifier", NormalizedKind::Class),
47 ("union_specifier", NormalizedKind::Class),
48 ("alias_declaration", NormalizedKind::Declaration),
49 ("namespace_definition", NormalizedKind::Module),
50 ("assignment_expression", NormalizedKind::Assignment),
51 ("init_declarator", NormalizedKind::Assignment),
52 ("preproc_include", NormalizedKind::Import),
53 ("identifier", NormalizedKind::Identifier),
54 ("field_identifier", NormalizedKind::Identifier),
55 ("namespace_identifier", NormalizedKind::Identifier),
56 ("qualified_identifier", NormalizedKind::Identifier),
57 ("type_identifier", NormalizedKind::Identifier),
58 ("template_function", NormalizedKind::Identifier),
59 ("template_method", NormalizedKind::Identifier),
60 ("template_type", NormalizedKind::Identifier),
61 ("dependent_name", NormalizedKind::Identifier),
62 ("destructor_name", NormalizedKind::Identifier),
63 ("operator_name", NormalizedKind::Identifier),
64 ("primitive_type", NormalizedKind::Identifier),
65 ("char_literal", NormalizedKind::StringLiteral),
66 ("string_literal", NormalizedKind::StringLiteral),
67 ("raw_string_literal", NormalizedKind::StringLiteral),
68 ("number_literal", NormalizedKind::NumericLiteral),
69 ("true", NormalizedKind::BooleanLiteral),
70 ("false", NormalizedKind::BooleanLiteral),
71 ("null", NormalizedKind::NullLiteral),
72 ("return_statement", NormalizedKind::Return),
73 ("throw_statement", NormalizedKind::Throw),
74 ("catch_clause", NormalizedKind::Catch),
75 ("if_statement", NormalizedKind::If),
76 ("for_statement", NormalizedKind::Loop),
77 ("for_range_loop", NormalizedKind::Loop),
78 ("compound_statement", NormalizedKind::Block),
81 ("switch_statement", NormalizedKind::Block),
82 ("while_statement", NormalizedKind::WhileLoop),
83 ("do_statement", NormalizedKind::WhileLoop),
84];
85
86pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
94 if node.kind() != "init_declarator" {
95 return false;
96 }
97 let Some(identifier) = node.child_by_field_name("declarator") else {
98 return false;
99 };
100 if identifier.kind() != "identifier" || identifier.is_missing() {
101 return false;
102 }
103 let Some(previous) = node.prev_named_sibling() else {
104 return false;
105 };
106 if previous.kind() != "ERROR"
107 || previous.named_child_count() != 0
108 || previous.child_count() != 1
109 || previous.end_byte() != node.start_byte()
110 {
111 return false;
112 }
113 previous.child(0).is_some_and(|child| {
114 child.kind() == "."
115 && !child.is_named()
116 && child.start_byte() == previous.start_byte()
117 && child.end_byte() == previous.end_byte()
118 })
119}
120
121fn last_named_field_child<'tree>(node: Node<'tree>, field: &str) -> Option<Node<'tree>> {
122 let mut cursor = node.walk();
123 node.children_by_field_name(field, &mut cursor)
124 .filter(|child| child.is_named())
125 .last()
126}
127
128fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
134 let mut current = namespace.child_by_field_name("name")?;
135 while current.kind() == "nested_namespace_specifier" {
136 current = current.named_child(current.named_child_count().checked_sub(1)?)?;
137 }
138 Some(current)
139}
140
141pub(crate) fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
142 let mut current = declarator;
143 loop {
144 match current.kind() {
145 "identifier"
146 | "field_identifier"
147 | "namespace_identifier"
148 | "type_identifier"
149 | "destructor_name"
150 | "operator_name"
151 | "primitive_type" => return Some(current),
152 "qualified_identifier" => current = last_named_field_child(current, "name")?,
153 "dependent_name" | "template_function" | "template_method" | "template_type" => {
154 current = current.child_by_field_name("name")?;
155 }
156 "function_declarator"
157 | "pointer_declarator"
158 | "array_declarator"
159 | "init_declarator" => current = current.child_by_field_name("declarator")?,
160 "reference_declarator" | "parenthesized_declarator" => {
161 current = first_named_child(current)?;
162 }
163 _ => return None,
164 }
165 }
166}
167
168fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
169 let mut current = expression;
170 loop {
171 match current.kind() {
172 "identifier"
173 | "field_identifier"
174 | "namespace_identifier"
175 | "type_identifier"
176 | "destructor_name"
177 | "operator_name"
178 | "primitive_type"
179 | "this" => return Some(current),
180 "qualified_identifier" => current = last_named_field_child(current, "name")?,
181 "dependent_name" | "template_function" | "template_method" | "template_type" => {
182 current = current.child_by_field_name("name")?;
183 }
184 "field_expression" => current = current.child_by_field_name("field")?,
185 "call_expression" => current = current.child_by_field_name("function")?,
186 "new_expression" => current = current.child_by_field_name("type")?,
187 "parenthesized_expression" => current = first_named_child(current)?,
188 _ => return declarator_name_node(current),
189 }
190 }
191}
192
193fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
194 if function.kind() != "qualified_identifier" {
195 return;
196 }
197 if let Some(scope) = function.child_by_field_name("scope") {
198 attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
199 }
200}
201
202fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
203 loop {
204 if node.kind() == "qualified_identifier" {
205 return Some(node);
206 }
207 node = node
208 .child_by_field_name("declarator")
209 .or_else(|| node.child_by_field_name("name"))
210 .or_else(|| first_named_child(node))?;
211 }
212}
213
214fn node_text<'source>(node: Node<'_>, source: &'source str) -> Option<&'source str> {
215 node.utf8_text(source.as_bytes()).ok()
216}
217
218fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
219 node.child_by_field_name("declarator")
220 .and_then(qualified_declarator_node)
221 .and_then(|qualified| qualified.child_by_field_name("scope"))
222}
223
224fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
225 node.child_by_field_name("declarator")
226 .and_then(qualified_declarator_node)
227 .and_then(|qualified| {
228 Some((
229 expression_name_node(qualified.child_by_field_name("scope")?)?,
230 expression_name_node(last_named_field_child(qualified, "name")?)?,
231 ))
232 })
233 .is_some_and(|(scope, name)| node_text(scope, source) == node_text(name, source))
234}
235
236fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
237 if !matches!(node.kind(), "string_literal" | "system_lib_string") {
238 return None;
239 }
240 let start = node.start_byte().checked_add(1)?;
241 let end = node.end_byte().checked_sub(1)?;
242 (start <= end).then_some(Span {
243 start_byte: start,
244 end_byte: end,
245 })
246}
247
248fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
256 let mut names = HashSet::default();
257 let mut stack = vec![root];
258 while let Some(node) = stack.pop() {
259 for child in named_children_iter(node) {
260 match child.kind() {
261 "preproc_function_def" => {
262 if let Some(name) = child.child_by_field_name("name") {
263 names.insert(source[name.start_byte()..name.end_byte()].to_owned());
264 }
265 }
266 "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
267 | "preproc_elifdef" => stack.push(child),
268 _ => {}
269 }
270 }
271 }
272 names
273}
274
275fn cpp_member_terminal_name<'tree>(field: Node<'tree>) -> Option<Node<'tree>> {
276 let field = if field.kind() == "dependent_name" {
277 first_named_child(field)?
278 } else {
279 field
280 };
281 expression_name_node(field)
282}
283
284fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
294 if !matches!(
295 node.kind(),
296 "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
297 ) {
298 return None;
299 }
300
301 let mut current = node;
302 while let Some(parent) = current.parent() {
303 if parent.kind() == "field_expression" {
304 let field = parent.child_by_field_name("field")?;
305 return cpp_member_terminal_name(field)
306 .filter(|name| name.id() == node.id())
307 .map(|_| OccurrenceRole::MemberPosition);
308 }
309
310 if matches!(
313 parent.kind(),
314 "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
315 ) {
316 current = parent;
317 } else {
318 return None;
319 }
320 }
321 None
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326enum CppBinderOwner<'tree> {
327 Parameter,
329 Local(Node<'tree>),
331 RangeFor(Node<'tree>),
333 StructuredBinding(Node<'tree>),
335 InitCapture,
337}
338
339const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
342 "pointer_declarator",
343 "array_declarator",
344 "reference_declarator",
345 "parenthesized_declarator",
346 "attributed_declarator",
347 "variadic_declarator",
348];
349
350fn cpp_binder_owner<'tree>(
360 node: Node<'tree>,
361 parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
362) -> Option<CppBinderOwner<'tree>> {
363 if node.kind() != "identifier" {
364 return None;
365 }
366 let mut child = node;
367 let mut current = parent(node)?;
368 let mut indirect = false;
369 loop {
370 let field = field_name_in_parent(current, child);
371 match current.kind() {
372 kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
373 if field.is_some() && field != Some("declarator") {
374 return None;
375 }
376 indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
377 }
378 "function_declarator" => {
379 if field != Some("declarator") || !indirect {
380 return None;
381 }
382 }
383 "init_declarator" => {
384 if field != Some("declarator") {
385 return None;
386 }
387 }
388 "declaration" => {
389 if field != Some("declarator") {
390 return None;
391 }
392 let local = std::iter::successors(parent(current), |node| parent(*node))
393 .take_while(|node| {
394 !matches!(
395 node.kind(),
396 "translation_unit"
397 | "field_declaration_list"
398 | "declaration_list"
399 | "namespace_definition"
400 )
401 })
402 .any(|node| {
403 matches!(
404 node.kind(),
405 "compound_statement" | "for_statement" | "condition_clause"
406 )
407 });
408 let declarator = if child.kind() == "init_declarator" {
411 child.child_by_field_name("declarator")?
412 } else {
413 child
414 };
415 return local.then_some(CppBinderOwner::Local(declarator));
416 }
417 "parameter_declaration"
418 | "optional_parameter_declaration"
419 | "variadic_parameter_declaration" => {
420 if field != Some("declarator") {
421 return None;
422 }
423 let list = parent(current)?;
424 let owner = parent(list)?;
425 let binds = match owner.kind() {
426 "catch_clause" => true,
427 "abstract_function_declarator" => {
428 parent(owner).is_some_and(|lambda| lambda.kind() == "lambda_expression")
429 }
430 "function_declarator" => {
431 let mut declarator = owner;
432 loop {
433 let Some(up) = parent(declarator) else {
434 break false;
435 };
436 if up.kind() == "function_definition" {
437 break field_name_in_parent(up, declarator) == Some("declarator");
438 }
439 if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
440 break false;
441 }
442 declarator = up;
443 }
444 }
445 _ => false,
446 };
447 return binds.then_some(CppBinderOwner::Parameter);
448 }
449 "for_range_loop" => {
450 return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
451 }
452 "structured_binding_declarator" => {
453 return Some(CppBinderOwner::StructuredBinding(current));
454 }
455 "lambda_capture_initializer" => {
456 return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
457 }
458 _ => return None,
459 }
460 child = current;
461 current = parent(current)?;
462 }
463}
464
465fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
469 cpp_binder_owner(node, |node| parents.parent(node)).is_some()
470}
471
472fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
483 let binding = |kind, hoisting, activation| {
484 Some(BindingActivation {
485 kind,
486 hoisting,
487 activation,
488 })
489 };
490 let after = |node: Node<'_>| Range {
491 start_byte: node.end_byte(),
492 end_byte: scope.end_byte,
493 start_line: node.end_position().row + 1,
494 end_line: scope.end_line,
495 };
496 match cpp_binder_owner(binder, |node| node.parent())? {
497 CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
498 binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
499 }
500 CppBinderOwner::Local(declarator) => binding(
501 BindingKind::Local,
502 HoistingClass::SourceOrder,
503 after(declarator),
504 ),
505 CppBinderOwner::RangeFor(loop_node) => binding(
506 BindingKind::LoopVariable,
507 HoistingClass::DeclaredHead,
508 node_range(loop_node.child_by_field_name("body")?),
509 ),
510 CppBinderOwner::StructuredBinding(declarator) => binding(
511 BindingKind::Local,
512 HoistingClass::SourceOrder,
513 after(declarator),
514 ),
515 }
516}
517
518impl StructuralSpec for CppStructuralSpec {
519 fn language(&self) -> Language {
520 Language::Cpp
521 }
522
523 fn supports_boolean_literal_value(&self) -> bool {
524 true
525 }
526
527 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
528 CPP_KIND_TABLE
529 }
530
531 fn refine_kind(
532 &self,
533 node: Node<'_>,
534 kind: NormalizedKind,
535 enclosing: Option<NormalizedKind>,
536 source: &str,
537 _context: &CallSiteContext,
538 ) -> NormalizedKind {
539 if kind == NormalizedKind::Function
540 && (enclosing == Some(NormalizedKind::Class)
541 || scoped_function_definition(node).is_some())
542 {
543 if is_constructor_definition(node, source) {
544 NormalizedKind::Constructor
545 } else {
546 NormalizedKind::Method
547 }
548 } else {
549 kind
550 }
551 }
552
553 fn supports_kind(&self, kind: NormalizedKind) -> bool {
554 matches!(kind, NormalizedKind::Method | NormalizedKind::Constructor)
555 || self
556 .kind_table()
557 .iter()
558 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
559 }
560
561 fn supports_role(&self, role: Role) -> bool {
562 !matches!(
563 role,
564 Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
565 )
566 }
567
568 fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
569 CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
570 }
571
572 fn call_site_facts(
585 &self,
586 node: Node<'_>,
587 source: &str,
588 context: &CallSiteContext,
589 ) -> Option<CallSiteFacts> {
590 if node.kind() == "new_expression" {
591 return Some(CallSiteFacts::of_kind(CallKind::Constructor));
592 }
593 let callee = node.child_by_field_name("function")?;
594 (callee.kind() == "identifier"
595 && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
596 .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
597 }
598
599 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
600 static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
601 .supported(OccurrenceRole::MemberPosition)
602 .supported(OccurrenceRole::ValueReference)
603 .supported(OccurrenceRole::Binder);
604 &SUPPORT
605 }
606
607 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
608 static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
612 .supported(EnvironmentAxis::Scopes)
613 .supported(EnvironmentAxis::BindingIntervals)
614 .supported(EnvironmentAxis::CallableApplicability);
615 &SUPPORT
616 }
617
618 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
621 if kind == NormalizedKind::If {
622 ScopeFormation::BindingScope
623 } else {
624 default_scope_formation(kind)
625 }
626 }
627
628 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
629 cpp_binding_activation(binder, scope)
630 }
631
632 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
633 &CPP_MATERIALIZATION_SUPPORT
634 }
635
636 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
637 &INVERSE_REFERENCE_EDGE_SUPPORT
638 }
639
640 fn identity_route_support(&self) -> &IdentityRouteSupport {
641 static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
652 .supported_axis(IdentityAxis::CanonicalIdentity)
653 .supported_axis(IdentityAxis::PhysicalGrouping)
654 .supported_relation(RouteHopKind::NestedOwner)
655 .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
656 &SUPPORT
657 }
658
659 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
660 if let Some(role) = cpp_member_position(node) {
661 sink.occurrence_role(node, role);
662 } else if cpp_is_binder(node, sink.parents()) {
663 sink.occurrence_role(node, OccurrenceRole::Binder);
664 }
665
666 match kind {
667 NormalizedKind::Call => {
668 let function_field = if node.kind() == "new_expression" {
669 "type"
670 } else {
671 "function"
672 };
673 if let Some(function) = node.child_by_field_name(function_field) {
674 attach_terminal_callee(sink, function, expression_name_node(function));
675 if node.kind() == "call_expression"
684 && !matches!(function.kind(), "field_expression" | "call_expression")
685 && let Some(callee) = expression_name_node(function)
686 {
687 sink.occurrence_role(callee, OccurrenceRole::ValueReference);
688 }
689 if function.kind() == "field_expression"
690 && let Some(argument) = function.child_by_field_name("argument")
691 {
692 attach_role_with_derived_name(
693 sink,
694 Role::Receiver,
695 argument,
696 expression_name_node,
697 );
698 }
699 attach_qualified_scope_receiver(sink, function);
700 }
701 if let Some(arguments) = node.child_by_field_name("arguments") {
702 attach_positional_argument_roles(sink, arguments, expression_name_node);
703 }
704 }
705 NormalizedKind::FieldAccess => {
706 if let Some(field) = node.child_by_field_name("field") {
707 attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
708 if let Some(name) = expression_name_node(field) {
709 sink.set_name(name);
710 }
711 }
712 if let Some(argument) = node.child_by_field_name("argument") {
713 attach_role_with_derived_name(
714 sink,
715 Role::Object,
716 argument,
717 expression_name_node,
718 );
719 }
720 }
721 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
722 if let Some(name) = node
723 .child_by_field_name("declarator")
724 .and_then(declarator_name_node)
725 {
726 sink.set_name(name);
727 }
728 }
729 NormalizedKind::Module => {
730 if let Some(name) = namespace_name_node(node) {
731 sink.set_name(name);
732 }
733 }
734 NormalizedKind::Class | NormalizedKind::Declaration => {
735 if let Some(name) = node
736 .child_by_field_name("name")
737 .and_then(declarator_name_node)
738 .or_else(|| node.child_by_field_name("name"))
739 {
740 sink.set_name(name);
741 }
742 }
743 NormalizedKind::Assignment => match node.kind() {
744 "init_declarator" => {
745 if let Some(declarator) = node.child_by_field_name("declarator") {
746 attach_role_with_derived_name(
747 sink,
748 Role::Left,
749 declarator,
750 declarator_name_node,
751 );
752 if let Some(name) = declarator_name_node(declarator) {
753 sink.set_name(name);
754 }
755 }
756 if let Some(value) = node.child_by_field_name("value") {
757 attach_role_with_derived_name(
758 sink,
759 Role::Right,
760 value,
761 expression_name_node,
762 );
763 }
764 }
765 "assignment_expression" => {
766 if let Some(left) = node.child_by_field_name("left") {
767 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
768 }
769 if let Some(right) = node.child_by_field_name("right") {
770 attach_role_with_derived_name(
771 sink,
772 Role::Right,
773 right,
774 expression_name_node,
775 );
776 }
777 }
778 _ => {}
779 },
780 NormalizedKind::Import => {
781 if let Some(path) = node.child_by_field_name("path") {
782 if let Some(name) = unquoted_include_span(path) {
783 sink.role_named_span(Role::Module, path, name);
784 } else {
785 attach_role_with_derived_name(
786 sink,
787 Role::Module,
788 path,
789 expression_name_node,
790 );
791 }
792 }
793 }
794 NormalizedKind::Identifier => match expression_name_node(node) {
795 Some(name) => sink.set_name(name),
796 None => sink.set_name(node),
797 },
798 _ => {}
799 }
800 }
801}
802
803#[cfg(test)]
804mod tests {
805 use super::CPP_STRUCTURAL_SPEC;
806 use super::cpp_member_position;
807 use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
808 use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
809 use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
810 use brokk_bifrost_core::hash::HashMap;
811 use tree_sitter::Parser;
812
813 fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
814 let mut parser = Parser::new();
815 parser
816 .set_language(&tree_sitter_cpp::LANGUAGE.into())
817 .expect("C++ grammar");
818 let tree = parser.parse(source, None).expect("C++ parse");
819 assert!(
820 !tree.root_node().has_error(),
821 "{}",
822 tree.root_node().to_sexp()
823 );
824 let mut found = Vec::new();
825 let mut pending = vec![tree.root_node()];
826 while let Some(node) = pending.pop() {
827 if let Some(role) = cpp_member_position(node) {
828 found.push((
829 node.start_byte(),
830 &source[node.start_byte()..node.end_byte()],
831 role,
832 ));
833 }
834 for index in (0..node.named_child_count()).rev() {
835 if let Some(child) = node.named_child(index) {
836 pending.push(child);
837 }
838 }
839 }
840 found
841 }
842
843 #[test]
844 fn cpp_member_position_is_limited_to_member_access_and_calls() {
845 let source = concat!(
846 "struct Widget {\n",
847 " int value;\n",
848 " int method(int label) {\n",
849 " return this->value + label.value + label.method()\n",
850 " + label.template convert<int>() + label.ns::member;\n",
851 " }\n",
852 "};\n",
853 "int build(Widget widget) {\n",
854 " Widget result{.value = widget.value};\n",
855 " return widget.method(result.value);\n",
856 "}\n",
857 );
858 let found = member_occurrences(source);
859 let member_texts = found
860 .iter()
861 .map(|(_, text, role)| (*text, *role))
862 .collect::<Vec<_>>();
863
864 assert_eq!(
865 member_texts,
866 vec![
867 ("value", OccurrenceRole::MemberPosition),
868 ("value", OccurrenceRole::MemberPosition),
869 ("method", OccurrenceRole::MemberPosition),
870 ("convert", OccurrenceRole::MemberPosition),
871 ("member", OccurrenceRole::MemberPosition),
872 ("value", OccurrenceRole::MemberPosition),
873 ("method", OccurrenceRole::MemberPosition),
874 ("value", OccurrenceRole::MemberPosition),
875 ]
876 );
877
878 let at = |needle: &str| source.find(needle).expect("fixture token");
879 for receiver in ["this->", "label.value", "widget.value", "result.value"] {
880 let receiver_start = at(receiver);
881 assert!(
882 found.iter().all(|(offset, _, _)| *offset != receiver_start),
883 "receiver was classified: {receiver:?}"
884 );
885 }
886 let non_members = [
887 at("Widget {"),
888 at("int value") + "int ".len(),
889 at("int method") + "int ".len(),
890 at(".value =") + 1,
891 ];
892 for unrelated_start in non_members {
893 assert!(
894 found
895 .iter()
896 .all(|(offset, _, _)| *offset != unrelated_start),
897 "non-member identifier at byte {unrelated_start} was classified"
898 );
899 }
900 }
901
902 #[test]
903 fn cpp_occurrence_support_declares_member_position_and_free_callees() {
904 let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
905 assert!(support.is_supported(OccurrenceRole::MemberPosition));
906 assert!(support.is_supported(OccurrenceRole::ValueReference));
907 for role in [
908 OccurrenceRole::ReceiverPosition,
909 OccurrenceRole::LabelOrKey,
910 OccurrenceRole::DeclarationName,
911 ] {
912 assert!(
913 !support.is_supported(role),
914 "unexpected C++ support for {role:?}"
915 );
916 }
917 }
918
919 #[test]
924 fn cpp_free_function_callees_are_value_references() {
925 let source = concat!(
926 "int bound(void) { return 1; }\n",
927 "struct Widget { int method(void) { return 0; } };\n",
928 "int go(struct Widget *widget) {\n",
929 " return getenv(\"PATH\") != 0\n",
930 " + ns::getenv(\"USER\") != 0\n",
931 " + bound()\n",
932 " + widget->method()\n",
933 " + bound()();\n",
934 "}\n",
935 );
936 let mut parser = Parser::new();
937 parser
938 .set_language(&tree_sitter_cpp::LANGUAGE.into())
939 .expect("C++ grammar");
940 let tree = parser.parse(source, None).expect("C++ parse");
941 assert!(
942 !tree.root_node().has_error(),
943 "{}",
944 tree.root_node().to_sexp()
945 );
946
947 let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
952 let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
953 let mut start_by_fact = HashMap::default();
954 let mut extractions = Vec::new();
955 let mut pending = vec![tree.root_node()];
956 while let Some(node) = pending.pop() {
957 if node.is_named()
958 && let Some(kind) = compiled.kind_of(&node)
959 && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
960 {
961 start_by_fact.insert(node.id(), node.start_byte());
962 extractions.push((node.start_byte(), node, kind));
963 }
964 for index in (0..node.named_child_count()).rev() {
965 if let Some(child) = node.named_child(index) {
966 pending.push(child);
967 }
968 }
969 }
970 extractions.sort_by_key(|(start, _, _)| *start);
971 let mut source_facts =
972 brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
973 let parents = ParentIndex::new(tree.root_node());
974 let mut sink = RoleSink::new(&mut source_facts, 64, None, &parents);
975 for (_, node, kind) in extractions {
976 CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
977 }
978 let (_, _, occurrences, stop) = sink.into_parts();
979 assert_eq!(stop, None);
980
981 let roles_at = |needle: &str| {
982 let start = source.find(needle).expect("fixture token");
983 occurrences
984 .iter()
985 .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
986 .map(|occurrence| occurrence.role)
987 .collect::<Vec<_>>()
988 };
989 assert_eq!(
993 roles_at("getenv(\"PATH\")"),
994 vec![OccurrenceRole::ValueReference]
995 );
996 assert_eq!(
997 roles_at("getenv(\"USER\")"),
998 vec![OccurrenceRole::ValueReference],
999 "the terminal name of a qualified callee is the value read"
1000 );
1001 assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
1002 assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
1005 assert!(roles_at("widget->").is_empty());
1008 }
1009}