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::callable::{
11 CallKind, CallShapeCoverage, CallSiteContext, CallSiteFacts,
12};
13use brokk_bifrost_core::analyzer::structural::edges::{
14 INVERSE_REFERENCE_EDGE_SUPPORT, ReferenceEdgeSupport,
15};
16use brokk_bifrost_core::analyzer::structural::facts::Span;
17use brokk_bifrost_core::analyzer::structural::kinds::{NormalizedKind, Role};
18use brokk_bifrost_core::analyzer::structural::materialization::{
19 CPP_MATERIALIZATION_SUPPORT, DeclarationMaterializationSupport,
20};
21use brokk_bifrost_core::analyzer::structural::occurrences::{
22 OccurrenceRole, OccurrenceRoleSupport,
23};
24use brokk_bifrost_core::analyzer::structural::resolution::{
25 BindingActivation, BindingKind, EnvironmentAxis, HoistingClass, LexicalEnvironmentSupport,
26 ScopeFormation, default_scope_formation,
27};
28use brokk_bifrost_core::analyzer::structural::result_use::{
29 CallResultUse, CallResultUseIndex, CallResultUseOpenReason, CallResultUseSite,
30 IntentionalDiscardKind,
31};
32use brokk_bifrost_core::analyzer::structural::routes::{
33 IdentityAxis, IdentityRouteSupport, RouteHopKind,
34};
35use brokk_bifrost_core::analyzer::structural::spec::{RoleSink, StructuralSpec};
36use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, named_children_iter, node_range};
37use brokk_bifrost_core::analyzer::{Language, Range};
38use brokk_bifrost_core::hash::HashSet;
39use tree_sitter::Node;
40
41#[derive(Debug, Default)]
42pub struct CppStructuralSpec;
43
44pub static CPP_STRUCTURAL_SPEC: CppStructuralSpec = CppStructuralSpec;
45
46pub(crate) fn is_cpp_preprocessor_conditional(node: Node<'_>) -> bool {
52 matches!(
53 node.kind(),
54 "preproc_if"
55 | "preproc_ifdef"
56 | "preproc_ifndef"
57 | "preproc_elif"
58 | "preproc_elifdef"
59 | "preproc_else"
60 )
61}
62
63pub fn cpp_call_result_use(call: Node<'_>, source: &str) -> CallResultUseSite {
67 let default = if !matches!(call.kind(), "call_expression" | "new_expression")
68 || call.has_error()
69 || has_recovery_ancestor(call)
70 {
71 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
72 } else if has_deferred_ancestor(call) {
73 CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
74 } else {
75 let mut expression = call;
76 loop {
77 let Some(parent) = expression.parent() else {
78 break CallResultUse::Open(CallResultUseOpenReason::UnclassifiedSyntax);
79 };
80 if parent.is_error() || parent.is_missing() {
81 break CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax);
82 }
83 match parent.kind() {
84 "parenthesized_expression" | "comma_expression"
85 if parent
86 .named_children(&mut parent.walk())
87 .any(|child| child.id() == expression.id()) =>
88 {
89 expression = parent;
90 }
91 "expression_statement"
92 if parent
93 .named_children(&mut parent.walk())
94 .any(|child| child.id() == expression.id()) =>
95 {
96 break CallResultUse::Discarded;
97 }
98 "for_statement"
99 if matches!(
100 field_name_in_parent(parent, expression),
101 Some("initializer" | "update")
102 ) =>
103 {
104 break CallResultUse::Discarded;
105 }
106 "cast_expression"
107 if field_name_in_parent(parent, expression) == Some("value")
108 && parent
109 .child_by_field_name("type")
110 .is_some_and(|type_node| cpp_type_is_void(type_node, source)) =>
111 {
112 break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
113 }
114 "argument_list"
115 if parent
116 .parent()
117 .is_some_and(|outer_call| cpp_static_cast_to_void(outer_call, source)) =>
118 {
119 break CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast);
120 }
121 "lambda_expression" => {
122 break CallResultUse::Open(CallResultUseOpenReason::DeferredContext);
123 }
124 _ => break CallResultUse::Consumed,
125 }
126 }
127 };
128 CallResultUseSite::new(call.start_byte(), call.end_byte(), default)
129}
130
131fn cpp_type_is_void(type_node: Node<'_>, source: &str) -> bool {
132 let base = match type_node.kind() {
133 "type_descriptor" if type_node.child_by_field_name("declarator").is_none() => {
134 type_node.child_by_field_name("type")
135 }
136 "primitive_type" => Some(type_node),
137 _ => None,
138 };
139 base.is_some_and(|base| {
140 base.kind() == "primitive_type" && node_text(base, source) == Some("void")
141 })
142}
143
144fn has_recovery_ancestor(mut node: Node<'_>) -> bool {
145 while let Some(parent) = node.parent() {
146 if parent.is_error() || parent.is_missing() {
147 return true;
148 }
149 node = parent;
150 }
151 false
152}
153
154fn has_deferred_ancestor(mut node: Node<'_>) -> bool {
155 while let Some(parent) = node.parent() {
156 if parent.kind() == "lambda_expression" {
157 return true;
158 }
159 node = parent;
160 }
161 false
162}
163
164fn cpp_static_cast_to_void(call: Node<'_>, source: &str) -> bool {
165 if call.kind() != "call_expression" {
166 return false;
167 }
168 let Some(function) = call.child_by_field_name("function") else {
169 return false;
170 };
171 if function.kind() != "template_function"
172 || function
173 .child_by_field_name("name")
174 .and_then(|name| node_text(name, source))
175 != Some("static_cast")
176 {
177 return false;
178 }
179 function
180 .child_by_field_name("arguments")
181 .and_then(|arguments| arguments.named_child(0))
182 .is_some_and(|type_node| cpp_type_is_void(type_node, source))
183}
184
185fn cpp_cast_call(call: Node<'_>, source: &str) -> bool {
186 let Some(function) = call.child_by_field_name("function") else {
187 return false;
188 };
189 if function.kind() != "template_function" {
190 return false;
191 }
192 matches!(
193 function
194 .child_by_field_name("name")
195 .and_then(|name| node_text(name, source)),
196 Some("static_cast" | "reinterpret_cast" | "const_cast" | "dynamic_cast")
197 )
198}
199
200#[derive(Debug, Clone)]
202pub struct CppCallResultUseIndex {
203 inner: CallResultUseIndex,
204}
205
206impl CppCallResultUseIndex {
207 pub fn new(root: Node<'_>, source: &str) -> Self {
208 let mut sites = Vec::new();
209 let mut stack = vec![root];
210 while let Some(node) = stack.pop() {
211 if matches!(node.kind(), "call_expression" | "new_expression")
212 && !cpp_cast_call(node, source)
213 {
214 sites.push(cpp_call_result_use(node, source));
215 }
216 for index in (0..node.named_child_count()).rev() {
217 stack.push(node.named_child(index).expect("named child exists"));
218 }
219 }
220 Self {
221 inner: CallResultUseIndex::new(sites),
222 }
223 }
224
225 pub fn at_span(
226 &self,
227 start_byte: usize,
228 end_byte: usize,
229 ) -> Result<&CallResultUseSite, CallResultUseOpenReason> {
230 self.inner.at_span(start_byte, end_byte)
231 }
232
233 pub fn sites(&self) -> &[CallResultUseSite] {
234 self.inner.sites()
235 }
236}
237
238pub const CPP_KIND_TABLE: &[(&str, NormalizedKind)] = &[
239 ("comment", NormalizedKind::Comment),
240 ("call_expression", NormalizedKind::Call),
241 ("new_expression", NormalizedKind::Call),
242 ("field_expression", NormalizedKind::FieldAccess),
243 ("function_definition", NormalizedKind::Function),
244 ("lambda_expression", NormalizedKind::Lambda),
245 ("class_specifier", NormalizedKind::Class),
246 ("struct_specifier", NormalizedKind::Class),
247 ("union_specifier", NormalizedKind::Class),
248 ("alias_declaration", NormalizedKind::Declaration),
249 ("namespace_definition", NormalizedKind::Module),
250 ("assignment_expression", NormalizedKind::Assignment),
251 ("init_declarator", NormalizedKind::Assignment),
252 ("preproc_include", NormalizedKind::Import),
253 ("identifier", NormalizedKind::Identifier),
254 ("field_identifier", NormalizedKind::Identifier),
255 ("namespace_identifier", NormalizedKind::Identifier),
256 ("qualified_identifier", NormalizedKind::Identifier),
257 ("type_identifier", NormalizedKind::Identifier),
258 ("template_function", NormalizedKind::Identifier),
259 ("template_method", NormalizedKind::Identifier),
260 ("template_type", NormalizedKind::Identifier),
261 ("dependent_name", NormalizedKind::Identifier),
262 ("destructor_name", NormalizedKind::Identifier),
263 ("operator_name", NormalizedKind::Identifier),
264 ("primitive_type", NormalizedKind::Identifier),
265 ("char_literal", NormalizedKind::StringLiteral),
266 ("string_literal", NormalizedKind::StringLiteral),
267 ("raw_string_literal", NormalizedKind::StringLiteral),
268 ("number_literal", NormalizedKind::NumericLiteral),
269 ("true", NormalizedKind::BooleanLiteral),
270 ("false", NormalizedKind::BooleanLiteral),
271 ("null", NormalizedKind::NullLiteral),
272 ("return_statement", NormalizedKind::Return),
273 ("throw_statement", NormalizedKind::Throw),
274 ("catch_clause", NormalizedKind::Catch),
275 ("if_statement", NormalizedKind::If),
276 ("for_statement", NormalizedKind::Loop),
277 ("for_range_loop", NormalizedKind::Loop),
278 ("compound_statement", NormalizedKind::Block),
279 ("switch_statement", NormalizedKind::Switch),
280 ("case_statement", NormalizedKind::Case),
281 ("labeled_statement", NormalizedKind::Label),
282 ("goto_statement", NormalizedKind::Goto),
283 ("break_statement", NormalizedKind::Break),
284 ("continue_statement", NormalizedKind::Continue),
285 ("while_statement", NormalizedKind::WhileLoop),
286 ("do_statement", NormalizedKind::WhileLoop),
287];
288
289fn cpp_escape_codepoint(digits: &str, radix: u32, byte_escape: bool) -> Option<char> {
290 let value = u32::from_str_radix(digits, radix).ok()?;
291 if byte_escape && value > 0x7f {
292 return None;
293 }
294 char::from_u32(value)
295}
296
297fn cpp_cooked_string_escape(escape: &str) -> Option<Option<char>> {
298 let escaped = escape.strip_prefix('\\')?;
299 if matches!(escaped, "\n" | "\r\n") {
300 return Some(None);
301 }
302 let character = match escaped {
303 "a" => '\x07',
304 "b" => '\x08',
305 "f" => '\x0c',
306 "n" => '\n',
307 "r" => '\r',
308 "t" => '\t',
309 "v" => '\x0b',
310 "0" => '\0',
311 "\\" => '\\',
312 "'" => '\'',
313 "\"" => '\"',
314 "?" => '?',
315 _ if escaped.starts_with('x') => {
316 let digits = escaped.get(1..)?;
317 (!digits.is_empty()
318 && digits.len() <= 4
319 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
320 .then_some(())?;
321 cpp_escape_codepoint(digits, 16, true)?
322 }
323 _ if escaped.starts_with('u') => {
324 let digits = escaped.get(1..)?;
325 (digits.len() == 4 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
326 .then_some(())?;
327 cpp_escape_codepoint(digits, 16, false)?
328 }
329 _ if escaped.starts_with('U') => {
330 let digits = escaped.get(1..)?;
331 (digits.len() == 8 && digits.bytes().all(|byte| byte.is_ascii_hexdigit()))
332 .then_some(())?;
333 cpp_escape_codepoint(digits, 16, false)?
334 }
335 _ if escaped.len() <= 3 && escaped.bytes().all(|byte| (b'0'..=b'7').contains(&byte)) => {
336 cpp_escape_codepoint(escaped, 8, true)?
337 }
338 _ => return None,
339 };
340 Some(Some(character))
341}
342
343fn cpp_string_literal_value(node: Node<'_>, source: &str) -> Option<String> {
344 let spelling = node_text(node, source)?;
345 match node.kind() {
346 "string_literal" => {
347 if !spelling.starts_with('"') {
351 return None;
352 }
353 let mut decoded = String::new();
354 let mut cursor = node.walk();
355 for child in node.named_children(&mut cursor) {
356 let text = node_text(child, source)?;
357 match child.kind() {
358 "string_content" => decoded.push_str(text),
359 "escape_sequence" => {
360 if let Some(character) = cpp_cooked_string_escape(text)? {
361 decoded.push(character);
362 }
363 }
364 _ => return None,
365 }
366 }
367 Some(decoded)
368 }
369 "raw_string_literal" => {
370 if !spelling.starts_with("R\"") {
371 return None;
372 }
373 let mut content = None;
374 let mut cursor = node.walk();
375 for child in node.named_children(&mut cursor) {
376 match child.kind() {
377 "raw_string_content" => {
378 if content.is_some() {
379 return None;
380 }
381 content = Some(node_text(child, source)?);
382 }
383 "raw_string_delimiter" => {}
384 _ => return None,
385 }
386 }
387 content.map(str::to_owned)
388 }
389 _ => None,
390 }
391}
392
393fn condition_expression(node: Node<'_>) -> Option<Node<'_>> {
394 let condition = node.child_by_field_name("condition")?;
395 if condition.kind() == "condition_clause" {
396 condition
397 .child_by_field_name("value")
398 .or_else(|| condition.named_child(0))
399 } else {
400 Some(condition)
401 }
402}
403
404#[derive(Clone, Copy)]
406struct CppSwitchDeclaration<'tree> {
407 type_node: Node<'tree>,
408 declarator: Node<'tree>,
409}
410
411fn cpp_switch_scope(node: Node<'_>) -> bool {
412 matches!(
413 node.kind(),
414 "compound_statement"
415 | "for_statement"
416 | "for_range_loop"
417 | "if_statement"
418 | "switch_statement"
419 | "while_statement"
420 | "catch_clause"
421 )
422}
423
424fn cpp_switch_local_declaration<'tree>(
425 switch: Node<'tree>,
426 name: &str,
427 source: &str,
428) -> Option<CppSwitchDeclaration<'tree>> {
429 let condition = condition_expression(switch)?;
430 let function = std::iter::successors(switch.parent(), |node| node.parent())
431 .find(|node| matches!(node.kind(), "function_definition" | "lambda_expression"))?;
432 enum Step<'tree> {
433 Enter(Node<'tree>),
434 LeaveScope,
435 }
436 enum Binding<'tree> {
437 Unknown,
438 Declaration(CppSwitchDeclaration<'tree>),
439 }
440 let mut stack = vec![Step::Enter(function)];
441 let mut scopes: Vec<Option<Binding<'tree>>> = vec![None];
443 while let Some(step) = stack.pop() {
444 let node = match step {
445 Step::LeaveScope => {
446 scopes.pop().expect("entered scope");
447 continue;
448 }
449 Step::Enter(node) => node,
450 };
451 if node == condition {
452 return scopes.iter().rev().find_map(Option::as_ref).and_then(
453 |binding| match binding {
454 Binding::Unknown => None,
455 Binding::Declaration(declaration) => Some(*declaration),
456 },
457 );
458 }
459 if node != function
460 && matches!(
461 node.kind(),
462 "function_definition"
463 | "lambda_expression"
464 | "class_specifier"
465 | "struct_specifier"
466 | "union_specifier"
467 | "enum_specifier"
468 )
469 {
470 continue;
471 }
472 if cpp_switch_scope(node) {
473 scopes.push(None);
474 stack.push(Step::LeaveScope);
475 }
476 if node.kind() == "declaration" {
477 for declarator in node.children_by_field_name("declarator", &mut node.walk()) {
478 if declarator_name_node(declarator).and_then(|node| node_text(node, source))
479 == Some(name)
480 {
481 *scopes.last_mut().expect("function scope") = Some(Binding::Unknown);
483 }
484 }
485 }
486 if node.kind() == "identifier"
487 && node_text(node, source) == Some(name)
488 && let Some(owner) = cpp_binder_owner(node, |node| node.parent(), field_name_in_parent)
489 {
490 let declaration = match owner {
491 CppBinderOwner::Local(declarator) => {
492 let owner = std::iter::successors(declarator.parent(), |node| node.parent())
493 .find(|node| node.kind() == "declaration")
494 .expect("local binder declaration");
495 owner
496 .child_by_field_name("type")
497 .map(|type_node| CppSwitchDeclaration {
498 type_node,
499 declarator,
500 })
501 }
502 CppBinderOwner::Parameter => {
503 let owner = std::iter::successors(node.parent(), |node| node.parent())
504 .find(|node| {
505 matches!(
506 node.kind(),
507 "parameter_declaration"
508 | "optional_parameter_declaration"
509 | "variadic_parameter_declaration"
510 )
511 })
512 .expect("parameter binder declaration");
513 owner
514 .child_by_field_name("type")
515 .zip(owner.child_by_field_name("declarator"))
516 .map(|(type_node, declarator)| CppSwitchDeclaration {
517 type_node,
518 declarator,
519 })
520 }
521 CppBinderOwner::RangeFor(owner) => owner
522 .child_by_field_name("type")
523 .zip(owner.child_by_field_name("declarator"))
524 .map(|(type_node, declarator)| CppSwitchDeclaration {
525 type_node,
526 declarator,
527 }),
528 _ => None,
529 };
530 *scopes.last_mut().expect("function scope") =
532 Some(declaration.map_or(Binding::Unknown, Binding::Declaration));
533 }
534 for index in (0..node.named_child_count()).rev() {
535 stack.push(Step::Enter(node.named_child(index).expect("named child")));
536 }
537 }
538 None
539}
540
541fn cpp_switch_type_in_scope<'tree>(
544 scope: Node<'tree>,
545 path: &[String],
546 before: usize,
547 source: &str,
548) -> Option<Node<'tree>> {
549 use crate::declarations::cpp_structured_type_path;
550 let mut stack = vec![(scope, 0)];
551 let mut found = None;
552 let mut unresolved_import = None;
553 while let Some((node, component)) = stack.pop() {
554 if node.start_byte() >= before {
555 continue;
556 }
557 if node != scope {
558 if node.kind() == "using_declaration" {
559 let imports_namespace = (0..node.child_count())
560 .any(|index| node.child(index).expect("child").kind() == "namespace");
561 let imported = first_named_child(node)
562 .and_then(declarator_name_node)
563 .and_then(|name| node_text(name, source));
564 if imports_namespace || imported == Some(path[component].as_str()) {
565 unresolved_import = Some(node);
566 }
567 continue;
568 }
569 let named_type = matches!(
570 node.kind(),
571 "enum_specifier"
572 | "class_specifier"
573 | "struct_specifier"
574 | "union_specifier"
575 | "namespace_definition"
576 | "namespace_alias_definition"
577 | "alias_declaration"
578 | "type_definition"
579 | "type_parameter_declaration"
580 | "variadic_type_parameter_declaration"
581 );
582 if named_type {
583 let names = if node.kind() == "type_definition" {
584 node.children_by_field_name("declarator", &mut node.walk())
585 .filter_map(declarator_name_node)
586 .filter_map(|name| cpp_structured_type_path(name, source))
587 .collect::<Vec<_>>()
588 } else if matches!(
589 node.kind(),
590 "type_parameter_declaration" | "variadic_type_parameter_declaration"
591 ) {
592 first_named_child(node)
593 .and_then(|name| cpp_structured_type_path(name, source))
594 .into_iter()
595 .collect()
596 } else {
597 node.child_by_field_name("name")
598 .and_then(|name| {
599 let mut stack = vec![name];
602 let mut path = Vec::new();
603 while let Some(part) = stack.pop() {
604 if part.kind() == "nested_namespace_specifier" {
605 for index in (0..part.named_child_count()).rev() {
606 stack.push(
607 part.named_child(index).expect("namespace component"),
608 );
609 }
610 } else {
611 path.extend(cpp_structured_type_path(part, source)?);
612 }
613 }
614 Some(path)
615 })
616 .into_iter()
617 .collect()
618 };
619 for name in names {
620 if path[component..].starts_with(&name) {
621 let next = component + name.len();
622 if next < path.len()
623 && let Some(body) = node.child_by_field_name("body")
624 {
625 stack.push((body, next));
626 } else if found.is_none_or(|previous: Node<'_>| {
627 previous.start_byte() < node.start_byte()
628 }) {
629 found = Some(node);
632 }
633 }
634 }
635 continue;
636 }
637 if cpp_switch_scope(node)
638 || matches!(node.kind(), "function_definition" | "lambda_expression")
639 {
640 continue;
641 }
642 }
643 for index in (0..node.named_child_count()).rev() {
644 stack.push((node.named_child(index).expect("named child"), component));
645 }
646 }
647 found.or(unresolved_import)
648}
649
650fn cpp_switch_named_type<'tree>(type_node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
651 use crate::declarations::cpp_structured_type_path;
652 if type_node.kind() == "enum_specifier"
653 && type_node.child_by_field_name("body").is_some()
654 && type_node.child_by_field_name("name").is_some()
655 {
656 return Some(type_node);
657 }
658 if matches!(
659 type_node.kind(),
660 "class_specifier" | "struct_specifier" | "union_specifier"
661 ) && type_node.child_by_field_name("body").is_some()
662 {
663 return Some(type_node);
664 }
665 let name = if matches!(
666 type_node.kind(),
667 "enum_specifier" | "class_specifier" | "struct_specifier" | "union_specifier"
668 ) {
669 type_node.child_by_field_name("name")?
670 } else {
671 type_node
672 };
673 let mut stack = vec![name];
675 while let Some(node) = stack.pop() {
676 if !matches!(
677 node.kind(),
678 "type_identifier"
679 | "identifier"
680 | "namespace_identifier"
681 | "qualified_identifier"
682 | "scoped_identifier"
683 | "scoped_type_identifier"
684 ) {
685 return None;
686 }
687 stack.extend(named_children_iter(node).filter(|node| !node.is_extra()));
688 }
689 let path = cpp_structured_type_path(name, source)?;
690 let global = name.child(0).is_some_and(|child| child.kind() == "::");
691 for scope in std::iter::successors(type_node.parent(), |node| node.parent()) {
692 if !(cpp_switch_scope(scope)
693 || matches!(
694 scope.kind(),
695 "function_definition"
696 | "template_declaration"
697 | "namespace_definition"
698 | "class_specifier"
699 | "struct_specifier"
700 | "union_specifier"
701 | "translation_unit"
702 ))
703 || (global && scope.kind() != "translation_unit")
704 {
705 continue;
706 }
707 if let Some(declaration) =
708 cpp_switch_type_in_scope(scope, &path, type_node.start_byte(), source)
709 {
710 return Some(declaration);
711 }
712 if !global
713 && matches!(
714 scope.kind(),
715 "class_specifier" | "struct_specifier" | "union_specifier"
716 )
717 {
718 if named_children_iter(scope).any(|node| node.kind() == "base_class_clause")
721 || cpp_switch_type_in_scope(scope, &path, usize::MAX, source)
722 .is_some_and(|declaration| declaration.start_byte() >= type_node.start_byte())
723 {
724 return None;
725 }
726 }
727 if !global
730 && (scope.kind() == "function_definition"
731 && scoped_function_definition(scope).is_some()
732 || scope
733 .child_by_field_name("name")
734 .is_some_and(|name| name.kind() == "qualified_identifier"))
735 {
736 return None;
737 }
738 }
739 None
740}
741
742fn cpp_switch_pointer_depth(mut declarator: Node<'_>) -> Option<usize> {
743 let mut depth = 0;
744 loop {
745 match declarator.kind() {
746 "identifier" | "field_identifier" => return Some(depth),
747 "pointer_declarator" => {
748 depth += 1;
749 declarator = declarator.child_by_field_name("declarator")?;
750 }
751 "init_declarator" => declarator = declarator.child_by_field_name("declarator")?,
752 "reference_declarator" | "parenthesized_declarator" | "attributed_declarator" => {
753 declarator = first_named_child(declarator)?
754 }
755 _ => return None,
756 }
757 }
758}
759
760fn cpp_switch_enum_type<'tree>(switch: Node<'tree>, source: &str) -> Option<Node<'tree>> {
761 let mut operand = condition_expression(switch)?;
762 let mut fields = Vec::new();
763 while operand.kind() == "field_expression" {
764 fields.push(operand);
765 operand = operand.child_by_field_name("argument")?;
766 }
767 if operand.kind() != "identifier" {
768 return None;
769 }
770 let mut declaration =
771 cpp_switch_local_declaration(switch, node_text(operand, source)?, source)?;
772 for field in fields.into_iter().rev() {
773 let operator = field.child_by_field_name("operator")?;
774 let expected_depth = match operator.kind() {
775 "." => 0,
776 "->" => 1,
777 _ => return None,
778 };
779 if cpp_switch_pointer_depth(declaration.declarator)? != expected_depth {
780 return None;
781 }
782 let record = cpp_switch_named_type(declaration.type_node, source)?;
783 if !matches!(
784 record.kind(),
785 "class_specifier" | "struct_specifier" | "union_specifier"
786 ) {
787 return None;
788 }
789 let name = node_text(field.child_by_field_name("field")?, source)?;
790 let mut member = None;
791 for field_declaration in named_children_iter(record.child_by_field_name("body")?)
792 .filter(|node| node.kind() == "field_declaration")
793 {
794 for declarator in field_declaration
795 .children_by_field_name("declarator", &mut field_declaration.walk())
796 {
797 if declarator_name_node(declarator).and_then(|name| node_text(name, source))
798 == Some(name)
799 {
800 member = Some(CppSwitchDeclaration {
801 type_node: field_declaration.child_by_field_name("type")?,
802 declarator,
803 });
804 }
805 }
806 }
807 declaration = member?;
808 }
809 if cpp_switch_pointer_depth(declaration.declarator)? != 0 {
810 return None;
811 }
812 let enum_type = cpp_switch_named_type(declaration.type_node, source)?;
813 (enum_type.kind() == "enum_specifier").then_some(enum_type)
814}
815
816fn declarator_identifier<'tree>(node: Node<'tree>) -> Option<Node<'tree>> {
817 let mut current = node;
818 loop {
819 match current.kind() {
820 "identifier" | "field_identifier" | "statement_identifier" => return Some(current),
821 "init_declarator"
822 | "pointer_declarator"
823 | "reference_declarator"
824 | "array_declarator"
825 | "parenthesized_declarator"
826 | "attributed_declarator" => {
827 current = current.child_by_field_name("declarator")?;
828 }
829 "qualified_identifier" => current = current.child_by_field_name("name")?,
830 _ => return None,
831 }
832 }
833}
834
835fn loop_variable<'tree>(loop_node: Node<'tree>, source: &'tree str) -> Option<&'tree str> {
836 let declarator = if loop_node.kind() == "for_range_loop" {
837 loop_node.child_by_field_name("declarator")
838 } else {
839 let initializer = loop_node.child_by_field_name("initializer")?;
840 if initializer.kind() == "declaration" {
841 initializer
842 .child_by_field_name("declarator")
843 .and_then(declarator_identifier)
844 } else if initializer.kind() == "assignment_expression" {
845 initializer
846 .child_by_field_name("left")
847 .and_then(expression_name_node)
848 } else {
849 None
850 }
851 }?;
852 let name = declarator_identifier(declarator).or(Some(declarator))?;
853 node_text(name, source)
854}
855
856impl brokk_bifrost_core::analyzer::statement_facts::producer::StatementFactSyntax
857 for CppStructuralSpec
858{
859 fn supports_kind(
860 &self,
861 kind: brokk_bifrost_core::analyzer::statement_facts::StatementFactKind,
862 ) -> bool {
863 use brokk_bifrost_core::analyzer::statement_facts::StatementFactKind;
864 !matches!(
865 kind,
866 StatementFactKind::NestedIfStatements | StatementFactKind::MisleadingIndentation
867 )
868 }
869
870 fn roles<'tree>(&self, node: Node<'tree>, role: Role) -> Vec<Node<'tree>> {
871 let target = match role {
872 Role::Test if node.kind() == "case_statement" => node.child_by_field_name("value"),
873 Role::Test => condition_expression(node),
874 Role::Consequence => node.child_by_field_name("consequence"),
875 Role::Alternative => {
876 brokk_bifrost_core::analyzer::structural::body_relations::if_alternative_body(
877 node,
878 brokk_bifrost_core::analyzer::structural::body_relations::syntax_for(
879 Language::Cpp,
880 )
881 .expect("C++ body syntax"),
882 )
883 }
884 Role::Body => node.child_by_field_name("body"),
885 Role::Operator => node.child_by_field_name("operator"),
886 Role::Left => node.child_by_field_name("left"),
887 Role::Right => node.child_by_field_name("right"),
888 Role::Label => node.child_by_field_name("label"),
889 _ => None,
890 };
891 target.into_iter().collect()
892 }
893
894 fn is_code_child(&self, node: Node<'_>) -> bool {
895 !node.is_extra() && !node.kind().starts_with("preproc_") && node.kind() != "comment"
896 }
897
898 fn loop_variable(&self, node: Node<'_>, source: &str) -> Option<String> {
899 matches!(node.kind(), "for_statement" | "for_range_loop")
900 .then(|| loop_variable(node, source).map(str::to_owned))
901 .flatten()
902 }
903
904 fn is_counted_loop(&self, node: Node<'_>) -> bool {
905 node.kind() == "for_statement"
906 }
907
908 fn loop_initializers<'tree>(&self, node: Node<'tree>) -> Vec<Node<'tree>> {
909 node.child_by_field_name("initializer")
910 .into_iter()
911 .collect()
912 }
913
914 fn is_alloca(&self, node: Node<'_>, source: &str) -> bool {
915 node.kind() == "call_expression"
916 && node
917 .child_by_field_name("function")
918 .and_then(|function| node_text(function, source))
919 .is_some_and(|name| {
920 matches!(name, "alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
921 })
922 }
923
924 fn switch_enum<'tree>(&self, node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
925 cpp_switch_enum_type(node, source)
926 }
927
928 fn enumeration(
929 &self,
930 node: Node<'_>,
931 source: &str,
932 ) -> Option<(
933 brokk_bifrost_core::analyzer::statement_facts::EnumFact,
934 Vec<brokk_bifrost_core::analyzer::statement_facts::EnumeratorFact>,
935 )> {
936 use brokk_bifrost_core::analyzer::statement_facts::{EnumFact, EnumeratorFact};
937 if node.kind() != "enum_specifier" {
938 return None;
939 }
940 let range = node_range(node);
941 let name = node
942 .child_by_field_name("name")
943 .and_then(|name| node_text(name, source))
944 .map(str::to_owned);
945 let mut enumerators = Vec::new();
946 let mut stack = vec![node];
947 while let Some(current) = stack.pop() {
948 if current != node && current.kind() == "enum_specifier" {
949 continue;
950 }
951 if current.kind() == "enumerator"
952 && let Some(name) = current.child_by_field_name("name")
953 && let Some(name) = node_text(name, source)
954 {
955 enumerators.push(EnumeratorFact {
956 range: node_range(current),
957 enum_range: range,
958 name: name.to_owned(),
959 });
960 }
961 stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
962 }
963 Some((EnumFact { range, name }, enumerators))
964 }
965
966 fn name<'tree>(&self, node: Node<'tree>) -> Option<Node<'tree>> {
967 expression_name_node(node)
968 }
969}
970
971pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
973 brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
974 &CPP_STRUCTURAL_SPEC,
975 )
976 .expect("C++ statement syntax")
977 .classify(node, source, &Default::default())
978}
979
980pub(crate) fn capture_statement_fact_rows(
981 node: Node<'_>,
982 source: &str,
983 rows: &mut brokk_bifrost_core::analyzer::statement_facts::StatementFactRows,
984) {
985 brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
986 &CPP_STRUCTURAL_SPEC,
987 )
988 .expect("C++ statement syntax")
989 .capture(node, source, &Default::default(), rows);
990}
991
992#[cfg(test)]
993fn alloca_is_in_unbounded_loop(node: Node<'_>, source: &str) -> bool {
994 statement_fact_kind(node, source) == Some(StatementFactKind::AllocaInLoop)
995}
996
997#[cfg(test)]
998fn enum_switch_is_missing_case(node: Node<'_>, source: &str) -> bool {
999 let producer = brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
1000 &CPP_STRUCTURAL_SPEC,
1001 )
1002 .unwrap();
1003 let mut rows = brokk_bifrost_core::analyzer::statement_facts::StatementFactRows::default();
1004 producer.capture(node, source, &Default::default(), &mut rows);
1005 rows.switches.iter().any(|fact| fact.missing_enum_case)
1006}
1007
1008pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
1016 if node.kind() != "init_declarator" {
1017 return false;
1018 }
1019 let Some(identifier) = node.child_by_field_name("declarator") else {
1020 return false;
1021 };
1022 if identifier.kind() != "identifier" || identifier.is_missing() {
1023 return false;
1024 }
1025 let Some(previous) = node.prev_named_sibling() else {
1026 return false;
1027 };
1028 if previous.kind() != "ERROR"
1029 || previous.named_child_count() != 0
1030 || previous.child_count() != 1
1031 || previous.end_byte() != node.start_byte()
1032 {
1033 return false;
1034 }
1035 previous.child(0).is_some_and(|child| {
1036 child.kind() == "."
1037 && !child.is_named()
1038 && child.start_byte() == previous.start_byte()
1039 && child.end_byte() == previous.end_byte()
1040 })
1041}
1042
1043use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
1044
1045fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
1051 let mut current = namespace.child_by_field_name("name")?;
1052 while current.kind() == "nested_namespace_specifier" {
1053 current = current.named_child(current.named_child_count().checked_sub(1)?)?;
1054 }
1055 Some(current)
1056}
1057
1058pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
1059 let mut current = declarator;
1060 loop {
1061 match current.kind() {
1062 "identifier"
1063 | "field_identifier"
1064 | "namespace_identifier"
1065 | "type_identifier"
1066 | "destructor_name"
1067 | "operator_name"
1068 | "operator_cast"
1069 | "primitive_type" => return Some(current),
1070 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1071 "dependent_name" => current = first_named_child(current)?,
1072 "template_function" | "template_method" | "template_type" => {
1073 current = current.child_by_field_name("name")?;
1074 }
1075 "function_declarator"
1076 | "pointer_declarator"
1077 | "pointer_type_declarator"
1078 | "macro_modifier_declarator"
1079 | "variadic_declarator"
1080 | "array_declarator"
1081 | "init_declarator" => current = current.child_by_field_name("declarator")?,
1082 "attributed_declarator" => current = first_named_child(current)?,
1083 "reference_declarator" | "parenthesized_declarator" => {
1084 current = first_named_child(current)?;
1085 }
1086 _ => return None,
1087 }
1088 }
1089}
1090
1091pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1093 use CalleeSpineStep::{Field, FieldOrOnlyChild, Name, OnlyChild};
1094 match kind {
1095 "identifier"
1096 | "field_identifier"
1097 | "type_identifier"
1098 | "operator_name"
1099 | "operator_cast"
1100 | "destructor_name"
1101 | "literal_operator_name"
1102 | "primitive_type" => Some(Name),
1103 "qualified_identifier"
1104 | "scoped_identifier"
1105 | "scoped_type_identifier"
1106 | "template_function"
1107 | "template_method"
1108 | "template_type" => Some(Field("name")),
1109 "dependent_name" => Some(OnlyChild),
1110 "field_expression" => Some(Field("field")),
1111 "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1112 _ => None,
1113 }
1114}
1115
1116fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1117 let mut current = expression;
1118 loop {
1119 match current.kind() {
1120 "identifier"
1121 | "field_identifier"
1122 | "namespace_identifier"
1123 | "type_identifier"
1124 | "destructor_name"
1125 | "operator_name"
1126 | "primitive_type"
1127 | "this" => return Some(current),
1128 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1129 "dependent_name" => current = first_named_child(current)?,
1130 "template_function" | "template_method" | "template_type" => {
1131 current = current.child_by_field_name("name")?;
1132 }
1133 "field_expression" => current = current.child_by_field_name("field")?,
1134 "call_expression" => current = current.child_by_field_name("function")?,
1135 "new_expression" => current = current.child_by_field_name("type")?,
1136 "parenthesized_expression" => current = first_named_child(current)?,
1137 _ => return declarator_name_node(current),
1138 }
1139 }
1140}
1141
1142fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1143 if function.kind() != "qualified_identifier" {
1144 return;
1145 }
1146 if let Some(scope) = function.child_by_field_name("scope") {
1147 attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1148 }
1149}
1150
1151fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1152 loop {
1153 if node.kind() == "qualified_identifier" {
1154 return Some(node);
1155 }
1156 node = node
1157 .child_by_field_name("declarator")
1158 .or_else(|| node.child_by_field_name("name"))
1159 .or_else(|| first_named_child(node))?;
1160 }
1161}
1162
1163fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1164 node.child_by_field_name("declarator")
1165 .and_then(qualified_declarator_node)
1166 .and_then(|qualified| qualified.child_by_field_name("scope"))
1167}
1168
1169pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1170 node_text(scope, source) == node_text(name, source)
1171}
1172
1173fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1174 node.child_by_field_name("declarator")
1175 .and_then(qualified_declarator_node)
1176 .and_then(|qualified| {
1177 Some((
1178 expression_name_node(qualified.child_by_field_name("scope")?)?,
1179 expression_name_node(last_named_field_child(qualified, "name")?)?,
1180 ))
1181 })
1182 .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1183}
1184
1185fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1186 if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1187 return None;
1188 }
1189 unquoted_string_content_span(node)
1190}
1191
1192fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1200 let mut names = HashSet::default();
1201 let mut stack = vec![root];
1202 while let Some(node) = stack.pop() {
1203 for child in named_children_iter(node) {
1204 match child.kind() {
1205 "preproc_function_def" => {
1206 if let Some(name) = child.child_by_field_name("name") {
1207 names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1208 }
1209 }
1210 "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1211 | "preproc_elifdef" => stack.push(child),
1212 _ => {}
1213 }
1214 }
1215 }
1216 names
1217}
1218
1219fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1229 if !matches!(
1230 node.kind(),
1231 "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1232 ) {
1233 return None;
1234 }
1235
1236 let mut current = node;
1237 while let Some(parent) = current.parent() {
1238 if parent.kind() == "field_expression" {
1239 let field = parent.child_by_field_name("field")?;
1240 return expression_name_node(field)
1241 .filter(|name| name.id() == node.id())
1242 .map(|_| OccurrenceRole::MemberPosition);
1243 }
1244
1245 if matches!(
1248 parent.kind(),
1249 "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1250 ) {
1251 current = parent;
1252 } else {
1253 return None;
1254 }
1255 }
1256 None
1257}
1258
1259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1261enum CppBinderOwner<'tree> {
1262 Parameter,
1264 Local(Node<'tree>),
1266 RangeFor(Node<'tree>),
1268 StructuredBinding(Node<'tree>),
1270 InitCapture,
1272}
1273
1274const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1277 "pointer_declarator",
1278 "array_declarator",
1279 "reference_declarator",
1280 "parenthesized_declarator",
1281 "attributed_declarator",
1282 "variadic_declarator",
1283];
1284
1285fn cpp_binder_owner<'tree>(
1295 node: Node<'tree>,
1296 parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1297 mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1298) -> Option<CppBinderOwner<'tree>> {
1299 if node.kind() != "identifier" {
1300 return None;
1301 }
1302 let mut child = node;
1303 let mut current = parent(node)?;
1304 let mut indirect = false;
1305 loop {
1306 match current.kind() {
1309 kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1310 let field = field_name(current, child);
1311 if field.is_some() && field != Some("declarator") {
1312 return None;
1313 }
1314 indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1315 }
1316 "function_declarator" => {
1317 let field = field_name(current, child);
1318 if field != Some("declarator") || !indirect {
1319 return None;
1320 }
1321 }
1322 "init_declarator" => {
1323 let field = field_name(current, child);
1324 if field != Some("declarator") {
1325 return None;
1326 }
1327 }
1328 "declaration" => {
1329 let field = field_name(current, child);
1330 if field != Some("declarator") {
1331 return None;
1332 }
1333 let local = std::iter::successors(parent(current), |node| parent(*node))
1334 .take_while(|node| {
1335 !matches!(
1336 node.kind(),
1337 "translation_unit"
1338 | "field_declaration_list"
1339 | "declaration_list"
1340 | "namespace_definition"
1341 )
1342 })
1343 .any(|node| {
1344 matches!(
1345 node.kind(),
1346 "compound_statement" | "for_statement" | "condition_clause"
1347 )
1348 });
1349 let declarator = if child.kind() == "init_declarator" {
1352 child.child_by_field_name("declarator")?
1353 } else {
1354 child
1355 };
1356 return local.then_some(CppBinderOwner::Local(declarator));
1357 }
1358 "parameter_declaration"
1359 | "optional_parameter_declaration"
1360 | "variadic_parameter_declaration" => {
1361 let field = field_name(current, child);
1362 if field != Some("declarator") {
1363 return None;
1364 }
1365 let parameter_parent = parent(current)?;
1366 let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1367 if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1370 || parameter_parent.named_child(1) != Some(current)
1371 {
1372 return None;
1373 }
1374 parent(parameter_parent)?
1375 } else {
1376 parameter_parent
1377 };
1378 if list.kind() != "parameter_list" {
1379 return None;
1380 }
1381 let owner = parent(list)?;
1382 let binds = match owner.kind() {
1383 "catch_clause" => true,
1384 "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1385 .is_some_and(|lambda| {
1386 lambda.kind() == "lambda_expression"
1387 && lambda.child_by_field_name("declarator") == Some(owner)
1388 }),
1389 "function_declarator" => {
1390 let mut declarator = owner;
1391 loop {
1392 let Some(up) = parent(declarator) else {
1393 break false;
1394 };
1395 if up.kind() == "function_definition" {
1396 break field_name_in_parent(up, declarator) == Some("declarator");
1397 }
1398 if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1399 break false;
1400 }
1401 declarator = up;
1402 }
1403 }
1404 _ => false,
1405 };
1406 return binds.then_some(CppBinderOwner::Parameter);
1407 }
1408 "for_range_loop" => {
1409 let field = field_name(current, child);
1410 return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1411 }
1412 "structured_binding_declarator" => {
1413 return Some(CppBinderOwner::StructuredBinding(current));
1414 }
1415 "lambda_capture_initializer" => {
1416 let field = field_name(current, child);
1417 return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1418 }
1419 _ => return None,
1420 }
1421 child = current;
1422 current = parent(current)?;
1423 }
1424}
1425
1426fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1430 cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1431}
1432
1433fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1444 let binding = |kind, hoisting, activation| {
1445 Some(BindingActivation {
1446 kind,
1447 hoisting,
1448 activation,
1449 })
1450 };
1451 let after = |node: Node<'_>| Range {
1452 start_byte: node.end_byte(),
1453 end_byte: scope.end_byte,
1454 start_line: node.end_position().row + 1,
1455 end_line: scope.end_line,
1456 };
1457 match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1458 CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1459 binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1460 }
1461 CppBinderOwner::Local(declarator) => binding(
1462 BindingKind::Local,
1463 HoistingClass::SourceOrder,
1464 after(declarator),
1465 ),
1466 CppBinderOwner::RangeFor(loop_node) => binding(
1467 BindingKind::LoopVariable,
1468 HoistingClass::DeclaredHead,
1469 node_range(loop_node.child_by_field_name("body")?),
1470 ),
1471 CppBinderOwner::StructuredBinding(declarator) => binding(
1472 BindingKind::Local,
1473 HoistingClass::SourceOrder,
1474 after(declarator),
1475 ),
1476 }
1477}
1478
1479impl StructuralSpec for CppStructuralSpec {
1480 fn language(&self) -> Language {
1481 Language::Cpp
1482 }
1483
1484 fn supports_boolean_literal_value(&self) -> bool {
1485 true
1486 }
1487
1488 fn supports_string_literal_value(&self) -> bool {
1489 true
1490 }
1491
1492 fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1493 matches!(node.kind(), "string_literal" | "raw_string_literal")
1494 && cpp_string_literal_value(node, source).is_none()
1495 }
1496
1497 fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1498 cpp_string_literal_value(node, source)
1499 }
1500
1501 fn extra_emitted_kinds(&self) -> &'static [NormalizedKind] {
1502 &[NormalizedKind::Constructor, NormalizedKind::Method]
1504 }
1505
1506 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1507 CPP_KIND_TABLE
1508 }
1509
1510 fn statement_fact_syntax(
1511 &self,
1512 ) -> Option<&dyn brokk_bifrost_core::analyzer::statement_facts::producer::StatementFactSyntax>
1513 {
1514 Some(self)
1515 }
1516
1517 fn generator_construct(
1518 &self,
1519 node: Node<'_>,
1520 _kind: NormalizedKind,
1521 source: &str,
1522 ) -> Option<&'static str> {
1523 statement_fact_kind(node, source).map(StatementFactKind::construct)
1524 }
1525
1526 fn refine_kind(
1527 &self,
1528 node: Node<'_>,
1529 kind: NormalizedKind,
1530 enclosing: Option<NormalizedKind>,
1531 source: &str,
1532 _context: &CallSiteContext,
1533 ) -> NormalizedKind {
1534 if kind == NormalizedKind::Function
1535 && (enclosing == Some(NormalizedKind::Class)
1536 || scoped_function_definition(node).is_some())
1537 {
1538 if is_constructor_definition(node, source) {
1539 NormalizedKind::Constructor
1540 } else {
1541 NormalizedKind::Method
1542 }
1543 } else {
1544 kind
1545 }
1546 }
1547
1548 fn supports_kind(&self, kind: NormalizedKind) -> bool {
1549 matches!(
1550 kind,
1551 NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1552 ) || self
1553 .kind_table()
1554 .iter()
1555 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1556 }
1557
1558 fn supports_role(&self, role: Role) -> bool {
1559 !matches!(
1560 role,
1561 Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1562 )
1563 }
1564
1565 fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1566 CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1567 }
1568
1569 fn call_site_facts(
1582 &self,
1583 node: Node<'_>,
1584 source: &str,
1585 context: &CallSiteContext,
1586 ) -> Option<CallSiteFacts> {
1587 if node.kind() == "new_expression" {
1588 return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1589 }
1590 let callee = node.child_by_field_name("function")?;
1591 (callee.kind() == "identifier"
1592 && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1593 .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1594 }
1595
1596 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1597 static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1598 .supported(OccurrenceRole::MemberPosition)
1599 .supported(OccurrenceRole::ValueReference)
1600 .supported(OccurrenceRole::Binder);
1601 &SUPPORT
1602 }
1603
1604 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1605 static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1609 .supported(EnvironmentAxis::Scopes)
1610 .supported(EnvironmentAxis::BindingIntervals)
1611 .supported(EnvironmentAxis::CallableApplicability);
1612 &SUPPORT
1613 }
1614
1615 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1618 if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1619 ScopeFormation::BindingScope
1620 } else {
1621 default_scope_formation(kind)
1622 }
1623 }
1624
1625 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1626 cpp_binding_activation(binder, scope)
1627 }
1628
1629 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1630 &CPP_MATERIALIZATION_SUPPORT
1631 }
1632
1633 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1634 &INVERSE_REFERENCE_EDGE_SUPPORT
1635 }
1636
1637 fn identity_route_support(&self) -> &IdentityRouteSupport {
1638 static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1649 .supported_axis(IdentityAxis::CanonicalIdentity)
1650 .supported_axis(IdentityAxis::PhysicalGrouping)
1651 .supported_relation(RouteHopKind::NestedOwner)
1652 .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1653 &SUPPORT
1654 }
1655
1656 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1657 if let Some(role) = cpp_member_position(node) {
1658 sink.occurrence_role(node, role);
1659 } else if cpp_is_binder(node, sink.parents()) {
1660 sink.occurrence_role(node, OccurrenceRole::Binder);
1661 }
1662
1663 match kind {
1664 NormalizedKind::Call => {
1665 let function_field = if node.kind() == "new_expression" {
1666 "type"
1667 } else {
1668 "function"
1669 };
1670 if let Some(function) = node.child_by_field_name(function_field) {
1671 attach_terminal_callee(
1672 sink,
1673 function,
1674 terminal_callee_leaf(function, callee_spine_step),
1675 );
1676 if node.kind() == "call_expression"
1685 && !matches!(function.kind(), "field_expression" | "call_expression")
1686 && let Some(callee) = expression_name_node(function)
1687 && self.kind_table().iter().any(|(node_kind, kind)| {
1688 *node_kind == callee.kind() && self.should_extract(callee, *kind)
1689 })
1690 {
1691 sink.occurrence_role(callee, OccurrenceRole::ValueReference);
1692 }
1693 if function.kind() == "field_expression"
1694 && let Some(argument) = function.child_by_field_name("argument")
1695 {
1696 attach_role_with_derived_name(
1697 sink,
1698 Role::Receiver,
1699 argument,
1700 expression_name_node,
1701 );
1702 }
1703 attach_qualified_scope_receiver(sink, function);
1704 }
1705 if let Some(arguments) = node.child_by_field_name("arguments") {
1706 attach_positional_argument_roles(sink, arguments, expression_name_node);
1707 }
1708 }
1709 NormalizedKind::FieldAccess => {
1710 if let Some(field) = node.child_by_field_name("field") {
1711 attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
1712 if let Some(name) = expression_name_node(field) {
1713 sink.set_name(name);
1714 }
1715 }
1716 if let Some(argument) = node.child_by_field_name("argument") {
1717 attach_role_with_derived_name(
1718 sink,
1719 Role::Object,
1720 argument,
1721 expression_name_node,
1722 );
1723 }
1724 }
1725 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
1726 if let Some(name) = node
1727 .child_by_field_name("declarator")
1728 .and_then(declarator_name_node)
1729 {
1730 sink.set_name(name);
1731 }
1732 }
1733 NormalizedKind::Module => {
1734 if let Some(name) = namespace_name_node(node) {
1735 sink.set_name(name);
1736 }
1737 }
1738 NormalizedKind::Class | NormalizedKind::Declaration => {
1739 if let Some(name) = node
1740 .child_by_field_name("name")
1741 .and_then(declarator_name_node)
1742 .or_else(|| node.child_by_field_name("name"))
1743 {
1744 sink.set_name(name);
1745 }
1746 }
1747 NormalizedKind::Assignment => match node.kind() {
1748 "init_declarator" => {
1749 if let Some(declarator) = node.child_by_field_name("declarator") {
1750 attach_role_with_derived_name(
1751 sink,
1752 Role::Left,
1753 declarator,
1754 declarator_name_node,
1755 );
1756 if let Some(name) = declarator_name_node(declarator) {
1757 sink.set_name(name);
1758 }
1759 }
1760 if let Some(value) = node.child_by_field_name("value") {
1761 attach_role_with_derived_name(
1762 sink,
1763 Role::Right,
1764 value,
1765 expression_name_node,
1766 );
1767 }
1768 }
1769 "assignment_expression" => {
1770 if let Some(left) = node.child_by_field_name("left") {
1771 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1772 }
1773 if let Some(right) = node.child_by_field_name("right") {
1774 attach_role_with_derived_name(
1775 sink,
1776 Role::Right,
1777 right,
1778 expression_name_node,
1779 );
1780 }
1781 }
1782 _ => {}
1783 },
1784 NormalizedKind::Import => {
1785 if let Some(path) = node.child_by_field_name("path") {
1786 if let Some(name) = unquoted_include_span(path) {
1787 sink.role_named_span(Role::Module, path, name);
1788 } else {
1789 attach_role_with_derived_name(
1790 sink,
1791 Role::Module,
1792 path,
1793 expression_name_node,
1794 );
1795 }
1796 }
1797 }
1798 NormalizedKind::Identifier => match expression_name_node(node) {
1799 Some(name) => sink.set_name(name),
1800 None => sink.set_name(node),
1801 },
1802 _ => {}
1803 }
1804 }
1805}
1806
1807#[cfg(test)]
1808mod tests {
1809 use super::CPP_STRUCTURAL_SPEC;
1810 use super::cpp_binder_owner;
1811 use super::cpp_member_position;
1812 use super::field_name_in_parent;
1813 use super::node_range;
1814 use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
1815 use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
1816 use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
1817 use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
1818 use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
1819 use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
1820 use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
1821 use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
1822 use brokk_bifrost_core::hash::HashMap;
1823 use brokk_bifrost_test_macros::hardwon;
1824 use tree_sitter::Parser;
1825
1826 #[test]
1827 #[hardwon(
1828 "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: unrelated comparisons suppress alloca-in-loop"
1829 )]
1830 fn alloca_bound_must_constrain_loop_variable() {
1831 for (condition, expected) in [
1832 ("i < n && enabled < 1", true),
1833 ("i < n || i < 1", true),
1834 ("i < 1", false),
1835 ("1 > i && enabled", false),
1836 ] {
1837 let source = format!(
1838 "void f(int n, int enabled) {{ for (int i=0; {condition}; ++i) {{ alloca(16); }} }}"
1839 );
1840 let mut parser = Parser::new();
1841 parser
1842 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1843 .unwrap();
1844 let tree = parser.parse(&source, None).unwrap();
1845 let mut stack = vec![tree.root_node()];
1846 let mut actual = None;
1847 while let Some(node) = stack.pop() {
1848 if node.kind() == "call_expression" {
1849 actual = Some(super::alloca_is_in_unbounded_loop(node, &source));
1850 }
1851 stack.extend(super::named_children_iter(node));
1852 }
1853 assert_eq!(actual, Some(expected), "{source}");
1854 }
1855 }
1856
1857 #[test]
1858 #[hardwon(
1859 "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: integer switches borrow unrelated enum cases"
1860 )]
1861 fn integer_switch_is_not_an_enum_switch() {
1862 let source = "enum E { A, B }; void f(int value) { switch(value) { case A: break; } }";
1863 let mut parser = Parser::new();
1864 parser
1865 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1866 .unwrap();
1867 let tree = parser.parse(source, None).unwrap();
1868 let mut stack = vec![tree.root_node()];
1869 let mut checked = false;
1870 while let Some(node) = stack.pop() {
1871 if node.kind() == "switch_statement" {
1872 checked = true;
1873 assert!(!super::enum_switch_is_missing_case(node, source));
1874 }
1875 stack.extend(super::named_children_iter(node));
1876 }
1877 assert!(checked);
1878 }
1879
1880 #[test]
1881 #[hardwon(
1882 "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: enum coverage needs scoped local declaration evidence"
1883 )]
1884 fn enum_switch_uses_local_type_evidence() {
1885 for (source, expected) in [
1886 (
1887 "enum E { A,B }; void f(E e) { switch(e) {case A:break;} }",
1888 vec![true],
1889 ),
1890 (
1891 "enum E { A,B }; void f() { E e=A; switch(e) {case A:break;} }",
1892 vec![true],
1893 ),
1894 (
1895 "enum class E { A,B }; void f(E e) { switch(e) {case E::A:break;} }",
1896 vec![true],
1897 ),
1898 (
1899 "namespace ns { enum class E { A,B }; } void f(ns::E e) { switch(e) {case ns::E::A:break;} }",
1900 vec![true],
1901 ),
1902 (
1903 "enum E { A,B }; struct S { E state; }; void f(S s) { switch(s.state) {case A:break;} }",
1904 vec![true],
1905 ),
1906 (
1907 "enum E { A,B }; struct S { E state; }; void f() { S s; switch(s.state) {case A:break;} }",
1908 vec![true],
1909 ),
1910 (
1911 "enum E { A,B }; void f(E e) { { int e=0; switch(e) {case A:break;} } switch(e) {case A:break;} }",
1912 vec![false, true],
1913 ),
1914 (
1915 "enum E { A,B }; void f(E e) { { int e=0; } switch(e) {case A:break;} }",
1916 vec![true],
1917 ),
1918 (
1919 "enum E { A,B }; void f() { switch(e) {case A:break;} E e=A; }",
1920 vec![false],
1921 ),
1922 (
1923 "enum E { A,B }; using Alias=E; void f(Alias e) { switch(e) {case A:break;} }",
1924 vec![false],
1925 ),
1926 (
1927 "enum E { A,B }; void f(E e) { switch(e==A) {case A:break;} }",
1928 vec![false],
1929 ),
1930 (
1931 "enum E { A,B }; E e; void f() { switch(e) {case A:break;} }",
1932 vec![false],
1933 ),
1934 (
1935 "enum E { A,B }; void f(E &e) { switch(e) {case A:break;} }",
1936 vec![true],
1937 ),
1938 (
1939 "enum E { A,B }; void f(E *e) { switch(e) {case A:break;} }",
1940 vec![false],
1941 ),
1942 (
1943 "enum E { A,B }; struct S { E state; }; void f(S *s) { switch(s->state) {case A:break;} }",
1944 vec![true],
1945 ),
1946 (
1947 "enum E { A,B }; struct S { int state; }; void f(S s) { switch(s.state) {case A:break;} }",
1948 vec![false],
1949 ),
1950 (
1951 "enum E { A,B }; struct Inner { E state; }; struct Outer { Inner inner; }; void f(Outer s) { switch(s.inner.state) {case A:break;} }",
1952 vec![true],
1953 ),
1954 (
1955 "void f() { enum class E { A,B }; E e=E::A; switch(e) {case E::A:break;} }",
1956 vec![true],
1957 ),
1958 (
1959 "struct S { enum class E { A,B }; E state; }; void f(S s) { switch(s.state) {case S::E::A:break;} }",
1960 vec![true],
1961 ),
1962 (
1963 "namespace n { namespace m { enum class E { A,B }; } } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1964 vec![true],
1965 ),
1966 (
1967 "enum E { A,B }; void f() { using E=int; E e=0; switch(e) {case A:break;} }",
1968 vec![false],
1969 ),
1970 (
1971 "enum E { A,B }; void f(E e) { { int e(); switch(e) {case A:break;} } }",
1972 vec![false],
1973 ),
1974 (
1975 "enum E { A,B }; void f(E e) { auto g=[e]() { switch(e) {case A:break;} }; }",
1976 vec![false],
1977 ),
1978 (
1979 "enum E { A,B }; void f() { auto g=[](E e) { switch(e) {case A:break;} }; }",
1980 vec![true],
1981 ),
1982 (
1983 "namespace n::m { enum class E { A,B }; } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1984 vec![true],
1985 ),
1986 (
1987 "void f() { enum E { A,B } e=A; switch(e) {case A:break;} }",
1988 vec![true],
1989 ),
1990 (
1991 "enum E { A,B }; void f(E (&values)[2]) { for (E e : values) { switch(e) {case A:break;} } }",
1992 vec![true],
1993 ),
1994 (
1995 "enum E { A,B }; namespace n { enum E { A,C }; } void f() { using n::E; E e; switch(e) {case A:break;} }",
1996 vec![false],
1997 ),
1998 (
1999 "enum E { A,B }; namespace n { enum E { A }; void f(E); } void n::f(E e) { switch(e) {case n::A:break;} }",
2000 vec![false],
2001 ),
2002 (
2003 "enum E { A,B }; void f(E e) { switch(int e=0; e) {case A:break;} }",
2004 vec![false],
2005 ),
2006 (
2007 "enum E { A,B }; void f() { switch(E e=A; e) {case A:break;} }",
2008 vec![true],
2009 ),
2010 (
2011 "enum E { A,B }; template<typename E> void f(E e) { switch(e) {case A:break;} }",
2012 vec![false],
2013 ),
2014 (
2015 "enum E { A,B }; template<typename... T> void f(T... e) { switch(e) {case A:break;} }",
2016 vec![false],
2017 ),
2018 (
2019 "namespace n { enum E { A,B }; } namespace m { enum E { A }; } namespace r { namespace n=m; void f(n::E e) { switch(e) {case m::A:break;} } }",
2020 vec![false],
2021 ),
2022 (
2023 "struct R { enum E { A,B }; }; struct S { enum E { A }; }; void f() { using R=S; R::E e; switch(e) {case S::A:break;} }",
2024 vec![false],
2025 ),
2026 (
2027 "enum E { A,B }; struct Base { enum E { A }; }; struct S: Base { void f(E e) { switch(e) {case A:break;} } };",
2028 vec![false],
2029 ),
2030 (
2031 "enum E { A,B }; struct S { void f() { E e; switch(e) {case A:break;} } enum E { A }; };",
2032 vec![false],
2033 ),
2034 ] {
2035 let mut parser = Parser::new();
2036 parser
2037 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2038 .unwrap();
2039 let tree = parser.parse(source, None).unwrap();
2040 assert!(
2041 !tree.root_node().has_error(),
2042 "{source}: {}",
2043 tree.root_node().to_sexp()
2044 );
2045 let mut stack = vec![tree.root_node()];
2046 let mut results = Vec::new();
2047 while let Some(node) = stack.pop() {
2048 if node.kind() == "switch_statement" {
2049 results.push((
2050 node.start_byte(),
2051 super::enum_switch_is_missing_case(node, source),
2052 ));
2053 }
2054 stack.extend(super::named_children_iter(node));
2055 }
2056 results.sort_unstable_by_key(|(start, _)| *start);
2057 assert_eq!(
2058 results.into_iter().map(|(_, v)| v).collect::<Vec<_>>(),
2059 expected,
2060 "{source}"
2061 );
2062 }
2063 }
2064
2065 fn collect_cpp_structural_facts(source: &str) {
2066 collect_structural_rows(source);
2067 }
2068
2069 fn collect_structural_rows(source: &str) -> StructuralFactRows {
2070 let mut parser = Parser::new();
2071 parser
2072 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2073 .expect("C++ grammar");
2074 let tree = parser.parse(source, None).expect("C++ parse");
2075 let root = tree.root_node();
2076 let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
2077 let compiled = CompiledKinds::compile(
2078 &tree_sitter_cpp::LANGUAGE.into(),
2079 super::CPP_STRUCTURAL_SPEC.kind_table(),
2080 );
2081 let mut structural = StructuralFactCollector::new(
2082 &super::CPP_STRUCTURAL_SPEC,
2083 source,
2084 &context,
2085 ParentIndex::new(root),
2086 usize::MAX,
2087 None,
2088 );
2089 let mut source_facts = PrimarySourceFactCollector::new(source);
2090 let mut pending = vec![(root, None)];
2091 while let Some((node, parent)) = pending.pop() {
2092 let mut child_parent = parent;
2093 if node.is_named()
2094 && let Some(raw_kind) = compiled.kind_of(&node)
2095 && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
2096 {
2097 let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
2098 node,
2099 raw_kind,
2100 parent.map(|id| structural.normalized_kind(id)),
2101 source,
2102 &context,
2103 );
2104 let id = structural
2105 .enter(node, kind, parent, &mut source_facts)
2106 .expect("unbounded C++ structural collector");
2107 let mut sink = structural.role_sink(&mut source_facts);
2108 super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2109 structural
2110 .accept_roles(id, sink.into_parts())
2111 .expect("unbounded C++ structural roles");
2112 child_parent = Some(id);
2113 }
2114 for index in (0..node.named_child_count()).rev() {
2115 if let Some(child) = node.named_child(index) {
2116 pending.push((child, child_parent));
2117 }
2118 }
2119 }
2120 structural
2121 .finish(root)
2122 .expect("C++ structural collection finishes")
2123 }
2124
2125 fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
2126 let mut parser = Parser::new();
2127 parser
2128 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2129 .expect("C++ grammar");
2130 let tree = parser.parse(source, None).expect("C++ parse");
2131 assert!(
2132 !tree.root_node().has_error(),
2133 "{}",
2134 tree.root_node().to_sexp()
2135 );
2136 let mut found = Vec::new();
2137 let mut pending = vec![tree.root_node()];
2138 while let Some(node) = pending.pop() {
2139 if let Some(role) = cpp_member_position(node) {
2140 found.push((
2141 node.start_byte(),
2142 &source[node.start_byte()..node.end_byte()],
2143 role,
2144 ));
2145 }
2146 for index in (0..node.named_child_count()).rev() {
2147 if let Some(child) = node.named_child(index) {
2148 pending.push(child);
2149 }
2150 }
2151 }
2152 found
2153 }
2154
2155 #[test]
2156 #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
2157 fn binder_classification_scans_large_initializer_lists_linearly() {
2158 use std::fmt::Write as _;
2159
2160 let mut source = String::from("int values[] = {");
2161 for index in 0..4096 {
2162 write!(source, "item_{index},").expect("append generated initializer item");
2163 }
2164 source.push_str("};\n");
2165
2166 let mut parser = Parser::new();
2167 parser
2168 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2169 .expect("C++ grammar");
2170 let tree = parser.parse(&source, None).expect("C++ parse");
2171 assert!(
2172 !tree.root_node().has_error(),
2173 "{}",
2174 tree.root_node().to_sexp()
2175 );
2176 let parents = ParentIndex::new(tree.root_node());
2177 let mut pending = vec![tree.root_node()];
2178 let mut identifiers = 0usize;
2179 let mut sibling_steps = 0usize;
2180 while let Some(node) = pending.pop() {
2181 if node.kind() == "identifier" {
2182 identifiers += 1;
2183 let _ = cpp_binder_owner(
2184 node,
2185 |node| parents.parent(node),
2186 |parent, child| {
2187 let mut cursor = parent.walk();
2188 if !cursor.goto_first_child() {
2189 return None;
2190 }
2191 loop {
2192 sibling_steps += 1;
2193 if cursor.node() == child {
2194 return cursor.field_name();
2195 }
2196 if !cursor.goto_next_sibling() {
2197 return None;
2198 }
2199 }
2200 },
2201 );
2202 }
2203 let mut cursor = node.walk();
2204 let children = node.children(&mut cursor).collect::<Vec<_>>();
2205 pending.extend(children.into_iter().rev());
2206 }
2207
2208 assert!(
2209 identifiers >= 4096,
2210 "parsed {identifiers} initializer identifiers"
2211 );
2212 assert!(
2213 sibling_steps <= identifiers * 4,
2214 "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
2215 );
2216 }
2217
2218 #[test]
2219 #[brokk_bifrost_test_macros::hardwon(
2220 "#4407: ABI 15 lambda declarators lost parameter binder identity"
2221 )]
2222 fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
2223 let source = concat!(
2224 "int enclosing(int outer) {\n",
2225 " return [outer](int lambda) {\n",
2226 " return [lambda](int nested) {\n",
2227 " return outer + lambda + nested;\n",
2228 " }(lambda);\n",
2229 " }(outer);\n",
2230 "}\n",
2231 );
2232 let mut parser = Parser::new();
2233 parser
2234 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2235 .expect("C++ grammar");
2236 let tree = parser.parse(source, None).expect("C++ parse");
2237 assert!(
2238 !tree.root_node().has_error(),
2239 "{}",
2240 tree.root_node().to_sexp()
2241 );
2242
2243 let identifier_at = |start_byte| {
2244 let mut pending = vec![tree.root_node()];
2245 while let Some(node) = pending.pop() {
2246 if node.kind() == "identifier" && node.start_byte() == start_byte {
2247 return Some(node);
2248 }
2249 for index in (0..node.named_child_count()).rev() {
2250 if let Some(child) = node.named_child(index) {
2251 pending.push(child);
2252 }
2253 }
2254 }
2255 None
2256 };
2257 let outer_parameter =
2258 identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
2259 .expect("outer parameter");
2260 let lambda_parameter =
2261 identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
2262 .expect("lambda parameter");
2263 let nested_parameter =
2264 identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
2265 .expect("nested parameter");
2266
2267 for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
2268 assert_eq!(
2269 cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
2270 Some(super::CppBinderOwner::Parameter)
2271 );
2272 assert_eq!(
2273 super::cpp_binding_activation(parameter, node_range(tree.root_node()))
2274 .map(|activation| activation.kind),
2275 Some(BindingKind::Parameter)
2276 );
2277 }
2278
2279 for (start, name) in [
2280 (
2281 source
2282 .find("return outer + lambda")
2283 .expect("outer reference")
2284 + "return ".len(),
2285 "outer",
2286 ),
2287 (
2288 source
2289 .find("outer + lambda + nested")
2290 .expect("lambda reference")
2291 + "outer + ".len(),
2292 "lambda",
2293 ),
2294 (
2295 source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
2296 "nested",
2297 ),
2298 ] {
2299 let reference = identifier_at(start).expect("body reference identifier");
2300 assert_eq!(
2301 cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
2302 None,
2303 "body reference {name:?} was classified as a binder"
2304 );
2305 }
2306 }
2307
2308 #[test]
2309 #[brokk_bifrost_test_macros::hardwon(
2310 "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
2311 )]
2312 fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
2313 let source = concat!(
2314 "struct Visitor {\n",
2315 " int visit(this Visitor const& self, int ordinary) {\n",
2316 " return self.value + ordinary;\n",
2317 " }\n",
2318 "};\n",
2319 "using Callback = void (*)(int nested);\n",
2320 );
2321 let mut parser = Parser::new();
2322 parser
2323 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2324 .expect("C++ grammar");
2325 let tree = parser.parse(source, None).expect("C++ parse");
2326 assert!(
2327 !tree.root_node().has_error(),
2328 "{}",
2329 tree.root_node().to_sexp()
2330 );
2331
2332 let identifier_at = |start: usize, len: usize| {
2333 tree.root_node()
2334 .descendant_for_byte_range(start, start + len)
2335 .filter(|node| node.kind() == "identifier")
2336 .expect("identifier at source span")
2337 };
2338 let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2339 let receiver_body = identifier_at(
2340 source.find("return self").expect("receiver body reference") + "return ".len(),
2341 4,
2342 );
2343 let function_type_parameter =
2344 identifier_at(source.find("nested").expect("function type"), 6);
2345 let parents = ParentIndex::new(tree.root_node());
2346
2347 assert_eq!(receiver.kind(), "identifier");
2348 assert_eq!(
2349 cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2350 Some(super::CppBinderOwner::Parameter)
2351 );
2352 assert!(super::cpp_is_binder(receiver, &parents));
2353 assert_eq!(
2354 super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2355 .map(|activation| activation.kind),
2356 Some(BindingKind::Parameter)
2357 );
2358 assert_eq!(
2359 cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2360 None,
2361 "the receiver read in the body is not another binder"
2362 );
2363 assert!(!super::cpp_is_binder(receiver_body, &parents));
2364 assert_eq!(
2365 cpp_binder_owner(
2366 function_type_parameter,
2367 |node| node.parent(),
2368 field_name_in_parent
2369 ),
2370 None,
2371 "a function-type parameter remains outside callable binding ownership"
2372 );
2373 }
2374
2375 #[test]
2376 fn attributed_declarator_resolves_its_declared_name() {
2377 let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2378 let mut parser = Parser::new();
2379 parser
2380 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2381 .expect("C++ grammar");
2382 let tree = parser.parse(source, None).expect("C++ parse");
2383 assert!(
2384 !tree.root_node().has_error(),
2385 "{}",
2386 tree.root_node().to_sexp()
2387 );
2388
2389 let mut pending = vec![tree.root_node()];
2390 let mut attributed = None;
2391 let mut conversion = None;
2392 while let Some(node) = pending.pop() {
2393 match node.kind() {
2394 "attributed_declarator" => attributed = Some(node),
2395 "operator_cast" => conversion = Some(node),
2396 _ => {}
2397 }
2398 for index in (0..node.named_child_count()).rev() {
2399 if let Some(child) = node.named_child(index) {
2400 pending.push(child);
2401 }
2402 }
2403 }
2404 let attributed = attributed.expect("attributed declarator");
2405 let name = super::declarator_name_node(attributed).expect("declarator name");
2406
2407 assert_eq!(&source[name.byte_range()], "x");
2408 let conversion = conversion.expect("conversion operator");
2409 assert_eq!(
2410 super::declarator_name_node(conversion).map(|node| node.kind()),
2411 Some("operator_cast")
2412 );
2413 }
2414
2415 #[test]
2416 fn cpp_member_position_is_limited_to_member_access_and_calls() {
2417 let source = concat!(
2418 "struct Widget {\n",
2419 " int value;\n",
2420 " int method(int label) {\n",
2421 " return this->value + label.value + label.method()\n",
2422 " + label.template convert<int>() + label.ns::member;\n",
2423 " }\n",
2424 "};\n",
2425 "int build(Widget widget) {\n",
2426 " Widget result{.value = widget.value};\n",
2427 " return widget.method(result.value);\n",
2428 "}\n",
2429 );
2430 let found = member_occurrences(source);
2431 let member_texts = found
2432 .iter()
2433 .map(|(_, text, role)| (*text, *role))
2434 .collect::<Vec<_>>();
2435
2436 assert_eq!(
2437 member_texts,
2438 vec![
2439 ("value", OccurrenceRole::MemberPosition),
2440 ("value", OccurrenceRole::MemberPosition),
2441 ("method", OccurrenceRole::MemberPosition),
2442 ("convert", OccurrenceRole::MemberPosition),
2443 ("member", OccurrenceRole::MemberPosition),
2444 ("value", OccurrenceRole::MemberPosition),
2445 ("method", OccurrenceRole::MemberPosition),
2446 ("value", OccurrenceRole::MemberPosition),
2447 ]
2448 );
2449
2450 let at = |needle: &str| source.find(needle).expect("fixture token");
2451 for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2452 let receiver_start = at(receiver);
2453 assert!(
2454 found.iter().all(|(offset, _, _)| *offset != receiver_start),
2455 "receiver was classified: {receiver:?}"
2456 );
2457 }
2458 let non_members = [
2459 at("Widget {"),
2460 at("int value") + "int ".len(),
2461 at("int method") + "int ".len(),
2462 at(".value =") + 1,
2463 ];
2464 for unrelated_start in non_members {
2465 assert!(
2466 found
2467 .iter()
2468 .all(|(offset, _, _)| *offset != unrelated_start),
2469 "non-member identifier at byte {unrelated_start} was classified"
2470 );
2471 }
2472 }
2473
2474 #[test]
2475 #[brokk_bifrost_test_macros::hardwon(
2476 "#3466: C and C++ free-function calls published no resolver occurrence"
2477 )]
2478 fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2479 let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2480 assert!(support.is_supported(OccurrenceRole::MemberPosition));
2481 assert!(support.is_supported(OccurrenceRole::ValueReference));
2482 for role in [
2483 OccurrenceRole::ReceiverPosition,
2484 OccurrenceRole::LabelOrKey,
2485 OccurrenceRole::DeclarationName,
2486 ] {
2487 assert!(
2488 !support.is_supported(role),
2489 "unexpected C++ support for {role:?}"
2490 );
2491 }
2492 }
2493
2494 #[test]
2495 fn cpp_string_literals_publish_decoded_values() {
2496 let source = r#"
2497const char *ordinary = "http://example.test/a\n";
2498const char *raw = R"(https://example.test/path)";
2499const char *wide = L"wide";
2500const char character = 'x';
2501const char *mixed = R"(\u0041)" "\u0042";
2502"#;
2503 let rows = collect_structural_rows(source);
2504 let values = rows
2505 .nodes()
2506 .iter()
2507 .filter(|node| node.kind == NormalizedKind::StringLiteral)
2508 .map(|node| node.string_value.clone())
2509 .collect::<Vec<_>>();
2510
2511 assert_eq!(
2512 values,
2513 vec![
2514 Some("http://example.test/a\n".to_owned()),
2515 Some("https://example.test/path".to_owned()),
2516 None,
2517 None,
2518 Some(r"\u0041".to_owned()),
2519 Some("B".to_owned()),
2520 ]
2521 );
2522 }
2523
2524 #[test]
2525 fn cpp_numeric_string_escapes_follow_scalar_and_ascii_boundaries() {
2526 let mut cases = vec![
2529 (
2530 r"\101\x42\u0043\U00000044".to_owned(),
2531 Some("ABCD".to_owned()),
2532 ),
2533 (r"\177".to_owned(), Some("\u{7f}".to_owned())),
2534 (r"\200".to_owned(), None),
2535 (r"\377".to_owned(), None),
2536 (r"\x7f".to_owned(), Some("\u{7f}".to_owned())),
2537 (r"\x80".to_owned(), None),
2538 (r"\x0041".to_owned(), Some("A".to_owned())),
2539 (r"\x007f".to_owned(), Some("\u{7f}".to_owned())),
2540 (r"\x0080".to_owned(), None),
2541 (r"\xfffffffff".to_owned(), None),
2542 ];
2543 for scalar in [
2544 0,
2545 0x7f,
2546 0x80,
2547 0xd7ff,
2548 0xd800,
2549 0xdfff,
2550 0xe000,
2551 0x10ffff,
2552 0x110000,
2553 u32::MAX,
2554 ] {
2555 let expected = char::from_u32(scalar).map(|character| character.to_string());
2556 cases.push((format!("\\U{scalar:08x}"), expected.clone()));
2557 if scalar <= 0xffff {
2558 cases.push((format!("\\u{scalar:04x}"), expected));
2559 }
2560 }
2561 for (escape, expected) in cases {
2562 let source = format!("const char *value = \"{escape}\";");
2563 let mut parser = Parser::new();
2564 parser
2565 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2566 .unwrap();
2567 let tree = parser.parse(&source, None).unwrap();
2568 assert!(!tree.root_node().has_error(), "{source}");
2569 let mut stack = vec![tree.root_node()];
2570 let mut escapes = Vec::new();
2571 while let Some(node) = stack.pop() {
2572 if node.kind() == "escape_sequence" {
2573 escapes.push(node.byte_range());
2574 }
2575 stack.extend(node.named_children(&mut node.walk()));
2576 }
2577 assert!(!escapes.is_empty(), "grammar did not select {source}");
2578 if escape.starts_with(r"\x00") {
2579 let expected_span = 21..21 + escape.len();
2580 assert_eq!(
2581 escapes.as_slice(),
2582 std::slice::from_ref(&expected_span),
2583 "{source}"
2584 );
2585 }
2586 let rows = collect_structural_rows(&source);
2587 let values: Vec<_> = rows
2588 .nodes()
2589 .iter()
2590 .filter(|node| node.kind == NormalizedKind::StringLiteral)
2591 .map(|node| node.string_value.clone())
2592 .collect();
2593 assert_eq!(values, vec![expected], "{source}, escapes={escapes:?}");
2594 }
2595 }
2596
2597 #[test]
2602 #[brokk_bifrost_test_macros::hardwon(
2603 "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2604 )]
2605 fn cpp_free_function_callees_are_value_references() {
2606 let source = concat!(
2607 "int bound(void) { return 1; }\n",
2608 "struct Widget { int method(void) { return 0; } };\n",
2609 "int go(struct Widget *widget) {\n",
2610 " return getenv(\"PATH\") != 0\n",
2611 " + ns::getenv(\"USER\") != 0\n",
2612 " + bound()\n",
2613 " + widget->method()\n",
2614 " + bound()();\n",
2615 "}\n",
2616 );
2617 let mut parser = Parser::new();
2618 parser
2619 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2620 .expect("C++ grammar");
2621 let tree = parser.parse(source, None).expect("C++ parse");
2622 assert!(
2623 !tree.root_node().has_error(),
2624 "{}",
2625 tree.root_node().to_sexp()
2626 );
2627
2628 let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2633 let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2634 let mut start_by_fact = HashMap::default();
2635 let mut extractions = Vec::new();
2636 let mut pending = vec![tree.root_node()];
2637 while let Some(node) = pending.pop() {
2638 if node.is_named()
2639 && let Some(kind) = compiled.kind_of(&node)
2640 && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2641 {
2642 start_by_fact.insert(node.id(), node.start_byte());
2643 extractions.push((node.start_byte(), node, kind));
2644 }
2645 for index in (0..node.named_child_count()).rev() {
2646 if let Some(child) = node.named_child(index) {
2647 pending.push(child);
2648 }
2649 }
2650 }
2651 extractions.sort_by_key(|(start, _, _)| *start);
2652 let mut source_facts =
2653 brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2654 let parents = ParentIndex::new(tree.root_node());
2655 let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2656 for (_, node, kind) in extractions {
2657 CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2658 }
2659 let (_, _, occurrences, _, stop) = sink.into_parts();
2660 assert_eq!(stop, None);
2661
2662 let roles_at = |needle: &str| {
2663 let start = source.find(needle).expect("fixture token");
2664 occurrences
2665 .iter()
2666 .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2667 .map(|occurrence| occurrence.role)
2668 .collect::<Vec<_>>()
2669 };
2670 assert_eq!(
2674 roles_at("getenv(\"PATH\")"),
2675 vec![OccurrenceRole::ValueReference]
2676 );
2677 assert_eq!(
2678 roles_at("getenv(\"USER\")"),
2679 vec![OccurrenceRole::ValueReference],
2680 "the terminal name of a qualified callee is the value read"
2681 );
2682 assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
2683 assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
2686 assert!(roles_at("widget->").is_empty());
2689 }
2690
2691 #[test]
2695 fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
2696 collect_cpp_structural_facts(
2697 "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
2698 );
2699 }
2700}
2701
2702#[cfg(test)]
2703mod grammar_boundary_tests {
2704 use super::*;
2705
2706 #[test]
2707 fn expression_and_pointer_declarator_name_fields() {
2708 let source = "void run() { auto p = new ns::Widget(); receiver.method<int>(); int (*callback)(int) = nullptr; int (ordinary)(int); }";
2709 let tree = crate::diagnostics::parse_cpp_tree(source);
2710 assert!(
2711 !tree.root_node().has_error(),
2712 "{}",
2713 tree.root_node().to_sexp()
2714 );
2715 let mut stack = vec![tree.root_node()];
2716 let mut names = Vec::new();
2717 let mut binders = Vec::new();
2718 while let Some(node) = stack.pop() {
2719 if matches!(node.kind(), "new_expression" | "field_expression") {
2720 let field = if node.kind() == "new_expression" {
2721 "type"
2722 } else {
2723 "field"
2724 };
2725 let child = node.child_by_field_name(field).unwrap();
2726 names.push((
2727 node.kind(),
2728 child.kind(),
2729 expression_name_node(node).map(|n| &source[n.byte_range()]),
2730 ));
2731 }
2732 if node.kind() == "identifier"
2733 && let Some(name @ ("callback" | "ordinary")) = node_text(node, source)
2734 {
2735 let wrapper = if name == "callback" {
2736 "pointer_declarator"
2737 } else {
2738 "parenthesized_declarator"
2739 };
2740 assert_eq!(node.parent().unwrap().kind(), wrapper);
2741 binders.push((
2742 name,
2743 cpp_binder_owner(node, |n| n.parent(), field_name_in_parent)
2744 .is_some_and(|owner| matches!(owner, CppBinderOwner::Local(_))),
2745 ));
2746 }
2747 stack.extend(
2748 node.named_children(&mut node.walk())
2749 .collect::<Vec<_>>()
2750 .into_iter()
2751 .rev(),
2752 );
2753 }
2754 assert_eq!(
2755 names,
2756 [
2757 ("new_expression", "qualified_identifier", Some("Widget")),
2758 ("field_expression", "template_method", Some("method"))
2759 ]
2760 );
2761 assert_eq!(binders, [("callback", true), ("ordinary", false)]);
2762 }
2763
2764 #[test]
2765 #[brokk_bifrost_test_macros::hardwon(
2766 "6ec52f232835: quicksilver round 4 survivor review: dependent member names lost their terminal token"
2767 )]
2768 fn dependent_member_name_grammar_boundary() {
2769 let source = "template<class T> void f(T receiver) { receiver.template method<int>(); }";
2770 let tree = crate::diagnostics::parse_cpp_tree(source);
2771 assert!(!tree.root_node().has_error());
2772 let mut stack = vec![tree.root_node()];
2773 let mut names = Vec::new();
2774 while let Some(node) = stack.pop() {
2775 if node.kind() == "dependent_name" {
2776 assert!(node.child_by_field_name("name").is_none());
2777 assert_eq!(node.named_child(0).unwrap().kind(), "template_method");
2778 assert_eq!(
2779 declarator_name_node(node).map(|name| &source[name.byte_range()]),
2780 Some("method")
2781 );
2782 assert_eq!(
2783 terminal_callee_leaf(node.parent().unwrap(), callee_spine_step)
2784 .map(|name| &source[name.byte_range()]),
2785 Some("method")
2786 );
2787 names.push(expression_name_node(node).map(|n| &source[n.byte_range()]));
2788 }
2789 stack.extend(node.named_children(&mut node.walk()));
2790 }
2791 assert_eq!(names, [Some("method")]);
2792 }
2793
2794 #[test]
2795 fn conditional_macro_name_fields() {
2796 let source = "#if ON\n#define FIRST(x) x\n#elif ALT\n#define SECOND(x) x\n#else\n#define THIRD(x) x\n#endif\nvoid f() {}\n";
2797 let tree = crate::diagnostics::parse_cpp_tree(source);
2798 assert!(!tree.root_node().has_error());
2799 let mut stack = vec![tree.root_node()];
2800 let mut definitions = Vec::new();
2801 while let Some(node) = stack.pop() {
2802 if node.kind() == "preproc_function_def" {
2803 let name = node.child_by_field_name("name").unwrap();
2804 definitions.push((
2805 node_text(name, source).unwrap(),
2806 node.parent().unwrap().kind(),
2807 ));
2808 }
2809 stack.extend(
2810 node.named_children(&mut node.walk())
2811 .collect::<Vec<_>>()
2812 .into_iter()
2813 .rev(),
2814 );
2815 }
2816 assert_eq!(
2817 definitions,
2818 [
2819 ("FIRST", "preproc_if"),
2820 ("SECOND", "preproc_elif"),
2821 ("THIRD", "preproc_else"),
2822 ]
2823 );
2824 let mut names: Vec<_> = function_like_macro_names(tree.root_node(), source)
2825 .into_iter()
2826 .collect();
2827 names.sort();
2828 assert_eq!(names, ["FIRST", "SECOND", "THIRD"]);
2829 }
2830}
2831
2832#[cfg(test)]
2833mod result_use_tests {
2834 use super::{CppCallResultUseIndex, cpp_call_result_use};
2835 use brokk_bifrost_core::analyzer::structural::result_use::{
2836 CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
2837 };
2838 use tree_sitter::Parser;
2839
2840 fn tree(source: &str) -> tree_sitter::Tree {
2841 let mut parser = Parser::new();
2842 parser
2843 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2844 .expect("C++ grammar");
2845 parser.parse(source, None).expect("C++ tree")
2846 }
2847
2848 fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
2849 let mut found = Vec::new();
2850 let mut stack = vec![tree.root_node()];
2851 while let Some(node) = stack.pop() {
2852 if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
2853 found.push(node);
2854 }
2855 for index in (0..node.named_child_count()).rev() {
2856 stack.push(node.named_child(index).expect("named child"));
2857 }
2858 }
2859 found.sort_by_key(|node| node.start_byte());
2860 found
2861 }
2862
2863 #[test]
2864 fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
2865 let source = concat!(
2866 "int f();\n",
2867 "void sink(int);\n",
2868 "void run() {\n",
2869 " f();\n",
2870 " (void)f();\n",
2871 " static_cast<void>(f());\n",
2872 " sink(f());\n",
2873 " return;\n",
2874 "}\n",
2875 );
2876 let tree = tree(source);
2877 assert!(
2878 !tree.root_node().has_error(),
2879 "{}",
2880 tree.root_node().to_sexp()
2881 );
2882 let calls = calls(&tree, source);
2883 assert_eq!(calls.len(), 5);
2884 assert_eq!(
2885 cpp_call_result_use(calls[0], source).for_port(0),
2886 CallResultUse::Discarded
2887 );
2888 assert!(matches!(
2889 cpp_call_result_use(calls[1], source).for_port(0),
2890 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2891 ));
2892 assert!(matches!(
2893 cpp_call_result_use(calls[2], source).for_port(0),
2894 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2895 ));
2896 assert_eq!(
2897 cpp_call_result_use(calls[3], source).for_port(0),
2898 CallResultUse::Discarded
2899 );
2900 assert_eq!(
2901 cpp_call_result_use(calls[4], source).for_port(0),
2902 CallResultUse::Consumed
2903 );
2904 }
2905
2906 #[test]
2907 fn lambda_tail_and_recovered_syntax_are_open() {
2908 let source = "int f(); void run() { auto value = [] { return f(); }; f(+); }";
2914 let tree = tree(source);
2915 assert!(tree.root_node().has_error());
2916 let calls = calls(&tree, source);
2917 assert!(matches!(
2918 cpp_call_result_use(calls[0], source).for_port(0),
2919 CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
2920 ));
2921 assert_eq!(
2922 cpp_call_result_use(calls[1], source).for_port(0),
2923 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
2924 );
2925 }
2926
2927 #[test]
2928 fn exact_index_keeps_call_spans_separate() {
2929 let source = "int f(); void run() { f(); }";
2930 let tree = tree(source);
2931 let calls = calls(&tree, source);
2932 let index = CppCallResultUseIndex::new(tree.root_node(), source);
2933 assert_eq!(
2934 index
2935 .at_span(calls[0].start_byte(), calls[0].end_byte())
2936 .expect("indexed call")
2937 .for_port(0),
2938 CallResultUse::Discarded
2939 );
2940 assert!(index.at_span(source.len(), source.len()).is_err());
2941 }
2942
2943 #[test]
2944 fn result_use_grammar_boundary_sites_and_recovery() {
2945 for (source, expected) in [
2948 (
2949 "void run() { dynamic_cast<T*>(a()); static_cast<T*>(b()); reinterpret_cast<T*>(c()); const_cast<T*>(d()); new W(); auto p = new X(); sink(new Y()); }",
2950 vec![
2951 ("a()", CallResultUse::Consumed),
2952 ("b()", CallResultUse::Consumed),
2953 ("c()", CallResultUse::Consumed),
2954 ("d()", CallResultUse::Consumed),
2955 ("new W()", CallResultUse::Discarded),
2956 ("new X()", CallResultUse::Consumed),
2957 ("sink(new Y())", CallResultUse::Discarded),
2958 ("new Y()", CallResultUse::Consumed),
2959 ],
2960 ),
2961 (
2962 "void run() { try { f(); } }",
2963 vec![(
2964 "f()",
2965 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax),
2966 )],
2967 ),
2968 ] {
2969 let tree = tree(source);
2970 let mut stack = vec![tree.root_node()];
2971 let mut grammar_sites = Vec::new();
2972 let mut recovery_witness = false;
2973 while let Some(node) = stack.pop() {
2974 if matches!(node.kind(), "call_expression" | "new_expression") {
2975 grammar_sites.push(node.byte_range());
2976 if source[node.byte_range()] == *"f()" {
2977 assert!(!node.has_error());
2978 assert!(!node.parent().unwrap().is_error());
2979 let mut parent = node.parent();
2980 while let Some(ancestor) = parent {
2981 recovery_witness |= ancestor.is_error();
2982 parent = ancestor.parent();
2983 }
2984 }
2985 }
2986 stack.extend(node.named_children(&mut node.walk()));
2987 }
2988 if source.contains("try") {
2989 assert!(recovery_witness, "{}", tree.root_node().to_sexp());
2990 } else {
2991 assert!(!tree.root_node().has_error());
2992 assert_eq!(
2993 grammar_sites.len(),
2994 12,
2995 "four cast wrappers plus eight sites"
2996 );
2997 }
2998 let index = CppCallResultUseIndex::new(tree.root_node(), source);
2999 let actual: Vec<_> = index
3000 .sites()
3001 .iter()
3002 .map(|site| (&source[site.start_byte..site.end_byte], site.for_port(0)))
3003 .collect();
3004 assert_eq!(actual, expected, "{source}");
3005 }
3006 }
3007
3008 #[test]
3009 #[brokk_bifrost_test_macros::hardwon(
3010 "6ec52f232835: quicksilver round 4 survivor review: void pointer casts falsely discarded source values"
3011 )]
3012 fn pointer_to_void_casts_consume_the_source_value() {
3013 let source = "int* make(); void run() { auto p = (void*)make(); auto q = static_cast<void*>(make()); }";
3014 let tree = tree(source);
3015 assert!(!tree.root_node().has_error());
3016 let mut stack = vec![tree.root_node()];
3017 let mut type_declarators = Vec::new();
3018 while let Some(node) = stack.pop() {
3019 if node.kind() == "type_descriptor" {
3020 type_declarators.push(node.child_by_field_name("declarator").map(|n| n.kind()));
3021 }
3022 stack.extend(node.named_children(&mut node.walk()));
3023 }
3024 assert_eq!(
3025 type_declarators,
3026 [
3027 Some("abstract_pointer_declarator"),
3028 Some("abstract_pointer_declarator")
3029 ]
3030 );
3031 let index = CppCallResultUseIndex::new(tree.root_node(), source);
3032 let uses: Vec<_> = index.sites().iter().map(|site| site.for_port(0)).collect();
3035 assert_eq!(uses, [CallResultUse::Consumed, CallResultUse::Consumed]);
3036 }
3037}