1use crate::declarations::{cpp_file_using_namespaces, cpp_member_fq, node_text};
19use crate::graph::CppGraphSource;
20use crate::graph::resolver::{
21 VisibilityIndex, cpp_type_name_components, declarator_name_node,
22 qualified_name_has_concrete_scope_separators,
23};
24use crate::graph_support::CppSource;
25use crate::imports::{IncludeTargetIndex, include_paths, resolve_include_targets_with_index};
26use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
27use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
28use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range};
29use brokk_bifrost_core::analyzer::query_token::QueryToken;
30use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
31use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
32use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
33use brokk_bifrost_core::hash::HashMap;
34use brokk_bifrost_core::path_utils::rel_path_string;
35use brokk_bifrost_core::profiling;
36use std::collections::BTreeSet;
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}
69
70impl CppOccurrenceClassifier {
71 pub fn new(source: &str) -> Option<Self> {
72 let mut parser = Parser::new();
73 parser
74 .set_language(&tree_sitter_cpp::LANGUAGE.into())
75 .ok()?;
76 parser.parse(source, None).map(|tree| Self { tree })
77 }
78
79 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
80 cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
81 }
82}
83
84pub fn cpp_callable_unit_role(
85 index: &dyn CodeUnitIndex,
86 callable: &CodeUnit,
87) -> CppCallableUnitRole {
88 if !callable.is_callable() {
89 return CppCallableUnitRole::Unknown;
90 }
91 let mut declaration = false;
92 let mut definition = false;
93 for metadata in index.signature_metadata(callable) {
94 if metadata.is_declaration_only() {
95 declaration = true;
96 } else {
97 definition = true;
98 }
99 }
100 match (declaration, definition) {
101 (true, false) => CppCallableUnitRole::DeclarationOnly,
102 (false, true) => CppCallableUnitRole::Definition,
103 (true, true) => CppCallableUnitRole::Both,
104 (false, false) => CppCallableUnitRole::Unknown,
105 }
106}
107
108pub fn cpp_indexed_callable_linkage(
109 index: &dyn CodeUnitIndex,
110 callable: &CodeUnit,
111) -> Option<CallableLinkage> {
112 let mut external = false;
113 for metadata in index.signature_metadata(callable) {
114 match metadata.callable_linkage() {
115 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
116 Some(CallableLinkage::External) => external = true,
117 None => {}
118 }
119 }
120 external.then_some(CallableLinkage::External)
121}
122
123pub fn cpp_callable_definitions_share_identity_evidence(
129 index: &dyn CodeUnitIndex,
130 left: &CodeUnit,
131 right: &CodeUnit,
132 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
133) -> bool {
134 left.source() == right.source()
135 || (left.fq_name() == right.fq_name()
136 && left.signature() == right.signature()
137 && matches!(
138 cpp_indexed_callable_linkage(index, left),
139 Some(CallableLinkage::External)
140 )
141 && matches!(
142 cpp_indexed_callable_linkage(index, right),
143 Some(CallableLinkage::External)
144 )
145 && header_body_related(left.source(), right.source()))
146}
147
148pub fn cpp_callable_definitions_share_identity_evidence_with_visibility(
164 analyzer: &CppGraphSource<'_>,
165 visibility: &VisibilityIndex<'_>,
166 left: &CodeUnit,
167 right: &CodeUnit,
168 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
169) -> bool {
170 left.source() == right.source()
171 || (left.fq_name() == right.fq_name()
172 && visibility.same_logical_callable(analyzer, left, right)
173 && matches!(
174 cpp_indexed_callable_linkage(analyzer.index, left),
175 Some(CallableLinkage::External)
176 )
177 && matches!(
178 cpp_indexed_callable_linkage(analyzer.index, right),
179 Some(CallableLinkage::External)
180 )
181 && header_body_related(left.source(), right.source()))
182}
183
184pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
188 let mut current = Some(node);
189 while let Some(candidate) = current {
190 let Some(parent) = candidate.parent() else {
191 return false;
192 };
193 if parent.kind() == "for_range_loop" {
194 return parent
195 .child_by_field_name("declarator")
196 .is_some_and(|declarator| {
197 cpp_range_for_declarator_contains_name(declarator, node)
198 });
199 }
200 current = Some(parent);
201 }
202 false
203}
204
205pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
214 while let Some(parent) = node.parent() {
215 if parent.kind() == "operator_cast" {
216 return true;
217 }
218 if matches!(
219 parent.kind(),
220 "function_declarator" | "declaration" | "function_definition" | "translation_unit"
221 ) {
222 return false;
223 }
224 node = parent;
225 }
226 false
227}
228
229pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
239 if node.kind() != "type_identifier" {
240 return false;
241 }
242 let Some(parameter) = node.parent() else {
243 return false;
244 };
245 if parameter.kind() != "parameter_declaration"
246 || parameter.child_by_field_name("type") != Some(node)
247 || parameter.child_by_field_name("declarator").is_some()
248 || parameter
249 .parent()
250 .is_none_or(|parent| parent.kind() != "parameter_list")
251 {
252 return false;
253 }
254
255 parameter
256 .prev_named_sibling()
257 .is_some_and(cpp_is_recovered_character_quote)
258 && parameter
259 .next_named_sibling()
260 .is_some_and(cpp_is_recovered_character_quote)
261}
262
263fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
264 node.is_error()
265 && node.child_count() == 1
266 && node
267 .child(0)
268 .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
269}
270
271pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
294 cpp_is_declared_constructor_or_destructor_name(node)
295 || cpp_is_recovered_constructor_or_destructor_name(node, source)
296}
297
298fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
304 let mut name = node;
305 if let Some(parent) = name.parent()
306 && parent.kind() == "destructor_name"
307 {
308 name = parent;
309 }
310 while let Some(parent) = name.parent() {
314 if parent.kind() != "qualified_identifier"
315 || parent.child_by_field_name("name") != Some(name)
316 {
317 break;
318 }
319 name = parent;
320 }
321 let Some(declarator) = name.parent() else {
322 return false;
323 };
324 if declarator.kind() != "function_declarator"
325 || declarator.child_by_field_name("declarator") != Some(name)
326 {
327 return false;
328 }
329 let Some(owner) = declarator.parent() else {
330 return false;
331 };
332 matches!(owner.kind(), "declaration" | "function_definition")
333 && owner.child_by_field_name("declarator") == Some(declarator)
334 && owner.child_by_field_name("type").is_none()
335}
336
337fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
355 if node.kind() != "identifier" {
356 return false;
357 }
358 let Some(call) = node.parent() else {
359 return false;
360 };
361 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
362 return false;
363 }
364 let mut current = call.parent();
365 while let Some(ancestor) = current {
366 if ancestor.kind() == "function_definition" {
367 return ancestor
368 .child_by_field_name("declarator")
369 .is_some_and(|declarator| declarator.kind() == "identifier")
370 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
371 }
372 current = ancestor.parent();
373 }
374 false
375}
376
377fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
380 let header_end = definition
381 .child_by_field_name("body")
382 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
383 let mut stack = vec![definition];
384 while let Some(node) = stack.pop() {
385 if node.start_byte() >= header_end {
386 continue;
387 }
388 if matches!(
389 node.kind(),
390 "identifier" | "type_identifier" | "namespace_identifier"
391 ) && node_text(node, source) == name
392 {
393 return true;
394 }
395 let mut cursor = node.walk();
396 for child in node.named_children(&mut cursor) {
397 stack.push(child);
398 }
399 }
400 false
401}
402
403fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
404 let mut pending = vec![declarator];
405 while let Some(candidate) = pending.pop() {
406 match candidate.kind() {
407 "identifier" | "field_identifier" => {
408 if cpp_same_node(candidate, target) {
409 return true;
410 }
411 }
412 "structured_binding_declarator" => {
413 let mut cursor = candidate.walk();
414 if candidate
415 .named_children(&mut cursor)
416 .any(|name| cpp_same_node(name, target))
417 {
418 return true;
419 }
420 }
421 "pointer_declarator"
422 | "reference_declarator"
423 | "array_declarator"
424 | "attributed_declarator"
425 | "parenthesized_declarator"
426 | "function_declarator"
427 | "init_declarator" => {
428 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
429 pending.push(inner);
430 }
431 }
432 _ => {}
433 }
434 }
435 false
436}
437
438fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
439 node.child_by_field_name("declarator").or_else(|| {
440 let mut cursor = node.walk();
441 node.named_children(&mut cursor).find(|child| {
442 matches!(
443 child.kind(),
444 "identifier"
445 | "field_identifier"
446 | "structured_binding_declarator"
447 | "pointer_declarator"
448 | "reference_declarator"
449 | "array_declarator"
450 | "attributed_declarator"
451 | "parenthesized_declarator"
452 | "function_declarator"
453 | "init_declarator"
454 )
455 })
456 })
457}
458
459fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
460 left.id() == right.id()
461 && left.start_byte() == right.start_byte()
462 && left.end_byte() == right.end_byte()
463}
464
465pub fn cpp_header_body_files_are_related(
472 left: &ProjectFile,
473 right: &ProjectFile,
474 implementation_imports: &[String],
475 include_targets: &IncludeTargetIndex,
476) -> bool {
477 let (header, implementation) = if cpp_source_path_is_header(left) {
478 (left, right)
479 } else if cpp_source_path_is_header(right) {
480 (right, left)
481 } else {
482 return false;
483 };
484 if cpp_source_path_is_header(implementation) {
485 return false;
486 }
487 implementation_imports
488 .iter()
489 .flat_map(|import| include_paths(std::slice::from_ref(import)))
490 .any(|include| {
491 let targets =
492 resolve_include_targets_with_index(implementation, &include, include_targets);
493 targets.len() == 1 && targets.first() == Some(header)
494 })
495}
496
497pub fn cpp_header_body_implementation_file<'a>(
501 left: &'a ProjectFile,
502 right: &'a ProjectFile,
503) -> Option<&'a ProjectFile> {
504 let implementation = if cpp_source_path_is_header(left) {
505 right
506 } else if cpp_source_path_is_header(right) {
507 left
508 } else {
509 return None;
510 };
511 (!cpp_source_path_is_header(implementation)).then_some(implementation)
512}
513
514pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
515 let path = rel_path_string(source).to_ascii_lowercase();
516 matches!(
517 path.rsplit('.').next(),
518 Some("h" | "hin" | "hh" | "hpp" | "hxx")
519 )
520}
521
522pub fn cpp_occurrence_role_for_range(
523 root: Node<'_>,
524 candidate: &CodeUnit,
525 range: &Range,
526) -> CppOccurrenceRole {
527 if !candidate.is_callable() && !candidate.is_class() {
528 return CppOccurrenceRole::Both;
529 }
530 let Some(node) = cpp_declaration_node_for_range(root, range) else {
531 return CppOccurrenceRole::Unknown;
532 };
533 if candidate.is_callable() {
534 return if subtree_contains(node, |descendant| {
535 descendant.kind() == "function_definition"
536 && descendant.child_by_field_name("body").is_some()
537 }) {
538 CppOccurrenceRole::Definition
539 } else {
540 CppOccurrenceRole::DeclarationOnly
541 };
542 }
543 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
544 return CppOccurrenceRole::Definition;
545 }
546 if !subtree_contains(node, |descendant| {
547 matches!(
548 descendant.kind(),
549 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
550 )
551 }) {
552 return CppOccurrenceRole::Both;
553 }
554 if subtree_contains(node, |descendant| {
555 matches!(
556 descendant.kind(),
557 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
558 ) && descendant.child_by_field_name("body").is_some()
559 }) {
560 CppOccurrenceRole::Definition
561 } else {
562 CppOccurrenceRole::DeclarationOnly
563 }
564}
565
566fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
567 node_for_exact_range(root, range).or_else(|| {
568 root.descendant_for_byte_range(range.start_byte, range.end_byte)
569 .and_then(|mut node| {
570 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
571 node = node.parent()?;
572 }
573 Some(node)
574 })
575 })
576}
577
578#[derive(Default)]
590pub struct CppReconciledDefinitionIndex {
591 pub rekeyed: Vec<CodeUnit>,
593 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Hash)]
613pub struct CppReconcileGroupKey {
614 pub member_identifier: String,
617 pub owner_terminal: Option<String>,
621}
622
623pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
630 let interner = segment_interner();
631 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
632 let (member_identifier, _) = interner.resolve(query_fq.last()?);
633 if member_identifier.is_empty() {
634 return None;
635 }
636 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
646 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
647 text.rsplit_once('$')
652 .map_or(text, |(_, tail)| tail)
653 .to_string()
654 });
655 Some(CppReconcileGroupKey {
656 member_identifier: member_identifier.to_string(),
657 owner_terminal,
658 })
659}
660
661pub struct CppReconcileCandidates {
667 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
668 all: Vec<CodeUnit>,
672}
673
674impl CppReconcileCandidates {
675 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
678 match &key.owner_terminal {
679 Some(owner_terminal) => self
680 .by_owner_terminal
681 .get(owner_terminal)
682 .map_or(&[][..], Vec::as_slice),
683 None => &self.all,
684 }
685 }
686
687 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
689 self.all.iter()
690 }
691
692 pub fn bucketed_len(&self) -> usize {
695 self.by_owner_terminal.values().map(Vec::len).sum()
696 }
697}
698
699pub fn cpp_reconcile_candidates(
707 cpp: &dyn CppSource,
708 member_identifier: &str,
709 keep_going: &dyn Fn() -> bool,
710) -> Option<CppReconcileCandidates> {
711 let candidates: BTreeSet<CodeUnit> = {
712 let _lookup =
713 profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
714 cpp.lookup_candidates_by_identifier(member_identifier)
715 };
716 profiling::note_with(|| {
717 format!(
718 "cpp.reconcile.candidates[{member_identifier}] n={}",
719 candidates.len()
720 )
721 });
722
723 let interner = segment_interner();
724 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
725 let mut all = Vec::new();
726 for (index, unit) in candidates.into_iter().enumerate() {
727 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
731 return None;
732 }
733 if !unit.is_callable() {
734 continue;
735 }
736 let owner_terminal = unit
737 .fq()
738 .segments()
739 .iter()
740 .filter_map(|&segment| {
741 let (text, kind) = interner.resolve(segment);
742 matches!(
746 kind,
747 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
748 )
749 .then_some(text)
750 })
751 .last();
752 if let Some(owner_terminal) = owner_terminal {
753 by_owner_terminal
754 .entry(owner_terminal.to_string())
755 .or_default()
756 .push(unit.clone());
757 }
758 all.push(unit);
759 }
760 Some(CppReconcileCandidates {
761 by_owner_terminal,
762 all,
763 })
764}
765
766const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
768
769pub fn cpp_reconcile_group(
786 cpp: &dyn CppSource,
787 token: QueryToken<'_>,
788 key: &CppReconcileGroupKey,
789 candidates: &CppReconcileCandidates,
790 keep_going: &dyn Fn() -> bool,
791 on_candidate: &dyn Fn(),
792) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
793 let _scope = profiling::scope_with(|| {
794 format!(
795 "cpp.reconciled.build[{}#{}]",
796 key.member_identifier,
797 key.owner_terminal.as_deref().unwrap_or("*")
798 )
799 });
800 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
801 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
802 for unit in candidates.for_group(key) {
803 if !keep_going() {
808 return None;
809 }
810 on_candidate();
811 let _candidate =
814 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
815 let role = {
816 let _role = profiling::scope("cpp.reconcile.role");
817 cpp_callable_unit_role(cpp, unit)
818 };
819 if !matches!(
820 role,
821 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
822 ) {
823 continue;
824 }
825 let Some(reconciled) =
826 cpp_reconcile_definition_identity(cpp, token, unit, &mut using_by_file)
827 else {
828 continue;
829 };
830 let canonical_fq = reconciled.fq_name();
831 if unit.fq_name() == canonical_fq {
838 continue;
839 }
840 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
849 let fq = cpp_member_fq(&reconciled.package, &short_name);
850 let rekeyed = CodeUnit::with_signature_and_fq(
851 unit.source().clone(),
852 unit.kind(),
853 reconciled.package,
854 short_name,
855 unit.signature().map(str::to_string),
856 unit.is_synthetic(),
857 fq,
858 );
859 let index = groups.entry(canonical_fq).or_default();
860 index.rekeyed.push(rekeyed.clone());
861 index.provisional_of.insert(rekeyed, unit.clone());
862 }
863 Some(
864 groups
865 .into_iter()
866 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
867 .collect(),
868 )
869}
870
871fn cpp_reconcile_definition_identity(
877 cpp: &dyn CppSource,
878 token: QueryToken<'_>,
879 unit: &CodeUnit,
880 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
881) -> Option<ReconciledIdentity> {
882 let interner = segment_interner();
892 let mut provisional_owner_segments: Vec<&str> = Vec::new();
893 let mut member: Option<&str> = None;
894 for &segment in unit.fq().segments() {
895 let (text, kind) = interner.resolve(segment);
896 match kind {
897 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
898 if member.is_some() {
902 return None;
903 }
904 if !text.is_empty() {
905 provisional_owner_segments.push(text);
906 }
907 }
908 SegmentKind::Member => member = Some(text),
909 _ => return None,
910 }
911 }
912 let member = member?;
913 let structured_owner_segments = cpp_structured_out_of_line_owner_segments(cpp, token, unit)
918 .filter(|segments| segments.len() == 1);
919 let owner_segments = structured_owner_segments.as_ref().map_or_else(
920 || provisional_owner_segments,
921 |segments| segments.iter().map(String::as_str).collect(),
922 );
923 if owner_segments.is_empty() {
924 return None;
925 }
926
927 let using = using_by_file
928 .entry(unit.source().clone())
929 .or_insert_with(|| {
930 Arc::new(
931 cpp.file_source(unit.source())
932 .map(|source| cpp_file_using_namespaces(&source))
933 .unwrap_or_default(),
934 )
935 })
936 .clone();
937 let mut namespace_candidates: Vec<&str> = vec![""];
938 namespace_candidates.extend(using.iter().map(String::as_str));
939
940 let visible = {
941 let _visible = profiling::scope_with(|| {
942 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
943 });
944 cpp.visible_type_units(unit.source())
945 };
946 let class_table: Vec<VisibleClass> = visible
947 .iter()
948 .filter(|candidate| candidate.is_class())
949 .map(|candidate| VisibleClass {
950 package: candidate.package_name(),
951 nested_short_name: candidate.short_name(),
952 })
953 .collect();
954
955 reconcile_out_of_line_member_identity(
956 &owner_segments,
957 member,
958 &namespace_candidates,
959 &class_table,
960 )
961}
962
963fn cpp_structured_out_of_line_owner_segments(
970 cpp: &dyn CppSource,
971 token: QueryToken<'_>,
972 unit: &CodeUnit,
973) -> Option<Vec<String>> {
974 let prepared = cpp.prepared_syntax(token, unit.source())?;
975 let root = prepared.tree().root_node();
976 for range in cpp.ranges(unit) {
977 let mut current = cpp_declaration_node_for_range(root, &range)?;
978 let function = loop {
979 if current.kind() == "function_definition" {
980 break current;
981 }
982 current = current.parent()?;
983 };
984 if function.child_by_field_name("body").is_none() {
985 continue;
986 }
987 let declarator = function.child_by_field_name("declarator")?;
988 let name = declarator_name_node(declarator)?;
989 if !qualified_name_has_concrete_scope_separators(name) {
990 continue;
991 }
992 let mut components = cpp_type_name_components(name, prepared.source())?;
993 components.pop()?;
994 if !components.is_empty() {
995 return Some(components);
996 }
997 }
998 None
999}
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::*;
1004
1005 fn parse_cpp(source: &str) -> Tree {
1006 let mut parser = Parser::new();
1007 parser
1008 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1009 .expect("cpp language");
1010 parser.parse(source, None).expect("cpp tree")
1011 }
1012
1013 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1014 let end = start + text.len();
1015 assert_eq!(&source[start..end], text, "the probe must name the token");
1016 let node = tree
1017 .root_node()
1018 .named_descendant_for_byte_range(start, end)
1019 .expect("a node spans the probed range");
1020 assert_eq!(
1021 (node.start_byte(), node.end_byte()),
1022 (start, end),
1023 "the probed range must be exactly one node: {}",
1024 node.to_sexp()
1025 );
1026 cpp_is_constructor_or_destructor_declarator_name(node, source)
1027 }
1028
1029 fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1030 let end = start + text.len();
1031 assert_eq!(&source[start..end], text, "the probe must name the token");
1032 let node = tree
1033 .root_node()
1034 .named_descendant_for_byte_range(start, end)
1035 .expect("a node spans the probed range");
1036 assert_eq!(
1037 (node.start_byte(), node.end_byte()),
1038 (start, end),
1039 "the probed range must be exactly one node: {}",
1040 node.to_sexp()
1041 );
1042 cpp_is_conversion_operator_target_type(node)
1043 }
1044
1045 fn is_recovered_macro_character_type(
1046 tree: &Tree,
1047 source: &str,
1048 start: usize,
1049 text: &str,
1050 ) -> bool {
1051 let end = start + text.len();
1052 assert_eq!(&source[start..end], text, "the probe must name the token");
1053 let node = tree
1054 .root_node()
1055 .named_descendant_for_byte_range(start, end)
1056 .expect("a node spans the probed range");
1057 cpp_is_recovered_macro_character_token_type(node)
1058 }
1059
1060 #[test]
1061 fn recovered_macro_character_tokens_are_not_type_references() {
1062 let source = concat!(
1063 "struct I {};\n",
1064 "#define STRING_TOKEN_(name, ...)\n",
1065 "struct Schema {\n",
1066 " STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1067 " void ordinary(I value);\n",
1068 " void malformed(I value, @);\n",
1069 " void use() { I value; consume('I'); }\n",
1070 "};\n",
1071 );
1072 let tree = parse_cpp(source);
1073 let recovered_m = source.find("'m'").expect("recovered m") + 1;
1074 let recovered_i = source.find("'I'").expect("recovered I") + 1;
1075 for (label, start, text) in [
1076 ("lowercase character token", recovered_m, "m"),
1077 ("uppercase character token", recovered_i, "I"),
1078 ] {
1079 assert!(
1080 is_recovered_macro_character_type(&tree, source, start, text),
1081 "{label} must match the exact recovery role"
1082 );
1083 }
1084
1085 let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1086 let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1087 let malformed =
1088 source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1089 let local = source
1090 .find("I value; consume")
1091 .expect("local type reference");
1092 let expression_character = source.rfind("'I'").expect("expression character") + 1;
1093 for (label, start, text) in [
1094 ("unquoted macro argument", macro_first_argument, "MaxItems"),
1095 ("ordinary parameter type", ordinary, "I"),
1096 ("parameter beside another error", malformed, "I"),
1097 ("local type reference", local, "I"),
1098 ("expression character literal", expression_character, "I"),
1099 ] {
1100 assert!(
1101 !is_recovered_macro_character_type(&tree, source, start, text),
1102 "{label} must remain outside the recovery role"
1103 );
1104 }
1105 }
1106
1107 #[test]
1108 fn conversion_operator_target_components_are_identity_syntax_only() {
1109 let source = concat!(
1110 "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1111 "using other::Target;\n",
1112 "struct Source {\n",
1113 " operator Target() const;\n",
1114 " operator other::Target() const { return other::Target{}; }\n",
1115 " template<class T> operator other::Box<T>() const { return {}; }\n",
1116 " operator other::Target const&() const;\n",
1117 " operator other::Target*() const;\n",
1118 " other::Target ordinary() const {\n",
1119 " return reinterpret_cast<other::Target&>(*this);\n",
1120 " }\n",
1121 " other::Target operator+() const { return {}; }\n",
1122 "};\n",
1123 );
1124 let tree = parse_cpp(source);
1125
1126 let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1127 let qualified = source
1128 .find("operator other::Target()")
1129 .expect("qualified target")
1130 + "operator ".len();
1131 let template = source
1132 .find("operator other::Box<T>")
1133 .expect("template target")
1134 + "operator ".len();
1135 let cv_reference = source
1136 .find("operator other::Target const&")
1137 .expect("cv-reference target")
1138 + "operator other::".len();
1139 let pointer = source
1140 .find("operator other::Target*")
1141 .expect("pointer target")
1142 + "operator other::".len();
1143
1144 for (label, start, text) in [
1145 ("bare target", bare, "Target"),
1146 ("qualified target scope", qualified, "other"),
1147 (
1148 "qualified target name",
1149 qualified + "other::".len(),
1150 "Target",
1151 ),
1152 ("template target scope", template, "other"),
1153 ("template target name", template + "other::".len(), "Box"),
1154 (
1155 "template target argument",
1156 template + "other::Box<".len(),
1157 "T",
1158 ),
1159 ("cv-reference target", cv_reference, "Target"),
1160 ("pointer target", pointer, "Target"),
1161 ] {
1162 assert!(
1163 is_conversion_target(&tree, source, start, text),
1164 "the {label} at byte {start} belongs to the conversion identity"
1165 );
1166 }
1167
1168 let ordinary_return = source
1169 .find("other::Target ordinary")
1170 .expect("ordinary return");
1171 let body_cast = source
1172 .find("reinterpret_cast<other::Target")
1173 .expect("body cast")
1174 + "reinterpret_cast<".len();
1175 let overloaded_return = source
1176 .find("other::Target operator+")
1177 .expect("overloaded operator return");
1178 for (label, start) in [
1179 ("ordinary return type", ordinary_return),
1180 ("body cast target", body_cast),
1181 ("overloaded-operator return type", overloaded_return),
1182 ] {
1183 assert!(
1184 !is_conversion_target(&tree, source, start, "other"),
1185 "the {label} at byte {start} stays a reference"
1186 );
1187 }
1188 }
1189
1190 #[test]
1195 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1196 let source = concat!(
1197 "class Foo {\n",
1198 "public:\n",
1199 " Foo();\n",
1200 " Foo(const Foo&);\n",
1201 " ~Foo();\n",
1202 " void m();\n",
1203 "};\n",
1204 "Foo::Foo() {}\n",
1205 "Foo::~Foo() {}\n",
1206 "void Foo::m() {}\n",
1207 );
1208 let tree = parse_cpp(source);
1209
1210 for (label, start, text) in [
1211 (
1212 "constructor declaration",
1213 source.find("Foo();").expect("ctor"),
1214 "Foo",
1215 ),
1216 (
1217 "copy constructor declaration",
1218 source.find("Foo(const Foo&);").expect("copy ctor"),
1219 "Foo",
1220 ),
1221 (
1222 "destructor name",
1223 source.find("~Foo();").expect("dtor"),
1224 "~Foo",
1225 ),
1226 (
1227 "identifier inside the destructor name",
1228 source.find("~Foo();").expect("dtor") + "~".len(),
1229 "Foo",
1230 ),
1231 (
1232 "out-of-line constructor definition name",
1233 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1234 "Foo",
1235 ),
1236 (
1237 "out-of-line destructor definition name",
1238 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1239 "~Foo",
1240 ),
1241 ] {
1242 assert!(
1243 is_declarator_name(&tree, source, start, text),
1244 "the {label} at byte {start} is a declaration occurrence"
1245 );
1246 }
1247
1248 for (label, start, text) in [
1249 (
1250 "class name",
1251 source.find("class Foo {").expect("class") + "class ".len(),
1252 "Foo",
1253 ),
1254 (
1255 "parameter type",
1256 source.find("const Foo&").expect("parameter type") + "const ".len(),
1257 "Foo",
1258 ),
1259 (
1260 "owning scope of an out-of-line constructor",
1261 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1262 "Foo",
1263 ),
1264 (
1265 "owning scope of an out-of-line destructor",
1266 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1267 "Foo",
1268 ),
1269 (
1270 "out-of-line method name",
1271 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1272 "m",
1273 ),
1274 ] {
1275 assert!(
1276 !is_declarator_name(&tree, source, start, text),
1277 "the {label} at byte {start} stays a reference"
1278 );
1279 }
1280 }
1281
1282 #[test]
1286 fn constructor_call_sites_stay_references() {
1287 let source = concat!(
1288 "struct B { B(int); };\n",
1289 "struct D : B {\n",
1290 " D(int x) : B(x), base_(x) {}\n",
1291 " int base_;\n",
1292 "};\n",
1293 "void g() {\n",
1294 " D* p = new D(1);\n",
1295 " D x(2);\n",
1296 " D(3);\n",
1297 " g();\n",
1298 "}\n",
1299 );
1300 let tree = parse_cpp(source);
1301
1302 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1303 assert!(
1304 is_declarator_name(&tree, source, inline_declarator, "D"),
1305 "an inline constructor definition name is still a declarator"
1306 );
1307
1308 for (label, start, text) in [
1309 (
1310 "base member initializer",
1311 source.find(": B(x)").expect("base initializer") + ": ".len(),
1312 "B",
1313 ),
1314 (
1315 "field member initializer",
1316 source.find("base_(x) {}").expect("field initializer"),
1317 "base_",
1318 ),
1319 (
1320 "new expression type",
1321 source.find("new D(1)").expect("new expression") + "new ".len(),
1322 "D",
1323 ),
1324 (
1325 "direct initialization type",
1326 source.find("D x(2)").expect("direct initialization"),
1327 "D",
1328 ),
1329 (
1330 "temporary construction statement",
1331 source.find("D(3)").expect("temporary"),
1332 "D",
1333 ),
1334 (
1335 "recursive call in a real body",
1336 source.find("g();").expect("recursive call"),
1337 "g",
1338 ),
1339 ] {
1340 assert!(
1341 !is_declarator_name(&tree, source, start, text),
1342 "the {label} at byte {start} is a reference"
1343 );
1344 }
1345 }
1346
1347 #[test]
1352 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1353 let source = concat!(
1354 "class SAMPLE_EXPORT Properties {\n",
1355 " public:\n",
1356 " Properties();\n",
1357 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1358 " int size() const;\n",
1359 " int total() { return size(); }\n",
1360 "};\n",
1361 );
1362 let tree = parse_cpp(source);
1363
1364 let recovered = source.find("Properties();").expect("recovered constructor");
1365 assert!(
1366 is_declarator_name(&tree, source, recovered, "Properties"),
1367 "a constructor declaration the parse read as a call is still a declarator"
1368 );
1369
1370 for (label, start, text) in [
1371 (
1372 "class name in the recovered header",
1373 source
1374 .find("class SAMPLE_EXPORT Properties")
1375 .expect("class")
1376 + "class SAMPLE_EXPORT ".len(),
1377 "Properties",
1378 ),
1379 (
1380 "macro invocation in the recovered body",
1381 source
1382 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1383 .expect("macro invocation"),
1384 "DISALLOW_COPY_AND_ASSIGN",
1385 ),
1386 (
1387 "call inside a method body the recovery kept",
1388 source.find("return size();").expect("member call") + "return ".len(),
1389 "size",
1390 ),
1391 ] {
1392 assert!(
1393 !is_declarator_name(&tree, source, start, text),
1394 "the {label} at byte {start} stays a reference"
1395 );
1396 }
1397 }
1398}