1use crate::declarations::{
18 CppRecoveredExportClassIndex, cpp_member_fq, extract_function_declarator, node_text,
19 recovered_callable_body_at, recovered_class_body_at,
20};
21use crate::graph::CppGraphSource;
22use crate::graph::resolver::{VisibilityIndex, cpp_include_closure_reaches};
23use crate::graph_support::CppSource;
24use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
25use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
26use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range, SignatureMetadata};
27use brokk_bifrost_core::analyzer::query_token::QueryToken;
28use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
29use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
30use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
31use brokk_bifrost_core::hash::HashMap;
32use brokk_bifrost_core::path_utils::rel_path_string;
33use brokk_bifrost_core::profiling;
34use std::sync::Arc;
35use tree_sitter::Node;
36use tree_sitter::{Parser, Tree};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum CppCallableUnitRole {
40 DeclarationOnly,
41 Definition,
42 Both,
43 Unknown,
44}
45
46pub use brokk_bifrost_core::analyzer::cpp_facts::CppOccurrenceRole;
47pub struct CppOccurrenceClassifier {
48 tree: Tree,
49 source: String,
52 recovered_export_classes: CppRecoveredExportClassIndex,
55}
56
57impl CppOccurrenceClassifier {
58 pub fn new(source: &str) -> Option<Self> {
59 let mut parser = Parser::new();
60 parser
61 .set_language(&tree_sitter_cpp::LANGUAGE.into())
62 .ok()?;
63 parser.parse(source, None).map(|tree| {
64 let recovered_export_classes =
65 CppRecoveredExportClassIndex::build(tree.root_node(), source);
66 Self {
67 tree,
68 source: source.to_owned(),
69 recovered_export_classes,
70 }
71 })
72 }
73
74 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
75 cpp_occurrence_role_for_range(
76 &self.recovered_export_classes,
77 self.tree.root_node(),
78 &self.source,
79 candidate,
80 range,
81 )
82 }
83}
84
85pub fn cpp_callable_unit_role(
86 index: &dyn CodeUnitIndex,
87 callable: &CodeUnit,
88) -> CppCallableUnitRole {
89 cpp_callable_unit_role_from_metadata(callable, index.signature_metadata(callable))
90}
91
92fn cpp_callable_unit_role_from_metadata(
93 callable: &CodeUnit,
94 metadata: impl IntoIterator<Item = SignatureMetadata>,
95) -> CppCallableUnitRole {
96 if !callable.is_callable() {
97 return CppCallableUnitRole::Unknown;
98 }
99 let mut declaration = false;
100 let mut definition = false;
101 for metadata in metadata {
102 if metadata.is_declaration_only() {
103 declaration = true;
104 } else {
105 definition = true;
106 }
107 }
108 match (declaration, definition) {
109 (true, false) => CppCallableUnitRole::DeclarationOnly,
110 (false, true) => CppCallableUnitRole::Definition,
111 (true, true) => CppCallableUnitRole::Both,
112 (false, false) => CppCallableUnitRole::Unknown,
113 }
114}
115
116pub fn cpp_indexed_callable_linkage(
117 index: &dyn CodeUnitIndex,
118 callable: &CodeUnit,
119) -> Option<CallableLinkage> {
120 let mut external = false;
121 for metadata in index.signature_metadata(callable) {
122 match metadata.callable_linkage() {
123 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
124 Some(CallableLinkage::External) => external = true,
125 None => {}
126 }
127 }
128 external.then_some(CallableLinkage::External)
129}
130
131pub fn cpp_callable_definitions_share_identity_evidence(
137 index: &dyn CodeUnitIndex,
138 left: &CodeUnit,
139 right: &CodeUnit,
140 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
141) -> bool {
142 left.fq_name() == right.fq_name()
143 && left.signature() == right.signature()
144 && (left.source() == right.source()
145 || (matches!(
146 cpp_indexed_callable_linkage(index, left),
147 Some(CallableLinkage::External)
148 ) && matches!(
149 cpp_indexed_callable_linkage(index, right),
150 Some(CallableLinkage::External)
151 ) && header_body_related(left.source(), right.source())))
152}
153
154pub fn cpp_callable_definitions_share_identity_evidence_with_visibility(
170 analyzer: &CppGraphSource<'_>,
171 visibility: &VisibilityIndex<'_>,
172 left: &CodeUnit,
173 right: &CodeUnit,
174 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
175) -> bool {
176 left.fq_name() == right.fq_name()
177 && visibility.same_logical_callable(analyzer, left, right)
178 && (left.source() == right.source()
179 || (matches!(
180 cpp_indexed_callable_linkage(analyzer.index, left),
181 Some(CallableLinkage::External)
182 ) && matches!(
183 cpp_indexed_callable_linkage(analyzer.index, right),
184 Some(CallableLinkage::External)
185 ) && header_body_related(left.source(), right.source())))
186}
187
188pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
192 let mut current = Some(node);
193 while let Some(candidate) = current {
194 let Some(parent) = candidate.parent() else {
195 return false;
196 };
197 if parent.kind() == "for_range_loop" {
198 return parent
199 .child_by_field_name("declarator")
200 .is_some_and(|declarator| {
201 cpp_range_for_declarator_contains_name(declarator, node)
202 });
203 }
204 current = Some(parent);
205 }
206 false
207}
208
209pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
218 while let Some(parent) = node.parent() {
219 if parent.kind() == "operator_cast" {
220 return true;
221 }
222 if matches!(
223 parent.kind(),
224 "function_declarator" | "declaration" | "function_definition" | "translation_unit"
225 ) {
226 return false;
227 }
228 node = parent;
229 }
230 false
231}
232
233pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
243 if node.kind() != "type_identifier" {
244 return false;
245 }
246 let Some(parameter) = node.parent() else {
247 return false;
248 };
249 if parameter.kind() != "parameter_declaration"
250 || parameter.child_by_field_name("type") != Some(node)
251 || parameter.child_by_field_name("declarator").is_some()
252 || parameter
253 .parent()
254 .is_none_or(|parent| parent.kind() != "parameter_list")
255 {
256 return false;
257 }
258
259 parameter
260 .prev_named_sibling()
261 .is_some_and(cpp_is_recovered_character_quote)
262 && parameter
263 .next_named_sibling()
264 .is_some_and(cpp_is_recovered_character_quote)
265}
266
267fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
268 node.is_error()
269 && node.child_count() == 1
270 && node
271 .child(0)
272 .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
273}
274
275pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
298 cpp_is_declared_constructor_or_destructor_name(node)
299 || cpp_is_recovered_constructor_or_destructor_name(node, source)
300}
301
302fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
308 let mut name = node;
309 if let Some(parent) = name.parent()
310 && parent.kind() == "destructor_name"
311 {
312 name = parent;
313 }
314 while let Some(parent) = name.parent() {
318 if parent.kind() != "qualified_identifier"
319 || parent.child_by_field_name("name") != Some(name)
320 {
321 break;
322 }
323 name = parent;
324 }
325 let Some(declarator) = name.parent() else {
326 return false;
327 };
328 if declarator.kind() != "function_declarator"
329 || declarator.child_by_field_name("declarator") != Some(name)
330 {
331 return false;
332 }
333 let Some(owner) = declarator.parent() else {
334 return false;
335 };
336 matches!(owner.kind(), "declaration" | "function_definition")
337 && owner.child_by_field_name("declarator") == Some(declarator)
338 && owner.child_by_field_name("type").is_none()
339}
340
341fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
359 if node.kind() != "identifier" {
360 return false;
361 }
362 let Some(call) = node.parent() else {
363 return false;
364 };
365 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
366 return false;
367 }
368 let mut current = call.parent();
369 while let Some(ancestor) = current {
370 if ancestor.kind() == "function_definition" {
371 return ancestor
372 .child_by_field_name("declarator")
373 .is_some_and(|declarator| declarator.kind() == "identifier")
374 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
375 }
376 current = ancestor.parent();
377 }
378 false
379}
380
381fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
384 let header_end = definition
385 .child_by_field_name("body")
386 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
387 let mut stack = vec![definition];
388 while let Some(node) = stack.pop() {
389 if node.start_byte() >= header_end {
390 continue;
391 }
392 if matches!(
393 node.kind(),
394 "identifier" | "type_identifier" | "namespace_identifier"
395 ) && node_text(node, source) == name
396 {
397 return true;
398 }
399 let mut cursor = node.walk();
400 for child in node.named_children(&mut cursor) {
401 stack.push(child);
402 }
403 }
404 false
405}
406
407fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
408 let mut pending = vec![declarator];
409 while let Some(candidate) = pending.pop() {
410 match candidate.kind() {
411 "identifier" | "field_identifier" => {
412 if cpp_same_node(candidate, target) {
413 return true;
414 }
415 }
416 "structured_binding_declarator" => {
417 let mut cursor = candidate.walk();
418 if candidate
419 .named_children(&mut cursor)
420 .any(|name| cpp_same_node(name, target))
421 {
422 return true;
423 }
424 }
425 "pointer_declarator"
426 | "reference_declarator"
427 | "array_declarator"
428 | "attributed_declarator"
429 | "parenthesized_declarator"
430 | "function_declarator"
431 | "init_declarator" => {
432 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
433 pending.push(inner);
434 }
435 }
436 _ => {}
437 }
438 }
439 false
440}
441
442fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
443 node.child_by_field_name("declarator").or_else(|| {
444 let mut cursor = node.walk();
445 node.named_children(&mut cursor).find(|child| {
446 matches!(
447 child.kind(),
448 "identifier"
449 | "field_identifier"
450 | "structured_binding_declarator"
451 | "pointer_declarator"
452 | "reference_declarator"
453 | "array_declarator"
454 | "attributed_declarator"
455 | "parenthesized_declarator"
456 | "function_declarator"
457 | "init_declarator"
458 )
459 })
460 })
461}
462
463fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
464 left.id() == right.id()
465 && left.start_byte() == right.start_byte()
466 && left.end_byte() == right.end_byte()
467}
468
469pub fn cpp_header_body_files_are_related(
486 source: &dyn CppSource,
487 token: QueryToken<'_>,
488 left: &ProjectFile,
489 right: &ProjectFile,
490) -> bool {
491 let (header, implementation) = if cpp_source_path_is_header(left) {
492 (left, right)
493 } else if cpp_source_path_is_header(right) {
494 (right, left)
495 } else {
496 return false;
497 };
498 if cpp_source_path_is_header(implementation) {
499 return false;
500 }
501 cpp_include_closure_reaches(source, token, implementation, header)
502}
503
504pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
505 let path = rel_path_string(source).to_ascii_lowercase();
506 matches!(
507 path.rsplit('.').next(),
508 Some("h" | "hin" | "hh" | "hpp" | "hxx")
509 )
510}
511
512pub(crate) fn cpp_occurrence_role_for_node(
513 node: Node<'_>,
514 candidate: &CodeUnit,
515) -> CppOccurrenceRole {
516 if !candidate.is_callable() && !candidate.is_class() {
517 return CppOccurrenceRole::Both;
518 }
519 if candidate.is_callable() {
520 return if subtree_contains(node, |descendant| {
521 descendant.kind() == "function_definition"
522 && descendant.child_by_field_name("body").is_some()
523 }) {
524 CppOccurrenceRole::Definition
525 } else {
526 CppOccurrenceRole::DeclarationOnly
527 };
528 }
529 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
530 return CppOccurrenceRole::Definition;
531 }
532 if !subtree_contains(node, |descendant| {
533 matches!(
534 descendant.kind(),
535 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
536 )
537 }) {
538 return CppOccurrenceRole::Both;
539 }
540 if subtree_contains(node, |descendant| {
541 matches!(
542 descendant.kind(),
543 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
544 ) && descendant.child_by_field_name("body").is_some()
545 }) {
546 CppOccurrenceRole::Definition
547 } else {
548 CppOccurrenceRole::DeclarationOnly
549 }
550}
551
552pub fn cpp_occurrence_role_for_range(
553 recovered_export_classes: &CppRecoveredExportClassIndex,
554 root: Node<'_>,
555 source: &str,
556 candidate: &CodeUnit,
557 range: &Range,
558) -> CppOccurrenceRole {
559 if !candidate.is_callable() && !candidate.is_class() {
560 return CppOccurrenceRole::Both;
561 }
562 let Some(node) = cpp_declaration_node_for_range(root, range) else {
563 return CppOccurrenceRole::Unknown;
564 };
565 if candidate.is_callable() {
566 if subtree_contains(node, |descendant| {
567 descendant.kind() == "function_definition"
568 && descendant.child_by_field_name("body").is_some()
569 }) {
570 return CppOccurrenceRole::Definition;
571 }
572 if !matches!(
579 node.kind(),
580 "declaration" | "field_declaration" | "function_definition"
581 ) && let Some(has_body) = recovered_callable_body_at(source, range)
582 {
583 return if has_body {
584 CppOccurrenceRole::Definition
585 } else {
586 CppOccurrenceRole::DeclarationOnly
587 };
588 }
589 return CppOccurrenceRole::DeclarationOnly;
590 }
591 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
592 return CppOccurrenceRole::Definition;
593 }
594 if let Some(has_body) = recovered_class_body_at(
602 recovered_export_classes,
603 root,
604 source,
605 candidate.identifier(),
606 range,
607 ) {
608 return if has_body {
609 CppOccurrenceRole::Definition
610 } else {
611 CppOccurrenceRole::DeclarationOnly
612 };
613 }
614 if !subtree_contains(node, |descendant| {
615 matches!(
616 descendant.kind(),
617 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
618 )
619 }) {
620 return CppOccurrenceRole::Both;
621 }
622 if subtree_contains(node, |descendant| {
623 matches!(
624 descendant.kind(),
625 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
626 ) && descendant.child_by_field_name("body").is_some()
627 }) {
628 CppOccurrenceRole::Definition
629 } else {
630 CppOccurrenceRole::DeclarationOnly
631 }
632}
633
634pub fn cpp_range_is_pure_virtual_declaration(root: Node<'_>, source: &str, range: &Range) -> bool {
645 let Some(node) = cpp_declaration_node_for_range(root, range) else {
646 return false;
647 };
648 let mut current = Some(node);
649 while let Some(node) = current {
650 match node.kind() {
651 "field_declaration" => {
652 return node
656 .child_by_field_name("default_value")
657 .is_some_and(|value| {
658 value.kind() == "number_literal" && node_text(value, source) == "0"
659 })
660 && node
661 .child_by_field_name("declarator")
662 .and_then(extract_function_declarator)
663 .is_some();
664 }
665 "field_declaration_list" | "function_definition" | "translation_unit" => return false,
668 _ => current = node.parent(),
669 }
670 }
671 false
672}
673
674fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
675 node_for_exact_range(root, range).or_else(|| {
676 root.descendant_for_byte_range(range.start_byte, range.end_byte)
677 .and_then(|mut node| {
678 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
679 node = node.parent()?;
680 }
681 Some(node)
682 })
683 })
684}
685
686#[derive(Default)]
698pub struct CppReconciledDefinitionIndex {
699 pub rekeyed: Vec<CodeUnit>,
701 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
703}
704
705#[derive(Debug, Clone, PartialEq, Eq, Hash)]
721pub struct CppReconcileGroupKey {
722 pub member_identifier: String,
725 pub owner_terminal: Option<String>,
729}
730
731pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
738 let interner = segment_interner();
739 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
740 let (member_identifier, _) = interner.resolve(query_fq.last()?);
741 if member_identifier.is_empty() {
742 return None;
743 }
744 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
754 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
755 text.rsplit_once('$')
760 .map_or(text, |(_, tail)| tail)
761 .to_string()
762 });
763 Some(CppReconcileGroupKey {
764 member_identifier: member_identifier.to_string(),
765 owner_terminal,
766 })
767}
768
769pub struct CppReconcileCandidates {
775 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
776 all: Vec<CodeUnit>,
780}
781
782impl CppReconcileCandidates {
783 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
786 match &key.owner_terminal {
787 Some(owner_terminal) => self
788 .by_owner_terminal
789 .get(owner_terminal)
790 .map_or(&[][..], Vec::as_slice),
791 None => &self.all,
792 }
793 }
794
795 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
797 self.all.iter()
798 }
799
800 pub fn bucketed_len(&self) -> usize {
803 self.by_owner_terminal.values().map(Vec::len).sum()
804 }
805}
806
807pub fn cpp_reconcile_candidates_from_units(
814 candidates: impl IntoIterator<Item = CodeUnit>,
815 keep_going: &dyn Fn() -> bool,
816) -> Option<CppReconcileCandidates> {
817 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
818 candidates.sort();
819 candidates.dedup();
820 let interner = segment_interner();
821 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
822 let mut all = Vec::new();
823 for (index, unit) in candidates.into_iter().enumerate() {
824 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
828 return None;
829 }
830 if !unit.is_callable() {
831 continue;
832 }
833 let owner_terminal = unit
834 .fq()
835 .segments()
836 .iter()
837 .filter_map(|&segment| {
838 let (text, kind) = interner.resolve(segment);
839 matches!(
843 kind,
844 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
845 )
846 .then_some(text)
847 })
848 .last();
849 if let Some(owner_terminal) = owner_terminal {
850 by_owner_terminal
851 .entry(owner_terminal.to_string())
852 .or_default()
853 .push(unit.clone());
854 }
855 all.push(unit);
856 }
857 Some(CppReconcileCandidates {
858 by_owner_terminal,
859 all,
860 })
861}
862
863const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
865
866pub fn cpp_reconcile_group(
883 cpp: &dyn CppSource,
884 token: QueryToken<'_>,
885 key: &CppReconcileGroupKey,
886 candidates: &CppReconcileCandidates,
887 keep_going: &dyn Fn() -> bool,
888 on_candidate: &dyn Fn(),
889) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
890 let _scope = profiling::scope_with(|| {
891 format!(
892 "cpp.reconciled.build[{}#{}]",
893 key.member_identifier,
894 key.owner_terminal.as_deref().unwrap_or("*")
895 )
896 });
897 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
898 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
899 for unit in candidates.for_group(key) {
900 if !keep_going() {
905 return None;
906 }
907 on_candidate();
908 let _candidate =
911 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
912 let role = {
913 let _role = profiling::scope("cpp.reconcile.role");
914 cpp.stored_callable_unit_role(unit)
915 };
916 if !matches!(
917 role,
918 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
919 ) {
920 continue;
921 }
922 let Some(reconciled) =
923 cpp_reconcile_definition_identity(cpp, token, unit, &mut using_by_file)
924 else {
925 continue;
926 };
927 let canonical_fq = reconciled.fq_name();
928 if unit.fq_name() == canonical_fq {
935 continue;
936 }
937 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
946 let fq = cpp_member_fq(&reconciled.package, &short_name);
947 let rekeyed = CodeUnit::with_signature_and_fq(
948 unit.source().clone(),
949 unit.kind(),
950 reconciled.package,
951 short_name,
952 unit.signature().map(str::to_string),
953 unit.is_synthetic(),
954 fq,
955 );
956 let index = groups.entry(canonical_fq).or_default();
957 index.rekeyed.push(rekeyed.clone());
958 index.provisional_of.insert(rekeyed, unit.clone());
959 }
960 Some(
961 groups
962 .into_iter()
963 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
964 .collect(),
965 )
966}
967
968fn cpp_reconcile_definition_identity(
974 cpp: &dyn CppSource,
975 token: QueryToken<'_>,
976 unit: &CodeUnit,
977 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
978) -> Option<ReconciledIdentity> {
979 let interner = segment_interner();
989 let mut provisional_owner_segments: Vec<&str> = Vec::new();
990 let mut member: Option<&str> = None;
991 for &segment in unit.fq().segments() {
992 let (text, kind) = interner.resolve(segment);
993 match kind {
994 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
995 if member.is_some() {
999 return None;
1000 }
1001 if !text.is_empty() {
1002 provisional_owner_segments.push(text);
1003 }
1004 }
1005 SegmentKind::Member => member = Some(text),
1006 _ => return None,
1007 }
1008 }
1009 let member = member?;
1010 let structured_owner_segments = cpp_structured_out_of_line_owner_segments(cpp, token, unit)
1015 .filter(|segments| segments.len() == 1);
1016 let owner_segments = structured_owner_segments.as_ref().map_or_else(
1017 || provisional_owner_segments,
1018 |segments| segments.iter().map(String::as_str).collect(),
1019 );
1020 if owner_segments.is_empty() {
1021 return None;
1022 }
1023
1024 let using = using_by_file
1025 .entry(unit.source().clone())
1026 .or_insert_with(|| {
1027 Arc::new(
1028 cpp.declaration_source_facts(token, unit.source())
1029 .map(|source| {
1030 source
1031 .facts
1032 .using_namespaces
1033 .iter()
1034 .map(|(_, name)| name.clone())
1035 .collect()
1036 })
1037 .unwrap_or_default(),
1038 )
1039 })
1040 .clone();
1041 let mut namespace_candidates: Vec<&str> = vec![""];
1042 namespace_candidates.extend(using.iter().map(String::as_str));
1043
1044 let visible = {
1045 let _visible = profiling::scope_with(|| {
1046 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
1047 });
1048 cpp.visible_type_units(unit.source())
1049 };
1050 let class_table: Vec<VisibleClass> = visible
1051 .iter()
1052 .filter(|candidate| candidate.is_class())
1053 .map(|candidate| VisibleClass {
1054 package: candidate.package_name(),
1055 nested_short_name: candidate.short_name(),
1056 })
1057 .collect();
1058
1059 reconcile_out_of_line_member_identity(
1060 &owner_segments,
1061 member,
1062 &namespace_candidates,
1063 &class_table,
1064 )
1065}
1066
1067fn cpp_structured_out_of_line_owner_segments(
1074 cpp: &dyn CppSource,
1075 token: QueryToken<'_>,
1076 unit: &CodeUnit,
1077) -> Option<Vec<String>> {
1078 let source = cpp.declaration_source_facts(token, unit.source())?;
1081 let mut owners = source
1082 .for_unit(unit)
1083 .filter(|fact| !fact.written_owner.is_empty());
1084 let first = &owners.next()?.written_owner;
1085 owners
1086 .all(|fact| &fact.written_owner == first)
1087 .then(|| first.clone())
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092 use super::*;
1093
1094 fn parse_cpp(source: &str) -> Tree {
1095 let mut parser = Parser::new();
1096 parser
1097 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1098 .expect("cpp language");
1099 parser.parse(source, None).expect("cpp tree")
1100 }
1101
1102 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1103 let end = start + text.len();
1104 assert_eq!(&source[start..end], text, "the probe must name the token");
1105 let node = tree
1106 .root_node()
1107 .named_descendant_for_byte_range(start, end)
1108 .expect("a node spans the probed range");
1109 assert_eq!(
1110 (node.start_byte(), node.end_byte()),
1111 (start, end),
1112 "the probed range must be exactly one node: {}",
1113 node.to_sexp()
1114 );
1115 cpp_is_constructor_or_destructor_declarator_name(node, source)
1116 }
1117
1118 fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1119 let end = start + text.len();
1120 assert_eq!(&source[start..end], text, "the probe must name the token");
1121 let node = tree
1122 .root_node()
1123 .named_descendant_for_byte_range(start, end)
1124 .expect("a node spans the probed range");
1125 assert_eq!(
1126 (node.start_byte(), node.end_byte()),
1127 (start, end),
1128 "the probed range must be exactly one node: {}",
1129 node.to_sexp()
1130 );
1131 cpp_is_conversion_operator_target_type(node)
1132 }
1133
1134 fn is_recovered_macro_character_type(
1135 tree: &Tree,
1136 source: &str,
1137 start: usize,
1138 text: &str,
1139 ) -> bool {
1140 let end = start + text.len();
1141 assert_eq!(&source[start..end], text, "the probe must name the token");
1142 let node = tree
1143 .root_node()
1144 .named_descendant_for_byte_range(start, end)
1145 .expect("a node spans the probed range");
1146 cpp_is_recovered_macro_character_token_type(node)
1147 }
1148
1149 #[test]
1150 fn recovered_macro_character_tokens_are_not_type_references() {
1151 let source = concat!(
1152 "struct I {};\n",
1153 "#define STRING_TOKEN_(name, ...)\n",
1154 "struct Schema {\n",
1155 " STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1156 " void ordinary(I value);\n",
1157 " void malformed(I value, @);\n",
1158 " void use() { I value; consume('I'); }\n",
1159 "};\n",
1160 );
1161 let tree = parse_cpp(source);
1162 let recovered_m = source.find("'m'").expect("recovered m") + 1;
1163 let recovered_i = source.find("'I'").expect("recovered I") + 1;
1164 for (label, start, text) in [
1165 ("lowercase character token", recovered_m, "m"),
1166 ("uppercase character token", recovered_i, "I"),
1167 ] {
1168 assert!(
1169 is_recovered_macro_character_type(&tree, source, start, text),
1170 "{label} must match the exact recovery role"
1171 );
1172 }
1173
1174 let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1175 let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1176 let malformed =
1177 source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1178 let local = source
1179 .find("I value; consume")
1180 .expect("local type reference");
1181 let expression_character = source.rfind("'I'").expect("expression character") + 1;
1182 for (label, start, text) in [
1183 ("unquoted macro argument", macro_first_argument, "MaxItems"),
1184 ("ordinary parameter type", ordinary, "I"),
1185 ("parameter beside another error", malformed, "I"),
1186 ("local type reference", local, "I"),
1187 ("expression character literal", expression_character, "I"),
1188 ] {
1189 assert!(
1190 !is_recovered_macro_character_type(&tree, source, start, text),
1191 "{label} must remain outside the recovery role"
1192 );
1193 }
1194 }
1195
1196 #[test]
1197 fn conversion_operator_target_components_are_identity_syntax_only() {
1198 let source = concat!(
1199 "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1200 "using other::Target;\n",
1201 "struct Source {\n",
1202 " operator Target() const;\n",
1203 " operator other::Target() const { return other::Target{}; }\n",
1204 " template<class T> operator other::Box<T>() const { return {}; }\n",
1205 " operator other::Target const&() const;\n",
1206 " operator other::Target*() const;\n",
1207 " other::Target ordinary() const {\n",
1208 " return reinterpret_cast<other::Target&>(*this);\n",
1209 " }\n",
1210 " other::Target operator+() const { return {}; }\n",
1211 "};\n",
1212 );
1213 let tree = parse_cpp(source);
1214
1215 let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1216 let qualified = source
1217 .find("operator other::Target()")
1218 .expect("qualified target")
1219 + "operator ".len();
1220 let template = source
1221 .find("operator other::Box<T>")
1222 .expect("template target")
1223 + "operator ".len();
1224 let cv_reference = source
1225 .find("operator other::Target const&")
1226 .expect("cv-reference target")
1227 + "operator other::".len();
1228 let pointer = source
1229 .find("operator other::Target*")
1230 .expect("pointer target")
1231 + "operator other::".len();
1232
1233 for (label, start, text) in [
1234 ("bare target", bare, "Target"),
1235 ("qualified target scope", qualified, "other"),
1236 (
1237 "qualified target name",
1238 qualified + "other::".len(),
1239 "Target",
1240 ),
1241 ("template target scope", template, "other"),
1242 ("template target name", template + "other::".len(), "Box"),
1243 (
1244 "template target argument",
1245 template + "other::Box<".len(),
1246 "T",
1247 ),
1248 ("cv-reference target", cv_reference, "Target"),
1249 ("pointer target", pointer, "Target"),
1250 ] {
1251 assert!(
1252 is_conversion_target(&tree, source, start, text),
1253 "the {label} at byte {start} belongs to the conversion identity"
1254 );
1255 }
1256
1257 let ordinary_return = source
1258 .find("other::Target ordinary")
1259 .expect("ordinary return");
1260 let body_cast = source
1261 .find("reinterpret_cast<other::Target")
1262 .expect("body cast")
1263 + "reinterpret_cast<".len();
1264 let overloaded_return = source
1265 .find("other::Target operator+")
1266 .expect("overloaded operator return");
1267 for (label, start) in [
1268 ("ordinary return type", ordinary_return),
1269 ("body cast target", body_cast),
1270 ("overloaded-operator return type", overloaded_return),
1271 ] {
1272 assert!(
1273 !is_conversion_target(&tree, source, start, "other"),
1274 "the {label} at byte {start} stays a reference"
1275 );
1276 }
1277 }
1278
1279 #[test]
1284 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1285 let source = concat!(
1286 "class Foo {\n",
1287 "public:\n",
1288 " Foo();\n",
1289 " Foo(const Foo&);\n",
1290 " ~Foo();\n",
1291 " void m();\n",
1292 "};\n",
1293 "Foo::Foo() {}\n",
1294 "Foo::~Foo() {}\n",
1295 "void Foo::m() {}\n",
1296 );
1297 let tree = parse_cpp(source);
1298
1299 for (label, start, text) in [
1300 (
1301 "constructor declaration",
1302 source.find("Foo();").expect("ctor"),
1303 "Foo",
1304 ),
1305 (
1306 "copy constructor declaration",
1307 source.find("Foo(const Foo&);").expect("copy ctor"),
1308 "Foo",
1309 ),
1310 (
1311 "destructor name",
1312 source.find("~Foo();").expect("dtor"),
1313 "~Foo",
1314 ),
1315 (
1316 "identifier inside the destructor name",
1317 source.find("~Foo();").expect("dtor") + "~".len(),
1318 "Foo",
1319 ),
1320 (
1321 "out-of-line constructor definition name",
1322 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1323 "Foo",
1324 ),
1325 (
1326 "out-of-line destructor definition name",
1327 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1328 "~Foo",
1329 ),
1330 ] {
1331 assert!(
1332 is_declarator_name(&tree, source, start, text),
1333 "the {label} at byte {start} is a declaration occurrence"
1334 );
1335 }
1336
1337 for (label, start, text) in [
1338 (
1339 "class name",
1340 source.find("class Foo {").expect("class") + "class ".len(),
1341 "Foo",
1342 ),
1343 (
1344 "parameter type",
1345 source.find("const Foo&").expect("parameter type") + "const ".len(),
1346 "Foo",
1347 ),
1348 (
1349 "owning scope of an out-of-line constructor",
1350 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1351 "Foo",
1352 ),
1353 (
1354 "owning scope of an out-of-line destructor",
1355 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1356 "Foo",
1357 ),
1358 (
1359 "out-of-line method name",
1360 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1361 "m",
1362 ),
1363 ] {
1364 assert!(
1365 !is_declarator_name(&tree, source, start, text),
1366 "the {label} at byte {start} stays a reference"
1367 );
1368 }
1369 }
1370
1371 #[test]
1375 fn constructor_call_sites_stay_references() {
1376 let source = concat!(
1377 "struct B { B(int); };\n",
1378 "struct D : B {\n",
1379 " D(int x) : B(x), base_(x) {}\n",
1380 " int base_;\n",
1381 "};\n",
1382 "void g() {\n",
1383 " D* p = new D(1);\n",
1384 " D x(2);\n",
1385 " D(3);\n",
1386 " g();\n",
1387 "}\n",
1388 );
1389 let tree = parse_cpp(source);
1390
1391 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1392 assert!(
1393 is_declarator_name(&tree, source, inline_declarator, "D"),
1394 "an inline constructor definition name is still a declarator"
1395 );
1396
1397 for (label, start, text) in [
1398 (
1399 "base member initializer",
1400 source.find(": B(x)").expect("base initializer") + ": ".len(),
1401 "B",
1402 ),
1403 (
1404 "field member initializer",
1405 source.find("base_(x) {}").expect("field initializer"),
1406 "base_",
1407 ),
1408 (
1409 "new expression type",
1410 source.find("new D(1)").expect("new expression") + "new ".len(),
1411 "D",
1412 ),
1413 (
1414 "direct initialization type",
1415 source.find("D x(2)").expect("direct initialization"),
1416 "D",
1417 ),
1418 (
1419 "temporary construction statement",
1420 source.find("D(3)").expect("temporary"),
1421 "D",
1422 ),
1423 (
1424 "recursive call in a real body",
1425 source.find("g();").expect("recursive call"),
1426 "g",
1427 ),
1428 ] {
1429 assert!(
1430 !is_declarator_name(&tree, source, start, text),
1431 "the {label} at byte {start} is a reference"
1432 );
1433 }
1434 }
1435
1436 #[test]
1441 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1442 let source = concat!(
1443 "class SAMPLE_EXPORT Properties {\n",
1444 " public:\n",
1445 " Properties();\n",
1446 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1447 " int size() const;\n",
1448 " int total() { return size(); }\n",
1449 "};\n",
1450 );
1451 let tree = parse_cpp(source);
1452
1453 let recovered = source.find("Properties();").expect("recovered constructor");
1454 assert!(
1455 is_declarator_name(&tree, source, recovered, "Properties"),
1456 "a constructor declaration the parse read as a call is still a declarator"
1457 );
1458
1459 for (label, start, text) in [
1460 (
1461 "class name in the recovered header",
1462 source
1463 .find("class SAMPLE_EXPORT Properties")
1464 .expect("class")
1465 + "class SAMPLE_EXPORT ".len(),
1466 "Properties",
1467 ),
1468 (
1469 "macro invocation in the recovered body",
1470 source
1471 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1472 .expect("macro invocation"),
1473 "DISALLOW_COPY_AND_ASSIGN",
1474 ),
1475 (
1476 "call inside a method body the recovery kept",
1477 source.find("return size();").expect("member call") + "return ".len(),
1478 "size",
1479 ),
1480 ] {
1481 assert!(
1482 !is_declarator_name(&tree, source, start, text),
1483 "the {label} at byte {start} stays a reference"
1484 );
1485 }
1486 }
1487
1488 fn class_roles(source: &str, name: &str) -> Vec<CppOccurrenceRole> {
1491 let tree = parse_cpp(source);
1492 let file = ProjectFile::new(std::env::temp_dir(), "occurrence-role.hpp");
1493 let parsed = crate::adapter::parse_cpp_file(&file, source, &tree);
1494 let unit = parsed
1495 .declarations()
1496 .iter()
1497 .find(|unit| unit.is_class() && unit.fq_name() == name)
1498 .unwrap_or_else(|| panic!("missing class {name}: {parsed:#?}"));
1499 let ranges = parsed.declaration_ranges(unit);
1500 assert!(!ranges.is_empty(), "{name} must have a declaration range");
1501 ranges
1502 .iter()
1503 .map(|range| {
1504 cpp_occurrence_role_for_range(
1505 &CppRecoveredExportClassIndex::build(tree.root_node(), source),
1506 tree.root_node(),
1507 source,
1508 unit,
1509 range,
1510 )
1511 })
1512 .collect()
1513 }
1514
1515 #[test]
1521 fn recovered_export_macro_classes_are_definitions_without_an_ordinary_class() {
1522 let source = concat!(
1523 "namespace api {\n",
1524 "class PROJECT_PUBLIC_API(2, 0) Name final {\n",
1525 " public:\n",
1526 " Name();\n",
1527 "};\n",
1528 "class PROJECT_PUBLIC_API(2, 0) Other : public Name {\n",
1529 " public:\n",
1530 " Other();\n",
1531 "};\n",
1532 "} // namespace api\n",
1533 );
1534 for name in ["api.Name", "api.Other"] {
1535 assert_eq!(
1536 class_roles(source, name),
1537 vec![CppOccurrenceRole::Definition],
1538 "{name} is a complete recovered definition"
1539 );
1540 }
1541 }
1542
1543 #[test]
1544 fn ordinary_class_roles_keep_their_plain_specifier_reading() {
1545 assert_eq!(
1546 class_roles("class Plain;\n", "Plain"),
1547 vec![CppOccurrenceRole::DeclarationOnly],
1548 "a forward declaration stays a declaration"
1549 );
1550 assert_eq!(
1551 class_roles("class Plain { };\n", "Plain"),
1552 vec![CppOccurrenceRole::Definition],
1553 "a complete class stays a definition"
1554 );
1555 }
1556}