1use crate::declarations::{
18 CppRecoveredExportClassIndex, cpp_file_using_namespaces, cpp_member_fq,
19 extract_function_declarator, node_text, recovered_callable_body_at, recovered_class_body_at,
20};
21use crate::graph::CppGraphSource;
22use crate::graph::resolver::{
23 VisibilityIndex, cpp_include_closure_reaches, cpp_type_name_components, declarator_name_node,
24 qualified_name_has_concrete_scope_separators,
25};
26use crate::graph_support::CppSource;
27use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
28use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
29use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range, SignatureMetadata};
30use brokk_bifrost_core::analyzer::query_token::QueryToken;
31use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
32use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
33use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
34use brokk_bifrost_core::hash::HashMap;
35use brokk_bifrost_core::path_utils::rel_path_string;
36use brokk_bifrost_core::profiling;
37use std::sync::Arc;
38use tree_sitter::{Node, Parser, Tree};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CppCallableUnitRole {
42 DeclarationOnly,
43 Definition,
44 Both,
45 Unknown,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum CppOccurrenceRole {
50 DeclarationOnly,
51 Definition,
52 Both,
53 Unknown,
54}
55
56impl CppOccurrenceRole {
57 pub fn api_label(self) -> Option<&'static str> {
58 match self {
59 Self::DeclarationOnly => Some("declaration"),
60 Self::Definition => Some("definition"),
61 Self::Both | Self::Unknown => None,
62 }
63 }
64}
65
66pub struct CppOccurrenceClassifier {
67 tree: Tree,
68 source: String,
71 recovered_export_classes: CppRecoveredExportClassIndex,
74}
75
76impl CppOccurrenceClassifier {
77 pub fn new(source: &str) -> Option<Self> {
78 let mut parser = Parser::new();
79 parser
80 .set_language(&tree_sitter_cpp::LANGUAGE.into())
81 .ok()?;
82 parser.parse(source, None).map(|tree| {
83 let recovered_export_classes =
84 CppRecoveredExportClassIndex::build(tree.root_node(), source);
85 Self {
86 tree,
87 source: source.to_owned(),
88 recovered_export_classes,
89 }
90 })
91 }
92
93 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
94 cpp_occurrence_role_for_range(
95 &self.recovered_export_classes,
96 self.tree.root_node(),
97 &self.source,
98 candidate,
99 range,
100 )
101 }
102}
103
104pub fn cpp_callable_unit_role(
105 index: &dyn CodeUnitIndex,
106 callable: &CodeUnit,
107) -> CppCallableUnitRole {
108 cpp_callable_unit_role_from_metadata(callable, index.signature_metadata(callable))
109}
110
111fn cpp_callable_unit_role_from_metadata(
112 callable: &CodeUnit,
113 metadata: impl IntoIterator<Item = SignatureMetadata>,
114) -> CppCallableUnitRole {
115 if !callable.is_callable() {
116 return CppCallableUnitRole::Unknown;
117 }
118 let mut declaration = false;
119 let mut definition = false;
120 for metadata in metadata {
121 if metadata.is_declaration_only() {
122 declaration = true;
123 } else {
124 definition = true;
125 }
126 }
127 match (declaration, definition) {
128 (true, false) => CppCallableUnitRole::DeclarationOnly,
129 (false, true) => CppCallableUnitRole::Definition,
130 (true, true) => CppCallableUnitRole::Both,
131 (false, false) => CppCallableUnitRole::Unknown,
132 }
133}
134
135pub fn cpp_indexed_callable_linkage(
136 index: &dyn CodeUnitIndex,
137 callable: &CodeUnit,
138) -> Option<CallableLinkage> {
139 let mut external = false;
140 for metadata in index.signature_metadata(callable) {
141 match metadata.callable_linkage() {
142 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
143 Some(CallableLinkage::External) => external = true,
144 None => {}
145 }
146 }
147 external.then_some(CallableLinkage::External)
148}
149
150pub fn cpp_callable_definitions_share_identity_evidence(
156 index: &dyn CodeUnitIndex,
157 left: &CodeUnit,
158 right: &CodeUnit,
159 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
160) -> bool {
161 left.source() == right.source()
162 || (left.fq_name() == right.fq_name()
163 && left.signature() == right.signature()
164 && matches!(
165 cpp_indexed_callable_linkage(index, left),
166 Some(CallableLinkage::External)
167 )
168 && matches!(
169 cpp_indexed_callable_linkage(index, right),
170 Some(CallableLinkage::External)
171 )
172 && header_body_related(left.source(), right.source()))
173}
174
175pub fn cpp_callable_definitions_share_identity_evidence_with_visibility(
191 analyzer: &CppGraphSource<'_>,
192 visibility: &VisibilityIndex<'_>,
193 left: &CodeUnit,
194 right: &CodeUnit,
195 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
196) -> bool {
197 left.source() == right.source()
198 || (left.fq_name() == right.fq_name()
199 && visibility.same_logical_callable(analyzer, left, right)
200 && matches!(
201 cpp_indexed_callable_linkage(analyzer.index, left),
202 Some(CallableLinkage::External)
203 )
204 && matches!(
205 cpp_indexed_callable_linkage(analyzer.index, right),
206 Some(CallableLinkage::External)
207 )
208 && header_body_related(left.source(), right.source()))
209}
210
211pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
215 let mut current = Some(node);
216 while let Some(candidate) = current {
217 let Some(parent) = candidate.parent() else {
218 return false;
219 };
220 if parent.kind() == "for_range_loop" {
221 return parent
222 .child_by_field_name("declarator")
223 .is_some_and(|declarator| {
224 cpp_range_for_declarator_contains_name(declarator, node)
225 });
226 }
227 current = Some(parent);
228 }
229 false
230}
231
232pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
241 while let Some(parent) = node.parent() {
242 if parent.kind() == "operator_cast" {
243 return true;
244 }
245 if matches!(
246 parent.kind(),
247 "function_declarator" | "declaration" | "function_definition" | "translation_unit"
248 ) {
249 return false;
250 }
251 node = parent;
252 }
253 false
254}
255
256pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
266 if node.kind() != "type_identifier" {
267 return false;
268 }
269 let Some(parameter) = node.parent() else {
270 return false;
271 };
272 if parameter.kind() != "parameter_declaration"
273 || parameter.child_by_field_name("type") != Some(node)
274 || parameter.child_by_field_name("declarator").is_some()
275 || parameter
276 .parent()
277 .is_none_or(|parent| parent.kind() != "parameter_list")
278 {
279 return false;
280 }
281
282 parameter
283 .prev_named_sibling()
284 .is_some_and(cpp_is_recovered_character_quote)
285 && parameter
286 .next_named_sibling()
287 .is_some_and(cpp_is_recovered_character_quote)
288}
289
290fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
291 node.is_error()
292 && node.child_count() == 1
293 && node
294 .child(0)
295 .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
296}
297
298pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
321 cpp_is_declared_constructor_or_destructor_name(node)
322 || cpp_is_recovered_constructor_or_destructor_name(node, source)
323}
324
325fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
331 let mut name = node;
332 if let Some(parent) = name.parent()
333 && parent.kind() == "destructor_name"
334 {
335 name = parent;
336 }
337 while let Some(parent) = name.parent() {
341 if parent.kind() != "qualified_identifier"
342 || parent.child_by_field_name("name") != Some(name)
343 {
344 break;
345 }
346 name = parent;
347 }
348 let Some(declarator) = name.parent() else {
349 return false;
350 };
351 if declarator.kind() != "function_declarator"
352 || declarator.child_by_field_name("declarator") != Some(name)
353 {
354 return false;
355 }
356 let Some(owner) = declarator.parent() else {
357 return false;
358 };
359 matches!(owner.kind(), "declaration" | "function_definition")
360 && owner.child_by_field_name("declarator") == Some(declarator)
361 && owner.child_by_field_name("type").is_none()
362}
363
364fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
382 if node.kind() != "identifier" {
383 return false;
384 }
385 let Some(call) = node.parent() else {
386 return false;
387 };
388 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
389 return false;
390 }
391 let mut current = call.parent();
392 while let Some(ancestor) = current {
393 if ancestor.kind() == "function_definition" {
394 return ancestor
395 .child_by_field_name("declarator")
396 .is_some_and(|declarator| declarator.kind() == "identifier")
397 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
398 }
399 current = ancestor.parent();
400 }
401 false
402}
403
404fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
407 let header_end = definition
408 .child_by_field_name("body")
409 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
410 let mut stack = vec![definition];
411 while let Some(node) = stack.pop() {
412 if node.start_byte() >= header_end {
413 continue;
414 }
415 if matches!(
416 node.kind(),
417 "identifier" | "type_identifier" | "namespace_identifier"
418 ) && node_text(node, source) == name
419 {
420 return true;
421 }
422 let mut cursor = node.walk();
423 for child in node.named_children(&mut cursor) {
424 stack.push(child);
425 }
426 }
427 false
428}
429
430fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
431 let mut pending = vec![declarator];
432 while let Some(candidate) = pending.pop() {
433 match candidate.kind() {
434 "identifier" | "field_identifier" => {
435 if cpp_same_node(candidate, target) {
436 return true;
437 }
438 }
439 "structured_binding_declarator" => {
440 let mut cursor = candidate.walk();
441 if candidate
442 .named_children(&mut cursor)
443 .any(|name| cpp_same_node(name, target))
444 {
445 return true;
446 }
447 }
448 "pointer_declarator"
449 | "reference_declarator"
450 | "array_declarator"
451 | "attributed_declarator"
452 | "parenthesized_declarator"
453 | "function_declarator"
454 | "init_declarator" => {
455 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
456 pending.push(inner);
457 }
458 }
459 _ => {}
460 }
461 }
462 false
463}
464
465fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
466 node.child_by_field_name("declarator").or_else(|| {
467 let mut cursor = node.walk();
468 node.named_children(&mut cursor).find(|child| {
469 matches!(
470 child.kind(),
471 "identifier"
472 | "field_identifier"
473 | "structured_binding_declarator"
474 | "pointer_declarator"
475 | "reference_declarator"
476 | "array_declarator"
477 | "attributed_declarator"
478 | "parenthesized_declarator"
479 | "function_declarator"
480 | "init_declarator"
481 )
482 })
483 })
484}
485
486fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
487 left.id() == right.id()
488 && left.start_byte() == right.start_byte()
489 && left.end_byte() == right.end_byte()
490}
491
492pub fn cpp_header_body_files_are_related(
509 source: &dyn CppSource,
510 token: QueryToken<'_>,
511 left: &ProjectFile,
512 right: &ProjectFile,
513) -> bool {
514 let (header, implementation) = if cpp_source_path_is_header(left) {
515 (left, right)
516 } else if cpp_source_path_is_header(right) {
517 (right, left)
518 } else {
519 return false;
520 };
521 if cpp_source_path_is_header(implementation) {
522 return false;
523 }
524 cpp_include_closure_reaches(source, token, implementation, header)
525}
526
527pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
528 let path = rel_path_string(source).to_ascii_lowercase();
529 matches!(
530 path.rsplit('.').next(),
531 Some("h" | "hin" | "hh" | "hpp" | "hxx")
532 )
533}
534
535pub fn cpp_occurrence_role_for_range(
536 recovered_export_classes: &CppRecoveredExportClassIndex,
537 root: Node<'_>,
538 source: &str,
539 candidate: &CodeUnit,
540 range: &Range,
541) -> CppOccurrenceRole {
542 if !candidate.is_callable() && !candidate.is_class() {
543 return CppOccurrenceRole::Both;
544 }
545 let Some(node) = cpp_declaration_node_for_range(root, range) else {
546 return CppOccurrenceRole::Unknown;
547 };
548 if candidate.is_callable() {
549 if subtree_contains(node, |descendant| {
550 descendant.kind() == "function_definition"
551 && descendant.child_by_field_name("body").is_some()
552 }) {
553 return CppOccurrenceRole::Definition;
554 }
555 if !matches!(
562 node.kind(),
563 "declaration" | "field_declaration" | "function_definition"
564 ) && let Some(has_body) = recovered_callable_body_at(source, range)
565 {
566 return if has_body {
567 CppOccurrenceRole::Definition
568 } else {
569 CppOccurrenceRole::DeclarationOnly
570 };
571 }
572 return CppOccurrenceRole::DeclarationOnly;
573 }
574 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
575 return CppOccurrenceRole::Definition;
576 }
577 if let Some(has_body) = recovered_class_body_at(
585 recovered_export_classes,
586 root,
587 source,
588 candidate.identifier(),
589 range,
590 ) {
591 return if has_body {
592 CppOccurrenceRole::Definition
593 } else {
594 CppOccurrenceRole::DeclarationOnly
595 };
596 }
597 if !subtree_contains(node, |descendant| {
598 matches!(
599 descendant.kind(),
600 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
601 )
602 }) {
603 return CppOccurrenceRole::Both;
604 }
605 if subtree_contains(node, |descendant| {
606 matches!(
607 descendant.kind(),
608 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
609 ) && descendant.child_by_field_name("body").is_some()
610 }) {
611 CppOccurrenceRole::Definition
612 } else {
613 CppOccurrenceRole::DeclarationOnly
614 }
615}
616
617pub fn cpp_range_is_pure_virtual_declaration(root: Node<'_>, source: &str, range: &Range) -> bool {
628 let Some(node) = cpp_declaration_node_for_range(root, range) else {
629 return false;
630 };
631 let mut current = Some(node);
632 while let Some(node) = current {
633 match node.kind() {
634 "field_declaration" => {
635 return node
639 .child_by_field_name("default_value")
640 .is_some_and(|value| {
641 value.kind() == "number_literal" && node_text(value, source) == "0"
642 })
643 && node
644 .child_by_field_name("declarator")
645 .and_then(extract_function_declarator)
646 .is_some();
647 }
648 "field_declaration_list" | "function_definition" | "translation_unit" => return false,
651 _ => current = node.parent(),
652 }
653 }
654 false
655}
656
657fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
658 node_for_exact_range(root, range).or_else(|| {
659 root.descendant_for_byte_range(range.start_byte, range.end_byte)
660 .and_then(|mut node| {
661 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
662 node = node.parent()?;
663 }
664 Some(node)
665 })
666 })
667}
668
669#[derive(Default)]
681pub struct CppReconciledDefinitionIndex {
682 pub rekeyed: Vec<CodeUnit>,
684 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
686}
687
688#[derive(Debug, Clone, PartialEq, Eq, Hash)]
704pub struct CppReconcileGroupKey {
705 pub member_identifier: String,
708 pub owner_terminal: Option<String>,
712}
713
714pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
721 let interner = segment_interner();
722 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
723 let (member_identifier, _) = interner.resolve(query_fq.last()?);
724 if member_identifier.is_empty() {
725 return None;
726 }
727 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
737 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
738 text.rsplit_once('$')
743 .map_or(text, |(_, tail)| tail)
744 .to_string()
745 });
746 Some(CppReconcileGroupKey {
747 member_identifier: member_identifier.to_string(),
748 owner_terminal,
749 })
750}
751
752pub struct CppReconcileCandidates {
758 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
759 all: Vec<CodeUnit>,
763}
764
765impl CppReconcileCandidates {
766 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
769 match &key.owner_terminal {
770 Some(owner_terminal) => self
771 .by_owner_terminal
772 .get(owner_terminal)
773 .map_or(&[][..], Vec::as_slice),
774 None => &self.all,
775 }
776 }
777
778 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
780 self.all.iter()
781 }
782
783 pub fn bucketed_len(&self) -> usize {
786 self.by_owner_terminal.values().map(Vec::len).sum()
787 }
788}
789
790pub fn cpp_reconcile_candidates_from_units(
797 candidates: impl IntoIterator<Item = CodeUnit>,
798 keep_going: &dyn Fn() -> bool,
799) -> Option<CppReconcileCandidates> {
800 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
801 candidates.sort();
802 candidates.dedup();
803 let interner = segment_interner();
804 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
805 let mut all = Vec::new();
806 for (index, unit) in candidates.into_iter().enumerate() {
807 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
811 return None;
812 }
813 if !unit.is_callable() {
814 continue;
815 }
816 let owner_terminal = unit
817 .fq()
818 .segments()
819 .iter()
820 .filter_map(|&segment| {
821 let (text, kind) = interner.resolve(segment);
822 matches!(
826 kind,
827 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
828 )
829 .then_some(text)
830 })
831 .last();
832 if let Some(owner_terminal) = owner_terminal {
833 by_owner_terminal
834 .entry(owner_terminal.to_string())
835 .or_default()
836 .push(unit.clone());
837 }
838 all.push(unit);
839 }
840 Some(CppReconcileCandidates {
841 by_owner_terminal,
842 all,
843 })
844}
845
846const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
848
849pub fn cpp_reconcile_group(
866 cpp: &dyn CppSource,
867 token: QueryToken<'_>,
868 key: &CppReconcileGroupKey,
869 candidates: &CppReconcileCandidates,
870 keep_going: &dyn Fn() -> bool,
871 on_candidate: &dyn Fn(),
872) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
873 let _scope = profiling::scope_with(|| {
874 format!(
875 "cpp.reconciled.build[{}#{}]",
876 key.member_identifier,
877 key.owner_terminal.as_deref().unwrap_or("*")
878 )
879 });
880 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
881 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
882 for unit in candidates.for_group(key) {
883 if !keep_going() {
888 return None;
889 }
890 on_candidate();
891 let _candidate =
894 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
895 let role = {
896 let _role = profiling::scope("cpp.reconcile.role");
897 cpp.stored_callable_unit_role(unit)
898 };
899 if !matches!(
900 role,
901 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
902 ) {
903 continue;
904 }
905 let Some(reconciled) =
906 cpp_reconcile_definition_identity(cpp, token, unit, &mut using_by_file)
907 else {
908 continue;
909 };
910 let canonical_fq = reconciled.fq_name();
911 if unit.fq_name() == canonical_fq {
918 continue;
919 }
920 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
929 let fq = cpp_member_fq(&reconciled.package, &short_name);
930 let rekeyed = CodeUnit::with_signature_and_fq(
931 unit.source().clone(),
932 unit.kind(),
933 reconciled.package,
934 short_name,
935 unit.signature().map(str::to_string),
936 unit.is_synthetic(),
937 fq,
938 );
939 let index = groups.entry(canonical_fq).or_default();
940 index.rekeyed.push(rekeyed.clone());
941 index.provisional_of.insert(rekeyed, unit.clone());
942 }
943 Some(
944 groups
945 .into_iter()
946 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
947 .collect(),
948 )
949}
950
951fn cpp_reconcile_definition_identity(
957 cpp: &dyn CppSource,
958 token: QueryToken<'_>,
959 unit: &CodeUnit,
960 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
961) -> Option<ReconciledIdentity> {
962 let interner = segment_interner();
972 let mut provisional_owner_segments: Vec<&str> = Vec::new();
973 let mut member: Option<&str> = None;
974 for &segment in unit.fq().segments() {
975 let (text, kind) = interner.resolve(segment);
976 match kind {
977 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
978 if member.is_some() {
982 return None;
983 }
984 if !text.is_empty() {
985 provisional_owner_segments.push(text);
986 }
987 }
988 SegmentKind::Member => member = Some(text),
989 _ => return None,
990 }
991 }
992 let member = member?;
993 let structured_owner_segments = cpp_structured_out_of_line_owner_segments(cpp, token, unit)
998 .filter(|segments| segments.len() == 1);
999 let owner_segments = structured_owner_segments.as_ref().map_or_else(
1000 || provisional_owner_segments,
1001 |segments| segments.iter().map(String::as_str).collect(),
1002 );
1003 if owner_segments.is_empty() {
1004 return None;
1005 }
1006
1007 let using = using_by_file
1008 .entry(unit.source().clone())
1009 .or_insert_with(|| {
1010 Arc::new(
1011 cpp.file_source(unit.source())
1012 .map(|source| cpp_file_using_namespaces(&source))
1013 .unwrap_or_default(),
1014 )
1015 })
1016 .clone();
1017 let mut namespace_candidates: Vec<&str> = vec![""];
1018 namespace_candidates.extend(using.iter().map(String::as_str));
1019
1020 let visible = {
1021 let _visible = profiling::scope_with(|| {
1022 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
1023 });
1024 cpp.visible_type_units(unit.source())
1025 };
1026 let class_table: Vec<VisibleClass> = visible
1027 .iter()
1028 .filter(|candidate| candidate.is_class())
1029 .map(|candidate| VisibleClass {
1030 package: candidate.package_name(),
1031 nested_short_name: candidate.short_name(),
1032 })
1033 .collect();
1034
1035 reconcile_out_of_line_member_identity(
1036 &owner_segments,
1037 member,
1038 &namespace_candidates,
1039 &class_table,
1040 )
1041}
1042
1043fn cpp_structured_out_of_line_owner_segments(
1050 cpp: &dyn CppSource,
1051 token: QueryToken<'_>,
1052 unit: &CodeUnit,
1053) -> Option<Vec<String>> {
1054 let prepared = cpp.prepared_syntax(token, unit.source())?;
1055 let root = prepared.tree().root_node();
1056 for range in cpp.ranges(unit) {
1057 let mut current = cpp_declaration_node_for_range(root, &range)?;
1058 let function = loop {
1059 if current.kind() == "function_definition" {
1060 break current;
1061 }
1062 current = current.parent()?;
1063 };
1064 if function.child_by_field_name("body").is_none() {
1065 continue;
1066 }
1067 let declarator = function.child_by_field_name("declarator")?;
1068 let name = declarator_name_node(declarator)?;
1069 if !qualified_name_has_concrete_scope_separators(name) {
1070 continue;
1071 }
1072 let mut components = cpp_type_name_components(name, prepared.source())?;
1073 components.pop()?;
1074 if !components.is_empty() {
1075 return Some(components);
1076 }
1077 }
1078 None
1079}
1080
1081#[cfg(test)]
1082mod tests {
1083 use super::*;
1084
1085 fn parse_cpp(source: &str) -> Tree {
1086 let mut parser = Parser::new();
1087 parser
1088 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1089 .expect("cpp language");
1090 parser.parse(source, None).expect("cpp tree")
1091 }
1092
1093 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1094 let end = start + text.len();
1095 assert_eq!(&source[start..end], text, "the probe must name the token");
1096 let node = tree
1097 .root_node()
1098 .named_descendant_for_byte_range(start, end)
1099 .expect("a node spans the probed range");
1100 assert_eq!(
1101 (node.start_byte(), node.end_byte()),
1102 (start, end),
1103 "the probed range must be exactly one node: {}",
1104 node.to_sexp()
1105 );
1106 cpp_is_constructor_or_destructor_declarator_name(node, source)
1107 }
1108
1109 fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1110 let end = start + text.len();
1111 assert_eq!(&source[start..end], text, "the probe must name the token");
1112 let node = tree
1113 .root_node()
1114 .named_descendant_for_byte_range(start, end)
1115 .expect("a node spans the probed range");
1116 assert_eq!(
1117 (node.start_byte(), node.end_byte()),
1118 (start, end),
1119 "the probed range must be exactly one node: {}",
1120 node.to_sexp()
1121 );
1122 cpp_is_conversion_operator_target_type(node)
1123 }
1124
1125 fn is_recovered_macro_character_type(
1126 tree: &Tree,
1127 source: &str,
1128 start: usize,
1129 text: &str,
1130 ) -> bool {
1131 let end = start + text.len();
1132 assert_eq!(&source[start..end], text, "the probe must name the token");
1133 let node = tree
1134 .root_node()
1135 .named_descendant_for_byte_range(start, end)
1136 .expect("a node spans the probed range");
1137 cpp_is_recovered_macro_character_token_type(node)
1138 }
1139
1140 #[test]
1141 fn recovered_macro_character_tokens_are_not_type_references() {
1142 let source = concat!(
1143 "struct I {};\n",
1144 "#define STRING_TOKEN_(name, ...)\n",
1145 "struct Schema {\n",
1146 " STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1147 " void ordinary(I value);\n",
1148 " void malformed(I value, @);\n",
1149 " void use() { I value; consume('I'); }\n",
1150 "};\n",
1151 );
1152 let tree = parse_cpp(source);
1153 let recovered_m = source.find("'m'").expect("recovered m") + 1;
1154 let recovered_i = source.find("'I'").expect("recovered I") + 1;
1155 for (label, start, text) in [
1156 ("lowercase character token", recovered_m, "m"),
1157 ("uppercase character token", recovered_i, "I"),
1158 ] {
1159 assert!(
1160 is_recovered_macro_character_type(&tree, source, start, text),
1161 "{label} must match the exact recovery role"
1162 );
1163 }
1164
1165 let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1166 let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1167 let malformed =
1168 source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1169 let local = source
1170 .find("I value; consume")
1171 .expect("local type reference");
1172 let expression_character = source.rfind("'I'").expect("expression character") + 1;
1173 for (label, start, text) in [
1174 ("unquoted macro argument", macro_first_argument, "MaxItems"),
1175 ("ordinary parameter type", ordinary, "I"),
1176 ("parameter beside another error", malformed, "I"),
1177 ("local type reference", local, "I"),
1178 ("expression character literal", expression_character, "I"),
1179 ] {
1180 assert!(
1181 !is_recovered_macro_character_type(&tree, source, start, text),
1182 "{label} must remain outside the recovery role"
1183 );
1184 }
1185 }
1186
1187 #[test]
1188 fn conversion_operator_target_components_are_identity_syntax_only() {
1189 let source = concat!(
1190 "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1191 "using other::Target;\n",
1192 "struct Source {\n",
1193 " operator Target() const;\n",
1194 " operator other::Target() const { return other::Target{}; }\n",
1195 " template<class T> operator other::Box<T>() const { return {}; }\n",
1196 " operator other::Target const&() const;\n",
1197 " operator other::Target*() const;\n",
1198 " other::Target ordinary() const {\n",
1199 " return reinterpret_cast<other::Target&>(*this);\n",
1200 " }\n",
1201 " other::Target operator+() const { return {}; }\n",
1202 "};\n",
1203 );
1204 let tree = parse_cpp(source);
1205
1206 let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1207 let qualified = source
1208 .find("operator other::Target()")
1209 .expect("qualified target")
1210 + "operator ".len();
1211 let template = source
1212 .find("operator other::Box<T>")
1213 .expect("template target")
1214 + "operator ".len();
1215 let cv_reference = source
1216 .find("operator other::Target const&")
1217 .expect("cv-reference target")
1218 + "operator other::".len();
1219 let pointer = source
1220 .find("operator other::Target*")
1221 .expect("pointer target")
1222 + "operator other::".len();
1223
1224 for (label, start, text) in [
1225 ("bare target", bare, "Target"),
1226 ("qualified target scope", qualified, "other"),
1227 (
1228 "qualified target name",
1229 qualified + "other::".len(),
1230 "Target",
1231 ),
1232 ("template target scope", template, "other"),
1233 ("template target name", template + "other::".len(), "Box"),
1234 (
1235 "template target argument",
1236 template + "other::Box<".len(),
1237 "T",
1238 ),
1239 ("cv-reference target", cv_reference, "Target"),
1240 ("pointer target", pointer, "Target"),
1241 ] {
1242 assert!(
1243 is_conversion_target(&tree, source, start, text),
1244 "the {label} at byte {start} belongs to the conversion identity"
1245 );
1246 }
1247
1248 let ordinary_return = source
1249 .find("other::Target ordinary")
1250 .expect("ordinary return");
1251 let body_cast = source
1252 .find("reinterpret_cast<other::Target")
1253 .expect("body cast")
1254 + "reinterpret_cast<".len();
1255 let overloaded_return = source
1256 .find("other::Target operator+")
1257 .expect("overloaded operator return");
1258 for (label, start) in [
1259 ("ordinary return type", ordinary_return),
1260 ("body cast target", body_cast),
1261 ("overloaded-operator return type", overloaded_return),
1262 ] {
1263 assert!(
1264 !is_conversion_target(&tree, source, start, "other"),
1265 "the {label} at byte {start} stays a reference"
1266 );
1267 }
1268 }
1269
1270 #[test]
1275 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1276 let source = concat!(
1277 "class Foo {\n",
1278 "public:\n",
1279 " Foo();\n",
1280 " Foo(const Foo&);\n",
1281 " ~Foo();\n",
1282 " void m();\n",
1283 "};\n",
1284 "Foo::Foo() {}\n",
1285 "Foo::~Foo() {}\n",
1286 "void Foo::m() {}\n",
1287 );
1288 let tree = parse_cpp(source);
1289
1290 for (label, start, text) in [
1291 (
1292 "constructor declaration",
1293 source.find("Foo();").expect("ctor"),
1294 "Foo",
1295 ),
1296 (
1297 "copy constructor declaration",
1298 source.find("Foo(const Foo&);").expect("copy ctor"),
1299 "Foo",
1300 ),
1301 (
1302 "destructor name",
1303 source.find("~Foo();").expect("dtor"),
1304 "~Foo",
1305 ),
1306 (
1307 "identifier inside the destructor name",
1308 source.find("~Foo();").expect("dtor") + "~".len(),
1309 "Foo",
1310 ),
1311 (
1312 "out-of-line constructor definition name",
1313 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1314 "Foo",
1315 ),
1316 (
1317 "out-of-line destructor definition name",
1318 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1319 "~Foo",
1320 ),
1321 ] {
1322 assert!(
1323 is_declarator_name(&tree, source, start, text),
1324 "the {label} at byte {start} is a declaration occurrence"
1325 );
1326 }
1327
1328 for (label, start, text) in [
1329 (
1330 "class name",
1331 source.find("class Foo {").expect("class") + "class ".len(),
1332 "Foo",
1333 ),
1334 (
1335 "parameter type",
1336 source.find("const Foo&").expect("parameter type") + "const ".len(),
1337 "Foo",
1338 ),
1339 (
1340 "owning scope of an out-of-line constructor",
1341 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1342 "Foo",
1343 ),
1344 (
1345 "owning scope of an out-of-line destructor",
1346 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1347 "Foo",
1348 ),
1349 (
1350 "out-of-line method name",
1351 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1352 "m",
1353 ),
1354 ] {
1355 assert!(
1356 !is_declarator_name(&tree, source, start, text),
1357 "the {label} at byte {start} stays a reference"
1358 );
1359 }
1360 }
1361
1362 #[test]
1366 fn constructor_call_sites_stay_references() {
1367 let source = concat!(
1368 "struct B { B(int); };\n",
1369 "struct D : B {\n",
1370 " D(int x) : B(x), base_(x) {}\n",
1371 " int base_;\n",
1372 "};\n",
1373 "void g() {\n",
1374 " D* p = new D(1);\n",
1375 " D x(2);\n",
1376 " D(3);\n",
1377 " g();\n",
1378 "}\n",
1379 );
1380 let tree = parse_cpp(source);
1381
1382 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1383 assert!(
1384 is_declarator_name(&tree, source, inline_declarator, "D"),
1385 "an inline constructor definition name is still a declarator"
1386 );
1387
1388 for (label, start, text) in [
1389 (
1390 "base member initializer",
1391 source.find(": B(x)").expect("base initializer") + ": ".len(),
1392 "B",
1393 ),
1394 (
1395 "field member initializer",
1396 source.find("base_(x) {}").expect("field initializer"),
1397 "base_",
1398 ),
1399 (
1400 "new expression type",
1401 source.find("new D(1)").expect("new expression") + "new ".len(),
1402 "D",
1403 ),
1404 (
1405 "direct initialization type",
1406 source.find("D x(2)").expect("direct initialization"),
1407 "D",
1408 ),
1409 (
1410 "temporary construction statement",
1411 source.find("D(3)").expect("temporary"),
1412 "D",
1413 ),
1414 (
1415 "recursive call in a real body",
1416 source.find("g();").expect("recursive call"),
1417 "g",
1418 ),
1419 ] {
1420 assert!(
1421 !is_declarator_name(&tree, source, start, text),
1422 "the {label} at byte {start} is a reference"
1423 );
1424 }
1425 }
1426
1427 #[test]
1432 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1433 let source = concat!(
1434 "class SAMPLE_EXPORT Properties {\n",
1435 " public:\n",
1436 " Properties();\n",
1437 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1438 " int size() const;\n",
1439 " int total() { return size(); }\n",
1440 "};\n",
1441 );
1442 let tree = parse_cpp(source);
1443
1444 let recovered = source.find("Properties();").expect("recovered constructor");
1445 assert!(
1446 is_declarator_name(&tree, source, recovered, "Properties"),
1447 "a constructor declaration the parse read as a call is still a declarator"
1448 );
1449
1450 for (label, start, text) in [
1451 (
1452 "class name in the recovered header",
1453 source
1454 .find("class SAMPLE_EXPORT Properties")
1455 .expect("class")
1456 + "class SAMPLE_EXPORT ".len(),
1457 "Properties",
1458 ),
1459 (
1460 "macro invocation in the recovered body",
1461 source
1462 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1463 .expect("macro invocation"),
1464 "DISALLOW_COPY_AND_ASSIGN",
1465 ),
1466 (
1467 "call inside a method body the recovery kept",
1468 source.find("return size();").expect("member call") + "return ".len(),
1469 "size",
1470 ),
1471 ] {
1472 assert!(
1473 !is_declarator_name(&tree, source, start, text),
1474 "the {label} at byte {start} stays a reference"
1475 );
1476 }
1477 }
1478
1479 fn class_roles(source: &str, name: &str) -> Vec<CppOccurrenceRole> {
1482 let tree = parse_cpp(source);
1483 let file = ProjectFile::new(std::env::temp_dir(), "occurrence-role.hpp");
1484 let parsed = crate::adapter::parse_cpp_file(&file, source, &tree);
1485 let unit = parsed
1486 .declarations()
1487 .iter()
1488 .find(|unit| unit.is_class() && unit.fq_name() == name)
1489 .unwrap_or_else(|| panic!("missing class {name}: {parsed:#?}"));
1490 let ranges = parsed.declaration_ranges(unit);
1491 assert!(!ranges.is_empty(), "{name} must have a declaration range");
1492 ranges
1493 .iter()
1494 .map(|range| {
1495 cpp_occurrence_role_for_range(
1496 &CppRecoveredExportClassIndex::build(tree.root_node(), source),
1497 tree.root_node(),
1498 source,
1499 unit,
1500 range,
1501 )
1502 })
1503 .collect()
1504 }
1505
1506 #[test]
1512 fn recovered_export_macro_classes_are_definitions_without_an_ordinary_class() {
1513 let source = concat!(
1514 "namespace api {\n",
1515 "class PROJECT_PUBLIC_API(2, 0) Name final {\n",
1516 " public:\n",
1517 " Name();\n",
1518 "};\n",
1519 "class PROJECT_PUBLIC_API(2, 0) Other : public Name {\n",
1520 " public:\n",
1521 " Other();\n",
1522 "};\n",
1523 "} // namespace api\n",
1524 );
1525 for name in ["api.Name", "api.Other"] {
1526 assert_eq!(
1527 class_roles(source, name),
1528 vec![CppOccurrenceRole::Definition],
1529 "{name} is a complete recovered definition"
1530 );
1531 }
1532 }
1533
1534 #[test]
1535 fn ordinary_class_roles_keep_their_plain_specifier_reading() {
1536 assert_eq!(
1537 class_roles("class Plain;\n", "Plain"),
1538 vec![CppOccurrenceRole::DeclarationOnly],
1539 "a forward declaration stays a declaration"
1540 );
1541 assert_eq!(
1542 class_roles("class Plain { };\n", "Plain"),
1543 vec![CppOccurrenceRole::Definition],
1544 "a complete class stays a definition"
1545 );
1546 }
1547}