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