1use crate::declarations::{cpp_file_using_namespaces, cpp_member_fq, node_text};
19use crate::graph::resolver::{
20 cpp_type_name_components, declarator_name_node, qualified_name_has_concrete_scope_separators,
21};
22use crate::graph_support::CppSource;
23use crate::imports::{IncludeTargetIndex, include_paths, resolve_include_targets_with_index};
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};
27use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
28use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
29use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
30use brokk_bifrost_core::hash::HashMap;
31use brokk_bifrost_core::path_utils::rel_path_string;
32use brokk_bifrost_core::profiling;
33use std::collections::BTreeSet;
34use std::sync::Arc;
35use tree_sitter::{Node, Parser, Tree};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum CppCallableUnitRole {
39 DeclarationOnly,
40 Definition,
41 Both,
42 Unknown,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum CppOccurrenceRole {
47 DeclarationOnly,
48 Definition,
49 Both,
50 Unknown,
51}
52
53impl CppOccurrenceRole {
54 pub fn api_label(self) -> Option<&'static str> {
55 match self {
56 Self::DeclarationOnly => Some("declaration"),
57 Self::Definition => Some("definition"),
58 Self::Both | Self::Unknown => None,
59 }
60 }
61}
62
63pub struct CppOccurrenceClassifier {
64 tree: Tree,
65}
66
67impl CppOccurrenceClassifier {
68 pub fn new(source: &str) -> Option<Self> {
69 let mut parser = Parser::new();
70 parser
71 .set_language(&tree_sitter_cpp::LANGUAGE.into())
72 .ok()?;
73 parser.parse(source, None).map(|tree| Self { tree })
74 }
75
76 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
77 cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
78 }
79}
80
81pub fn cpp_callable_unit_role(
82 index: &dyn CodeUnitIndex,
83 callable: &CodeUnit,
84) -> CppCallableUnitRole {
85 if !callable.is_callable() {
86 return CppCallableUnitRole::Unknown;
87 }
88 let mut declaration = false;
89 let mut definition = false;
90 for metadata in index.signature_metadata(callable) {
91 if metadata.is_declaration_only() {
92 declaration = true;
93 } else {
94 definition = true;
95 }
96 }
97 match (declaration, definition) {
98 (true, false) => CppCallableUnitRole::DeclarationOnly,
99 (false, true) => CppCallableUnitRole::Definition,
100 (true, true) => CppCallableUnitRole::Both,
101 (false, false) => CppCallableUnitRole::Unknown,
102 }
103}
104
105pub fn cpp_indexed_callable_linkage(
106 index: &dyn CodeUnitIndex,
107 callable: &CodeUnit,
108) -> Option<CallableLinkage> {
109 let mut external = false;
110 for metadata in index.signature_metadata(callable) {
111 match metadata.callable_linkage() {
112 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
113 Some(CallableLinkage::External) => external = true,
114 None => {}
115 }
116 }
117 external.then_some(CallableLinkage::External)
118}
119
120pub fn cpp_callable_definitions_share_identity_evidence(
126 index: &dyn CodeUnitIndex,
127 left: &CodeUnit,
128 right: &CodeUnit,
129 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
130) -> bool {
131 left.source() == right.source()
132 || (left.fq_name() == right.fq_name()
133 && left.signature() == right.signature()
134 && matches!(
135 cpp_indexed_callable_linkage(index, left),
136 Some(CallableLinkage::External)
137 )
138 && matches!(
139 cpp_indexed_callable_linkage(index, right),
140 Some(CallableLinkage::External)
141 )
142 && header_body_related(left.source(), right.source()))
143}
144
145pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
149 let mut current = Some(node);
150 while let Some(candidate) = current {
151 let Some(parent) = candidate.parent() else {
152 return false;
153 };
154 if parent.kind() == "for_range_loop" {
155 return parent
156 .child_by_field_name("declarator")
157 .is_some_and(|declarator| {
158 cpp_range_for_declarator_contains_name(declarator, node)
159 });
160 }
161 current = Some(parent);
162 }
163 false
164}
165
166pub fn cpp_is_conversion_operator_target_type(mut node: Node<'_>) -> bool {
175 while let Some(parent) = node.parent() {
176 if parent.kind() == "operator_cast" {
177 return true;
178 }
179 if matches!(
180 parent.kind(),
181 "function_declarator" | "declaration" | "function_definition" | "translation_unit"
182 ) {
183 return false;
184 }
185 node = parent;
186 }
187 false
188}
189
190pub fn cpp_is_recovered_macro_character_token_type(node: Node<'_>) -> bool {
200 if node.kind() != "type_identifier" {
201 return false;
202 }
203 let Some(parameter) = node.parent() else {
204 return false;
205 };
206 if parameter.kind() != "parameter_declaration"
207 || parameter.child_by_field_name("type") != Some(node)
208 || parameter.child_by_field_name("declarator").is_some()
209 || parameter
210 .parent()
211 .is_none_or(|parent| parent.kind() != "parameter_list")
212 {
213 return false;
214 }
215
216 parameter
217 .prev_named_sibling()
218 .is_some_and(cpp_is_recovered_character_quote)
219 && parameter
220 .next_named_sibling()
221 .is_some_and(cpp_is_recovered_character_quote)
222}
223
224fn cpp_is_recovered_character_quote(node: Node<'_>) -> bool {
225 node.is_error()
226 && node.child_count() == 1
227 && node
228 .child(0)
229 .is_some_and(|quote| !quote.is_named() && quote.kind() == "'")
230}
231
232pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
255 cpp_is_declared_constructor_or_destructor_name(node)
256 || cpp_is_recovered_constructor_or_destructor_name(node, source)
257}
258
259fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
265 let mut name = node;
266 if let Some(parent) = name.parent()
267 && parent.kind() == "destructor_name"
268 {
269 name = parent;
270 }
271 while let Some(parent) = name.parent() {
275 if parent.kind() != "qualified_identifier"
276 || parent.child_by_field_name("name") != Some(name)
277 {
278 break;
279 }
280 name = parent;
281 }
282 let Some(declarator) = name.parent() else {
283 return false;
284 };
285 if declarator.kind() != "function_declarator"
286 || declarator.child_by_field_name("declarator") != Some(name)
287 {
288 return false;
289 }
290 let Some(owner) = declarator.parent() else {
291 return false;
292 };
293 matches!(owner.kind(), "declaration" | "function_definition")
294 && owner.child_by_field_name("declarator") == Some(declarator)
295 && owner.child_by_field_name("type").is_none()
296}
297
298fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
316 if node.kind() != "identifier" {
317 return false;
318 }
319 let Some(call) = node.parent() else {
320 return false;
321 };
322 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
323 return false;
324 }
325 let mut current = call.parent();
326 while let Some(ancestor) = current {
327 if ancestor.kind() == "function_definition" {
328 return ancestor
329 .child_by_field_name("declarator")
330 .is_some_and(|declarator| declarator.kind() == "identifier")
331 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
332 }
333 current = ancestor.parent();
334 }
335 false
336}
337
338fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
341 let header_end = definition
342 .child_by_field_name("body")
343 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
344 let mut stack = vec![definition];
345 while let Some(node) = stack.pop() {
346 if node.start_byte() >= header_end {
347 continue;
348 }
349 if matches!(
350 node.kind(),
351 "identifier" | "type_identifier" | "namespace_identifier"
352 ) && node_text(node, source) == name
353 {
354 return true;
355 }
356 let mut cursor = node.walk();
357 for child in node.named_children(&mut cursor) {
358 stack.push(child);
359 }
360 }
361 false
362}
363
364fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
365 let mut pending = vec![declarator];
366 while let Some(candidate) = pending.pop() {
367 match candidate.kind() {
368 "identifier" | "field_identifier" => {
369 if cpp_same_node(candidate, target) {
370 return true;
371 }
372 }
373 "structured_binding_declarator" => {
374 let mut cursor = candidate.walk();
375 if candidate
376 .named_children(&mut cursor)
377 .any(|name| cpp_same_node(name, target))
378 {
379 return true;
380 }
381 }
382 "pointer_declarator"
383 | "reference_declarator"
384 | "array_declarator"
385 | "attributed_declarator"
386 | "parenthesized_declarator"
387 | "function_declarator"
388 | "init_declarator" => {
389 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
390 pending.push(inner);
391 }
392 }
393 _ => {}
394 }
395 }
396 false
397}
398
399fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
400 node.child_by_field_name("declarator").or_else(|| {
401 let mut cursor = node.walk();
402 node.named_children(&mut cursor).find(|child| {
403 matches!(
404 child.kind(),
405 "identifier"
406 | "field_identifier"
407 | "structured_binding_declarator"
408 | "pointer_declarator"
409 | "reference_declarator"
410 | "array_declarator"
411 | "attributed_declarator"
412 | "parenthesized_declarator"
413 | "function_declarator"
414 | "init_declarator"
415 )
416 })
417 })
418}
419
420fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
421 left.id() == right.id()
422 && left.start_byte() == right.start_byte()
423 && left.end_byte() == right.end_byte()
424}
425
426pub fn cpp_header_body_files_are_related(
433 left: &ProjectFile,
434 right: &ProjectFile,
435 implementation_imports: &[String],
436 include_targets: &IncludeTargetIndex,
437) -> bool {
438 let (header, implementation) = if cpp_source_path_is_header(left) {
439 (left, right)
440 } else if cpp_source_path_is_header(right) {
441 (right, left)
442 } else {
443 return false;
444 };
445 if cpp_source_path_is_header(implementation) {
446 return false;
447 }
448 implementation_imports
449 .iter()
450 .flat_map(|import| include_paths(std::slice::from_ref(import)))
451 .any(|include| {
452 let targets =
453 resolve_include_targets_with_index(implementation, &include, include_targets);
454 targets.len() == 1 && targets.first() == Some(header)
455 })
456}
457
458pub fn cpp_header_body_implementation_file<'a>(
462 left: &'a ProjectFile,
463 right: &'a ProjectFile,
464) -> Option<&'a ProjectFile> {
465 let implementation = if cpp_source_path_is_header(left) {
466 right
467 } else if cpp_source_path_is_header(right) {
468 left
469 } else {
470 return None;
471 };
472 (!cpp_source_path_is_header(implementation)).then_some(implementation)
473}
474
475pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
476 let path = rel_path_string(source).to_ascii_lowercase();
477 matches!(path.rsplit('.').next(), Some("h" | "hh" | "hpp" | "hxx"))
478}
479
480pub fn cpp_occurrence_role_for_range(
481 root: Node<'_>,
482 candidate: &CodeUnit,
483 range: &Range,
484) -> CppOccurrenceRole {
485 if !candidate.is_callable() && !candidate.is_class() {
486 return CppOccurrenceRole::Both;
487 }
488 let Some(node) = cpp_declaration_node_for_range(root, range) else {
489 return CppOccurrenceRole::Unknown;
490 };
491 if candidate.is_callable() {
492 return if subtree_contains(node, |descendant| {
493 descendant.kind() == "function_definition"
494 && descendant.child_by_field_name("body").is_some()
495 }) {
496 CppOccurrenceRole::Definition
497 } else {
498 CppOccurrenceRole::DeclarationOnly
499 };
500 }
501 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
502 return CppOccurrenceRole::Definition;
503 }
504 if !subtree_contains(node, |descendant| {
505 matches!(
506 descendant.kind(),
507 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
508 )
509 }) {
510 return CppOccurrenceRole::Both;
511 }
512 if subtree_contains(node, |descendant| {
513 matches!(
514 descendant.kind(),
515 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
516 ) && descendant.child_by_field_name("body").is_some()
517 }) {
518 CppOccurrenceRole::Definition
519 } else {
520 CppOccurrenceRole::DeclarationOnly
521 }
522}
523
524fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
525 node_for_exact_range(root, range).or_else(|| {
526 root.descendant_for_byte_range(range.start_byte, range.end_byte)
527 .and_then(|mut node| {
528 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
529 node = node.parent()?;
530 }
531 Some(node)
532 })
533 })
534}
535
536#[derive(Default)]
548pub struct CppReconciledDefinitionIndex {
549 pub rekeyed: Vec<CodeUnit>,
551 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Hash)]
571pub struct CppReconcileGroupKey {
572 pub member_identifier: String,
575 pub owner_terminal: Option<String>,
579}
580
581pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
588 let interner = segment_interner();
589 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
590 let (member_identifier, _) = interner.resolve(query_fq.last()?);
591 if member_identifier.is_empty() {
592 return None;
593 }
594 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
604 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
605 text.rsplit_once('$')
610 .map_or(text, |(_, tail)| tail)
611 .to_string()
612 });
613 Some(CppReconcileGroupKey {
614 member_identifier: member_identifier.to_string(),
615 owner_terminal,
616 })
617}
618
619pub struct CppReconcileCandidates {
625 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
626 all: Vec<CodeUnit>,
630}
631
632impl CppReconcileCandidates {
633 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
636 match &key.owner_terminal {
637 Some(owner_terminal) => self
638 .by_owner_terminal
639 .get(owner_terminal)
640 .map_or(&[][..], Vec::as_slice),
641 None => &self.all,
642 }
643 }
644
645 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
647 self.all.iter()
648 }
649
650 pub fn bucketed_len(&self) -> usize {
653 self.by_owner_terminal.values().map(Vec::len).sum()
654 }
655}
656
657pub fn cpp_reconcile_candidates(
665 cpp: &dyn CppSource,
666 member_identifier: &str,
667 keep_going: &dyn Fn() -> bool,
668) -> Option<CppReconcileCandidates> {
669 let candidates: BTreeSet<CodeUnit> = {
670 let _lookup =
671 profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
672 cpp.lookup_candidates_by_identifier(member_identifier)
673 };
674 profiling::note_with(|| {
675 format!(
676 "cpp.reconcile.candidates[{member_identifier}] n={}",
677 candidates.len()
678 )
679 });
680
681 let interner = segment_interner();
682 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
683 let mut all = Vec::new();
684 for (index, unit) in candidates.into_iter().enumerate() {
685 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
689 return None;
690 }
691 if !unit.is_callable() {
692 continue;
693 }
694 let owner_terminal = unit
695 .fq()
696 .segments()
697 .iter()
698 .filter_map(|&segment| {
699 let (text, kind) = interner.resolve(segment);
700 matches!(
704 kind,
705 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
706 )
707 .then_some(text)
708 })
709 .last();
710 if let Some(owner_terminal) = owner_terminal {
711 by_owner_terminal
712 .entry(owner_terminal.to_string())
713 .or_default()
714 .push(unit.clone());
715 }
716 all.push(unit);
717 }
718 Some(CppReconcileCandidates {
719 by_owner_terminal,
720 all,
721 })
722}
723
724const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
726
727pub fn cpp_reconcile_group(
744 cpp: &dyn CppSource,
745 key: &CppReconcileGroupKey,
746 candidates: &CppReconcileCandidates,
747 keep_going: &dyn Fn() -> bool,
748 on_candidate: &dyn Fn(),
749) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
750 let _scope = profiling::scope_with(|| {
751 format!(
752 "cpp.reconciled.build[{}#{}]",
753 key.member_identifier,
754 key.owner_terminal.as_deref().unwrap_or("*")
755 )
756 });
757 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
758 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
759 for unit in candidates.for_group(key) {
760 if !keep_going() {
765 return None;
766 }
767 on_candidate();
768 let _candidate =
771 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
772 let role = {
773 let _role = profiling::scope("cpp.reconcile.role");
774 cpp_callable_unit_role(cpp, unit)
775 };
776 if !matches!(
777 role,
778 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
779 ) {
780 continue;
781 }
782 let Some(reconciled) = cpp_reconcile_definition_identity(cpp, unit, &mut using_by_file)
783 else {
784 continue;
785 };
786 let canonical_fq = reconciled.fq_name();
787 if unit.fq_name() == canonical_fq {
794 continue;
795 }
796 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
805 let fq = cpp_member_fq(&reconciled.package, &short_name);
806 let rekeyed = CodeUnit::with_signature_and_fq(
807 unit.source().clone(),
808 unit.kind(),
809 reconciled.package,
810 short_name,
811 unit.signature().map(str::to_string),
812 unit.is_synthetic(),
813 fq,
814 );
815 let index = groups.entry(canonical_fq).or_default();
816 index.rekeyed.push(rekeyed.clone());
817 index.provisional_of.insert(rekeyed, unit.clone());
818 }
819 Some(
820 groups
821 .into_iter()
822 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
823 .collect(),
824 )
825}
826
827fn cpp_reconcile_definition_identity(
833 cpp: &dyn CppSource,
834 unit: &CodeUnit,
835 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
836) -> Option<ReconciledIdentity> {
837 let interner = segment_interner();
847 let mut provisional_owner_segments: Vec<&str> = Vec::new();
848 let mut member: Option<&str> = None;
849 for &segment in unit.fq().segments() {
850 let (text, kind) = interner.resolve(segment);
851 match kind {
852 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
853 if member.is_some() {
857 return None;
858 }
859 if !text.is_empty() {
860 provisional_owner_segments.push(text);
861 }
862 }
863 SegmentKind::Member => member = Some(text),
864 _ => return None,
865 }
866 }
867 let member = member?;
868 let structured_owner_segments =
873 cpp_structured_out_of_line_owner_segments(cpp, unit).filter(|segments| segments.len() == 1);
874 let owner_segments = structured_owner_segments.as_ref().map_or_else(
875 || provisional_owner_segments,
876 |segments| segments.iter().map(String::as_str).collect(),
877 );
878 if owner_segments.is_empty() {
879 return None;
880 }
881
882 let using = using_by_file
883 .entry(unit.source().clone())
884 .or_insert_with(|| {
885 Arc::new(
886 cpp.file_source(unit.source())
887 .map(|source| cpp_file_using_namespaces(&source))
888 .unwrap_or_default(),
889 )
890 })
891 .clone();
892 let mut namespace_candidates: Vec<&str> = vec![""];
893 namespace_candidates.extend(using.iter().map(String::as_str));
894
895 let visible = {
896 let _visible = profiling::scope_with(|| {
897 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
898 });
899 cpp.visible_type_units(unit.source())
900 };
901 let class_table: Vec<VisibleClass> = visible
902 .iter()
903 .filter(|candidate| candidate.is_class())
904 .map(|candidate| VisibleClass {
905 package: candidate.package_name(),
906 nested_short_name: candidate.short_name(),
907 })
908 .collect();
909
910 reconcile_out_of_line_member_identity(
911 &owner_segments,
912 member,
913 &namespace_candidates,
914 &class_table,
915 )
916}
917
918fn cpp_structured_out_of_line_owner_segments(
925 cpp: &dyn CppSource,
926 unit: &CodeUnit,
927) -> Option<Vec<String>> {
928 let prepared = cpp.prepared_syntax(unit.source())?;
929 let root = prepared.tree().root_node();
930 for range in cpp.ranges(unit) {
931 let mut current = cpp_declaration_node_for_range(root, &range)?;
932 let function = loop {
933 if current.kind() == "function_definition" {
934 break current;
935 }
936 current = current.parent()?;
937 };
938 if function.child_by_field_name("body").is_none() {
939 continue;
940 }
941 let declarator = function.child_by_field_name("declarator")?;
942 let name = declarator_name_node(declarator)?;
943 if !qualified_name_has_concrete_scope_separators(name) {
944 continue;
945 }
946 let mut components = cpp_type_name_components(name, prepared.source())?;
947 components.pop()?;
948 if !components.is_empty() {
949 return Some(components);
950 }
951 }
952 None
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958
959 fn parse_cpp(source: &str) -> Tree {
960 let mut parser = Parser::new();
961 parser
962 .set_language(&tree_sitter_cpp::LANGUAGE.into())
963 .expect("cpp language");
964 parser.parse(source, None).expect("cpp tree")
965 }
966
967 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
968 let end = start + text.len();
969 assert_eq!(&source[start..end], text, "the probe must name the token");
970 let node = tree
971 .root_node()
972 .named_descendant_for_byte_range(start, end)
973 .expect("a node spans the probed range");
974 assert_eq!(
975 (node.start_byte(), node.end_byte()),
976 (start, end),
977 "the probed range must be exactly one node: {}",
978 node.to_sexp()
979 );
980 cpp_is_constructor_or_destructor_declarator_name(node, source)
981 }
982
983 fn is_conversion_target(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
984 let end = start + text.len();
985 assert_eq!(&source[start..end], text, "the probe must name the token");
986 let node = tree
987 .root_node()
988 .named_descendant_for_byte_range(start, end)
989 .expect("a node spans the probed range");
990 assert_eq!(
991 (node.start_byte(), node.end_byte()),
992 (start, end),
993 "the probed range must be exactly one node: {}",
994 node.to_sexp()
995 );
996 cpp_is_conversion_operator_target_type(node)
997 }
998
999 fn is_recovered_macro_character_type(
1000 tree: &Tree,
1001 source: &str,
1002 start: usize,
1003 text: &str,
1004 ) -> bool {
1005 let end = start + text.len();
1006 assert_eq!(&source[start..end], text, "the probe must name the token");
1007 let node = tree
1008 .root_node()
1009 .named_descendant_for_byte_range(start, end)
1010 .expect("a node spans the probed range");
1011 cpp_is_recovered_macro_character_token_type(node)
1012 }
1013
1014 #[test]
1015 fn recovered_macro_character_tokens_are_not_type_references() {
1016 let source = concat!(
1017 "struct I {};\n",
1018 "#define STRING_TOKEN_(name, ...)\n",
1019 "struct Schema {\n",
1020 " STRING_TOKEN_(MaxItems, 'm', 'I')\n",
1021 " void ordinary(I value);\n",
1022 " void malformed(I value, @);\n",
1023 " void use() { I value; consume('I'); }\n",
1024 "};\n",
1025 );
1026 let tree = parse_cpp(source);
1027 let recovered_m = source.find("'m'").expect("recovered m") + 1;
1028 let recovered_i = source.find("'I'").expect("recovered I") + 1;
1029 for (label, start, text) in [
1030 ("lowercase character token", recovered_m, "m"),
1031 ("uppercase character token", recovered_i, "I"),
1032 ] {
1033 assert!(
1034 is_recovered_macro_character_type(&tree, source, start, text),
1035 "{label} must match the exact recovery role"
1036 );
1037 }
1038
1039 let macro_first_argument = source.find("MaxItems").expect("macro first argument");
1040 let ordinary = source.find("ordinary(I").expect("ordinary parameter") + "ordinary(".len();
1041 let malformed =
1042 source.find("malformed(I").expect("malformed parameter") + "malformed(".len();
1043 let local = source
1044 .find("I value; consume")
1045 .expect("local type reference");
1046 let expression_character = source.rfind("'I'").expect("expression character") + 1;
1047 for (label, start, text) in [
1048 ("unquoted macro argument", macro_first_argument, "MaxItems"),
1049 ("ordinary parameter type", ordinary, "I"),
1050 ("parameter beside another error", malformed, "I"),
1051 ("local type reference", local, "I"),
1052 ("expression character literal", expression_character, "I"),
1053 ] {
1054 assert!(
1055 !is_recovered_macro_character_type(&tree, source, start, text),
1056 "{label} must remain outside the recovery role"
1057 );
1058 }
1059 }
1060
1061 #[test]
1062 fn conversion_operator_target_components_are_identity_syntax_only() {
1063 let source = concat!(
1064 "namespace other { struct Target {}; template<class T> struct Box {}; }\n",
1065 "using other::Target;\n",
1066 "struct Source {\n",
1067 " operator Target() const;\n",
1068 " operator other::Target() const { return other::Target{}; }\n",
1069 " template<class T> operator other::Box<T>() const { return {}; }\n",
1070 " operator other::Target const&() const;\n",
1071 " operator other::Target*() const;\n",
1072 " other::Target ordinary() const {\n",
1073 " return reinterpret_cast<other::Target&>(*this);\n",
1074 " }\n",
1075 " other::Target operator+() const { return {}; }\n",
1076 "};\n",
1077 );
1078 let tree = parse_cpp(source);
1079
1080 let bare = source.find("operator Target").expect("bare target") + "operator ".len();
1081 let qualified = source
1082 .find("operator other::Target()")
1083 .expect("qualified target")
1084 + "operator ".len();
1085 let template = source
1086 .find("operator other::Box<T>")
1087 .expect("template target")
1088 + "operator ".len();
1089 let cv_reference = source
1090 .find("operator other::Target const&")
1091 .expect("cv-reference target")
1092 + "operator other::".len();
1093 let pointer = source
1094 .find("operator other::Target*")
1095 .expect("pointer target")
1096 + "operator other::".len();
1097
1098 for (label, start, text) in [
1099 ("bare target", bare, "Target"),
1100 ("qualified target scope", qualified, "other"),
1101 (
1102 "qualified target name",
1103 qualified + "other::".len(),
1104 "Target",
1105 ),
1106 ("template target scope", template, "other"),
1107 ("template target name", template + "other::".len(), "Box"),
1108 (
1109 "template target argument",
1110 template + "other::Box<".len(),
1111 "T",
1112 ),
1113 ("cv-reference target", cv_reference, "Target"),
1114 ("pointer target", pointer, "Target"),
1115 ] {
1116 assert!(
1117 is_conversion_target(&tree, source, start, text),
1118 "the {label} at byte {start} belongs to the conversion identity"
1119 );
1120 }
1121
1122 let ordinary_return = source
1123 .find("other::Target ordinary")
1124 .expect("ordinary return");
1125 let body_cast = source
1126 .find("reinterpret_cast<other::Target")
1127 .expect("body cast")
1128 + "reinterpret_cast<".len();
1129 let overloaded_return = source
1130 .find("other::Target operator+")
1131 .expect("overloaded operator return");
1132 for (label, start) in [
1133 ("ordinary return type", ordinary_return),
1134 ("body cast target", body_cast),
1135 ("overloaded-operator return type", overloaded_return),
1136 ] {
1137 assert!(
1138 !is_conversion_target(&tree, source, start, "other"),
1139 "the {label} at byte {start} stays a reference"
1140 );
1141 }
1142 }
1143
1144 #[test]
1149 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
1150 let source = concat!(
1151 "class Foo {\n",
1152 "public:\n",
1153 " Foo();\n",
1154 " Foo(const Foo&);\n",
1155 " ~Foo();\n",
1156 " void m();\n",
1157 "};\n",
1158 "Foo::Foo() {}\n",
1159 "Foo::~Foo() {}\n",
1160 "void Foo::m() {}\n",
1161 );
1162 let tree = parse_cpp(source);
1163
1164 for (label, start, text) in [
1165 (
1166 "constructor declaration",
1167 source.find("Foo();").expect("ctor"),
1168 "Foo",
1169 ),
1170 (
1171 "copy constructor declaration",
1172 source.find("Foo(const Foo&);").expect("copy ctor"),
1173 "Foo",
1174 ),
1175 (
1176 "destructor name",
1177 source.find("~Foo();").expect("dtor"),
1178 "~Foo",
1179 ),
1180 (
1181 "identifier inside the destructor name",
1182 source.find("~Foo();").expect("dtor") + "~".len(),
1183 "Foo",
1184 ),
1185 (
1186 "out-of-line constructor definition name",
1187 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
1188 "Foo",
1189 ),
1190 (
1191 "out-of-line destructor definition name",
1192 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
1193 "~Foo",
1194 ),
1195 ] {
1196 assert!(
1197 is_declarator_name(&tree, source, start, text),
1198 "the {label} at byte {start} is a declaration occurrence"
1199 );
1200 }
1201
1202 for (label, start, text) in [
1203 (
1204 "class name",
1205 source.find("class Foo {").expect("class") + "class ".len(),
1206 "Foo",
1207 ),
1208 (
1209 "parameter type",
1210 source.find("const Foo&").expect("parameter type") + "const ".len(),
1211 "Foo",
1212 ),
1213 (
1214 "owning scope of an out-of-line constructor",
1215 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
1216 "Foo",
1217 ),
1218 (
1219 "owning scope of an out-of-line destructor",
1220 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
1221 "Foo",
1222 ),
1223 (
1224 "out-of-line method name",
1225 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
1226 "m",
1227 ),
1228 ] {
1229 assert!(
1230 !is_declarator_name(&tree, source, start, text),
1231 "the {label} at byte {start} stays a reference"
1232 );
1233 }
1234 }
1235
1236 #[test]
1240 fn constructor_call_sites_stay_references() {
1241 let source = concat!(
1242 "struct B { B(int); };\n",
1243 "struct D : B {\n",
1244 " D(int x) : B(x), base_(x) {}\n",
1245 " int base_;\n",
1246 "};\n",
1247 "void g() {\n",
1248 " D* p = new D(1);\n",
1249 " D x(2);\n",
1250 " D(3);\n",
1251 " g();\n",
1252 "}\n",
1253 );
1254 let tree = parse_cpp(source);
1255
1256 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1257 assert!(
1258 is_declarator_name(&tree, source, inline_declarator, "D"),
1259 "an inline constructor definition name is still a declarator"
1260 );
1261
1262 for (label, start, text) in [
1263 (
1264 "base member initializer",
1265 source.find(": B(x)").expect("base initializer") + ": ".len(),
1266 "B",
1267 ),
1268 (
1269 "field member initializer",
1270 source.find("base_(x) {}").expect("field initializer"),
1271 "base_",
1272 ),
1273 (
1274 "new expression type",
1275 source.find("new D(1)").expect("new expression") + "new ".len(),
1276 "D",
1277 ),
1278 (
1279 "direct initialization type",
1280 source.find("D x(2)").expect("direct initialization"),
1281 "D",
1282 ),
1283 (
1284 "temporary construction statement",
1285 source.find("D(3)").expect("temporary"),
1286 "D",
1287 ),
1288 (
1289 "recursive call in a real body",
1290 source.find("g();").expect("recursive call"),
1291 "g",
1292 ),
1293 ] {
1294 assert!(
1295 !is_declarator_name(&tree, source, start, text),
1296 "the {label} at byte {start} is a reference"
1297 );
1298 }
1299 }
1300
1301 #[test]
1306 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1307 let source = concat!(
1308 "class SAMPLE_EXPORT Properties {\n",
1309 " public:\n",
1310 " Properties();\n",
1311 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1312 " int size() const;\n",
1313 " int total() { return size(); }\n",
1314 "};\n",
1315 );
1316 let tree = parse_cpp(source);
1317
1318 let recovered = source.find("Properties();").expect("recovered constructor");
1319 assert!(
1320 is_declarator_name(&tree, source, recovered, "Properties"),
1321 "a constructor declaration the parse read as a call is still a declarator"
1322 );
1323
1324 for (label, start, text) in [
1325 (
1326 "class name in the recovered header",
1327 source
1328 .find("class SAMPLE_EXPORT Properties")
1329 .expect("class")
1330 + "class SAMPLE_EXPORT ".len(),
1331 "Properties",
1332 ),
1333 (
1334 "macro invocation in the recovered body",
1335 source
1336 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1337 .expect("macro invocation"),
1338 "DISALLOW_COPY_AND_ASSIGN",
1339 ),
1340 (
1341 "call inside a method body the recovery kept",
1342 source.find("return size();").expect("member call") + "return ".len(),
1343 "size",
1344 ),
1345 ] {
1346 assert!(
1347 !is_declarator_name(&tree, source, start, text),
1348 "the {label} at byte {start} stays a reference"
1349 );
1350 }
1351 }
1352}