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 roles<'tree>(&self, node: Node<'tree>, role: Role) -> Vec<Node<'tree>> {
860 let target = match role {
861 Role::Test if node.kind() == "case_statement" => node.child_by_field_name("value"),
862 Role::Test => condition_expression(node),
863 Role::Consequence => node.child_by_field_name("consequence"),
864 Role::Alternative => {
865 brokk_bifrost_core::analyzer::structural::body_relations::if_alternative_body(
866 node,
867 brokk_bifrost_core::analyzer::structural::body_relations::syntax_for(
868 Language::Cpp,
869 )
870 .expect("C++ body syntax"),
871 )
872 }
873 Role::Body => node.child_by_field_name("body"),
874 Role::Operator => node.child_by_field_name("operator"),
875 Role::Left => node.child_by_field_name("left"),
876 Role::Right => node.child_by_field_name("right"),
877 Role::Label => node.child_by_field_name("label"),
878 _ => None,
879 };
880 target.into_iter().collect()
881 }
882
883 fn is_code_child(&self, node: Node<'_>) -> bool {
884 !node.is_extra() && !node.kind().starts_with("preproc_") && node.kind() != "comment"
885 }
886
887 fn loop_variable(&self, node: Node<'_>, source: &str) -> Option<String> {
888 matches!(node.kind(), "for_statement" | "for_range_loop")
889 .then(|| loop_variable(node, source).map(str::to_owned))
890 .flatten()
891 }
892
893 fn is_counted_loop(&self, node: Node<'_>) -> bool {
894 node.kind() == "for_statement"
895 }
896
897 fn loop_initializers<'tree>(&self, node: Node<'tree>) -> Vec<Node<'tree>> {
898 node.child_by_field_name("initializer")
899 .into_iter()
900 .collect()
901 }
902
903 fn is_alloca(&self, node: Node<'_>, source: &str) -> bool {
904 node.kind() == "call_expression"
905 && node
906 .child_by_field_name("function")
907 .and_then(|function| node_text(function, source))
908 .is_some_and(|name| {
909 matches!(name, "alloca" | "__builtin_alloca" | "_alloca" | "_malloca")
910 })
911 }
912
913 fn switch_enum<'tree>(&self, node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
914 cpp_switch_enum_type(node, source)
915 }
916
917 fn enumeration(
918 &self,
919 node: Node<'_>,
920 source: &str,
921 ) -> Option<(
922 brokk_bifrost_core::analyzer::statement_facts::EnumFact,
923 Vec<brokk_bifrost_core::analyzer::statement_facts::EnumeratorFact>,
924 )> {
925 use brokk_bifrost_core::analyzer::statement_facts::{EnumFact, EnumeratorFact};
926 if node.kind() != "enum_specifier" {
927 return None;
928 }
929 let range = node_range(node);
930 let name = node
931 .child_by_field_name("name")
932 .and_then(|name| node_text(name, source))
933 .map(str::to_owned);
934 let mut enumerators = Vec::new();
935 let mut stack = vec![node];
936 while let Some(current) = stack.pop() {
937 if current != node && current.kind() == "enum_specifier" {
938 continue;
939 }
940 if current.kind() == "enumerator"
941 && let Some(name) = current.child_by_field_name("name")
942 && let Some(name) = node_text(name, source)
943 {
944 enumerators.push(EnumeratorFact {
945 range: node_range(current),
946 enum_range: range,
947 name: name.to_owned(),
948 });
949 }
950 stack.extend(named_children_iter(current).filter(|child| !child.is_extra()));
951 }
952 Some((EnumFact { range, name }, enumerators))
953 }
954
955 fn name<'tree>(&self, node: Node<'tree>) -> Option<Node<'tree>> {
956 expression_name_node(node)
957 }
958}
959
960pub fn statement_fact_kind(node: Node<'_>, source: &str) -> Option<StatementFactKind> {
962 brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
963 &CPP_STRUCTURAL_SPEC,
964 )
965 .expect("C++ statement syntax")
966 .classify(node, source, &Default::default())
967}
968
969pub(crate) fn capture_statement_fact_rows(
970 node: Node<'_>,
971 source: &str,
972 rows: &mut brokk_bifrost_core::analyzer::statement_facts::StatementFactRows,
973) {
974 brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
975 &CPP_STRUCTURAL_SPEC,
976 )
977 .expect("C++ statement syntax")
978 .capture(node, source, &Default::default(), rows);
979}
980
981#[cfg(test)]
982fn alloca_is_in_unbounded_loop(node: Node<'_>, source: &str) -> bool {
983 statement_fact_kind(node, source) == Some(StatementFactKind::AllocaInLoop)
984}
985
986#[cfg(test)]
987fn enum_switch_is_missing_case(node: Node<'_>, source: &str) -> bool {
988 let producer = brokk_bifrost_core::analyzer::statement_facts::producer::StatementFacts::new(
989 &CPP_STRUCTURAL_SPEC,
990 )
991 .unwrap();
992 let mut rows = brokk_bifrost_core::analyzer::statement_facts::StatementFactRows::default();
993 producer.capture(node, source, &Default::default(), &mut rows);
994 rows.switches.iter().any(|fact| fact.missing_enum_case)
995}
996
997pub fn is_recovered_designator_init_declarator(node: Node<'_>) -> bool {
1005 if node.kind() != "init_declarator" {
1006 return false;
1007 }
1008 let Some(identifier) = node.child_by_field_name("declarator") else {
1009 return false;
1010 };
1011 if identifier.kind() != "identifier" || identifier.is_missing() {
1012 return false;
1013 }
1014 let Some(previous) = node.prev_named_sibling() else {
1015 return false;
1016 };
1017 if previous.kind() != "ERROR"
1018 || previous.named_child_count() != 0
1019 || previous.child_count() != 1
1020 || previous.end_byte() != node.start_byte()
1021 {
1022 return false;
1023 }
1024 previous.child(0).is_some_and(|child| {
1025 child.kind() == "."
1026 && !child.is_named()
1027 && child.start_byte() == previous.start_byte()
1028 && child.end_byte() == previous.end_byte()
1029 })
1030}
1031
1032use brokk_bifrost_core::analyzer::tree_walk::last_named_field_child;
1033
1034fn namespace_name_node<'tree>(namespace: Node<'tree>) -> Option<Node<'tree>> {
1040 let mut current = namespace.child_by_field_name("name")?;
1041 while current.kind() == "nested_namespace_specifier" {
1042 current = current.named_child(current.named_child_count().checked_sub(1)?)?;
1043 }
1044 Some(current)
1045}
1046
1047pub fn declarator_name_node<'tree>(declarator: Node<'tree>) -> Option<Node<'tree>> {
1048 let mut current = declarator;
1049 loop {
1050 match current.kind() {
1051 "identifier"
1052 | "field_identifier"
1053 | "namespace_identifier"
1054 | "type_identifier"
1055 | "destructor_name"
1056 | "operator_name"
1057 | "operator_cast"
1058 | "primitive_type" => return Some(current),
1059 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1060 "dependent_name" => current = first_named_child(current)?,
1061 "template_function" | "template_method" | "template_type" => {
1062 current = current.child_by_field_name("name")?;
1063 }
1064 "function_declarator"
1065 | "pointer_declarator"
1066 | "pointer_type_declarator"
1067 | "macro_modifier_declarator"
1068 | "variadic_declarator"
1069 | "array_declarator"
1070 | "init_declarator" => current = current.child_by_field_name("declarator")?,
1071 "attributed_declarator" => current = first_named_child(current)?,
1072 "reference_declarator" | "parenthesized_declarator" => {
1073 current = first_named_child(current)?;
1074 }
1075 _ => return None,
1076 }
1077 }
1078}
1079
1080pub fn callee_spine_step(kind: &str) -> Option<CalleeSpineStep> {
1082 use CalleeSpineStep::{Field, FieldOrOnlyChild, Name, OnlyChild};
1083 match kind {
1084 "identifier"
1085 | "field_identifier"
1086 | "type_identifier"
1087 | "operator_name"
1088 | "operator_cast"
1089 | "destructor_name"
1090 | "literal_operator_name"
1091 | "primitive_type" => Some(Name),
1092 "qualified_identifier"
1093 | "scoped_identifier"
1094 | "scoped_type_identifier"
1095 | "template_function"
1096 | "template_method"
1097 | "template_type" => Some(Field("name")),
1098 "dependent_name" => Some(OnlyChild),
1099 "field_expression" => Some(Field("field")),
1100 "pointer_expression" => Some(FieldOrOnlyChild("argument")),
1101 _ => None,
1102 }
1103}
1104
1105fn expression_name_node<'tree>(expression: Node<'tree>) -> Option<Node<'tree>> {
1106 let mut current = expression;
1107 loop {
1108 match current.kind() {
1109 "identifier"
1110 | "field_identifier"
1111 | "namespace_identifier"
1112 | "type_identifier"
1113 | "destructor_name"
1114 | "operator_name"
1115 | "primitive_type"
1116 | "this" => return Some(current),
1117 "qualified_identifier" => current = last_named_field_child(current, "name")?,
1118 "dependent_name" => current = first_named_child(current)?,
1119 "template_function" | "template_method" | "template_type" => {
1120 current = current.child_by_field_name("name")?;
1121 }
1122 "field_expression" => current = current.child_by_field_name("field")?,
1123 "call_expression" => current = current.child_by_field_name("function")?,
1124 "new_expression" => current = current.child_by_field_name("type")?,
1125 "parenthesized_expression" => current = first_named_child(current)?,
1126 _ => return declarator_name_node(current),
1127 }
1128 }
1129}
1130
1131fn attach_qualified_scope_receiver(sink: &mut RoleSink<'_>, function: Node<'_>) {
1132 if function.kind() != "qualified_identifier" {
1133 return;
1134 }
1135 if let Some(scope) = function.child_by_field_name("scope") {
1136 attach_role_with_derived_name(sink, Role::Receiver, scope, expression_name_node);
1137 }
1138}
1139
1140fn qualified_declarator_node(mut node: Node<'_>) -> Option<Node<'_>> {
1141 loop {
1142 if node.kind() == "qualified_identifier" {
1143 return Some(node);
1144 }
1145 node = node
1146 .child_by_field_name("declarator")
1147 .or_else(|| node.child_by_field_name("name"))
1148 .or_else(|| first_named_child(node))?;
1149 }
1150}
1151
1152fn scoped_function_definition(node: Node<'_>) -> Option<Node<'_>> {
1153 node.child_by_field_name("declarator")
1154 .and_then(qualified_declarator_node)
1155 .and_then(|qualified| qualified.child_by_field_name("scope"))
1156}
1157
1158pub fn constructor_scope_matches_name(scope: Node<'_>, name: Node<'_>, source: &str) -> bool {
1159 node_text(scope, source) == node_text(name, source)
1160}
1161
1162fn is_constructor_definition(node: Node<'_>, source: &str) -> bool {
1163 node.child_by_field_name("declarator")
1164 .and_then(qualified_declarator_node)
1165 .and_then(|qualified| {
1166 Some((
1167 expression_name_node(qualified.child_by_field_name("scope")?)?,
1168 expression_name_node(last_named_field_child(qualified, "name")?)?,
1169 ))
1170 })
1171 .is_some_and(|(scope, name)| constructor_scope_matches_name(scope, name, source))
1172}
1173
1174fn unquoted_include_span(node: Node<'_>) -> Option<Span> {
1175 if !matches!(node.kind(), "string_literal" | "system_lib_string") {
1176 return None;
1177 }
1178 unquoted_string_content_span(node)
1179}
1180
1181fn function_like_macro_names(root: Node<'_>, source: &str) -> HashSet<String> {
1189 let mut names = HashSet::default();
1190 let mut stack = vec![root];
1191 while let Some(node) = stack.pop() {
1192 for child in named_children_iter(node) {
1193 match child.kind() {
1194 "preproc_function_def" => {
1195 if let Some(name) = child.child_by_field_name("name") {
1196 names.insert(source[name.start_byte()..name.end_byte()].to_owned());
1197 }
1198 }
1199 "preproc_if" | "preproc_ifdef" | "preproc_else" | "preproc_elif"
1200 | "preproc_elifdef" => stack.push(child),
1201 _ => {}
1202 }
1203 }
1204 }
1205 names
1206}
1207
1208fn cpp_member_position(node: Node<'_>) -> Option<OccurrenceRole> {
1218 if !matches!(
1219 node.kind(),
1220 "identifier" | "field_identifier" | "type_identifier" | "operator_name" | "destructor_name"
1221 ) {
1222 return None;
1223 }
1224
1225 let mut current = node;
1226 while let Some(parent) = current.parent() {
1227 if parent.kind() == "field_expression" {
1228 let field = parent.child_by_field_name("field")?;
1229 return expression_name_node(field)
1230 .filter(|name| name.id() == node.id())
1231 .map(|_| OccurrenceRole::MemberPosition);
1232 }
1233
1234 if matches!(
1237 parent.kind(),
1238 "qualified_identifier" | "dependent_name" | "template_method" | "destructor_name"
1239 ) {
1240 current = parent;
1241 } else {
1242 return None;
1243 }
1244 }
1245 None
1246}
1247
1248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1250enum CppBinderOwner<'tree> {
1251 Parameter,
1253 Local(Node<'tree>),
1255 RangeFor(Node<'tree>),
1257 StructuredBinding(Node<'tree>),
1259 InitCapture,
1261}
1262
1263const CPP_DECLARATOR_WRAPPERS: &[&str] = &[
1266 "pointer_declarator",
1267 "array_declarator",
1268 "reference_declarator",
1269 "parenthesized_declarator",
1270 "attributed_declarator",
1271 "variadic_declarator",
1272];
1273
1274fn cpp_binder_owner<'tree>(
1284 node: Node<'tree>,
1285 parent: impl Fn(Node<'tree>) -> Option<Node<'tree>>,
1286 mut field_name: impl FnMut(Node<'tree>, Node<'tree>) -> Option<&'static str>,
1287) -> Option<CppBinderOwner<'tree>> {
1288 if node.kind() != "identifier" {
1289 return None;
1290 }
1291 let mut child = node;
1292 let mut current = parent(node)?;
1293 let mut indirect = false;
1294 loop {
1295 match current.kind() {
1298 kind if CPP_DECLARATOR_WRAPPERS.contains(&kind) => {
1299 let field = field_name(current, child);
1300 if field.is_some() && field != Some("declarator") {
1301 return None;
1302 }
1303 indirect |= matches!(kind, "pointer_declarator" | "reference_declarator");
1304 }
1305 "function_declarator" => {
1306 let field = field_name(current, child);
1307 if field != Some("declarator") || !indirect {
1308 return None;
1309 }
1310 }
1311 "init_declarator" => {
1312 let field = field_name(current, child);
1313 if field != Some("declarator") {
1314 return None;
1315 }
1316 }
1317 "declaration" => {
1318 let field = field_name(current, child);
1319 if field != Some("declarator") {
1320 return None;
1321 }
1322 let local = std::iter::successors(parent(current), |node| parent(*node))
1323 .take_while(|node| {
1324 !matches!(
1325 node.kind(),
1326 "translation_unit"
1327 | "field_declaration_list"
1328 | "declaration_list"
1329 | "namespace_definition"
1330 )
1331 })
1332 .any(|node| {
1333 matches!(
1334 node.kind(),
1335 "compound_statement" | "for_statement" | "condition_clause"
1336 )
1337 });
1338 let declarator = if child.kind() == "init_declarator" {
1341 child.child_by_field_name("declarator")?
1342 } else {
1343 child
1344 };
1345 return local.then_some(CppBinderOwner::Local(declarator));
1346 }
1347 "parameter_declaration"
1348 | "optional_parameter_declaration"
1349 | "variadic_parameter_declaration" => {
1350 let field = field_name(current, child);
1351 if field != Some("declarator") {
1352 return None;
1353 }
1354 let parameter_parent = parent(current)?;
1355 let list = if parameter_parent.kind() == "explicit_object_parameter_declaration" {
1356 if parameter_parent.named_child(0).map(|node| node.kind()) != Some("this")
1359 || parameter_parent.named_child(1) != Some(current)
1360 {
1361 return None;
1362 }
1363 parent(parameter_parent)?
1364 } else {
1365 parameter_parent
1366 };
1367 if list.kind() != "parameter_list" {
1368 return None;
1369 }
1370 let owner = parent(list)?;
1371 let binds = match owner.kind() {
1372 "catch_clause" => true,
1373 "lambda_declarator" | "abstract_function_declarator" => parent(owner)
1374 .is_some_and(|lambda| {
1375 lambda.kind() == "lambda_expression"
1376 && lambda.child_by_field_name("declarator") == Some(owner)
1377 }),
1378 "function_declarator" => {
1379 let mut declarator = owner;
1380 loop {
1381 let Some(up) = parent(declarator) else {
1382 break false;
1383 };
1384 if up.kind() == "function_definition" {
1385 break field_name_in_parent(up, declarator) == Some("declarator");
1386 }
1387 if !CPP_DECLARATOR_WRAPPERS.contains(&up.kind()) {
1388 break false;
1389 }
1390 declarator = up;
1391 }
1392 }
1393 _ => false,
1394 };
1395 return binds.then_some(CppBinderOwner::Parameter);
1396 }
1397 "for_range_loop" => {
1398 let field = field_name(current, child);
1399 return (field == Some("declarator")).then_some(CppBinderOwner::RangeFor(current));
1400 }
1401 "structured_binding_declarator" => {
1402 return Some(CppBinderOwner::StructuredBinding(current));
1403 }
1404 "lambda_capture_initializer" => {
1405 let field = field_name(current, child);
1406 return (field == Some("left")).then_some(CppBinderOwner::InitCapture);
1407 }
1408 _ => return None,
1409 }
1410 child = current;
1411 current = parent(current)?;
1412 }
1413}
1414
1415fn cpp_is_binder<'tree>(node: Node<'tree>, parents: &ParentIndex<'tree>) -> bool {
1419 cpp_binder_owner(node, |node| parents.parent(node), field_name_in_parent).is_some()
1420}
1421
1422fn cpp_binding_activation(binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1433 let binding = |kind, hoisting, activation| {
1434 Some(BindingActivation {
1435 kind,
1436 hoisting,
1437 activation,
1438 })
1439 };
1440 let after = |node: Node<'_>| Range {
1441 start_byte: node.end_byte(),
1442 end_byte: scope.end_byte,
1443 start_line: node.end_position().row + 1,
1444 end_line: scope.end_line,
1445 };
1446 match cpp_binder_owner(binder, |node| node.parent(), field_name_in_parent)? {
1447 CppBinderOwner::Parameter | CppBinderOwner::InitCapture => {
1448 binding(BindingKind::Parameter, HoistingClass::ScopeWide, scope)
1449 }
1450 CppBinderOwner::Local(declarator) => binding(
1451 BindingKind::Local,
1452 HoistingClass::SourceOrder,
1453 after(declarator),
1454 ),
1455 CppBinderOwner::RangeFor(loop_node) => binding(
1456 BindingKind::LoopVariable,
1457 HoistingClass::DeclaredHead,
1458 node_range(loop_node.child_by_field_name("body")?),
1459 ),
1460 CppBinderOwner::StructuredBinding(declarator) => binding(
1461 BindingKind::Local,
1462 HoistingClass::SourceOrder,
1463 after(declarator),
1464 ),
1465 }
1466}
1467
1468impl StructuralSpec for CppStructuralSpec {
1469 fn language(&self) -> Language {
1470 Language::Cpp
1471 }
1472
1473 fn supports_boolean_literal_value(&self) -> bool {
1474 true
1475 }
1476
1477 fn supports_string_literal_value(&self) -> bool {
1478 true
1479 }
1480
1481 fn string_literal_value_may_be_absent(&self, node: Node<'_>, source: &str) -> bool {
1482 matches!(node.kind(), "string_literal" | "raw_string_literal")
1483 && cpp_string_literal_value(node, source).is_none()
1484 }
1485
1486 fn string_literal_value(&self, node: Node<'_>, source: &str) -> Option<String> {
1487 cpp_string_literal_value(node, source)
1488 }
1489
1490 fn kind_table(&self) -> &'static [(&'static str, NormalizedKind)] {
1491 CPP_KIND_TABLE
1492 }
1493
1494 fn statement_fact_syntax(
1495 &self,
1496 ) -> Option<&dyn brokk_bifrost_core::analyzer::statement_facts::producer::StatementFactSyntax>
1497 {
1498 Some(self)
1499 }
1500
1501 fn generator_construct(
1502 &self,
1503 node: Node<'_>,
1504 _kind: NormalizedKind,
1505 source: &str,
1506 ) -> Option<&'static str> {
1507 statement_fact_kind(node, source).map(StatementFactKind::construct)
1508 }
1509
1510 fn refine_kind(
1511 &self,
1512 node: Node<'_>,
1513 kind: NormalizedKind,
1514 enclosing: Option<NormalizedKind>,
1515 source: &str,
1516 _context: &CallSiteContext,
1517 ) -> NormalizedKind {
1518 if kind == NormalizedKind::Function
1519 && (enclosing == Some(NormalizedKind::Class)
1520 || scoped_function_definition(node).is_some())
1521 {
1522 if is_constructor_definition(node, source) {
1523 NormalizedKind::Constructor
1524 } else {
1525 NormalizedKind::Method
1526 }
1527 } else {
1528 kind
1529 }
1530 }
1531
1532 fn supports_kind(&self, kind: NormalizedKind) -> bool {
1533 matches!(
1534 kind,
1535 NormalizedKind::Method | NormalizedKind::Constructor | NormalizedKind::SyntaxError
1536 ) || self
1537 .kind_table()
1538 .iter()
1539 .any(|(_, fact_kind)| fact_kind.satisfies(kind))
1540 }
1541
1542 fn supports_role(&self, role: Role) -> bool {
1543 !matches!(
1544 role,
1545 Role::Kwarg | Role::Decorator | Role::Iterable | Role::Element
1546 )
1547 }
1548
1549 fn call_site_context(&self, root: Node<'_>, source: &str) -> CallSiteContext {
1550 CallSiteContext::with_macro_derived_callees(function_like_macro_names(root, source))
1551 }
1552
1553 fn call_site_facts(
1566 &self,
1567 node: Node<'_>,
1568 source: &str,
1569 context: &CallSiteContext,
1570 ) -> Option<CallSiteFacts> {
1571 if node.kind() == "new_expression" {
1572 return Some(CallSiteFacts::of_kind(CallKind::Constructor));
1573 }
1574 let callee = node.child_by_field_name("function")?;
1575 (callee.kind() == "identifier"
1576 && context.is_macro_derived_callee(&source[callee.start_byte()..callee.end_byte()]))
1577 .then(|| CallSiteFacts::of_coverage(CallShapeCoverage::UnknownMacroDerived))
1578 }
1579
1580 fn occurrence_role_support(&self) -> &OccurrenceRoleSupport {
1581 static SUPPORT: OccurrenceRoleSupport = OccurrenceRoleSupport::NONE
1582 .supported(OccurrenceRole::MemberPosition)
1583 .supported(OccurrenceRole::ValueReference)
1584 .supported(OccurrenceRole::Binder);
1585 &SUPPORT
1586 }
1587
1588 fn lexical_environment_support(&self) -> &LexicalEnvironmentSupport {
1589 static SUPPORT: LexicalEnvironmentSupport = LexicalEnvironmentSupport::NONE
1593 .supported(EnvironmentAxis::Scopes)
1594 .supported(EnvironmentAxis::BindingIntervals)
1595 .supported(EnvironmentAxis::CallableApplicability);
1596 &SUPPORT
1597 }
1598
1599 fn scope_formation(&self, kind: NormalizedKind) -> ScopeFormation {
1602 if matches!(kind, NormalizedKind::If | NormalizedKind::Switch) {
1603 ScopeFormation::BindingScope
1604 } else {
1605 default_scope_formation(kind)
1606 }
1607 }
1608
1609 fn binding_activation(&self, binder: Node<'_>, scope: Range) -> Option<BindingActivation> {
1610 cpp_binding_activation(binder, scope)
1611 }
1612
1613 fn materialization_support(&self) -> &DeclarationMaterializationSupport {
1614 &CPP_MATERIALIZATION_SUPPORT
1615 }
1616
1617 fn reference_edge_support(&self) -> &ReferenceEdgeSupport {
1618 &INVERSE_REFERENCE_EDGE_SUPPORT
1619 }
1620
1621 fn identity_route_support(&self) -> &IdentityRouteSupport {
1622 static SUPPORT: IdentityRouteSupport = IdentityRouteSupport::NONE
1633 .supported_axis(IdentityAxis::CanonicalIdentity)
1634 .supported_axis(IdentityAxis::PhysicalGrouping)
1635 .supported_relation(RouteHopKind::NestedOwner)
1636 .supported_relation(RouteHopKind::DeclarationDefinitionPeer);
1637 &SUPPORT
1638 }
1639
1640 fn extract(&self, node: Node<'_>, kind: NormalizedKind, sink: &mut RoleSink<'_>) {
1641 if let Some(role) = cpp_member_position(node) {
1642 sink.occurrence_role(node, role);
1643 } else if cpp_is_binder(node, sink.parents()) {
1644 sink.occurrence_role(node, OccurrenceRole::Binder);
1645 }
1646
1647 match kind {
1648 NormalizedKind::Call => {
1649 let function_field = if node.kind() == "new_expression" {
1650 "type"
1651 } else {
1652 "function"
1653 };
1654 if let Some(function) = node.child_by_field_name(function_field) {
1655 attach_terminal_callee(
1656 sink,
1657 function,
1658 terminal_callee_leaf(function, callee_spine_step),
1659 );
1660 if node.kind() == "call_expression"
1669 && !matches!(function.kind(), "field_expression" | "call_expression")
1670 && let Some(callee) = expression_name_node(function)
1671 && self.kind_table().iter().any(|(node_kind, kind)| {
1672 *node_kind == callee.kind() && self.should_extract(callee, *kind)
1673 })
1674 {
1675 sink.occurrence_role(callee, OccurrenceRole::ValueReference);
1676 }
1677 if function.kind() == "field_expression"
1678 && let Some(argument) = function.child_by_field_name("argument")
1679 {
1680 attach_role_with_derived_name(
1681 sink,
1682 Role::Receiver,
1683 argument,
1684 expression_name_node,
1685 );
1686 }
1687 attach_qualified_scope_receiver(sink, function);
1688 }
1689 if let Some(arguments) = node.child_by_field_name("arguments") {
1690 attach_positional_argument_roles(sink, arguments, expression_name_node);
1691 }
1692 }
1693 NormalizedKind::FieldAccess => {
1694 if let Some(field) = node.child_by_field_name("field") {
1695 attach_role_with_derived_name(sink, Role::Field, field, expression_name_node);
1696 if let Some(name) = expression_name_node(field) {
1697 sink.set_name(name);
1698 }
1699 }
1700 if let Some(argument) = node.child_by_field_name("argument") {
1701 attach_role_with_derived_name(
1702 sink,
1703 Role::Object,
1704 argument,
1705 expression_name_node,
1706 );
1707 }
1708 }
1709 NormalizedKind::Function | NormalizedKind::Method | NormalizedKind::Constructor => {
1710 if let Some(name) = node
1711 .child_by_field_name("declarator")
1712 .and_then(declarator_name_node)
1713 {
1714 sink.set_name(name);
1715 }
1716 }
1717 NormalizedKind::Module => {
1718 if let Some(name) = namespace_name_node(node) {
1719 sink.set_name(name);
1720 }
1721 }
1722 NormalizedKind::Class | NormalizedKind::Declaration => {
1723 if let Some(name) = node
1724 .child_by_field_name("name")
1725 .and_then(declarator_name_node)
1726 .or_else(|| node.child_by_field_name("name"))
1727 {
1728 sink.set_name(name);
1729 }
1730 }
1731 NormalizedKind::Assignment => match node.kind() {
1732 "init_declarator" => {
1733 if let Some(declarator) = node.child_by_field_name("declarator") {
1734 attach_role_with_derived_name(
1735 sink,
1736 Role::Left,
1737 declarator,
1738 declarator_name_node,
1739 );
1740 if let Some(name) = declarator_name_node(declarator) {
1741 sink.set_name(name);
1742 }
1743 }
1744 if let Some(value) = node.child_by_field_name("value") {
1745 attach_role_with_derived_name(
1746 sink,
1747 Role::Right,
1748 value,
1749 expression_name_node,
1750 );
1751 }
1752 }
1753 "assignment_expression" => {
1754 if let Some(left) = node.child_by_field_name("left") {
1755 attach_role_with_derived_name(sink, Role::Left, left, expression_name_node);
1756 }
1757 if let Some(right) = node.child_by_field_name("right") {
1758 attach_role_with_derived_name(
1759 sink,
1760 Role::Right,
1761 right,
1762 expression_name_node,
1763 );
1764 }
1765 }
1766 _ => {}
1767 },
1768 NormalizedKind::Import => {
1769 if let Some(path) = node.child_by_field_name("path") {
1770 if let Some(name) = unquoted_include_span(path) {
1771 sink.role_named_span(Role::Module, path, name);
1772 } else {
1773 attach_role_with_derived_name(
1774 sink,
1775 Role::Module,
1776 path,
1777 expression_name_node,
1778 );
1779 }
1780 }
1781 }
1782 NormalizedKind::Identifier => match expression_name_node(node) {
1783 Some(name) => sink.set_name(name),
1784 None => sink.set_name(node),
1785 },
1786 _ => {}
1787 }
1788 }
1789}
1790
1791#[cfg(test)]
1792mod tests {
1793 use super::CPP_STRUCTURAL_SPEC;
1794 use super::cpp_binder_owner;
1795 use super::cpp_member_position;
1796 use super::field_name_in_parent;
1797 use super::node_range;
1798 use brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector;
1799 use brokk_bifrost_core::analyzer::structural::collector::StructuralFactCollector;
1800 use brokk_bifrost_core::analyzer::structural::facts::StructuralFactRows;
1801 use brokk_bifrost_core::analyzer::structural::kinds::NormalizedKind;
1802 use brokk_bifrost_core::analyzer::structural::occurrences::OccurrenceRole;
1803 use brokk_bifrost_core::analyzer::structural::resolution::BindingKind;
1804 use brokk_bifrost_core::analyzer::structural::spec::{CompiledKinds, RoleSink, StructuralSpec};
1805 use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
1806 use brokk_bifrost_core::hash::HashMap;
1807 use brokk_bifrost_test_macros::hardwon;
1808 use tree_sitter::Parser;
1809
1810 #[test]
1811 #[hardwon(
1812 "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: unrelated comparisons suppress alloca-in-loop"
1813 )]
1814 fn alloca_bound_must_constrain_loop_variable() {
1815 for (condition, expected) in [
1816 ("i < n && enabled < 1", true),
1817 ("i < n || i < 1", true),
1818 ("i < 1", false),
1819 ("1 > i && enabled", false),
1820 ] {
1821 let source = format!(
1822 "void f(int n, int enabled) {{ for (int i=0; {condition}; ++i) {{ alloca(16); }} }}"
1823 );
1824 let mut parser = Parser::new();
1825 parser
1826 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1827 .unwrap();
1828 let tree = parser.parse(&source, None).unwrap();
1829 let mut stack = vec![tree.root_node()];
1830 let mut actual = None;
1831 while let Some(node) = stack.pop() {
1832 if node.kind() == "call_expression" {
1833 actual = Some(super::alloca_is_in_unbounded_loop(node, &source));
1834 }
1835 stack.extend(super::named_children_iter(node));
1836 }
1837 assert_eq!(actual, Some(expected), "{source}");
1838 }
1839 }
1840
1841 #[test]
1842 #[hardwon(
1843 "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: integer switches borrow unrelated enum cases"
1844 )]
1845 fn integer_switch_is_not_an_enum_switch() {
1846 let source = "enum E { A, B }; void f(int value) { switch(value) { case A: break; } }";
1847 let mut parser = Parser::new();
1848 parser
1849 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1850 .unwrap();
1851 let tree = parser.parse(source, None).unwrap();
1852 let mut stack = vec![tree.root_node()];
1853 let mut checked = false;
1854 while let Some(node) = stack.pop() {
1855 if node.kind() == "switch_statement" {
1856 checked = true;
1857 assert!(!super::enum_switch_is_missing_case(node, source));
1858 }
1859 stack.extend(super::named_children_iter(node));
1860 }
1861 assert!(checked);
1862 }
1863
1864 #[test]
1865 #[hardwon(
1866 "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: enum coverage needs scoped local declaration evidence"
1867 )]
1868 fn enum_switch_uses_local_type_evidence() {
1869 for (source, expected) in [
1870 (
1871 "enum E { A,B }; void f(E e) { switch(e) {case A:break;} }",
1872 vec![true],
1873 ),
1874 (
1875 "enum E { A,B }; void f() { E e=A; switch(e) {case A:break;} }",
1876 vec![true],
1877 ),
1878 (
1879 "enum class E { A,B }; void f(E e) { switch(e) {case E::A:break;} }",
1880 vec![true],
1881 ),
1882 (
1883 "namespace ns { enum class E { A,B }; } void f(ns::E e) { switch(e) {case ns::E::A:break;} }",
1884 vec![true],
1885 ),
1886 (
1887 "enum E { A,B }; struct S { E state; }; void f(S s) { switch(s.state) {case A:break;} }",
1888 vec![true],
1889 ),
1890 (
1891 "enum E { A,B }; struct S { E state; }; void f() { S s; switch(s.state) {case A:break;} }",
1892 vec![true],
1893 ),
1894 (
1895 "enum E { A,B }; void f(E e) { { int e=0; switch(e) {case A:break;} } switch(e) {case A:break;} }",
1896 vec![false, true],
1897 ),
1898 (
1899 "enum E { A,B }; void f(E e) { { int e=0; } switch(e) {case A:break;} }",
1900 vec![true],
1901 ),
1902 (
1903 "enum E { A,B }; void f() { switch(e) {case A:break;} E e=A; }",
1904 vec![false],
1905 ),
1906 (
1907 "enum E { A,B }; using Alias=E; void f(Alias e) { switch(e) {case A:break;} }",
1908 vec![false],
1909 ),
1910 (
1911 "enum E { A,B }; void f(E e) { switch(e==A) {case A:break;} }",
1912 vec![false],
1913 ),
1914 (
1915 "enum E { A,B }; E e; void f() { switch(e) {case A:break;} }",
1916 vec![false],
1917 ),
1918 (
1919 "enum E { A,B }; void f(E &e) { switch(e) {case A:break;} }",
1920 vec![true],
1921 ),
1922 (
1923 "enum E { A,B }; void f(E *e) { switch(e) {case A:break;} }",
1924 vec![false],
1925 ),
1926 (
1927 "enum E { A,B }; struct S { E state; }; void f(S *s) { switch(s->state) {case A:break;} }",
1928 vec![true],
1929 ),
1930 (
1931 "enum E { A,B }; struct S { int state; }; void f(S s) { switch(s.state) {case A:break;} }",
1932 vec![false],
1933 ),
1934 (
1935 "enum E { A,B }; struct Inner { E state; }; struct Outer { Inner inner; }; void f(Outer s) { switch(s.inner.state) {case A:break;} }",
1936 vec![true],
1937 ),
1938 (
1939 "void f() { enum class E { A,B }; E e=E::A; switch(e) {case E::A:break;} }",
1940 vec![true],
1941 ),
1942 (
1943 "struct S { enum class E { A,B }; E state; }; void f(S s) { switch(s.state) {case S::E::A:break;} }",
1944 vec![true],
1945 ),
1946 (
1947 "namespace n { namespace m { enum class E { A,B }; } } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1948 vec![true],
1949 ),
1950 (
1951 "enum E { A,B }; void f() { using E=int; E e=0; switch(e) {case A:break;} }",
1952 vec![false],
1953 ),
1954 (
1955 "enum E { A,B }; void f(E e) { { int e(); switch(e) {case A:break;} } }",
1956 vec![false],
1957 ),
1958 (
1959 "enum E { A,B }; void f(E e) { auto g=[e]() { switch(e) {case A:break;} }; }",
1960 vec![false],
1961 ),
1962 (
1963 "enum E { A,B }; void f() { auto g=[](E e) { switch(e) {case A:break;} }; }",
1964 vec![true],
1965 ),
1966 (
1967 "namespace n::m { enum class E { A,B }; } void f(n::m::E e) { switch(e) {case n::m::E::A:break;} }",
1968 vec![true],
1969 ),
1970 (
1971 "void f() { enum E { A,B } e=A; switch(e) {case A:break;} }",
1972 vec![true],
1973 ),
1974 (
1975 "enum E { A,B }; void f(E (&values)[2]) { for (E e : values) { switch(e) {case A:break;} } }",
1976 vec![true],
1977 ),
1978 (
1979 "enum E { A,B }; namespace n { enum E { A,C }; } void f() { using n::E; E e; switch(e) {case A:break;} }",
1980 vec![false],
1981 ),
1982 (
1983 "enum E { A,B }; namespace n { enum E { A }; void f(E); } void n::f(E e) { switch(e) {case n::A:break;} }",
1984 vec![false],
1985 ),
1986 (
1987 "enum E { A,B }; void f(E e) { switch(int e=0; e) {case A:break;} }",
1988 vec![false],
1989 ),
1990 (
1991 "enum E { A,B }; void f() { switch(E e=A; e) {case A:break;} }",
1992 vec![true],
1993 ),
1994 (
1995 "enum E { A,B }; template<typename E> void f(E e) { switch(e) {case A:break;} }",
1996 vec![false],
1997 ),
1998 (
1999 "enum E { A,B }; template<typename... T> void f(T... e) { switch(e) {case A:break;} }",
2000 vec![false],
2001 ),
2002 (
2003 "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;} } }",
2004 vec![false],
2005 ),
2006 (
2007 "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;} }",
2008 vec![false],
2009 ),
2010 (
2011 "enum E { A,B }; struct Base { enum E { A }; }; struct S: Base { void f(E e) { switch(e) {case A:break;} } };",
2012 vec![false],
2013 ),
2014 (
2015 "enum E { A,B }; struct S { void f() { E e; switch(e) {case A:break;} } enum E { A }; };",
2016 vec![false],
2017 ),
2018 ] {
2019 let mut parser = Parser::new();
2020 parser
2021 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2022 .unwrap();
2023 let tree = parser.parse(source, None).unwrap();
2024 assert!(
2025 !tree.root_node().has_error(),
2026 "{source}: {}",
2027 tree.root_node().to_sexp()
2028 );
2029 let mut stack = vec![tree.root_node()];
2030 let mut results = Vec::new();
2031 while let Some(node) = stack.pop() {
2032 if node.kind() == "switch_statement" {
2033 results.push((
2034 node.start_byte(),
2035 super::enum_switch_is_missing_case(node, source),
2036 ));
2037 }
2038 stack.extend(super::named_children_iter(node));
2039 }
2040 results.sort_unstable_by_key(|(start, _)| *start);
2041 assert_eq!(
2042 results.into_iter().map(|(_, v)| v).collect::<Vec<_>>(),
2043 expected,
2044 "{source}"
2045 );
2046 }
2047 }
2048
2049 fn collect_cpp_structural_facts(source: &str) {
2050 collect_structural_rows(source);
2051 }
2052
2053 fn collect_structural_rows(source: &str) -> StructuralFactRows {
2054 let mut parser = Parser::new();
2055 parser
2056 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2057 .expect("C++ grammar");
2058 let tree = parser.parse(source, None).expect("C++ parse");
2059 let root = tree.root_node();
2060 let context = super::CPP_STRUCTURAL_SPEC.call_site_context(root, source);
2061 let compiled = CompiledKinds::compile(
2062 &tree_sitter_cpp::LANGUAGE.into(),
2063 super::CPP_STRUCTURAL_SPEC.kind_table(),
2064 );
2065 let mut structural = StructuralFactCollector::new(
2066 &super::CPP_STRUCTURAL_SPEC,
2067 source,
2068 &context,
2069 ParentIndex::new(root),
2070 usize::MAX,
2071 None,
2072 );
2073 let mut source_facts = PrimarySourceFactCollector::new(source);
2074 let mut pending = vec![(root, None)];
2075 while let Some((node, parent)) = pending.pop() {
2076 let mut child_parent = parent;
2077 if node.is_named()
2078 && let Some(raw_kind) = compiled.kind_of(&node)
2079 && super::CPP_STRUCTURAL_SPEC.should_extract(node, raw_kind)
2080 {
2081 let kind = super::CPP_STRUCTURAL_SPEC.refine_kind(
2082 node,
2083 raw_kind,
2084 parent.map(|id| structural.normalized_kind(id)),
2085 source,
2086 &context,
2087 );
2088 let id = structural
2089 .enter(node, kind, parent, &mut source_facts)
2090 .expect("unbounded C++ structural collector");
2091 let mut sink = structural.role_sink(&mut source_facts);
2092 super::CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2093 structural
2094 .accept_roles(id, sink.into_parts())
2095 .expect("unbounded C++ structural roles");
2096 child_parent = Some(id);
2097 }
2098 for index in (0..node.named_child_count()).rev() {
2099 if let Some(child) = node.named_child(index) {
2100 pending.push((child, child_parent));
2101 }
2102 }
2103 }
2104 structural
2105 .finish(root)
2106 .expect("C++ structural collection finishes")
2107 }
2108
2109 fn member_occurrences(source: &str) -> Vec<(usize, &str, OccurrenceRole)> {
2110 let mut parser = Parser::new();
2111 parser
2112 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2113 .expect("C++ grammar");
2114 let tree = parser.parse(source, None).expect("C++ parse");
2115 assert!(
2116 !tree.root_node().has_error(),
2117 "{}",
2118 tree.root_node().to_sexp()
2119 );
2120 let mut found = Vec::new();
2121 let mut pending = vec![tree.root_node()];
2122 while let Some(node) = pending.pop() {
2123 if let Some(role) = cpp_member_position(node) {
2124 found.push((
2125 node.start_byte(),
2126 &source[node.start_byte()..node.end_byte()],
2127 role,
2128 ));
2129 }
2130 for index in (0..node.named_child_count()).rev() {
2131 if let Some(child) = node.named_child(index) {
2132 pending.push(child);
2133 }
2134 }
2135 }
2136 found
2137 }
2138
2139 #[test]
2140 #[hardwon("#4505: generated C initializer identifiers triggered repeated sibling scans")]
2141 fn binder_classification_scans_large_initializer_lists_linearly() {
2142 use std::fmt::Write as _;
2143
2144 let mut source = String::from("int values[] = {");
2145 for index in 0..4096 {
2146 write!(source, "item_{index},").expect("append generated initializer item");
2147 }
2148 source.push_str("};\n");
2149
2150 let mut parser = Parser::new();
2151 parser
2152 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2153 .expect("C++ grammar");
2154 let tree = parser.parse(&source, None).expect("C++ parse");
2155 assert!(
2156 !tree.root_node().has_error(),
2157 "{}",
2158 tree.root_node().to_sexp()
2159 );
2160 let parents = ParentIndex::new(tree.root_node());
2161 let mut pending = vec![tree.root_node()];
2162 let mut identifiers = 0usize;
2163 let mut sibling_steps = 0usize;
2164 while let Some(node) = pending.pop() {
2165 if node.kind() == "identifier" {
2166 identifiers += 1;
2167 let _ = cpp_binder_owner(
2168 node,
2169 |node| parents.parent(node),
2170 |parent, child| {
2171 let mut cursor = parent.walk();
2172 if !cursor.goto_first_child() {
2173 return None;
2174 }
2175 loop {
2176 sibling_steps += 1;
2177 if cursor.node() == child {
2178 return cursor.field_name();
2179 }
2180 if !cursor.goto_next_sibling() {
2181 return None;
2182 }
2183 }
2184 },
2185 );
2186 }
2187 let mut cursor = node.walk();
2188 let children = node.children(&mut cursor).collect::<Vec<_>>();
2189 pending.extend(children.into_iter().rev());
2190 }
2191
2192 assert!(
2193 identifiers >= 4096,
2194 "parsed {identifiers} initializer identifiers"
2195 );
2196 assert!(
2197 sibling_steps <= identifiers * 4,
2198 "binder field lookup walked {sibling_steps} siblings for {identifiers} identifiers"
2199 );
2200 }
2201
2202 #[test]
2203 #[brokk_bifrost_test_macros::hardwon(
2204 "#4407: ABI 15 lambda declarators lost parameter binder identity"
2205 )]
2206 fn lambda_parameters_are_binders_without_leaking_into_callable_bodies() {
2207 let source = concat!(
2208 "int enclosing(int outer) {\n",
2209 " return [outer](int lambda) {\n",
2210 " return [lambda](int nested) {\n",
2211 " return outer + lambda + nested;\n",
2212 " }(lambda);\n",
2213 " }(outer);\n",
2214 "}\n",
2215 );
2216 let mut parser = Parser::new();
2217 parser
2218 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2219 .expect("C++ grammar");
2220 let tree = parser.parse(source, None).expect("C++ parse");
2221 assert!(
2222 !tree.root_node().has_error(),
2223 "{}",
2224 tree.root_node().to_sexp()
2225 );
2226
2227 let identifier_at = |start_byte| {
2228 let mut pending = vec![tree.root_node()];
2229 while let Some(node) = pending.pop() {
2230 if node.kind() == "identifier" && node.start_byte() == start_byte {
2231 return Some(node);
2232 }
2233 for index in (0..node.named_child_count()).rev() {
2234 if let Some(child) = node.named_child(index) {
2235 pending.push(child);
2236 }
2237 }
2238 }
2239 None
2240 };
2241 let outer_parameter =
2242 identifier_at(source.find("int outer").expect("outer parameter") + "int ".len())
2243 .expect("outer parameter");
2244 let lambda_parameter =
2245 identifier_at(source.find("int lambda").expect("lambda parameter") + "int ".len())
2246 .expect("lambda parameter");
2247 let nested_parameter =
2248 identifier_at(source.find("int nested").expect("nested parameter") + "int ".len())
2249 .expect("nested parameter");
2250
2251 for parameter in [outer_parameter, lambda_parameter, nested_parameter] {
2252 assert_eq!(
2253 cpp_binder_owner(parameter, |node| node.parent(), field_name_in_parent),
2254 Some(super::CppBinderOwner::Parameter)
2255 );
2256 assert_eq!(
2257 super::cpp_binding_activation(parameter, node_range(tree.root_node()))
2258 .map(|activation| activation.kind),
2259 Some(BindingKind::Parameter)
2260 );
2261 }
2262
2263 for (start, name) in [
2264 (
2265 source
2266 .find("return outer + lambda")
2267 .expect("outer reference")
2268 + "return ".len(),
2269 "outer",
2270 ),
2271 (
2272 source
2273 .find("outer + lambda + nested")
2274 .expect("lambda reference")
2275 + "outer + ".len(),
2276 "lambda",
2277 ),
2278 (
2279 source.find("lambda + nested;").expect("nested reference") + "lambda + ".len(),
2280 "nested",
2281 ),
2282 ] {
2283 let reference = identifier_at(start).expect("body reference identifier");
2284 assert_eq!(
2285 cpp_binder_owner(reference, |node| node.parent(), field_name_in_parent),
2286 None,
2287 "body reference {name:?} was classified as a binder"
2288 );
2289 }
2290 }
2291
2292 #[test]
2293 #[brokk_bifrost_test_macros::hardwon(
2294 "#4407: ABI 15 explicit-object receivers lost parameter binder identity"
2295 )]
2296 fn cpp_explicit_object_parameter_is_a_parameter_binder_without_body_leak() {
2297 let source = concat!(
2298 "struct Visitor {\n",
2299 " int visit(this Visitor const& self, int ordinary) {\n",
2300 " return self.value + ordinary;\n",
2301 " }\n",
2302 "};\n",
2303 "using Callback = void (*)(int nested);\n",
2304 );
2305 let mut parser = Parser::new();
2306 parser
2307 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2308 .expect("C++ grammar");
2309 let tree = parser.parse(source, None).expect("C++ parse");
2310 assert!(
2311 !tree.root_node().has_error(),
2312 "{}",
2313 tree.root_node().to_sexp()
2314 );
2315
2316 let identifier_at = |start: usize, len: usize| {
2317 tree.root_node()
2318 .descendant_for_byte_range(start, start + len)
2319 .filter(|node| node.kind() == "identifier")
2320 .expect("identifier at source span")
2321 };
2322 let receiver = identifier_at(source.find("self,").expect("receiver binder"), 4);
2323 let receiver_body = identifier_at(
2324 source.find("return self").expect("receiver body reference") + "return ".len(),
2325 4,
2326 );
2327 let function_type_parameter =
2328 identifier_at(source.find("nested").expect("function type"), 6);
2329 let parents = ParentIndex::new(tree.root_node());
2330
2331 assert_eq!(receiver.kind(), "identifier");
2332 assert_eq!(
2333 cpp_binder_owner(receiver, |node| node.parent(), field_name_in_parent),
2334 Some(super::CppBinderOwner::Parameter)
2335 );
2336 assert!(super::cpp_is_binder(receiver, &parents));
2337 assert_eq!(
2338 super::cpp_binding_activation(receiver, node_range(tree.root_node()))
2339 .map(|activation| activation.kind),
2340 Some(BindingKind::Parameter)
2341 );
2342 assert_eq!(
2343 cpp_binder_owner(receiver_body, |node| node.parent(), field_name_in_parent),
2344 None,
2345 "the receiver read in the body is not another binder"
2346 );
2347 assert!(!super::cpp_is_binder(receiver_body, &parents));
2348 assert_eq!(
2349 cpp_binder_owner(
2350 function_type_parameter,
2351 |node| node.parent(),
2352 field_name_in_parent
2353 ),
2354 None,
2355 "a function-type parameter remains outside callable binding ownership"
2356 );
2357 }
2358
2359 #[test]
2360 fn attributed_declarator_resolves_its_declared_name() {
2361 let source = "int x [[maybe_unused]]; struct Widget { operator bool() const; };";
2362 let mut parser = Parser::new();
2363 parser
2364 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2365 .expect("C++ grammar");
2366 let tree = parser.parse(source, None).expect("C++ parse");
2367 assert!(
2368 !tree.root_node().has_error(),
2369 "{}",
2370 tree.root_node().to_sexp()
2371 );
2372
2373 let mut pending = vec![tree.root_node()];
2374 let mut attributed = None;
2375 let mut conversion = None;
2376 while let Some(node) = pending.pop() {
2377 match node.kind() {
2378 "attributed_declarator" => attributed = Some(node),
2379 "operator_cast" => conversion = Some(node),
2380 _ => {}
2381 }
2382 for index in (0..node.named_child_count()).rev() {
2383 if let Some(child) = node.named_child(index) {
2384 pending.push(child);
2385 }
2386 }
2387 }
2388 let attributed = attributed.expect("attributed declarator");
2389 let name = super::declarator_name_node(attributed).expect("declarator name");
2390
2391 assert_eq!(&source[name.byte_range()], "x");
2392 let conversion = conversion.expect("conversion operator");
2393 assert_eq!(
2394 super::declarator_name_node(conversion).map(|node| node.kind()),
2395 Some("operator_cast")
2396 );
2397 }
2398
2399 #[test]
2400 fn cpp_member_position_is_limited_to_member_access_and_calls() {
2401 let source = concat!(
2402 "struct Widget {\n",
2403 " int value;\n",
2404 " int method(int label) {\n",
2405 " return this->value + label.value + label.method()\n",
2406 " + label.template convert<int>() + label.ns::member;\n",
2407 " }\n",
2408 "};\n",
2409 "int build(Widget widget) {\n",
2410 " Widget result{.value = widget.value};\n",
2411 " return widget.method(result.value);\n",
2412 "}\n",
2413 );
2414 let found = member_occurrences(source);
2415 let member_texts = found
2416 .iter()
2417 .map(|(_, text, role)| (*text, *role))
2418 .collect::<Vec<_>>();
2419
2420 assert_eq!(
2421 member_texts,
2422 vec![
2423 ("value", OccurrenceRole::MemberPosition),
2424 ("value", OccurrenceRole::MemberPosition),
2425 ("method", OccurrenceRole::MemberPosition),
2426 ("convert", OccurrenceRole::MemberPosition),
2427 ("member", OccurrenceRole::MemberPosition),
2428 ("value", OccurrenceRole::MemberPosition),
2429 ("method", OccurrenceRole::MemberPosition),
2430 ("value", OccurrenceRole::MemberPosition),
2431 ]
2432 );
2433
2434 let at = |needle: &str| source.find(needle).expect("fixture token");
2435 for receiver in ["this->", "label.value", "widget.value", "result.value"] {
2436 let receiver_start = at(receiver);
2437 assert!(
2438 found.iter().all(|(offset, _, _)| *offset != receiver_start),
2439 "receiver was classified: {receiver:?}"
2440 );
2441 }
2442 let non_members = [
2443 at("Widget {"),
2444 at("int value") + "int ".len(),
2445 at("int method") + "int ".len(),
2446 at(".value =") + 1,
2447 ];
2448 for unrelated_start in non_members {
2449 assert!(
2450 found
2451 .iter()
2452 .all(|(offset, _, _)| *offset != unrelated_start),
2453 "non-member identifier at byte {unrelated_start} was classified"
2454 );
2455 }
2456 }
2457
2458 #[test]
2459 #[brokk_bifrost_test_macros::hardwon(
2460 "#3466: C and C++ free-function calls published no resolver occurrence"
2461 )]
2462 fn cpp_occurrence_support_declares_member_position_and_free_callees() {
2463 let support = super::CPP_STRUCTURAL_SPEC.occurrence_role_support();
2464 assert!(support.is_supported(OccurrenceRole::MemberPosition));
2465 assert!(support.is_supported(OccurrenceRole::ValueReference));
2466 for role in [
2467 OccurrenceRole::ReceiverPosition,
2468 OccurrenceRole::LabelOrKey,
2469 OccurrenceRole::DeclarationName,
2470 ] {
2471 assert!(
2472 !support.is_supported(role),
2473 "unexpected C++ support for {role:?}"
2474 );
2475 }
2476 }
2477
2478 #[test]
2479 fn cpp_string_literals_publish_decoded_values() {
2480 let source = r#"
2481const char *ordinary = "http://example.test/a\n";
2482const char *raw = R"(https://example.test/path)";
2483const char *wide = L"wide";
2484const char character = 'x';
2485const char *mixed = R"(\u0041)" "\u0042";
2486"#;
2487 let rows = collect_structural_rows(source);
2488 let values = rows
2489 .nodes()
2490 .iter()
2491 .filter(|node| node.kind == NormalizedKind::StringLiteral)
2492 .map(|node| node.string_value.clone())
2493 .collect::<Vec<_>>();
2494
2495 assert_eq!(
2496 values,
2497 vec![
2498 Some("http://example.test/a\n".to_owned()),
2499 Some("https://example.test/path".to_owned()),
2500 None,
2501 None,
2502 Some(r"\u0041".to_owned()),
2503 Some("B".to_owned()),
2504 ]
2505 );
2506 }
2507
2508 #[test]
2509 fn cpp_numeric_string_escapes_follow_scalar_and_ascii_boundaries() {
2510 let mut cases = vec![
2513 (
2514 r"\101\x42\u0043\U00000044".to_owned(),
2515 Some("ABCD".to_owned()),
2516 ),
2517 (r"\177".to_owned(), Some("\u{7f}".to_owned())),
2518 (r"\200".to_owned(), None),
2519 (r"\377".to_owned(), None),
2520 (r"\x7f".to_owned(), Some("\u{7f}".to_owned())),
2521 (r"\x80".to_owned(), None),
2522 (r"\x0041".to_owned(), Some("A".to_owned())),
2523 (r"\x007f".to_owned(), Some("\u{7f}".to_owned())),
2524 (r"\x0080".to_owned(), None),
2525 (r"\xfffffffff".to_owned(), None),
2526 ];
2527 for scalar in [
2528 0,
2529 0x7f,
2530 0x80,
2531 0xd7ff,
2532 0xd800,
2533 0xdfff,
2534 0xe000,
2535 0x10ffff,
2536 0x110000,
2537 u32::MAX,
2538 ] {
2539 let expected = char::from_u32(scalar).map(|character| character.to_string());
2540 cases.push((format!("\\U{scalar:08x}"), expected.clone()));
2541 if scalar <= 0xffff {
2542 cases.push((format!("\\u{scalar:04x}"), expected));
2543 }
2544 }
2545 for (escape, expected) in cases {
2546 let source = format!("const char *value = \"{escape}\";");
2547 let mut parser = Parser::new();
2548 parser
2549 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2550 .unwrap();
2551 let tree = parser.parse(&source, None).unwrap();
2552 assert!(!tree.root_node().has_error(), "{source}");
2553 let mut stack = vec![tree.root_node()];
2554 let mut escapes = Vec::new();
2555 while let Some(node) = stack.pop() {
2556 if node.kind() == "escape_sequence" {
2557 escapes.push(node.byte_range());
2558 }
2559 stack.extend(node.named_children(&mut node.walk()));
2560 }
2561 assert!(!escapes.is_empty(), "grammar did not select {source}");
2562 if escape.starts_with(r"\x00") {
2563 let expected_span = 21..21 + escape.len();
2564 assert_eq!(
2565 escapes.as_slice(),
2566 std::slice::from_ref(&expected_span),
2567 "{source}"
2568 );
2569 }
2570 let rows = collect_structural_rows(&source);
2571 let values: Vec<_> = rows
2572 .nodes()
2573 .iter()
2574 .filter(|node| node.kind == NormalizedKind::StringLiteral)
2575 .map(|node| node.string_value.clone())
2576 .collect();
2577 assert_eq!(values, vec![expected], "{source}, escapes={escapes:?}");
2578 }
2579 }
2580
2581 #[test]
2586 #[brokk_bifrost_test_macros::hardwon(
2587 "#3466: C/C++ resolver call sites had no boundary row or callee occurrence"
2588 )]
2589 fn cpp_free_function_callees_are_value_references() {
2590 let source = concat!(
2591 "int bound(void) { return 1; }\n",
2592 "struct Widget { int method(void) { return 0; } };\n",
2593 "int go(struct Widget *widget) {\n",
2594 " return getenv(\"PATH\") != 0\n",
2595 " + ns::getenv(\"USER\") != 0\n",
2596 " + bound()\n",
2597 " + widget->method()\n",
2598 " + bound()();\n",
2599 "}\n",
2600 );
2601 let mut parser = Parser::new();
2602 parser
2603 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2604 .expect("C++ grammar");
2605 let tree = parser.parse(source, None).expect("C++ parse");
2606 assert!(
2607 !tree.root_node().has_error(),
2608 "{}",
2609 tree.root_node().to_sexp()
2610 );
2611
2612 let language: tree_sitter::Language = tree_sitter_cpp::LANGUAGE.into();
2617 let compiled = CompiledKinds::compile(&language, CPP_STRUCTURAL_SPEC.kind_table());
2618 let mut start_by_fact = HashMap::default();
2619 let mut extractions = Vec::new();
2620 let mut pending = vec![tree.root_node()];
2621 while let Some(node) = pending.pop() {
2622 if node.is_named()
2623 && let Some(kind) = compiled.kind_of(&node)
2624 && CPP_STRUCTURAL_SPEC.should_extract(node, kind)
2625 {
2626 start_by_fact.insert(node.id(), node.start_byte());
2627 extractions.push((node.start_byte(), node, kind));
2628 }
2629 for index in (0..node.named_child_count()).rev() {
2630 if let Some(child) = node.named_child(index) {
2631 pending.push(child);
2632 }
2633 }
2634 }
2635 extractions.sort_by_key(|(start, _, _)| *start);
2636 let mut source_facts =
2637 brokk_bifrost_core::analyzer::source_facts::PrimarySourceFactCollector::new(source);
2638 let parents = ParentIndex::new(tree.root_node());
2639 let mut sink = RoleSink::new(source, &mut source_facts, 64, None, &parents);
2640 for (_, node, kind) in extractions {
2641 CPP_STRUCTURAL_SPEC.extract(node, kind, &mut sink);
2642 }
2643 let (_, _, occurrences, stop) = sink.into_parts();
2644 assert_eq!(stop, None);
2645
2646 let roles_at = |needle: &str| {
2647 let start = source.find(needle).expect("fixture token");
2648 occurrences
2649 .iter()
2650 .filter(|occurrence| start_by_fact[&occurrence.target_node] == start)
2651 .map(|occurrence| occurrence.role)
2652 .collect::<Vec<_>>()
2653 };
2654 assert_eq!(
2658 roles_at("getenv(\"PATH\")"),
2659 vec![OccurrenceRole::ValueReference]
2660 );
2661 assert_eq!(
2662 roles_at("getenv(\"USER\")"),
2663 vec![OccurrenceRole::ValueReference],
2664 "the terminal name of a qualified callee is the value read"
2665 );
2666 assert_eq!(roles_at("bound()\n"), vec![OccurrenceRole::ValueReference]);
2667 assert_eq!(roles_at("method()"), vec![OccurrenceRole::MemberPosition]);
2670 assert!(roles_at("widget->").is_empty());
2673 }
2674
2675 #[test]
2679 fn cpp_subscript_operator_through_this_does_not_emit_role_for_non_fact() {
2680 collect_cpp_structural_facts(
2681 "struct Channel { int operator[](int); int at(int i) { return this->operator[](i); } };",
2682 );
2683 }
2684}
2685
2686#[cfg(test)]
2687mod grammar_boundary_tests {
2688 use super::*;
2689
2690 #[test]
2691 fn expression_and_pointer_declarator_name_fields() {
2692 let source = "void run() { auto p = new ns::Widget(); receiver.method<int>(); int (*callback)(int) = nullptr; int (ordinary)(int); }";
2693 let tree = crate::diagnostics::parse_cpp_tree(source);
2694 assert!(
2695 !tree.root_node().has_error(),
2696 "{}",
2697 tree.root_node().to_sexp()
2698 );
2699 let mut stack = vec![tree.root_node()];
2700 let mut names = Vec::new();
2701 let mut binders = Vec::new();
2702 while let Some(node) = stack.pop() {
2703 if matches!(node.kind(), "new_expression" | "field_expression") {
2704 let field = if node.kind() == "new_expression" {
2705 "type"
2706 } else {
2707 "field"
2708 };
2709 let child = node.child_by_field_name(field).unwrap();
2710 names.push((
2711 node.kind(),
2712 child.kind(),
2713 expression_name_node(node).map(|n| &source[n.byte_range()]),
2714 ));
2715 }
2716 if node.kind() == "identifier"
2717 && let Some(name @ ("callback" | "ordinary")) = node_text(node, source)
2718 {
2719 let wrapper = if name == "callback" {
2720 "pointer_declarator"
2721 } else {
2722 "parenthesized_declarator"
2723 };
2724 assert_eq!(node.parent().unwrap().kind(), wrapper);
2725 binders.push((
2726 name,
2727 cpp_binder_owner(node, |n| n.parent(), field_name_in_parent)
2728 .is_some_and(|owner| matches!(owner, CppBinderOwner::Local(_))),
2729 ));
2730 }
2731 stack.extend(
2732 node.named_children(&mut node.walk())
2733 .collect::<Vec<_>>()
2734 .into_iter()
2735 .rev(),
2736 );
2737 }
2738 assert_eq!(
2739 names,
2740 [
2741 ("new_expression", "qualified_identifier", Some("Widget")),
2742 ("field_expression", "template_method", Some("method"))
2743 ]
2744 );
2745 assert_eq!(binders, [("callback", true), ("ordinary", false)]);
2746 }
2747
2748 #[test]
2749 #[brokk_bifrost_test_macros::hardwon(
2750 "6ec52f232835: quicksilver round 4 survivor review: dependent member names lost their terminal token"
2751 )]
2752 fn dependent_member_name_grammar_boundary() {
2753 let source = "template<class T> void f(T receiver) { receiver.template method<int>(); }";
2754 let tree = crate::diagnostics::parse_cpp_tree(source);
2755 assert!(!tree.root_node().has_error());
2756 let mut stack = vec![tree.root_node()];
2757 let mut names = Vec::new();
2758 while let Some(node) = stack.pop() {
2759 if node.kind() == "dependent_name" {
2760 assert!(node.child_by_field_name("name").is_none());
2761 assert_eq!(node.named_child(0).unwrap().kind(), "template_method");
2762 assert_eq!(
2763 declarator_name_node(node).map(|name| &source[name.byte_range()]),
2764 Some("method")
2765 );
2766 assert_eq!(
2767 terminal_callee_leaf(node.parent().unwrap(), callee_spine_step)
2768 .map(|name| &source[name.byte_range()]),
2769 Some("method")
2770 );
2771 names.push(expression_name_node(node).map(|n| &source[n.byte_range()]));
2772 }
2773 stack.extend(node.named_children(&mut node.walk()));
2774 }
2775 assert_eq!(names, [Some("method")]);
2776 }
2777
2778 #[test]
2779 fn conditional_macro_name_fields() {
2780 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";
2781 let tree = crate::diagnostics::parse_cpp_tree(source);
2782 assert!(!tree.root_node().has_error());
2783 let mut stack = vec![tree.root_node()];
2784 let mut definitions = Vec::new();
2785 while let Some(node) = stack.pop() {
2786 if node.kind() == "preproc_function_def" {
2787 let name = node.child_by_field_name("name").unwrap();
2788 definitions.push((
2789 node_text(name, source).unwrap(),
2790 node.parent().unwrap().kind(),
2791 ));
2792 }
2793 stack.extend(
2794 node.named_children(&mut node.walk())
2795 .collect::<Vec<_>>()
2796 .into_iter()
2797 .rev(),
2798 );
2799 }
2800 assert_eq!(
2801 definitions,
2802 [
2803 ("FIRST", "preproc_if"),
2804 ("SECOND", "preproc_elif"),
2805 ("THIRD", "preproc_else"),
2806 ]
2807 );
2808 let mut names: Vec<_> = function_like_macro_names(tree.root_node(), source)
2809 .into_iter()
2810 .collect();
2811 names.sort();
2812 assert_eq!(names, ["FIRST", "SECOND", "THIRD"]);
2813 }
2814}
2815
2816#[cfg(test)]
2817mod result_use_tests {
2818 use super::{CppCallResultUseIndex, cpp_call_result_use};
2819 use brokk_bifrost_core::analyzer::structural::result_use::{
2820 CallResultUse, CallResultUseOpenReason, IntentionalDiscardKind,
2821 };
2822 use tree_sitter::Parser;
2823
2824 fn tree(source: &str) -> tree_sitter::Tree {
2825 let mut parser = Parser::new();
2826 parser
2827 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2828 .expect("C++ grammar");
2829 parser.parse(source, None).expect("C++ tree")
2830 }
2831
2832 fn calls<'tree>(tree: &'tree tree_sitter::Tree, source: &str) -> Vec<tree_sitter::Node<'tree>> {
2833 let mut found = Vec::new();
2834 let mut stack = vec![tree.root_node()];
2835 while let Some(node) = stack.pop() {
2836 if node.kind() == "call_expression" && !super::cpp_cast_call(node, source) {
2837 found.push(node);
2838 }
2839 for index in (0..node.named_child_count()).rev() {
2840 stack.push(node.named_child(index).expect("named child"));
2841 }
2842 }
2843 found.sort_by_key(|node| node.start_byte());
2844 found
2845 }
2846
2847 #[test]
2848 fn expression_statement_void_cast_and_nested_contexts_are_distinct() {
2849 let source = concat!(
2850 "int f();\n",
2851 "void sink(int);\n",
2852 "void run() {\n",
2853 " f();\n",
2854 " (void)f();\n",
2855 " static_cast<void>(f());\n",
2856 " sink(f());\n",
2857 " return;\n",
2858 "}\n",
2859 );
2860 let tree = tree(source);
2861 assert!(
2862 !tree.root_node().has_error(),
2863 "{}",
2864 tree.root_node().to_sexp()
2865 );
2866 let calls = calls(&tree, source);
2867 assert_eq!(calls.len(), 5);
2868 assert_eq!(
2869 cpp_call_result_use(calls[0], source).for_port(0),
2870 CallResultUse::Discarded
2871 );
2872 assert!(matches!(
2873 cpp_call_result_use(calls[1], source).for_port(0),
2874 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2875 ));
2876 assert!(matches!(
2877 cpp_call_result_use(calls[2], source).for_port(0),
2878 CallResultUse::IntentionalDiscard(IntentionalDiscardKind::CppVoidCast)
2879 ));
2880 assert_eq!(
2881 cpp_call_result_use(calls[3], source).for_port(0),
2882 CallResultUse::Discarded
2883 );
2884 assert_eq!(
2885 cpp_call_result_use(calls[4], source).for_port(0),
2886 CallResultUse::Consumed
2887 );
2888 }
2889
2890 #[test]
2891 fn lambda_tail_and_recovered_syntax_are_open() {
2892 let source = "int f(); void run() { auto value = [] { return f(); }; f(+); }";
2898 let tree = tree(source);
2899 assert!(tree.root_node().has_error());
2900 let calls = calls(&tree, source);
2901 assert!(matches!(
2902 cpp_call_result_use(calls[0], source).for_port(0),
2903 CallResultUse::Open(CallResultUseOpenReason::DeferredContext)
2904 ));
2905 assert_eq!(
2906 cpp_call_result_use(calls[1], source).for_port(0),
2907 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax)
2908 );
2909 }
2910
2911 #[test]
2912 fn exact_index_keeps_call_spans_separate() {
2913 let source = "int f(); void run() { f(); }";
2914 let tree = tree(source);
2915 let calls = calls(&tree, source);
2916 let index = CppCallResultUseIndex::new(tree.root_node(), source);
2917 assert_eq!(
2918 index
2919 .at_span(calls[0].start_byte(), calls[0].end_byte())
2920 .expect("indexed call")
2921 .for_port(0),
2922 CallResultUse::Discarded
2923 );
2924 assert!(index.at_span(source.len(), source.len()).is_err());
2925 }
2926
2927 #[test]
2928 fn result_use_grammar_boundary_sites_and_recovery() {
2929 for (source, expected) in [
2932 (
2933 "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()); }",
2934 vec![
2935 ("a()", CallResultUse::Consumed),
2936 ("b()", CallResultUse::Consumed),
2937 ("c()", CallResultUse::Consumed),
2938 ("d()", CallResultUse::Consumed),
2939 ("new W()", CallResultUse::Discarded),
2940 ("new X()", CallResultUse::Consumed),
2941 ("sink(new Y())", CallResultUse::Discarded),
2942 ("new Y()", CallResultUse::Consumed),
2943 ],
2944 ),
2945 (
2946 "void run() { try { f(); } }",
2947 vec![(
2948 "f()",
2949 CallResultUse::Open(CallResultUseOpenReason::RecoveredSyntax),
2950 )],
2951 ),
2952 ] {
2953 let tree = tree(source);
2954 let mut stack = vec![tree.root_node()];
2955 let mut grammar_sites = Vec::new();
2956 let mut recovery_witness = false;
2957 while let Some(node) = stack.pop() {
2958 if matches!(node.kind(), "call_expression" | "new_expression") {
2959 grammar_sites.push(node.byte_range());
2960 if source[node.byte_range()] == *"f()" {
2961 assert!(!node.has_error());
2962 assert!(!node.parent().unwrap().is_error());
2963 let mut parent = node.parent();
2964 while let Some(ancestor) = parent {
2965 recovery_witness |= ancestor.is_error();
2966 parent = ancestor.parent();
2967 }
2968 }
2969 }
2970 stack.extend(node.named_children(&mut node.walk()));
2971 }
2972 if source.contains("try") {
2973 assert!(recovery_witness, "{}", tree.root_node().to_sexp());
2974 } else {
2975 assert!(!tree.root_node().has_error());
2976 assert_eq!(
2977 grammar_sites.len(),
2978 12,
2979 "four cast wrappers plus eight sites"
2980 );
2981 }
2982 let index = CppCallResultUseIndex::new(tree.root_node(), source);
2983 let actual: Vec<_> = index
2984 .sites()
2985 .iter()
2986 .map(|site| (&source[site.start_byte..site.end_byte], site.for_port(0)))
2987 .collect();
2988 assert_eq!(actual, expected, "{source}");
2989 }
2990 }
2991
2992 #[test]
2993 #[brokk_bifrost_test_macros::hardwon(
2994 "6ec52f232835: quicksilver round 4 survivor review: void pointer casts falsely discarded source values"
2995 )]
2996 fn pointer_to_void_casts_consume_the_source_value() {
2997 let source = "int* make(); void run() { auto p = (void*)make(); auto q = static_cast<void*>(make()); }";
2998 let tree = tree(source);
2999 assert!(!tree.root_node().has_error());
3000 let mut stack = vec![tree.root_node()];
3001 let mut type_declarators = Vec::new();
3002 while let Some(node) = stack.pop() {
3003 if node.kind() == "type_descriptor" {
3004 type_declarators.push(node.child_by_field_name("declarator").map(|n| n.kind()));
3005 }
3006 stack.extend(node.named_children(&mut node.walk()));
3007 }
3008 assert_eq!(
3009 type_declarators,
3010 [
3011 Some("abstract_pointer_declarator"),
3012 Some("abstract_pointer_declarator")
3013 ]
3014 );
3015 let index = CppCallResultUseIndex::new(tree.root_node(), source);
3016 let uses: Vec<_> = index.sites().iter().map(|site| site.for_port(0)).collect();
3019 assert_eq!(uses, [CallResultUse::Consumed, CallResultUse::Consumed]);
3020 }
3021}