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, SignatureMetadata};
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::sync::Arc;
37use tree_sitter::{Node, Parser, Tree};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum CppCallableUnitRole {
41 DeclarationOnly,
42 Definition,
43 Both,
44 Unknown,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum CppOccurrenceRole {
49 DeclarationOnly,
50 Definition,
51 Both,
52 Unknown,
53}
54
55impl CppOccurrenceRole {
56 pub fn api_label(self) -> Option<&'static str> {
57 match self {
58 Self::DeclarationOnly => Some("declaration"),
59 Self::Definition => Some("definition"),
60 Self::Both | Self::Unknown => None,
61 }
62 }
63}
64
65pub struct CppOccurrenceClassifier {
66 tree: Tree,
67}
68
69impl CppOccurrenceClassifier {
70 pub fn new(source: &str) -> Option<Self> {
71 let mut parser = Parser::new();
72 parser
73 .set_language(&tree_sitter_cpp::LANGUAGE.into())
74 .ok()?;
75 parser.parse(source, None).map(|tree| Self { tree })
76 }
77
78 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
79 cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
80 }
81}
82
83pub fn cpp_callable_unit_role(
84 index: &dyn CodeUnitIndex,
85 callable: &CodeUnit,
86) -> CppCallableUnitRole {
87 cpp_callable_unit_role_from_metadata(callable, index.signature_metadata(callable))
88}
89
90fn cpp_callable_unit_role_from_metadata(
91 callable: &CodeUnit,
92 metadata: impl IntoIterator<Item = SignatureMetadata>,
93) -> CppCallableUnitRole {
94 if !callable.is_callable() {
95 return CppCallableUnitRole::Unknown;
96 }
97 let mut declaration = false;
98 let mut definition = false;
99 for metadata in metadata {
100 if metadata.is_declaration_only() {
101 declaration = true;
102 } else {
103 definition = true;
104 }
105 }
106 match (declaration, definition) {
107 (true, false) => CppCallableUnitRole::DeclarationOnly,
108 (false, true) => CppCallableUnitRole::Definition,
109 (true, true) => CppCallableUnitRole::Both,
110 (false, false) => CppCallableUnitRole::Unknown,
111 }
112}
113
114pub fn cpp_indexed_callable_linkage(
115 index: &dyn CodeUnitIndex,
116 callable: &CodeUnit,
117) -> Option<CallableLinkage> {
118 let mut external = false;
119 for metadata in index.signature_metadata(callable) {
120 match metadata.callable_linkage() {
121 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
122 Some(CallableLinkage::External) => external = true,
123 None => {}
124 }
125 }
126 external.then_some(CallableLinkage::External)
127}
128
129pub fn cpp_callable_definitions_share_identity_evidence(
135 index: &dyn CodeUnitIndex,
136 left: &CodeUnit,
137 right: &CodeUnit,
138 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
139) -> bool {
140 left.source() == right.source()
141 || (left.fq_name() == right.fq_name()
142 && left.signature() == right.signature()
143 && matches!(
144 cpp_indexed_callable_linkage(index, left),
145 Some(CallableLinkage::External)
146 )
147 && matches!(
148 cpp_indexed_callable_linkage(index, right),
149 Some(CallableLinkage::External)
150 )
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.source() == right.source()
177 || (left.fq_name() == right.fq_name()
178 && visibility.same_logical_callable(analyzer, left, right)
179 && matches!(
180 cpp_indexed_callable_linkage(analyzer.index, left),
181 Some(CallableLinkage::External)
182 )
183 && matches!(
184 cpp_indexed_callable_linkage(analyzer.index, right),
185 Some(CallableLinkage::External)
186 )
187 && header_body_related(left.source(), right.source()))
188}
189
190pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
194 let mut current = Some(node);
195 while let Some(candidate) = current {
196 let Some(parent) = candidate.parent() else {
197 return false;
198 };
199 if parent.kind() == "for_range_loop" {
200 return parent
201 .child_by_field_name("declarator")
202 .is_some_and(|declarator| {
203 cpp_range_for_declarator_contains_name(declarator, node)
204 });
205 }
206 current = Some(parent);
207 }
208 false
209}
210
211pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
220 while let Some(parent) = node.parent() {
221 if parent.kind() == "operator_cast" {
222 return true;
223 }
224 if matches!(
225 parent.kind(),
226 "function_declarator" | "declaration" | "function_definition" | "translation_unit"
227 ) {
228 return false;
229 }
230 node = parent;
231 }
232 false
233}
234
235pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
245 if node.kind() != "type_identifier" {
246 return false;
247 }
248 let Some(parameter) = node.parent() else {
249 return false;
250 };
251 if parameter.kind() != "parameter_declaration"
252 || parameter.child_by_field_name("type") != Some(node)
253 || parameter.child_by_field_name("declarator").is_some()
254 || parameter
255 .parent()
256 .is_none_or(|parent| parent.kind() != "parameter_list")
257 {
258 return false;
259 }
260
261 parameter
262 .prev_named_sibling()
263 .is_some_and(cpp_is_recovered_character_quote)
264 && parameter
265 .next_named_sibling()
266 .is_some_and(cpp_is_recovered_character_quote)
267}
268
269fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
270 node.is_error()
271 && node.child_count() == 1
272 && node
273 .child(0)
274 .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
275}
276
277pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
300 cpp_is_declared_constructor_or_destructor_name(node)
301 || cpp_is_recovered_constructor_or_destructor_name(node, source)
302}
303
304fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
310 let mut name = node;
311 if let Some(parent) = name.parent()
312 && parent.kind() == "destructor_name"
313 {
314 name = parent;
315 }
316 while let Some(parent) = name.parent() {
320 if parent.kind() != "qualified_identifier"
321 || parent.child_by_field_name("name") != Some(name)
322 {
323 break;
324 }
325 name = parent;
326 }
327 let Some(declarator) = name.parent() else {
328 return false;
329 };
330 if declarator.kind() != "function_declarator"
331 || declarator.child_by_field_name("declarator") != Some(name)
332 {
333 return false;
334 }
335 let Some(owner) = declarator.parent() else {
336 return false;
337 };
338 matches!(owner.kind(), "declaration" | "function_definition")
339 && owner.child_by_field_name("declarator") == Some(declarator)
340 && owner.child_by_field_name("type").is_none()
341}
342
343fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
361 if node.kind() != "identifier" {
362 return false;
363 }
364 let Some(call) = node.parent() else {
365 return false;
366 };
367 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
368 return false;
369 }
370 let mut current = call.parent();
371 while let Some(ancestor) = current {
372 if ancestor.kind() == "function_definition" {
373 return ancestor
374 .child_by_field_name("declarator")
375 .is_some_and(|declarator| declarator.kind() == "identifier")
376 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
377 }
378 current = ancestor.parent();
379 }
380 false
381}
382
383fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
386 let header_end = definition
387 .child_by_field_name("body")
388 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
389 let mut stack = vec![definition];
390 while let Some(node) = stack.pop() {
391 if node.start_byte() >= header_end {
392 continue;
393 }
394 if matches!(
395 node.kind(),
396 "identifier" | "type_identifier" | "namespace_identifier"
397 ) && node_text(node, source) == name
398 {
399 return true;
400 }
401 let mut cursor = node.walk();
402 for child in node.named_children(&mut cursor) {
403 stack.push(child);
404 }
405 }
406 false
407}
408
409fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
410 let mut pending = vec![declarator];
411 while let Some(candidate) = pending.pop() {
412 match candidate.kind() {
413 "identifier" | "field_identifier" => {
414 if cpp_same_node(candidate, target) {
415 return true;
416 }
417 }
418 "structured_binding_declarator" => {
419 let mut cursor = candidate.walk();
420 if candidate
421 .named_children(&mut cursor)
422 .any(|name| cpp_same_node(name, target))
423 {
424 return true;
425 }
426 }
427 "pointer_declarator"
428 | "reference_declarator"
429 | "array_declarator"
430 | "attributed_declarator"
431 | "parenthesized_declarator"
432 | "function_declarator"
433 | "init_declarator" => {
434 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
435 pending.push(inner);
436 }
437 }
438 _ => {}
439 }
440 }
441 false
442}
443
444fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
445 node.child_by_field_name("declarator").or_else(|| {
446 let mut cursor = node.walk();
447 node.named_children(&mut cursor).find(|child| {
448 matches!(
449 child.kind(),
450 "identifier"
451 | "field_identifier"
452 | "structured_binding_declarator"
453 | "pointer_declarator"
454 | "reference_declarator"
455 | "array_declarator"
456 | "attributed_declarator"
457 | "parenthesized_declarator"
458 | "function_declarator"
459 | "init_declarator"
460 )
461 })
462 })
463}
464
465fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
466 left.id() == right.id()
467 && left.start_byte() == right.start_byte()
468 && left.end_byte() == right.end_byte()
469}
470
471pub fn cpp_header_body_files_are_related(
478 left: &ProjectFile,
479 right: &ProjectFile,
480 implementation_imports: &[String],
481 include_targets: &IncludeTargetIndex,
482) -> bool {
483 let (header, implementation) = if cpp_source_path_is_header(left) {
484 (left, right)
485 } else if cpp_source_path_is_header(right) {
486 (right, left)
487 } else {
488 return false;
489 };
490 if cpp_source_path_is_header(implementation) {
491 return false;
492 }
493 implementation_imports
494 .iter()
495 .flat_map(|import| include_paths(std::slice::from_ref(import)))
496 .any(|include| {
497 let targets =
498 resolve_include_targets_with_index(implementation, &include, include_targets);
499 targets.len() == 1 && targets.first() == Some(header)
500 })
501}
502
503pub fn cpp_header_body_implementation_file<'a>(
507 left: &'a ProjectFile,
508 right: &'a ProjectFile,
509) -> Option<&'a ProjectFile> {
510 let implementation = if cpp_source_path_is_header(left) {
511 right
512 } else if cpp_source_path_is_header(right) {
513 left
514 } else {
515 return None;
516 };
517 (!cpp_source_path_is_header(implementation)).then_some(implementation)
518}
519
520pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
521 let path = rel_path_string(source).to_ascii_lowercase();
522 matches!(
523 path.rsplit('.').next(),
524 Some("h" | "hin" | "hh" | "hpp" | "hxx")
525 )
526}
527
528pub fn cpp_occurrence_role_for_range(
529 root: Node<'_>,
530 candidate: &CodeUnit,
531 range: &Range,
532) -> CppOccurrenceRole {
533 if !candidate.is_callable() && !candidate.is_class() {
534 return CppOccurrenceRole::Both;
535 }
536 let Some(node) = cpp_declaration_node_for_range(root, range) else {
537 return CppOccurrenceRole::Unknown;
538 };
539 if candidate.is_callable() {
540 return if subtree_contains(node, |descendant| {
541 descendant.kind() == "function_definition"
542 && descendant.child_by_field_name("body").is_some()
543 }) {
544 CppOccurrenceRole::Definition
545 } else {
546 CppOccurrenceRole::DeclarationOnly
547 };
548 }
549 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
550 return CppOccurrenceRole::Definition;
551 }
552 if !subtree_contains(node, |descendant| {
553 matches!(
554 descendant.kind(),
555 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
556 )
557 }) {
558 return CppOccurrenceRole::Both;
559 }
560 if subtree_contains(node, |descendant| {
561 matches!(
562 descendant.kind(),
563 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
564 ) && descendant.child_by_field_name("body").is_some()
565 }) {
566 CppOccurrenceRole::Definition
567 } else {
568 CppOccurrenceRole::DeclarationOnly
569 }
570}
571
572fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
573 node_for_exact_range(root, range).or_else(|| {
574 root.descendant_for_byte_range(range.start_byte, range.end_byte)
575 .and_then(|mut node| {
576 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
577 node = node.parent()?;
578 }
579 Some(node)
580 })
581 })
582}
583
584#[derive(Default)]
596pub struct CppReconciledDefinitionIndex {
597 pub rekeyed: Vec<CodeUnit>,
599 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Hash)]
619pub struct CppReconcileGroupKey {
620 pub member_identifier: String,
623 pub owner_terminal: Option<String>,
627}
628
629pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
636 let interner = segment_interner();
637 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
638 let (member_identifier, _) = interner.resolve(query_fq.last()?);
639 if member_identifier.is_empty() {
640 return None;
641 }
642 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
652 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
653 text.rsplit_once('$')
658 .map_or(text, |(_, tail)| tail)
659 .to_string()
660 });
661 Some(CppReconcileGroupKey {
662 member_identifier: member_identifier.to_string(),
663 owner_terminal,
664 })
665}
666
667pub struct CppReconcileCandidates {
673 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
674 all: Vec<CodeUnit>,
678}
679
680impl CppReconcileCandidates {
681 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
684 match &key.owner_terminal {
685 Some(owner_terminal) => self
686 .by_owner_terminal
687 .get(owner_terminal)
688 .map_or(&[][..], Vec::as_slice),
689 None => &self.all,
690 }
691 }
692
693 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
695 self.all.iter()
696 }
697
698 pub fn bucketed_len(&self) -> usize {
701 self.by_owner_terminal.values().map(Vec::len).sum()
702 }
703}
704
705pub fn cpp_reconcile_candidates_from_units(
712 candidates: impl IntoIterator<Item = CodeUnit>,
713 keep_going: &dyn Fn() -> bool,
714) -> Option<CppReconcileCandidates> {
715 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
716 candidates.sort();
717 candidates.dedup();
718 let interner = segment_interner();
719 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
720 let mut all = Vec::new();
721 for (index, unit) in candidates.into_iter().enumerate() {
722 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
726 return None;
727 }
728 if !unit.is_callable() {
729 continue;
730 }
731 let owner_terminal = unit
732 .fq()
733 .segments()
734 .iter()
735 .filter_map(|&segment| {
736 let (text, kind) = interner.resolve(segment);
737 matches!(
741 kind,
742 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
743 )
744 .then_some(text)
745 })
746 .last();
747 if let Some(owner_terminal) = owner_terminal {
748 by_owner_terminal
749 .entry(owner_terminal.to_string())
750 .or_default()
751 .push(unit.clone());
752 }
753 all.push(unit);
754 }
755 Some(CppReconcileCandidates {
756 by_owner_terminal,
757 all,
758 })
759}
760
761const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
763
764pub fn cpp_reconcile_group(
781 cpp: &dyn CppSource,
782 token: QueryToken<'_>,
783 key: &CppReconcileGroupKey,
784 candidates: &CppReconcileCandidates,
785 keep_going: &dyn Fn() -> bool,
786 on_candidate: &dyn Fn(),
787) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
788 let _scope = profiling::scope_with(|| {
789 format!(
790 "cpp.reconciled.build[{}#{}]",
791 key.member_identifier,
792 key.owner_terminal.as_deref().unwrap_or("*")
793 )
794 });
795 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
796 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
797 for unit in candidates.for_group(key) {
798 if !keep_going() {
803 return None;
804 }
805 on_candidate();
806 let _candidate =
809 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
810 let role = {
811 let _role = profiling::scope("cpp.reconcile.role");
812 cpp.stored_callable_unit_role(unit)
813 };
814 if !matches!(
815 role,
816 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
817 ) {
818 continue;
819 }
820 let Some(reconciled) =
821 cpp_reconcile_definition_identity(cpp, token, unit, &mut using_by_file)
822 else {
823 continue;
824 };
825 let canonical_fq = reconciled.fq_name();
826 if unit.fq_name() == canonical_fq {
833 continue;
834 }
835 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
844 let fq = cpp_member_fq(&reconciled.package, &short_name);
845 let rekeyed = CodeUnit::with_signature_and_fq(
846 unit.source().clone(),
847 unit.kind(),
848 reconciled.package,
849 short_name,
850 unit.signature().map(str::to_string),
851 unit.is_synthetic(),
852 fq,
853 );
854 let index = groups.entry(canonical_fq).or_default();
855 index.rekeyed.push(rekeyed.clone());
856 index.provisional_of.insert(rekeyed, unit.clone());
857 }
858 Some(
859 groups
860 .into_iter()
861 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
862 .collect(),
863 )
864}
865
866fn cpp_reconcile_definition_identity(
872 cpp: &dyn CppSource,
873 token: QueryToken<'_>,
874 unit: &CodeUnit,
875 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
876) -> Option<ReconciledIdentity> {
877 let interner = segment_interner();
887 let mut provisional_owner_segments: Vec<&str> = Vec::new();
888 let mut member: Option<&str> = None;
889 for &segment in unit.fq().segments() {
890 let (text, kind) = interner.resolve(segment);
891 match kind {
892 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
893 if member.is_some() {
897 return None;
898 }
899 if !text.is_empty() {
900 provisional_owner_segments.push(text);
901 }
902 }
903 SegmentKind::Member => member = Some(text),
904 _ => return None,
905 }
906 }
907 let member = member?;
908 let structured_owner_segments = cpp_structured_out_of_line_owner_segments(cpp, token, unit)
913 .filter(|segments| segments.len() == 1);
914 let owner_segments = structured_owner_segments.as_ref().map_or_else(
915 || provisional_owner_segments,
916 |segments| segments.iter().map(String::as_str).collect(),
917 );
918 if owner_segments.is_empty() {
919 return None;
920 }
921
922 let using = using_by_file
923 .entry(unit.source().clone())
924 .or_insert_with(|| {
925 Arc::new(
926 cpp.file_source(unit.source())
927 .map(|source| cpp_file_using_namespaces(&source))
928 .unwrap_or_default(),
929 )
930 })
931 .clone();
932 let mut namespace_candidates: Vec<&str> = vec![""];
933 namespace_candidates.extend(using.iter().map(String::as_str));
934
935 let visible = {
936 let _visible = profiling::scope_with(|| {
937 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
938 });
939 cpp.visible_type_units(unit.source())
940 };
941 let class_table: Vec<VisibleClass> = visible
942 .iter()
943 .filter(|candidate| candidate.is_class())
944 .map(|candidate| VisibleClass {
945 package: candidate.package_name(),
946 nested_short_name: candidate.short_name(),
947 })
948 .collect();
949
950 reconcile_out_of_line_member_identity(
951 &owner_segments,
952 member,
953 &namespace_candidates,
954 &class_table,
955 )
956}
957
958fn cpp_structured_out_of_line_owner_segments(
965 cpp: &dyn CppSource,
966 token: QueryToken<'_>,
967 unit: &CodeUnit,
968) -> Option<Vec<String>> {
969 let prepared = cpp.prepared_syntax(token, unit.source())?;
970 let root = prepared.tree().root_node();
971 for range in cpp.ranges(unit) {
972 let mut current = cpp_declaration_node_for_range(root, &range)?;
973 let function = loop {
974 if current.kind() == "function_definition" {
975 break current;
976 }
977 current = current.parent()?;
978 };
979 if function.child_by_field_name("body").is_none() {
980 continue;
981 }
982 let declarator = function.child_by_field_name("declarator")?;
983 let name = declarator_name_node(declarator)?;
984 if !qualified_name_has_concrete_scope_separators(name) {
985 continue;
986 }
987 let mut components = cpp_type_name_components(name, prepared.source())?;
988 components.pop()?;
989 if !components.is_empty() {
990 return Some(components);
991 }
992 }
993 None
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999
1000 fn parse_cpp(source: &str) -> Tree {
1001 let mut parser = Parser::new();
1002 parser
1003 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1004 .expect("cpp language");
1005 parser.parse(source, None).expect("cpp tree")
1006 }
1007
1008 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1009 let end = start + text.len();
1010 assert_eq!(&source[start..end], text, "the probe must name the token");
1011 let node = tree
1012 .root_node()
1013 .named_descendant_for_byte_range(start, end)
1014 .expect("a node spans the probed range");
1015 assert_eq!(
1016 (node.start_byte(), node.end_byte()),
1017 (start, end),
1018 "the probed range must be exactly one node: {}",
1019 node.to_sexp()
1020 );
1021 cpp_is_constructor_or_destructor_declarator_name(node, source)
1022 }
1023
1024 fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
1025 let end = start + text.len();
1026 assert_eq!(&source[start..end], text, "the probe must name the token");
1027 let node = tree
1028 .root_node()
1029 .named_descendant_for_byte_range(start, end)
1030 .expect("a node spans the probed range");
1031 assert_eq!(
1032 (node.start_byte(), node.end_byte()),
1033 (start, end),
1034 "the probed range must be exactly one node: {}",
1035 node.to_sexp()
1036 );
1037 cpp_is_conversion_operator_target_type(node)
1038 }
1039
1040 fn is_recovered_macro_character_type(
1041 tree: &Tree,
1042 source: &str,
1043 start: usize,
1044 text: &str,
1045 ) -> bool {
1046 let end = start + text.len();
1047 assert_eq!(&source[start..end], text, "the probe must name the token");
1048 let node = tree
1049 .root_node()
1050 .named_descendant_for_byte_range(start, end)
1051 .expect("a node spans the probed range");
1052 cpp_is_recovered_macro_character_token_type(node)
1053 }
1054
1055 #[test]
1056 fn recovered_macro_character_tokens_are_not_type_references() {
1057 let source = concat!(
1058 "struct I {};\n",
1059 "#define STRING_TOKEN_(name, ...)\n",
1060 "struct Schema {\n",
1061 " STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1062 " void ordinary(I value);\n",
1063 " void malformed(I value, @);\n",
1064 " void use() { I value; consume('I'); }\n",
1065 "};\n",
1066 );
1067 let tree = parse_cpp(source);
1068 let recovered_m = source.find("'m'").expect("recovered m") + 1;
1069 let recovered_i = source.find("'I'").expect("recovered I") + 1;
1070 for (label, start, text) in [
1071 ("lowercase character token", recovered_m, "m"),
1072 ("uppercase character token", recovered_i, "I"),
1073 ] {
1074 assert!(
1075 is_recovered_macro_character_type(&tree, source, start, text),
1076 "{label} must match the exact recovery role"
1077 );
1078 }
1079
1080 let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1081 let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1082 let malformed =
1083 source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1084 let local = source
1085 .find("I value; consume")
1086 .expect("local type reference");
1087 let expression_character = source.rfind("'I'").expect("expression character") + 1;
1088 for (label, start, text) in [
1089 ("unquoted macro argument", macro_first_argument, "MaxItems"),
1090 ("ordinary parameter type", ordinary, "I"),
1091 ("parameter beside another error", malformed, "I"),
1092 ("local type reference", local, "I"),
1093 ("expression character literal", expression_character, "I"),
1094 ] {
1095 assert!(
1096 !is_recovered_macro_character_type(&tree, source, start, text),
1097 "{label} must remain outside the recovery role"
1098 );
1099 }
1100 }
1101
1102 #[test]
1103 fn conversion_operator_target_components_are_identity_syntax_only() {
1104 let source = concat!(
1105 "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1106 "using other::Target;\n",
1107 "struct Source {\n",
1108 " operator Target() const;\n",
1109 " operator other::Target() const { return other::Target{}; }\n",
1110 " template<class T> operator other::Box<T>() const { return {}; }\n",
1111 " operator other::Target const&() const;\n",
1112 " operator other::Target*() const;\n",
1113 " other::Target ordinary() const {\n",
1114 " return reinterpret_cast<other::Target&>(*this);\n",
1115 " }\n",
1116 " other::Target operator+() const { return {}; }\n",
1117 "};\n",
1118 );
1119 let tree = parse_cpp(source);
1120
1121 let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1122 let qualified = source
1123 .find("operator other::Target()")
1124 .expect("qualified target")
1125 + "operator ".len();
1126 let template = source
1127 .find("operator other::Box<T>")
1128 .expect("template target")
1129 + "operator ".len();
1130 let cv_reference = source
1131 .find("operator other::Target const&")
1132 .expect("cv-reference target")
1133 + "operator other::".len();
1134 let pointer = source
1135 .find("operator other::Target*")
1136 .expect("pointer target")
1137 + "operator other::".len();
1138
1139 for (label, start, text) in [
1140 ("bare target", bare, "Target"),
1141 ("qualified target scope", qualified, "other"),
1142 (
1143 "qualified target name",
1144 qualified + "other::".len(),
1145 "Target",
1146 ),
1147 ("template target scope", template, "other"),
1148 ("template target name", template + "other::".len(), "Box"),
1149 (
1150 "template target argument",
1151 template + "other::Box<".len(),
1152 "T",
1153 ),
1154 ("cv-reference target", cv_reference, "Target"),
1155 ("pointer target", pointer, "Target"),
1156 ] {
1157 assert!(
1158 is_conversion_target(&tree, source, start, text),
1159 "the {label} at byte {start} belongs to the conversion identity"
1160 );
1161 }
1162
1163 let ordinary_return = source
1164 .find("other::Target ordinary")
1165 .expect("ordinary return");
1166 let body_cast = source
1167 .find("reinterpret_cast<other::Target")
1168 .expect("body cast")
1169 + "reinterpret_cast<".len();
1170 let overloaded_return = source
1171 .find("other::Target operator+")
1172 .expect("overloaded operator return");
1173 for (label, start) in [
1174 ("ordinary return type", ordinary_return),
1175 ("body cast target", body_cast),
1176 ("overloaded-operator return type", overloaded_return),
1177 ] {
1178 assert!(
1179 !is_conversion_target(&tree, source, start, "other"),
1180 "the {label} at byte {start} stays a reference"
1181 );
1182 }
1183 }
1184
1185 #[test]
1190 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1191 let source = concat!(
1192 "class Foo {\n",
1193 "public:\n",
1194 " Foo();\n",
1195 " Foo(const Foo&);\n",
1196 " ~Foo();\n",
1197 " void m();\n",
1198 "};\n",
1199 "Foo::Foo() {}\n",
1200 "Foo::~Foo() {}\n",
1201 "void Foo::m() {}\n",
1202 );
1203 let tree = parse_cpp(source);
1204
1205 for (label, start, text) in [
1206 (
1207 "constructor declaration",
1208 source.find("Foo();").expect("ctor"),
1209 "Foo",
1210 ),
1211 (
1212 "copy constructor declaration",
1213 source.find("Foo(const Foo&);").expect("copy ctor"),
1214 "Foo",
1215 ),
1216 (
1217 "destructor name",
1218 source.find("~Foo();").expect("dtor"),
1219 "~Foo",
1220 ),
1221 (
1222 "identifier inside the destructor name",
1223 source.find("~Foo();").expect("dtor") + "~".len(),
1224 "Foo",
1225 ),
1226 (
1227 "out-of-line constructor definition name",
1228 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1229 "Foo",
1230 ),
1231 (
1232 "out-of-line destructor definition name",
1233 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1234 "~Foo",
1235 ),
1236 ] {
1237 assert!(
1238 is_declarator_name(&tree, source, start, text),
1239 "the {label} at byte {start} is a declaration occurrence"
1240 );
1241 }
1242
1243 for (label, start, text) in [
1244 (
1245 "class name",
1246 source.find("class Foo {").expect("class") + "class ".len(),
1247 "Foo",
1248 ),
1249 (
1250 "parameter type",
1251 source.find("const Foo&").expect("parameter type") + "const ".len(),
1252 "Foo",
1253 ),
1254 (
1255 "owning scope of an out-of-line constructor",
1256 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1257 "Foo",
1258 ),
1259 (
1260 "owning scope of an out-of-line destructor",
1261 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1262 "Foo",
1263 ),
1264 (
1265 "out-of-line method name",
1266 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1267 "m",
1268 ),
1269 ] {
1270 assert!(
1271 !is_declarator_name(&tree, source, start, text),
1272 "the {label} at byte {start} stays a reference"
1273 );
1274 }
1275 }
1276
1277 #[test]
1281 fn constructor_call_sites_stay_references() {
1282 let source = concat!(
1283 "struct B { B(int); };\n",
1284 "struct D : B {\n",
1285 " D(int x) : B(x), base_(x) {}\n",
1286 " int base_;\n",
1287 "};\n",
1288 "void g() {\n",
1289 " D* p = new D(1);\n",
1290 " D x(2);\n",
1291 " D(3);\n",
1292 " g();\n",
1293 "}\n",
1294 );
1295 let tree = parse_cpp(source);
1296
1297 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1298 assert!(
1299 is_declarator_name(&tree, source, inline_declarator, "D"),
1300 "an inline constructor definition name is still a declarator"
1301 );
1302
1303 for (label, start, text) in [
1304 (
1305 "base member initializer",
1306 source.find(": B(x)").expect("base initializer") + ": ".len(),
1307 "B",
1308 ),
1309 (
1310 "field member initializer",
1311 source.find("base_(x) {}").expect("field initializer"),
1312 "base_",
1313 ),
1314 (
1315 "new expression type",
1316 source.find("new D(1)").expect("new expression") + "new ".len(),
1317 "D",
1318 ),
1319 (
1320 "direct initialization type",
1321 source.find("D x(2)").expect("direct initialization"),
1322 "D",
1323 ),
1324 (
1325 "temporary construction statement",
1326 source.find("D(3)").expect("temporary"),
1327 "D",
1328 ),
1329 (
1330 "recursive call in a real body",
1331 source.find("g();").expect("recursive call"),
1332 "g",
1333 ),
1334 ] {
1335 assert!(
1336 !is_declarator_name(&tree, source, start, text),
1337 "the {label} at byte {start} is a reference"
1338 );
1339 }
1340 }
1341
1342 #[test]
1347 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1348 let source = concat!(
1349 "class SAMPLE_EXPORT Properties {\n",
1350 " public:\n",
1351 " Properties();\n",
1352 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1353 " int size() const;\n",
1354 " int total() { return size(); }\n",
1355 "};\n",
1356 );
1357 let tree = parse_cpp(source);
1358
1359 let recovered = source.find("Properties();").expect("recovered constructor");
1360 assert!(
1361 is_declarator_name(&tree, source, recovered, "Properties"),
1362 "a constructor declaration the parse read as a call is still a declarator"
1363 );
1364
1365 for (label, start, text) in [
1366 (
1367 "class name in the recovered header",
1368 source
1369 .find("class SAMPLE_EXPORT Properties")
1370 .expect("class")
1371 + "class SAMPLE_EXPORT ".len(),
1372 "Properties",
1373 ),
1374 (
1375 "macro invocation in the recovered body",
1376 source
1377 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1378 .expect("macro invocation"),
1379 "DISALLOW_COPY_AND_ASSIGN",
1380 ),
1381 (
1382 "call inside a method body the recovery kept",
1383 source.find("return size();").expect("member call") + "return ".len(),
1384 "size",
1385 ),
1386 ] {
1387 assert!(
1388 !is_declarator_name(&tree, source, start, text),
1389 "the {label} at byte {start} stays a reference"
1390 );
1391 }
1392 }
1393}