1use brokk_bifrost_core::analyzer::common::try_node_source_text as node_text;
4use brokk_bifrost_core::analyzer::statement_facts::StatementFactKind;
5use brokk_bifrost_core::analyzer::structural::adapter_helpers::{
6 CalleeSpineStep, attach_positional_argument_roles, attach_role_with_derived_name,
7 attach_terminal_callee, field_name_in_parent, first_named_child, terminal_callee_leaf,
8 unquoted_string_content_span,
9};
10use brokk_bifrost_core::analyzer::structural::body_relations::{
11 BodyOwner, BodyState, body_relation_for_block,
12};
13use brokk_bifrost_core::analyzer::structural::callable::{
14 CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
15};
16use brokk_bifrost_core::analyzer::structural::edges::{
17 INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
18};
19use brokk_bifrost_core::analyzer::structural::facts::Span;
20use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
21use brokk_bifrost_core::analyzer::structural::materialization::{
22 CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
23};
24use brokk_bifrost_core::analyzer::structural::occurrences::{
25 OccurrenceRole, OccurrenceRoleSupport,
26};
27use brokk_bifrost_core::analyzer::structural::resolution::{
28 BindingActivation, BindingKind, EnvironmentAxis, HoistingClass, LexicalEnvironmentSupport,
29 ScopeFormation, default_scope_formation,
30};
31use brokk_bifrost_core::analyzer::structural::result_use::{
32 CallResultUse, CallResultUseIndex, CallResultUseOpenReason, CallResultUseSite,
33 IntentionalDiscardKind,
34};
35use brokk_bifrost_core::analyzer::structural::routes::{
36 IdentityAxis, IdentityRouteSupport, RouteHopKind,
37};
38use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
39use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, named_children_iter, node_range};
40use brokk_bifrost_core::analyzer::{Language, Range};
41use brokk_bifrost_core::hash::{HashMap, HashSet};
42use tree_sitter::Node;
43
44#[derive(Debug, Default)]
45pub struct CppStructuralSpec;
46
47pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
48
49pub(crate) fn is_cpp_preprocessor_conditional(node: Node<'_>) -> bool {
55 matches!(
56 node.kind(),
57 "preproc_if"
58 | "preproc_ifdef"
59 | "preproc_ifndef"
60 | "preproc_elif"
61 | "preproc_elifdef"
62 | "preproc_else"
63 )
64}
65
66pub fn cpp_call_result_use(call: Node<'_>, source: &str) -> CallResultUseSite {
70 let default = if !matches!(call.kind(), "call_expression" | "new_expression")
71 || call.has_error()
72 || has_recovery_ancestor(call)
73 {
74 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
75 } else if has_deferred_ancestor(call) {
76 CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
77 } else {
78 let mut expression = call;
79 loop {
80 let Some(parent) = expression.parent() else {
81 break CallResultUse::Open(CallResultUseOpenReason::UnclassifiedSyntax);
82 };
83 if parent.is_error() || parent.is_missing() {
84 break CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax);
85 }
86 match parent.kind() {
87 "parenthesized_expression" | "comma_expression"
88 if parent
89 .named_children(&mut parent.walk())
90 .any(|child| child.id() == expression.id()) =>
91 {
92 expression = parent;
93 }
94 "expression_statement"
95 if parent
96 .named_children(&mut parent.walk())
97 .any(|child| child.id() == expression.id()) =>
98 {
99 break CallResultUse::Discarded;
100 }
101 "for_statement"
102 if matches!(
103 field_name_in_parent(parent, expression),
104 Some("initializer" | "update")
105 ) =>
106 {
107 break CallResultUse::Discarded;
108 }
109 "cast_expression"
110 if field_name_in_parent(parent, expression) == Some("value")
111 && parent
112 .child_by_field_name("type")
113 .is_some_and(|type_node| cpp_type_is_void(type_node, source)) =>
114 {
115 break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
116 }
117 "argument_list"
118 if parent
119 .parent()
120 .is_some_and(|outer_call| cpp_static_cast_to_void(outer_call, source)) =>
121 {
122 break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
123 }
124 "lambda_expression" => {
125 break CallResultUse::Open(CallResultUseOpenReason::DeferredContext);
126 }
127 _ => break CallResultUse::Consumed,
128 }
129 }
130 };
131 CallResultUseSite::new(call.start_byte(), call.end_byte(), default)
132}
133
134fn cpp_type_is_void(type_node: Node<'_>, source: &str) -> bool {
135 let mut stack = vec![type_node];
136 while let Some(node) = stack.pop() {
137 if node.kind() == "primitive_type" && node_text(node, source) == Some("void") {
138 return true;
139 }
140 for index in (0..node.named_child_count()).rev() {
141 stack.push(node.named_child(index).expect("named child exists"));
142 }
143 }
144 false
145}
146
147fn has_recovery_ancestor(mut node: Node<'_>) -> bool {
148 while let Some(parent) = node.parent() {
149 if parent.is_error() || parent.is_missing() {
150 return true;
151 }
152 node = parent;
153 }
154 false
155}
156
157fn has_deferred_ancestor(mut node: Node<'_>) -> bool {
158 while let Some(parent) = node.parent() {
159 if parent.kind() == "lambda_expression" {
160 return true;
161 }
162 node = parent;
163 }
164 false
165}
166
167fn cpp_static_cast_to_void(call: Node<'_>, source: &str) -> bool {
168 if call.kind() != "call_expression" {
169 return false;
170 }
171 let Some(function) = call.child_by_field_name("function") else {
172 return false;
173 };
174 if function.kind() != "template_function"
175 || function
176 .child_by_field_name("name")
177 .and_then(|name| node_text(name, source))
178 != Some("static_cast")
179 {
180 return false;
181 }
182 function
183 .child_by_field_name("arguments")
184 .and_then(|arguments| arguments.named_child(0))
185 .is_some_and(|type_node| cpp_type_is_void(type_node, source))
186}
187
188fn cpp_cast_call(call: Node<'_>, source: &str) -> bool {
189 let Some(function) = call.child_by_field_name("function") else {
190 return false;
191 };
192 if function.kind() != "template_function" {
193 return false;
194 }
195 matches!(
196 function
197 .child_by_field_name("name")
198 .and_then(|name| node_text(name, source)),
199 Some("static_cast" | "reinterpret_cast" | "const_cast" | "dynamic_cast")
200 )
201}
202
203#[derive(Debug, Clone)]
205pub struct CppCallResultUseIndex {
206 inner: CallResultUseIndex,
207}
208
209impl CppCallResultUseIndex {
210 pub fn new(root: Node<'_>, source: &str) -> Self {
211 let mut sites = Vec::new();
212 let mut stack = vec![root];
213 while let Some(node) = stack.pop() {
214 if matches!(node.kind(), "call_expression" | "new_expression")
215 && !cpp_cast_call(node, source)
216 {
217 sites.push(cpp_call_result_use(node, source));
218 }
219 for index in (0..node.named_child_count()).rev() {
220 stack.push(node.named_child(index).expect("named child exists"));
221 }
222 }
223 Self {
224 inner: CallResultUseIndex::new(sites),
225 }
226 }
227
228 pub fn at_span(
229 &self,
230 start_byte: usize,
231 end_byte: usize,
232 ) -> Result<&CallResultUseSite, CallResultUseOpenReason> {
233 self.inner.at_span(start_byte, end_byte)
234 }
235
236 pub fn sites(&self) -> &[CallResultUseSite] {
237 self.inner.sites()
238 }
239}
240
241pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
242 ("comment", NormalizedKind::Comment),
243 ("call_expression", NormalizedKind::Call),
244 ("new_expression", NormalizedKind::Call),
245 ("field_expression", NormalizedKind::FieldAccess),
246 ("function_definition", NormalizedKind::Function),
247 ("lambda_expression", NormalizedKind::Lambda),
248 ("class_specifier", NormalizedKind::Class),
249 ("struct_specifier", NormalizedKind::Class),
250 ("union_specifier", NormalizedKind::Class),
251 ("alias_declaration", NormalizedKind::Declaration),
252 ("namespace_definition", NormalizedKind::Module),
253 ("assignment_expression", NormalizedKind::Assignment),
254 ("init_declarator", NormalizedKind::Assignment),
255 ("preproc_include", NormalizedKind::Import),
256 ("identifier", NormalizedKind::Identifier),
257 ("field_identifier", NormalizedKind::Identifier),
258 ("namespace_identifier", NormalizedKind::Identifier),
259 ("qualified_identifier", NormalizedKind::Identifier),
260 ("type_identifier", NormalizedKind::Identifier),
261 ("template_function", NormalizedKind::Identifier),
262 ("template_method", NormalizedKind::Identifier),
263 ("template_type", NormalizedKind::Identifier),
264 ("dependent_name", NormalizedKind::Identifier),
265 ("destructor_name", NormalizedKind::Identifier),
266 ("operator_name", NormalizedKind::Identifier),
267 ("primitive_type", NormalizedKind::Identifier),
268 ("char_literal", NormalizedKind::StringLiteral),
269 ("string_literal", NormalizedKind::StringLiteral),
270 ("raw_string_literal", NormalizedKind::StringLiteral),
271 ("number_literal", NormalizedKind::NumericLiteral),
272 ("true", NormalizedKind::BooleanLiteral),
273 ("false", NormalizedKind::BooleanLiteral),
274 ("null", NormalizedKind::NullLiteral),
275 ("return_statement", NormalizedKind::Return),
276 ("throw_statement", NormalizedKind::Throw),
277 ("catch_clause", NormalizedKind::Catch),
278 ("if_statement", NormalizedKind::If),
279 ("for_statement", NormalizedKind::Loop),
280 ("for_range_loop", NormalizedKind::Loop),
281 ("compound_statement", NormalizedKind::Block),
282 ("switch_statement", NormalizedKind::Switch),
283 ("case_statement", NormalizedKind::Case),
284 ("labeled_statement", NormalizedKind::Label),
285 ("goto_statement", NormalizedKind::Goto),
286 ("break_statement", NormalizedKind::Break),
287 ("continue_statement", NormalizedKind::Continue),
288 ("while_statement", NormalizedKind::WhileLoop),
289 ("do_statement", NormalizedKind::WhileLoop),
290];
291
292fn cpp_escape_codepoint(digits: &str, radix: u32, byte_escape: bool) -> Option<char> {
293 let value = u32::from_str_radix(digits, radix).ok()?;
294 if byte_escape && value > 0x7f {
295 return None;
296 }
297 char::from_u32(value)
298}
299
300fn cpp_cooked_string_escape(escape: &str) -> Option<Option<char>> {
301 let escaped = escape.strip_prefix('\\')?;
302 if matches!(escaped, "\n" | "\r\n") {
303 return Some(None);
304 }
305 let character = match escaped {
306 "a" => '\x07',
307 "b" => '\x08',
308 "f" => '\x0c',
309 "n" => '\n',
310 "r" => '\r',
311 "t" => '\t',
312 "v" => '\x0b',
313 "0" => '\0',
314 "\\" => '\\',
315 "'" => '\'',
316 "\"" => '\"',
317 "?" => '?',
318 _ if escaped.starts_with('x') => {
319 let digits = escaped.get(1..)?;
320 (!digits.is_empty()
321 && digits.len() <= 4
322 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
323 .then_some(())?;
324 cpp_escape_codepoint(digits, 16, true)?
325 }
326 _ if escaped.starts_with('u') => {
327 let digits = escaped.get(1..)?;
328 (digits.len() == 4 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
329 .then_some(())?;
330 cpp_escape_codepoint(digits, 16, false)?
331 }
332 _ if escaped.starts_with('U') => {
333 let digits = escaped.get(1..)?;
334 (digits.len() == 8 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
335 .then_some(())?;
336 cpp_escape_codepoint(digits, 16, false)?
337 }
338 _ if escaped.len() <= 3 && escaped.bytes().all(|byte| (b'0'..=b'7').contains(&byte)) => {
339 cpp_escape_codepoint(escaped, 8, true)?
340 }
341 _ => return None,
342 };
343 Some(Some(character))
344}
345
346fn cpp_string_literal_value(node: Node<'_>, source: &str) -> Option<String> {
347 let spelling = node_text(node, source)?;
348 match node.kind() {
349 "string_literal" => {
350 if !spelling.starts_with('"') {
354 return None;
355 }
356 let mut decoded = String::new();
357 let mut cursor = node.walk();
358 for child in node.named_children(&mut cursor) {
359 let text = node_text(child, source)?;
360 match child.kind() {
361 "string_content" => decoded.push_str(text),
362 "escape_sequence" => {
363 if let Some(character) = cpp_cooked_string_escape(text)? {
364 decoded.push(character);
365 }
366 }
367 _ => return None,
368 }
369 }
370 Some(decoded)
371 }
372 "raw_string_literal" => {
373 if !spelling.starts_with("R\"") {
374 return None;
375 }
376 let mut content = None;
377 let mut cursor = node.walk();
378 for child in node.named_children(&mut cursor) {
379 match child.kind() {
380 "raw_string_content" => {
381 if content.is_some() {
382 return None;
383 }
384 content = Some(node_text(child, source)?);
385 }
386 "raw_string_delimiter" => {}
387 _ => return None,
388 }
389 }
390 content.map(str::to_owned)
391 }
392 _ => None,
393 }
394}
395
396fn direct_code_children(node: Node<'_>) -> impl Iterator<Item = Node<'_>> {
397 named_children_iter(node).filter(|child| {
398 !child.is_extra() && !child.kind().starts_with("preproc_") && child.kind() != "comment"
399 })
400}
401
402fn direct_code_child_count(node: Node<'_>) -> usize {
403 direct_code_children(node).count()
404}
405
406fn loop_body_is_complex(loop_node: Node<'_>) -> bool {
407 loop_node
408 .child_by_field_name("body")
409 .is_some_and(|body| direct_code_child_count(body) > 6)
410}
411
412fn complex_statement_count(block: Node<'_>) -> usize {
413 direct_code_children(block)
414 .filter(|child| {
415 matches!(
416 child.kind(),
417 "for_statement"
418 | "for_range_loop"
419 | "while_statement"
420 | "do_statement"
421 | "switch_statement"
422 )
423 })
424 .filter(|child| child.kind() == "switch_statement" || loop_body_is_complex(*child))
425 .count()
426}
427
428fn operator_kind(node: Node<'_>) -> Option<&'static str> {
429 let operator = node.child_by_field_name("operator")?;
430 match operator.kind() {
431 "&&" => Some("&&"),
432 "||" => Some("||"),
433 _ => None,
434 }
435}
436
437fn logical_operator_count(expression: Node<'_>) -> usize {
438 let mut count = 0usize;
439 let mut stack = vec![expression];
440 while let Some(node) = stack.pop() {
441 if let Some(operator) = operator_kind(node) {
442 let parent_operator = node.parent().and_then(operator_kind);
443 if parent_operator != Some(operator) {
444 count = count.saturating_add(1);
445 }
446 }
447 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
448 }
449 count
450}
451
452fn condition_expression(node: Node<'_>) -> Option<Node<'_>> {
453 let condition = node.child_by_field_name("condition")?;
454 if condition.kind() == "condition_clause" {
455 condition
456 .child_by_field_name("value")
457 .or_else(|| condition.named_child(0))
458 } else {
459 Some(condition)
460 }
461}
462
463fn switch_cases(body: Node<'_>) -> Vec<Node<'_>> {
464 let mut cases = Vec::new();
465 let mut stack = vec![body];
466 while let Some(node) = stack.pop() {
467 if node != body
468 && matches!(
469 node.kind(),
470 "switch_statement" | "function_definition" | "lambda_expression"
471 )
472 {
473 continue;
474 }
475 if node != body && node.kind() == "case_statement" {
476 cases.push(node);
477 }
478 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
479 }
480 cases.sort_unstable_by_key(Node::start_byte);
481 cases
482}
483
484fn switch_case_is_long(switch: Node<'_>) -> bool {
485 let Some(body) = switch.child_by_field_name("body") else {
486 return false;
487 };
488 let cases = switch_cases(body);
489 cases.iter().enumerate().any(|(index, case)| {
490 let end_line = cases.get(index + 1).map_or_else(
491 || switch.end_position().row + 1,
492 |next| next.start_position().row + 1,
493 );
494 end_line.saturating_sub(case.start_position().row + 1) > 30
495 })
496}
497
498fn enum_name_sets(root: Node<'_>, source: &str) -> Vec<HashSet<String>> {
499 let mut names = Vec::new();
500 let mut stack = vec![root];
501 while let Some(node) = stack.pop() {
502 if node.kind() == "enum_specifier" {
503 let mut declared = HashSet::default();
504 let mut enum_stack = vec![node];
505 while let Some(enum_node) = enum_stack.pop() {
506 if enum_node.kind() == "enumerator"
507 && let Some(name) = enum_node.child_by_field_name("name")
508 && let Some(text) = node_text(name, source)
509 {
510 declared.insert(text.to_owned());
511 }
512 enum_stack.extend(
513 named_children_iter(enum_node)
514 .filter(|child| !child.is_extra() && child.kind() != "enum_specifier"),
515 );
516 }
517 if !declared.is_empty() {
518 names.push(declared);
519 }
520 continue;
521 }
522 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
523 }
524 names
525}
526
527fn switch_case_names(switch: Node<'_>, source: &str) -> HashSet<String> {
528 let Some(body) = switch.child_by_field_name("body") else {
529 return HashSet::default();
530 };
531 switch_cases(body)
532 .into_iter()
533 .filter_map(|case| case.child_by_field_name("value"))
534 .filter_map(|value| {
535 expression_name_node(value)
536 .and_then(|name| node_text(name, source))
537 .map(str::to_owned)
538 })
539 .collect()
540}
541
542fn switch_has_default(switch: Node<'_>) -> bool {
543 switch
544 .child_by_field_name("body")
545 .map(switch_cases)
546 .is_some_and(|cases| {
547 cases
548 .iter()
549 .any(|case| case.child_by_field_name("value").is_none())
550 })
551}
552
553fn enum_switch_is_missing_case(switch: Node<'_>, source: &str) -> bool {
554 if switch_has_default(switch) {
555 return false;
556 }
557 let mut root = switch;
558 while let Some(parent) = root.parent() {
559 root = parent;
560 }
561 let cases = switch_case_names(switch, source);
562 if cases.is_empty() {
563 return false;
564 }
565 enum_name_sets(root, source)
566 .into_iter()
567 .filter(|declared| cases.iter().all(|name| declared.contains(name)))
568 .min_by_key(HashSet::len)
569 .is_some_and(|declared| declared.iter().any(|name| !cases.contains(name)))
570}
571
572fn declarator_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
573 let mut current = node;
574 loop {
575 match current.kind() {
576 "identifier" | "field_identifier" | "statement_identifier" => return Some(current),
577 "init_declarator"
578 | "pointer_declarator"
579 | "reference_declarator"
580 | "array_declarator"
581 | "parenthesized_declarator"
582 | "attributed_declarator" => {
583 current = current.child_by_field_name("declarator")?;
584 }
585 "qualified_identifier" => current = current.child_by_field_name("name")?,
586 _ => return None,
587 }
588 }
589}
590
591fn loop_variable<'tree>(loop_node: Node<'tree>, source: &'tree str) -> Option<&'tree str> {
592 let declarator = if loop_node.kind() == "for_range_loop" {
593 loop_node.child_by_field_name("declarator")
594 } else {
595 let initializer = loop_node.child_by_field_name("initializer")?;
596 if initializer.kind() == "declaration" {
597 initializer
598 .child_by_field_name("declarator")
599 .and_then(declarator_identifier)
600 } else if initializer.kind() == "assignment_expression" {
601 initializer
602 .child_by_field_name("left")
603 .and_then(expression_name_node)
604 } else {
605 None
606 }
607 }?;
608 let name = declarator_identifier(declarator).or(Some(declarator))?;
609 node_text(name, source)
610}
611
612fn numeric_literal_value(node: Node<'_>, source: &str) -> Option<i64> {
613 if node.kind() != "number_literal" {
614 return None;
615 }
616 node_text(node, source)?.parse().ok()
617}
618
619fn for_loop_has_small_literal_bound(loop_node: Node<'_>, source: &str) -> bool {
620 let Some(condition) = loop_node.child_by_field_name("condition") else {
621 return false;
622 };
623 let mut stack = vec![condition];
624 while let Some(node) = stack.pop() {
625 if node.kind() == "binary_expression"
626 && let Some(operator) = node.child_by_field_name("operator")
627 {
628 let bound = match operator.kind() {
629 "<" | "<=" => node
630 .child_by_field_name("right")
631 .and_then(|value| numeric_literal_value(value, source)),
632 ">" | ">=" => node
633 .child_by_field_name("left")
634 .and_then(|value| numeric_literal_value(value, source)),
635 _ => None,
636 };
637 if bound.is_some_and(|value| (0..=1).contains(&value)) {
638 return true;
639 }
640 }
641 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
642 }
643 false
644}
645
646fn alloca_is_in_unbounded_loop(call: Node<'_>, source: &str) -> bool {
647 let Some(function) = call.child_by_field_name("function") else {
648 return false;
649 };
650 if !matches!(
651 node_text(function, source),
652 Some("alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
653 ) {
654 return false;
655 }
656 let mut current = call.parent();
657 while let Some(parent) = current {
658 if matches!(parent.kind(), "for_statement" | "for_range_loop") {
659 if parent
660 .child_by_field_name("initializer")
661 .is_some_and(|initializer| {
662 initializer.start_byte() <= call.start_byte()
663 && call.end_byte() <= initializer.end_byte()
664 })
665 {
666 return false;
667 }
668 return !for_loop_has_small_literal_bound(parent, source);
669 }
670 if matches!(parent.kind(), "while_statement" | "do_statement") {
671 return true;
672 }
673 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
674 break;
675 }
676 current = parent.parent();
677 }
678 false
679}
680
681fn goto_counts(function: Node<'_>, source: &str) -> (usize, usize) {
682 let mut labels = HashMap::<String, usize>::default();
683 let mut gotos = Vec::new();
684 let mut stack = vec![function];
685 while let Some(node) = stack.pop() {
686 if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
687 continue;
688 }
689 match node.kind() {
690 "labeled_statement" => {
691 if let Some(label) = node.child_by_field_name("label")
692 && let Some(text) = node_text(label, source)
693 {
694 labels.insert(text.to_owned(), node.start_position().row);
695 }
696 }
697 "goto_statement" => {
698 if let Some(label) = node.child_by_field_name("label")
699 && let Some(text) = node_text(label, source)
700 {
701 gotos.push((text.to_owned(), node.start_position().row));
702 }
703 }
704 _ => {}
705 }
706 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
707 }
708 gotos
709 .into_iter()
710 .fold((0, 0), |(forward, backward), (name, line)| {
711 match labels.get(&name).copied() {
712 Some(target) if line < target => (forward + 1, backward),
713 Some(target) if target < line => (forward, backward + 1),
714 _ => (forward, backward),
715 }
716 })
717}
718
719fn conditional_block_is_empty(block: Node<'_>) -> bool {
720 body_relation_for_block(block, Language::Cpp).is_some_and(|relation| {
721 relation.state == BodyState::Empty
722 && matches!(
723 relation.owner_kind,
724 BodyOwner::IfConsequence | BodyOwner::IfAlternative
725 )
726 })
727}
728
729pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
735 match node.kind() {
736 "call_expression" if alloca_is_in_unbounded_loop(node, source) => {
737 Some(StatementFactKind::AllocaInLoop)
738 }
739 "compound_statement" => {
740 if complex_statement_count(node) > 3 {
741 Some(StatementFactKind::ComplexBlock)
742 } else if conditional_block_is_empty(node) {
743 Some(StatementFactKind::EmptyBlock)
744 } else {
745 None
746 }
747 }
748 "if_statement"
749 if condition_expression(node)
750 .is_some_and(|condition| logical_operator_count(condition) > 5) =>
751 {
752 Some(StatementFactKind::ComplexCondition)
753 }
754 "switch_statement" => {
755 if switch_case_is_long(node) {
756 Some(StatementFactKind::LongSwitch)
757 } else if enum_switch_is_missing_case(node, source) {
758 Some(StatementFactKind::MissingCaseInSwitch)
759 } else {
760 None
761 }
762 }
763 "function_definition" => {
764 let (forward, backward) = goto_counts(node, source);
765 (forward > 0 && backward > 0 && forward != 1 && backward != 1)
766 .then_some(StatementFactKind::UseOfGoto)
767 }
768 _ => None,
769 }
770}
771
772pub(crate) fn capture_statement_fact_rows(
776 node: Node<'_>,
777 source: &str,
778 rows: &mut brokk_bifrost_core::analyzer::statement_facts::CppStatementFactRows,
779) {
780 use brokk_bifrost_core::analyzer::statement_facts::{
781 CppConditionFact, CppEnumFact, CppEnumeratorFact, CppGotoFact, CppLabelFact, CppLoopFact,
782 CppStatementFact, CppSwitchCaseFact, CppSwitchFact,
783 };
784
785 let range = node_range(node);
786 if let Some(kind) = statement_fact_kind(node, source) {
787 rows.statements.push(CppStatementFact { range, kind });
788 }
789 if node.kind() == "switch_statement" {
790 let long_case = switch_case_is_long(node);
791 let missing_case = enum_switch_is_missing_case(node, source);
792 if long_case
793 && !rows
794 .statements
795 .iter()
796 .any(|fact| fact.range == range && fact.kind == StatementFactKind::LongSwitch)
797 {
798 rows.statements.push(CppStatementFact {
799 range,
800 kind: StatementFactKind::LongSwitch,
801 });
802 }
803 if missing_case
804 && !rows.statements.iter().any(|fact| {
805 fact.range == range && fact.kind == StatementFactKind::MissingCaseInSwitch
806 })
807 {
808 rows.statements.push(CppStatementFact {
809 range,
810 kind: StatementFactKind::MissingCaseInSwitch,
811 });
812 }
813 let cases = node
814 .child_by_field_name("body")
815 .map(switch_cases)
816 .unwrap_or_default();
817 for case in &cases {
818 let value = case
819 .child_by_field_name("value")
820 .and_then(expression_name_node)
821 .and_then(|name| node_text(name, source))
822 .map(str::to_owned);
823 rows.switch_cases.push(CppSwitchCaseFact {
824 range: node_range(*case),
825 value,
826 });
827 }
828 rows.switches.push(CppSwitchFact {
829 range,
830 case_count: cases.len(),
831 default_present: switch_has_default(node),
832 long_case,
833 missing_enum_case: missing_case,
834 });
835 }
836 if node.kind() == "if_statement"
837 && let Some(condition) = condition_expression(node)
838 {
839 rows.conditions.push(CppConditionFact {
840 range: node_range(condition),
841 logical_operator_count: logical_operator_count(condition),
842 });
843 }
844 if matches!(
845 node.kind(),
846 "for_statement" | "for_range_loop" | "while_statement" | "do_statement"
847 ) {
848 let body_statement_count = node
849 .child_by_field_name("body")
850 .map(direct_code_child_count)
851 .unwrap_or_default();
852 let variable = if matches!(node.kind(), "for_statement" | "for_range_loop") {
853 loop_variable(node, source).map(str::to_owned)
854 } else {
855 None
856 };
857 rows.loops.push(CppLoopFact {
858 range,
859 variable,
860 body_statement_count,
861 tightly_bounded: node.kind() == "for_statement"
862 && for_loop_has_small_literal_bound(node, source),
863 });
864 }
865 if node.kind() == "labeled_statement"
866 && let Some(label) = node.child_by_field_name("label")
867 && let Some(name) = node_text(label, source)
868 {
869 rows.labels.push(CppLabelFact {
870 range,
871 name: name.to_owned(),
872 });
873 }
874 if node.kind() == "goto_statement"
875 && let Some(label) = node.child_by_field_name("label")
876 && let Some(name) = node_text(label, source)
877 {
878 let forward = goto_target_line(node, name, source)
879 .is_some_and(|target| node.start_position().row < target);
880 rows.gotos.push(CppGotoFact {
881 range,
882 target: name.to_owned(),
883 forward,
884 });
885 }
886 if node.kind() == "enum_specifier" {
887 let name = node
888 .child_by_field_name("name")
889 .and_then(|name| node_text(name, source))
890 .map(str::to_owned);
891 rows.enums.push(CppEnumFact { range, name });
892 let mut stack = vec![node];
893 while let Some(current) = stack.pop() {
894 if current != node && current.kind() == "enum_specifier" {
895 continue;
896 }
897 if current.kind() == "enumerator"
898 && let Some(name) = current.child_by_field_name("name")
899 && let Some(text) = node_text(name, source)
900 {
901 rows.enumerators.push(CppEnumeratorFact {
902 range: node_range(current),
903 enum_range: range,
904 name: text.to_owned(),
905 });
906 }
907 stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
908 }
909 }
910}
911
912fn goto_target_line(goto: Node<'_>, target: &str, source: &str) -> Option<usize> {
913 let mut function = goto;
914 while let Some(parent) = function.parent() {
915 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
916 function = parent;
917 break;
918 }
919 function = parent;
920 }
921 let mut stack = vec![function];
922 while let Some(node) = stack.pop() {
923 if node != function && matches!(node.kind(), "function_definition" | "lambda_expression") {
924 continue;
925 }
926 if node.kind() == "labeled_statement"
927 && let Some(label) = node.child_by_field_name("label")
928 && node_text(label, source) == Some(target)
929 {
930 return Some(node.start_position().row);
931 }
932 stack.extend(named_children_iter(node).filter(|child| !child.is_extra()));
933 }
934 None
935}
936
937pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
945 if node.kind() != "init_declarator" {
946 return false;
947 }
948 let Some(identifier) = node.child_by_field_name("declarator") else {
949 return false;
950 };
951 if identifier.kind() != "identifier" || identifier.is_missing() {
952 return false;
953 }
954 let Some(previous) = node.prev_named_sibling() else {
955 return false;
956 };
957 if previous.kind() != "ERROR"
958 || previous.named_child_count() != 0
959 || previous.child_count() != 1
960 || previous.end_byte() != node.start_byte()
961 {
962 return false;
963 }
964 previous.child(0).is_some_and(|child| {
965 child.kind() == "."
966 && !child.is_named()
967 && child.start_byte() == previous.start_byte()
968 && child.end_byte() == previous.end_byte()
969 })
970}
971
972use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
973
974fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
980 let mut current = namespace.child_by_field_name("name")?;
981 while current.kind() == "nested_namespace_specifier" {
982 current = current.named_child(current.named_child_count().checked_sub(1)?)?;
983 }
984 Some(current)
985}
986
987pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
988 let mut current = declarator;
989 loop {
990 match current.kind() {
991 "identifier"
992 | "field_identifier"
993 | "namespace_identifier"
994 | "type_identifier"
995 | "destructor_name"
996 | "operator_name"
997 | "operator_cast"
998 | "primitive_type" => return Some(current),
999 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1000 "dependent_name" | "template_function" | "template_method" | "template_type" => {
1001 current = current.child_by_field_name("name")?;
1002 }
1003 "function_declarator"
1004 | "pointer_declarator"
1005 | "array_declarator"
1006 | "init_declarator" => current = current.child_by_field_name("declarator")?,
1007 "attributed_declarator" => current = first_named_child(current)?,
1008 "reference_declarator" | "parenthesized_declarator" => {
1009 current = first_named_child(current)?;
1010 }
1011 _ => return None,
1012 }
1013 }
1014}
1015
1016pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1018 use CalleeSpineStep::{Field, FieldOrOnlyChild, Name};
1019 match kind {
1020 "identifier"
1021 | "field_identifier"
1022 | "type_identifier"
1023 | "operator_name"
1024 | "operator_cast"
1025 | "destructor_name"
1026 | "literal_operator_name"
1027 | "primitive_type" => Some(Name),
1028 "qualified_identifier"
1029 | "scoped_identifier"
1030 | "scoped_type_identifier"
1031 | "dependent_name"
1032 | "template_function"
1033 | "template_method"
1034 | "template_type" => Some(Field("name")),
1035 "field_expression" => Some(Field("field")),
1036 "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1037 _ => None,
1038 }
1039}
1040
1041fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1042 let mut current = expression;
1043 loop {
1044 match current.kind() {
1045 "identifier"
1046 | "field_identifier"
1047 | "namespace_identifier"
1048 | "type_identifier"
1049 | "destructor_name"
1050 | "operator_name"
1051 | "primitive_type"
1052 | "this" => return Some(current),
1053 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1054 "dependent_name" | "template_function" | "template_method" | "template_type" => {
1055 current = current.child_by_field_name("name")?;
1056 }
1057 "field_expression" => current = current.child_by_field_name("field")?,
1058 "call_expression" => current = current.child_by_field_name("function")?,
1059 "new_expression" => current = current.child_by_field_name("type")?,
1060 "parenthesized_expression" => current = first_named_child(current)?,
1061 _ => return declarator_name_node(current),
1062 }
1063 }
1064}
1065
1066fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1067 if function.kind() != "qualified_identifier" {
1068 return;
1069 }
1070 if let Some(scope) = function.child_by_field_name("scope") {
1071 attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1072 }
1073}
1074
1075fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1076 loop {
1077 if node.kind() == "qualified_identifier" {
1078 return Some(node);
1079 }
1080 node = node
1081 .child_by_field_name("declarator")
1082 .or_else(|| node.child_by_field_name("name"))
1083 .or_else(|| first_named_child(node))?;
1084 }
1085}
1086
1087fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1088 node.child_by_field_name("declarator")
1089 .and_then(qualified_declarator_node)
1090 .and_then(|qualified| qualified.child_by_field_name("scope"))
1091}
1092
1093pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1094 node_text(scope, source) == node_text(name, source)
1095}
1096
1097fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1098 node.child_by_field_name("declarator")
1099 .and_then(qualified_declarator_node)
1100 .and_then(|qualified| {
1101 Some((
1102 expression_name_node(qualified.child_by_field_name("scope")?)?,
1103 expression_name_node(last_named_field_child(qualified, "name")?)?,
1104 ))
1105 })
1106 .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1107}
1108
1109fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1110 if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1111 return None;
1112 }
1113 unquoted_string_content_span(node)
1114}
1115
1116fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1124 let mut names = HashSet::default();
1125 let mut stack = vec![root];
1126 while let Some(node) = stack.pop() {
1127 for child in named_children_iter(node) {
1128 match child.kind() {
1129 "preproc_function_def" => {
1130 if let Some(name) = child.child_by_field_name("name") {
1131 names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1132 }
1133 }
1134 "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1135 | "preproc_elifdef" => stack.push(child),
1136 _ => {}
1137 }
1138 }
1139 }
1140 names
1141}
1142
1143fn cpp_member_terminal_name<'tree>(field: Node<'tree>) -> Option<Node<'tree>> {
1144 let field = if field.kind() == "dependent_name" {
1145 first_named_child(field)?
1146 } else {
1147 field
1148 };
1149 expression_name_node(field)
1150}
1151
1152fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1162 if !matches!(
1163 node.kind(),
1164 "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1165 ) {
1166 return None;
1167 }
1168
1169 let mut current = node;
1170 while let Some(parent) = current.parent() {
1171 if parent.kind() == "field_expression" {
1172 let field = parent.child_by_field_name("field")?;
1173 return cpp_member_terminal_name(field)
1174 .filter(|name| name.id() == node.id())
1175 .map(|_| OccurrenceRole::MemberPosition);
1176 }
1177
1178 if matches!(
1181 parent.kind(),
1182 "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1183 ) {
1184 current = parent;
1185 } else {
1186 return None;
1187 }
1188 }
1189 None
1190}
1191
1192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1194enum CppBinderOwner<'tree> {
1195 Parameter,
1197 Local(Node<'tree>),
1199 RangeFor(Node<'tree>),
1201 StructuredBinding(Node<'tree>),
1203 InitCapture,
1205}
1206
1207const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1210 "pointer_declarator",
1211 "array_declarator",
1212 "reference_declarator",
1213 "parenthesized_declarator",
1214 "attributed_declarator",
1215 "variadic_declarator",
1216];
1217
1218fn cpp_binder_owner<'tree>(
1228 node: Node<'tree>,
1229 parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1230 mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1231) -> Option<CppBinderOwner<'tree>> {
1232 if node.kind() != "identifier" {
1233 return None;
1234 }
1235 let mut child = node;
1236 let mut current = parent(node)?;
1237 let mut indirect = false;
1238 loop {
1239 match current.kind() {
1242 kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1243 let field = field_name(current, child);
1244 if field.is_some() && field != Some("declarator") {
1245 return None;
1246 }
1247 indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1248 }
1249 "function_declarator" => {
1250 let field = field_name(current, child);
1251 if field != Some("declarator") || !indirect {
1252 return None;
1253 }
1254 }
1255 "init_declarator" => {
1256 let field = field_name(current, child);
1257 if field != Some("declarator") {
1258 return None;
1259 }
1260 }
1261 "declaration" => {
1262 let field = field_name(current, child);
1263 if field != Some("declarator") {
1264 return None;
1265 }
1266 let local = std::iter::successors(parent(current), |node| parent(*node))
1267 .take_while(|node| {
1268 !matches!(
1269 node.kind(),
1270 "translation_unit"
1271 | "field_declaration_list"
1272 | "declaration_list"
1273 | "namespace_definition"
1274 )
1275 })
1276 .any(|node| {
1277 matches!(
1278 node.kind(),
1279 "compound_statement" | "for_statement" | "condition_clause"
1280 )
1281 });
1282 let declarator = if child.kind() == "init_declarator" {
1285 child.child_by_field_name("declarator")?
1286 } else {
1287 child
1288 };
1289 return local.then_some(CppBinderOwner::Local(declarator));
1290 }
1291 "parameter_declaration"
1292 | "optional_parameter_declaration"
1293 | "variadic_parameter_declaration" => {
1294 let field = field_name(current, child);
1295 if field != Some("declarator") {
1296 return None;
1297 }
1298 let parameter_parent = parent(current)?;
1299 let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1300 if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1303 || parameter_parent.named_child(1) != Some(current)
1304 {
1305 return None;
1306 }
1307 parent(parameter_parent)?
1308 } else {
1309 parameter_parent
1310 };
1311 if list.kind() != "parameter_list" {
1312 return None;
1313 }
1314 let owner = parent(list)?;
1315 let binds = match owner.kind() {
1316 "catch_clause" => true,
1317 "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1318 .is_some_and(|lambda| {
1319 lambda.kind() == "lambda_expression"
1320 && lambda.child_by_field_name("declarator") == Some(owner)
1321 }),
1322 "function_declarator" => {
1323 let mut declarator = owner;
1324 loop {
1325 let Some(up) = parent(declarator) else {
1326 break false;
1327 };
1328 if up.kind() == "function_definition" {
1329 break field_name_in_parent(up, declarator) == Some("declarator");
1330 }
1331 if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1332 break false;
1333 }
1334 declarator = up;
1335 }
1336 }
1337 _ => false,
1338 };
1339 return binds.then_some(CppBinderOwner::Parameter);
1340 }
1341 "for_range_loop" => {
1342 let field = field_name(current, child);
1343 return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1344 }
1345 "structured_binding_declarator" => {
1346 return Some(CppBinderOwner::StructuredBinding(current));
1347 }
1348 "lambda_capture_initializer" => {
1349 let field = field_name(current, child);
1350 return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1351 }
1352 _ => return None,
1353 }
1354 child = current;
1355 current = parent(current)?;
1356 }
1357}
1358
1359fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1363 cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1364}
1365
1366fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1377 let binding = |kind, hoisting, activation| {
1378 Some(BindingActivation {
1379 kind,
1380 hoisting,
1381 activation,
1382 })
1383 };
1384 let after = |node: Node<'_>| Range {
1385 start_byte: node.end_byte(),
1386 end_byte: scope.end_byte,
1387 start_line: node.end_position().row + 1,
1388 end_line: scope.end_line,
1389 };
1390 match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1391 CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1392 binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1393 }
1394 CppBinderOwner::Local(declarator) => binding(
1395 BindingKind::Local,
1396 HoistingClass::SourceOrder,
1397 after(declarator),
1398 ),
1399 CppBinderOwner::RangeFor(loop_node) => binding(
1400 BindingKind::LoopVariable,
1401 HoistingClass::DeclaredHead,
1402 node_range(loop_node.child_by_field_name("body")?),
1403 ),
1404 CppBinderOwner::StructuredBinding(declarator) => binding(
1405 BindingKind::Local,
1406 HoistingClass::SourceOrder,
1407 after(declarator),
1408 ),
1409 }
1410}
1411
1412impl StructuralSpec for CppStructuralSpec {
1413 fn language(&self) -> Language {
1414 Language::Cpp
1415 }
1416
1417 fn supports_boolean_literal_value(&self) -> bool {
1418 true
1419 }
1420
1421 fn supports_string_literal_value(&self) -> bool {
1422 true
1423 }
1424
1425 fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1426 matches!(node.kind(), "string_literal" | "raw_string_literal")
1427 && cpp_string_literal_value(node, source).is_none()
1428 }
1429
1430 fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1431 cpp_string_literal_value(node, source)
1432 }
1433
1434 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1435 CPP_KIND_TABLE
1436 }
1437
1438 fn generator_construct(
1439 &self,
1440 node: Node<'_>,
1441 _kind: NormalizedKind,
1442 source: &str,
1443 ) -> Option<&'static str> {
1444 statement_fact_kind(node, source).map(StatementFactKind::construct)
1445 }
1446
1447 fn refine_kind(
1448 &self,
1449 node: Node<'_>,
1450 kind: NormalizedKind,
1451 enclosing: Option<NormalizedKind>,
1452 source: &str,
1453 _context: &CallSiteContext,
1454 ) -> NormalizedKind {
1455 if kind == NormalizedKind::Function
1456 && (enclosing == Some(NormalizedKind::Class)
1457 || scoped_function_definition(node).is_some())
1458 {
1459 if is_constructor_definition(node, source) {
1460 NormalizedKind::Constructor
1461 } else {
1462 NormalizedKind::Method
1463 }
1464 } else {
1465 kind
1466 }
1467 }
1468
1469 fn supports_kind(&self, kind: NormalizedKind) -> bool {
1470 matches!(
1471 kind,
1472 NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1473 ) || self
1474 .kind_table()
1475 .iter()
1476 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1477 }
1478
1479 fn supports_role(&self, role: Role) -> bool {
1480 !matches!(
1481 role,
1482 Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1483 )
1484 }
1485
1486 fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1487 CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1488 }
1489
1490 fn call_site_facts(
1503 &self,
1504 node: Node<'_>,
1505 source: &str,
1506 context: &CallSiteContext,
1507 ) -> Option<CallSiteFacts> {
1508 if node.kind() == "new_expression" {
1509 return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1510 }
1511 let callee = node.child_by_field_name("function")?;
1512 (callee.kind() == "identifier"
1513 && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1514 .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1515 }
1516
1517 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1518 static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1519 .supported(OccurrenceRole::MemberPosition)
1520 .supported(OccurrenceRole::ValueReference)
1521 .supported(OccurrenceRole::Binder);
1522 &SUPPORT
1523 }
1524
1525 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1526 static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1530 .supported(EnvironmentAxis::Scopes)
1531 .supported(EnvironmentAxis::BindingIntervals)
1532 .supported(EnvironmentAxis::CallableApplicability);
1533 &SUPPORT
1534 }
1535
1536 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1539 if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1540 ScopeFormation::BindingScope
1541 } else {
1542 default_scope_formation(kind)
1543 }
1544 }
1545
1546 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1547 cpp_binding_activation(binder, scope)
1548 }
1549
1550 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1551 &CPP_MATERIALIZATION_SUPPORT
1552 }
1553
1554 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1555 &INVERSE_REFERENCE_EDGE_SUPPORT
1556 }
1557
1558 fn identity_route_support(&self) -> &IdentityRouteSupport {
1559 static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1570 .supported_axis(IdentityAxis::CanonicalIdentity)
1571 .supported_axis(IdentityAxis::PhysicalGrouping)
1572 .supported_relation(RouteHopKind::NestedOwner)
1573 .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1574 &SUPPORT
1575 }
1576
1577 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1578 if let Some(role) = cpp_member_position(node) {
1579 sink.occurrence_role(node, role);
1580 } else if cpp_is_binder(node, sink.parents()) {
1581 sink.occurrence_role(node, OccurrenceRole::Binder);
1582 }
1583
1584 match kind {
1585 NormalizedKind::Call => {
1586 let function_field = if node.kind() == "new_expression" {
1587 "type"
1588 } else {
1589 "function"
1590 };
1591 if let Some(function) = node.child_by_field_name(function_field) {
1592 attach_terminal_callee(
1593 sink,
1594 function,
1595 terminal_callee_leaf(function, callee_spine_step),
1596 );
1597 if node.kind() == "call_expression"
1606 && !matches!(function.kind(), "field_expression" | "call_expression")
1607 && let Some(callee) = expression_name_node(function)
1608 && self.kind_table().iter().any(|(node_kind, kind)| {
1609 *node_kind == callee.kind() && self.should_extract(callee, *kind)
1610 })
1611 {
1612 sink.occurrence_role(callee, OccurrenceRole::ValueReference);
1613 }
1614 if function.kind() == "field_expression"
1615 && let Some(argument) = function.child_by_field_name("argument")
1616 {
1617 attach_role_with_derived_name(
1618 sink,
1619 Role::Receiver,
1620 argument,
1621 expression_name_node,
1622 );
1623 }
1624 attach_qualified_scope_receiver(sink, function);
1625 }
1626 if let Some(arguments) = node.child_by_field_name("arguments") {
1627 attach_positional_argument_roles(sink, arguments, expression_name_node);
1628 }
1629 }
1630 NormalizedKind::FieldAccess => {
1631 if let Some(field) = node.child_by_field_name("field") {
1632 attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
1633 if let Some(name) = expression_name_node(field) {
1634 sink.set_name(name);
1635 }
1636 }
1637 if let Some(argument) = node.child_by_field_name("argument") {
1638 attach_role_with_derived_name(
1639 sink,
1640 Role::Object,
1641 argument,
1642 expression_name_node,
1643 );
1644 }
1645 }
1646 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
1647 if let Some(name) = node
1648 .child_by_field_name("declarator")
1649 .and_then(declarator_name_node)
1650 {
1651 sink.set_name(name);
1652 }
1653 }
1654 NormalizedKind::Module => {
1655 if let Some(name) = namespace_name_node(node) {
1656 sink.set_name(name);
1657 }
1658 }
1659 NormalizedKind::Class | NormalizedKind::Declaration => {
1660 if let Some(name) = node
1661 .child_by_field_name("name")
1662 .and_then(declarator_name_node)
1663 .or_else(|| node.child_by_field_name("name"))
1664 {
1665 sink.set_name(name);
1666 }
1667 }
1668 NormalizedKind::Assignment => match node.kind() {
1669 "init_declarator" => {
1670 if let Some(declarator) = node.child_by_field_name("declarator") {
1671 attach_role_with_derived_name(
1672 sink,
1673 Role::Left,
1674 declarator,
1675 declarator_name_node,
1676 );
1677 if let Some(name) = declarator_name_node(declarator) {
1678 sink.set_name(name);
1679 }
1680 }
1681 if let Some(value) = node.child_by_field_name("value") {
1682 attach_role_with_derived_name(
1683 sink,
1684 Role::Right,
1685 value,
1686 expression_name_node,
1687 );
1688 }
1689 }
1690 "assignment_expression" => {
1691 if let Some(left) = node.child_by_field_name("left") {
1692 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1693 }
1694 if let Some(right) = node.child_by_field_name("right") {
1695 attach_role_with_derived_name(
1696 sink,
1697 Role::Right,
1698 right,
1699 expression_name_node,
1700 );
1701 }
1702 }
1703 _ => {}
1704 },
1705 NormalizedKind::Import => {
1706 if let Some(path) = node.child_by_field_name("path") {
1707 if let Some(name) = unquoted_include_span(path) {
1708 sink.role_named_span(Role::Module, path, name);
1709 } else {
1710 attach_role_with_derived_name(
1711 sink,
1712 Role::Module,
1713 path,
1714 expression_name_node,
1715 );
1716 }
1717 }
1718 }
1719 NormalizedKind::Identifier => match expression_name_node(node) {
1720 Some(name) => sink.set_name(name),
1721 None => sink.set_name(node),
1722 },
1723 _ => {}
1724 }
1725 }
1726}
1727
1728#[cfg(test)]
1729mod tests {
1730 use super::CPP_STRUCTURAL_SPEC;
1731 use super::cpp_binder_owner;
1732 use super::cpp_member_position;
1733 use super::field_name_in_parent;
1734 use super::node_range;
1735 use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
1736 use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
1737 use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
1738 use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
1739 use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
1740 use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
1741 use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
1742 use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
1743 use brokk_bifrost_core::hash::HashMap;
1744 use brokk_bifrost_test_macros::hardwon;
1745 use tree_sitter::Parser;
1746
1747 fn collect_cpp_structural_facts(source: &str) {
1748 collect_structural_rows(source);
1749 }
1750
1751 fn collect_structural_rows(source: &str) -> StructuralFactRows {
1752 let mut parser = Parser::new();
1753 parser
1754 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1755 .expect("C++ grammar");
1756 let tree = parser.parse(source, None).expect("C++ parse");
1757 let root = tree.root_node();
1758 let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
1759 let compiled = CompiledKinds::compile(
1760 &tree_sitter_cpp::LANGUAGE.into(),
1761 super::CPP_STRUCTURAL_SPEC.kind_table(),
1762 );
1763 let mut structural = StructuralFactCollector::new(
1764 &super::CPP_STRUCTURAL_SPEC,
1765 source,
1766 &context,
1767 ParentIndex::new(root),
1768 usize::MAX,
1769 None,
1770 );
1771 let mut source_facts = PrimarySourceFactCollector::new(source);
1772 let mut pending = vec![(root, None)];
1773 while let Some((node, parent)) = pending.pop() {
1774 let mut child_parent = parent;
1775 if node.is_named()
1776 && let Some(raw_kind) = compiled.kind_of(&node)
1777 && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
1778 {
1779 let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
1780 node,
1781 raw_kind,
1782 parent.map(|id| structural.normalized_kind(id)),
1783 source,
1784 &context,
1785 );
1786 let id = structural
1787 .enter(node, kind, parent, &mut source_facts)
1788 .expect("unbounded C++ structural collector");
1789 let mut sink = structural.role_sink(&mut source_facts);
1790 super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
1791 structural
1792 .accept_roles(id, sink.into_parts())
1793 .expect("unbounded C++ structural roles");
1794 child_parent = Some(id);
1795 }
1796 for index in (0..node.named_child_count()).rev() {
1797 if let Some(child) = node.named_child(index) {
1798 pending.push((child, child_parent));
1799 }
1800 }
1801 }
1802 structural
1803 .finish(root)
1804 .expect("C++ structural collection finishes")
1805 }
1806
1807 fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
1808 let mut parser = Parser::new();
1809 parser
1810 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1811 .expect("C++ grammar");
1812 let tree = parser.parse(source, None).expect("C++ parse");
1813 assert!(
1814 !tree.root_node().has_error(),
1815 "{}",
1816 tree.root_node().to_sexp()
1817 );
1818 let mut found = Vec::new();
1819 let mut pending = vec![tree.root_node()];
1820 while let Some(node) = pending.pop() {
1821 if let Some(role) = cpp_member_position(node) {
1822 found.push((
1823 node.start_byte(),
1824 &source[node.start_byte()..node.end_byte()],
1825 role,
1826 ));
1827 }
1828 for index in (0..node.named_child_count()).rev() {
1829 if let Some(child) = node.named_child(index) {
1830 pending.push(child);
1831 }
1832 }
1833 }
1834 found
1835 }
1836
1837 #[test]
1838 #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
1839 fn binder_classification_scans_large_initializer_lists_linearly() {
1840 use std::fmt::Write as _;
1841
1842 let mut source = String::from("int values[] = {");
1843 for index in 0..4096 {
1844 write!(source, "item_{index},").expect("append generated initializer item");
1845 }
1846 source.push_str("};\n");
1847
1848 let mut parser = Parser::new();
1849 parser
1850 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1851 .expect("C++ grammar");
1852 let tree = parser.parse(&source, None).expect("C++ parse");
1853 assert!(
1854 !tree.root_node().has_error(),
1855 "{}",
1856 tree.root_node().to_sexp()
1857 );
1858 let parents = ParentIndex::new(tree.root_node());
1859 let mut pending = vec![tree.root_node()];
1860 let mut identifiers = 0usize;
1861 let mut sibling_steps = 0usize;
1862 while let Some(node) = pending.pop() {
1863 if node.kind() == "identifier" {
1864 identifiers += 1;
1865 let _ = cpp_binder_owner(
1866 node,
1867 |node| parents.parent(node),
1868 |parent, child| {
1869 let mut cursor = parent.walk();
1870 if !cursor.goto_first_child() {
1871 return None;
1872 }
1873 loop {
1874 sibling_steps += 1;
1875 if cursor.node() == child {
1876 return cursor.field_name();
1877 }
1878 if !cursor.goto_next_sibling() {
1879 return None;
1880 }
1881 }
1882 },
1883 );
1884 }
1885 let mut cursor = node.walk();
1886 let children = node.children(&mut cursor).collect::<Vec<_>>();
1887 pending.extend(children.into_iter().rev());
1888 }
1889
1890 assert!(
1891 identifiers >= 4096,
1892 "parsed {identifiers} initializer identifiers"
1893 );
1894 assert!(
1895 sibling_steps <= identifiers * 4,
1896 "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
1897 );
1898 }
1899
1900 #[test]
1901 #[brokk_bifrost_test_macros::hardwon(
1902 "#4407: ABI 15 lambda declarators lost parameter binder identity"
1903 )]
1904 fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
1905 let source = concat!(
1906 "int enclosing(int outer) {\n",
1907 " return [outer](int lambda) {\n",
1908 " return [lambda](int nested) {\n",
1909 " return outer + lambda + nested;\n",
1910 " }(lambda);\n",
1911 " }(outer);\n",
1912 "}\n",
1913 );
1914 let mut parser = Parser::new();
1915 parser
1916 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1917 .expect("C++ grammar");
1918 let tree = parser.parse(source, None).expect("C++ parse");
1919 assert!(
1920 !tree.root_node().has_error(),
1921 "{}",
1922 tree.root_node().to_sexp()
1923 );
1924
1925 let identifier_at = |start_byte| {
1926 let mut pending = vec![tree.root_node()];
1927 while let Some(node) = pending.pop() {
1928 if node.kind() == "identifier" && node.start_byte() == start_byte {
1929 return Some(node);
1930 }
1931 for index in (0..node.named_child_count()).rev() {
1932 if let Some(child) = node.named_child(index) {
1933 pending.push(child);
1934 }
1935 }
1936 }
1937 None
1938 };
1939 let outer_parameter =
1940 identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
1941 .expect("outer parameter");
1942 let lambda_parameter =
1943 identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
1944 .expect("lambda parameter");
1945 let nested_parameter =
1946 identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
1947 .expect("nested parameter");
1948
1949 for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
1950 assert_eq!(
1951 cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
1952 Some(super::CppBinderOwner::Parameter)
1953 );
1954 assert_eq!(
1955 super::cpp_binding_activation(parameter, node_range(tree.root_node()))
1956 .map(|activation| activation.kind),
1957 Some(BindingKind::Parameter)
1958 );
1959 }
1960
1961 for (start, name) in [
1962 (
1963 source
1964 .find("return outer + lambda")
1965 .expect("outer reference")
1966 + "return ".len(),
1967 "outer",
1968 ),
1969 (
1970 source
1971 .find("outer + lambda + nested")
1972 .expect("lambda reference")
1973 + "outer + ".len(),
1974 "lambda",
1975 ),
1976 (
1977 source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
1978 "nested",
1979 ),
1980 ] {
1981 let reference = identifier_at(start).expect("body reference identifier");
1982 assert_eq!(
1983 cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
1984 None,
1985 "body reference {name:?} was classified as a binder"
1986 );
1987 }
1988 }
1989
1990 #[test]
1991 #[brokk_bifrost_test_macros::hardwon(
1992 "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
1993 )]
1994 fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
1995 let source = concat!(
1996 "struct Visitor {\n",
1997 " int visit(this Visitor const& self, int ordinary) {\n",
1998 " return self.value + ordinary;\n",
1999 " }\n",
2000 "};\n",
2001 "using Callback = void (*)(int nested);\n",
2002 );
2003 let mut parser = Parser::new();
2004 parser
2005 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2006 .expect("C++ grammar");
2007 let tree = parser.parse(source, None).expect("C++ parse");
2008 assert!(
2009 !tree.root_node().has_error(),
2010 "{}",
2011 tree.root_node().to_sexp()
2012 );
2013
2014 let identifier_at = |start: usize, len: usize| {
2015 tree.root_node()
2016 .descendant_for_byte_range(start, start + len)
2017 .filter(|node| node.kind() == "identifier")
2018 .expect("identifier at source span")
2019 };
2020 let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2021 let receiver_body = identifier_at(
2022 source.find("return self").expect("receiver body reference") + "return ".len(),
2023 4,
2024 );
2025 let function_type_parameter =
2026 identifier_at(source.find("nested").expect("function type"), 6);
2027 let parents = ParentIndex::new(tree.root_node());
2028
2029 assert_eq!(receiver.kind(), "identifier");
2030 assert_eq!(
2031 cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2032 Some(super::CppBinderOwner::Parameter)
2033 );
2034 assert!(super::cpp_is_binder(receiver, &parents));
2035 assert_eq!(
2036 super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2037 .map(|activation| activation.kind),
2038 Some(BindingKind::Parameter)
2039 );
2040 assert_eq!(
2041 cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2042 None,
2043 "the receiver read in the body is not another binder"
2044 );
2045 assert!(!super::cpp_is_binder(receiver_body, &parents));
2046 assert_eq!(
2047 cpp_binder_owner(
2048 function_type_parameter,
2049 |node| node.parent(),
2050 field_name_in_parent
2051 ),
2052 None,
2053 "a function-type parameter remains outside callable binding ownership"
2054 );
2055 }
2056
2057 #[test]
2058 fn attributed_declarator_resolves_its_declared_name() {
2059 let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2060 let mut parser = Parser::new();
2061 parser
2062 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2063 .expect("C++ grammar");
2064 let tree = parser.parse(source, None).expect("C++ parse");
2065 assert!(
2066 !tree.root_node().has_error(),
2067 "{}",
2068 tree.root_node().to_sexp()
2069 );
2070
2071 let mut pending = vec![tree.root_node()];
2072 let mut attributed = None;
2073 let mut conversion = None;
2074 while let Some(node) = pending.pop() {
2075 match node.kind() {
2076 "attributed_declarator" => attributed = Some(node),
2077 "operator_cast" => conversion = Some(node),
2078 _ => {}
2079 }
2080 for index in (0..node.named_child_count()).rev() {
2081 if let Some(child) = node.named_child(index) {
2082 pending.push(child);
2083 }
2084 }
2085 }
2086 let attributed = attributed.expect("attributed declarator");
2087 let name = super::declarator_name_node(attributed).expect("declarator name");
2088
2089 assert_eq!(&source[name.byte_range()], "x");
2090 let conversion = conversion.expect("conversion operator");
2091 assert_eq!(
2092 super::declarator_name_node(conversion).map(|node| node.kind()),
2093 Some("operator_cast")
2094 );
2095 }
2096
2097 #[test]
2098 fn cpp_member_position_is_limited_to_member_access_and_calls() {
2099 let source = concat!(
2100 "struct Widget {\n",
2101 " int value;\n",
2102 " int method(int label) {\n",
2103 " return this->value + label.value + label.method()\n",
2104 " + label.template convert<int>() + label.ns::member;\n",
2105 " }\n",
2106 "};\n",
2107 "int build(Widget widget) {\n",
2108 " Widget result{.value = widget.value};\n",
2109 " return widget.method(result.value);\n",
2110 "}\n",
2111 );
2112 let found = member_occurrences(source);
2113 let member_texts = found
2114 .iter()
2115 .map(|(_, text, role)| (*text, *role))
2116 .collect::<Vec<_>>();
2117
2118 assert_eq!(
2119 member_texts,
2120 vec![
2121 ("value", OccurrenceRole::MemberPosition),
2122 ("value", OccurrenceRole::MemberPosition),
2123 ("method", OccurrenceRole::MemberPosition),
2124 ("convert", OccurrenceRole::MemberPosition),
2125 ("member", OccurrenceRole::MemberPosition),
2126 ("value", OccurrenceRole::MemberPosition),
2127 ("method", OccurrenceRole::MemberPosition),
2128 ("value", OccurrenceRole::MemberPosition),
2129 ]
2130 );
2131
2132 let at = |needle: &str| source.find(needle).expect("fixture token");
2133 for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2134 let receiver_start = at(receiver);
2135 assert!(
2136 found.iter().all(|(offset, _, _)| *offset != receiver_start),
2137 "receiver was classified: {receiver:?}"
2138 );
2139 }
2140 let non_members = [
2141 at("Widget {"),
2142 at("int value") + "int ".len(),
2143 at("int method") + "int ".len(),
2144 at(".value =") + 1,
2145 ];
2146 for unrelated_start in non_members {
2147 assert!(
2148 found
2149 .iter()
2150 .all(|(offset, _, _)| *offset != unrelated_start),
2151 "non-member identifier at byte {unrelated_start} was classified"
2152 );
2153 }
2154 }
2155
2156 #[test]
2157 #[brokk_bifrost_test_macros::hardwon(
2158 "#3466: C and C++ free-function calls published no resolver occurrence"
2159 )]
2160 fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2161 let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2162 assert!(support.is_supported(OccurrenceRole::MemberPosition));
2163 assert!(support.is_supported(OccurrenceRole::ValueReference));
2164 for role in [
2165 OccurrenceRole::ReceiverPosition,
2166 OccurrenceRole::LabelOrKey,
2167 OccurrenceRole::DeclarationName,
2168 ] {
2169 assert!(
2170 !support.is_supported(role),
2171 "unexpected C++ support for {role:?}"
2172 );
2173 }
2174 }
2175
2176 #[test]
2177 fn cpp_string_literals_publish_decoded_values() {
2178 let source = r#"
2179const char *ordinary = "http://example.test/a\n";
2180const char *raw = R"(https://example.test/path)";
2181const char *wide = L"wide";
2182const char character = 'x';
2183"#;
2184 let rows = collect_structural_rows(source);
2185 let values = rows
2186 .nodes()
2187 .iter()
2188 .filter(|node| node.kind == NormalizedKind::StringLiteral)
2189 .map(|node| node.string_value.clone())
2190 .collect::<Vec<_>>();
2191
2192 assert_eq!(
2193 values,
2194 vec![
2195 Some("http://example.test/a\n".to_owned()),
2196 Some("https://example.test/path".to_owned()),
2197 None,
2198 None,
2199 ]
2200 );
2201 }
2202
2203 #[test]
2208 #[brokk_bifrost_test_macros::hardwon(
2209 "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2210 )]
2211 fn cpp_free_function_callees_are_value_references() {
2212 let source = concat!(
2213 "int bound(void) { return 1; }\n",
2214 "struct Widget { int method(void) { return 0; } };\n",
2215 "int go(struct Widget *widget) {\n",
2216 " return getenv(\"PATH\") != 0\n",
2217 " + ns::getenv(\"USER\") != 0\n",
2218 " + bound()\n",
2219 " + widget->method()\n",
2220 " + bound()();\n",
2221 "}\n",
2222 );
2223 let mut parser = Parser::new();
2224 parser
2225 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2226 .expect("C++ grammar");
2227 let tree = parser.parse(source, None).expect("C++ parse");
2228 assert!(
2229 !tree.root_node().has_error(),
2230 "{}",
2231 tree.root_node().to_sexp()
2232 );
2233
2234 let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2239 let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2240 let mut start_by_fact = HashMap::default();
2241 let mut extractions = Vec::new();
2242 let mut pending = vec![tree.root_node()];
2243 while let Some(node) = pending.pop() {
2244 if node.is_named()
2245 && let Some(kind) = compiled.kind_of(&node)
2246 && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2247 {
2248 start_by_fact.insert(node.id(), node.start_byte());
2249 extractions.push((node.start_byte(), node, kind));
2250 }
2251 for index in (0..node.named_child_count()).rev() {
2252 if let Some(child) = node.named_child(index) {
2253 pending.push(child);
2254 }
2255 }
2256 }
2257 extractions.sort_by_key(|(start, _, _)| *start);
2258 let mut source_facts =
2259 brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2260 let parents = ParentIndex::new(tree.root_node());
2261 let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2262 for (_, node, kind) in extractions {
2263 CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2264 }
2265 let (_, _, occurrences, stop) = sink.into_parts();
2266 assert_eq!(stop, None);
2267
2268 let roles_at = |needle: &str| {
2269 let start = source.find(needle).expect("fixture token");
2270 occurrences
2271 .iter()
2272 .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2273 .map(|occurrence| occurrence.role)
2274 .collect::<Vec<_>>()
2275 };
2276 assert_eq!(
2280 roles_at("getenv(\"PATH\")"),
2281 vec![OccurrenceRole::ValueReference]
2282 );
2283 assert_eq!(
2284 roles_at("getenv(\"USER\")"),
2285 vec![OccurrenceRole::ValueReference],
2286 "the terminal name of a qualified callee is the value read"
2287 );
2288 assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
2289 assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
2292 assert!(roles_at("widget->").is_empty());
2295 }
2296
2297 #[test]
2301 fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
2302 collect_cpp_structural_facts(
2303 "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
2304 );
2305 }
2306}
2307
2308#[cfg(test)]
2309mod result_use_tests {
2310 use super::{CppCallResultUseIndex, cpp_call_result_use};
2311 use brokk_bifrost_core::analyzer::structural::result_use::{
2312 CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
2313 };
2314 use tree_sitter::Parser;
2315
2316 fn tree(source: &str) -> tree_sitter::Tree {
2317 let mut parser = Parser::new();
2318 parser
2319 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2320 .expect("C++ grammar");
2321 parser.parse(source, None).expect("C++ tree")
2322 }
2323
2324 fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
2325 let mut found = Vec::new();
2326 let mut stack = vec![tree.root_node()];
2327 while let Some(node) = stack.pop() {
2328 if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
2329 found.push(node);
2330 }
2331 for index in (0..node.named_child_count()).rev() {
2332 stack.push(node.named_child(index).expect("named child"));
2333 }
2334 }
2335 found.sort_by_key(|node| node.start_byte());
2336 found
2337 }
2338
2339 #[test]
2340 fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
2341 let source = concat!(
2342 "int f();\n",
2343 "void sink(int);\n",
2344 "void run() {\n",
2345 " f();\n",
2346 " (void)f();\n",
2347 " static_cast<void>(f());\n",
2348 " sink(f());\n",
2349 " return;\n",
2350 "}\n",
2351 );
2352 let tree = tree(source);
2353 assert!(
2354 !tree.root_node().has_error(),
2355 "{}",
2356 tree.root_node().to_sexp()
2357 );
2358 let calls = calls(&tree, source);
2359 assert_eq!(calls.len(), 5);
2360 assert_eq!(
2361 cpp_call_result_use(calls[0], source).for_port(0),
2362 CallResultUse::Discarded
2363 );
2364 assert!(matches!(
2365 cpp_call_result_use(calls[1], source).for_port(0),
2366 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2367 ));
2368 assert!(matches!(
2369 cpp_call_result_use(calls[2], source).for_port(0),
2370 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2371 ));
2372 assert_eq!(
2373 cpp_call_result_use(calls[3], source).for_port(0),
2374 CallResultUse::Discarded
2375 );
2376 assert_eq!(
2377 cpp_call_result_use(calls[4], source).for_port(0),
2378 CallResultUse::Consumed
2379 );
2380 }
2381
2382 #[test]
2383 fn lambda_tail_and_recovered_syntax_are_open() {
2384 let source = "int f(); void run() { auto value = [] { return f(); }; f(,); }";
2385 let tree = tree(source);
2386 assert!(tree.root_node().has_error());
2387 let calls = calls(&tree, source);
2388 assert!(matches!(
2389 cpp_call_result_use(calls[0], source).for_port(0),
2390 CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
2391 ));
2392 assert_eq!(
2393 cpp_call_result_use(calls[1], source).for_port(0),
2394 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
2395 );
2396 }
2397
2398 #[test]
2399 fn exact_index_keeps_call_spans_separate() {
2400 let source = "int f(); void run() { f(); }";
2401 let tree = tree(source);
2402 let calls = calls(&tree, source);
2403 let index = CppCallResultUseIndex::new(tree.root_node(), source);
2404 assert_eq!(
2405 index
2406 .at_span(calls[0].start_byte(), calls[0].end_byte())
2407 .expect("indexed call")
2408 .for_port(0),
2409 CallResultUse::Discarded
2410 );
2411 assert!(index.at_span(source.len(), source.len()).is_err());
2412 }
2413}