1use crate::declarations::{cpp_file_using_namespaces, cpp_member_fq, node_text};
19use crate::graph_support::CppSource;
20use crate::imports::{IncludeTargetIndex, include_paths, resolve_include_targets_with_index};
21use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
22use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
23use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range};
24use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
25use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
26use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
27use brokk_bifrost_core::hash::HashMap;
28use brokk_bifrost_core::path_utils::rel_path_string;
29use brokk_bifrost_core::profiling;
30use std::collections::BTreeSet;
31use std::sync::Arc;
32use tree_sitter::{Node, Parser, Tree};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum CppCallableUnitRole {
36 DeclarationOnly,
37 Definition,
38 Both,
39 Unknown,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum CppOccurrenceRole {
44 DeclarationOnly,
45 Definition,
46 Both,
47 Unknown,
48}
49
50impl CppOccurrenceRole {
51 pub fn api_label(self) -> Option<&'static str> {
52 match self {
53 Self::DeclarationOnly => Some("declaration"),
54 Self::Definition => Some("definition"),
55 Self::Both | Self::Unknown => None,
56 }
57 }
58}
59
60pub struct CppOccurrenceClassifier {
61 tree: Tree,
62}
63
64impl CppOccurrenceClassifier {
65 pub fn new(source: &str) -> Option<Self> {
66 let mut parser = Parser::new();
67 parser
68 .set_language(&tree_sitter_cpp::LANGUAGE.into())
69 .ok()?;
70 parser.parse(source, None).map(|tree| Self { tree })
71 }
72
73 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
74 cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
75 }
76}
77
78pub fn cpp_callable_unit_role(
79 index: &dyn CodeUnitIndex,
80 callable: &CodeUnit,
81) -> CppCallableUnitRole {
82 if !callable.is_callable() {
83 return CppCallableUnitRole::Unknown;
84 }
85 let mut declaration = false;
86 let mut definition = false;
87 for metadata in index.signature_metadata(callable) {
88 if metadata.is_declaration_only() {
89 declaration = true;
90 } else {
91 definition = true;
92 }
93 }
94 match (declaration, definition) {
95 (true, false) => CppCallableUnitRole::DeclarationOnly,
96 (false, true) => CppCallableUnitRole::Definition,
97 (true, true) => CppCallableUnitRole::Both,
98 (false, false) => CppCallableUnitRole::Unknown,
99 }
100}
101
102pub fn cpp_indexed_callable_linkage(
103 index: &dyn CodeUnitIndex,
104 callable: &CodeUnit,
105) -> Option<CallableLinkage> {
106 let mut external = false;
107 for metadata in index.signature_metadata(callable) {
108 match metadata.callable_linkage() {
109 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
110 Some(CallableLinkage::External) => external = true,
111 None => {}
112 }
113 }
114 external.then_some(CallableLinkage::External)
115}
116
117pub fn cpp_callable_definitions_share_identity_evidence(
123 index: &dyn CodeUnitIndex,
124 left: &CodeUnit,
125 right: &CodeUnit,
126 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
127) -> bool {
128 left.source() == right.source()
129 || (left.fq_name() == right.fq_name()
130 && left.signature() == right.signature()
131 && matches!(
132 cpp_indexed_callable_linkage(index, left),
133 Some(CallableLinkage::External)
134 )
135 && matches!(
136 cpp_indexed_callable_linkage(index, right),
137 Some(CallableLinkage::External)
138 )
139 && header_body_related(left.source(), right.source()))
140}
141
142pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
146 let mut current = Some(node);
147 while let Some(candidate) = current {
148 let Some(parent) = candidate.parent() else {
149 return false;
150 };
151 if parent.kind() == "for_range_loop" {
152 return parent
153 .child_by_field_name("declarator")
154 .is_some_and(|declarator| {
155 cpp_range_for_declarator_contains_name(declarator, node)
156 });
157 }
158 current = Some(parent);
159 }
160 false
161}
162
163pub fn cpp_is_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
186 cpp_is_declared_constructor_or_destructor_name(node)
187 || cpp_is_recovered_constructor_or_destructor_name(node, source)
188}
189
190fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
196 let mut name = node;
197 if let Some(parent) = name.parent()
198 && parent.kind() == "destructor_name"
199 {
200 name = parent;
201 }
202 while let Some(parent) = name.parent() {
206 if parent.kind() != "qualified_identifier"
207 || parent.child_by_field_name("name") != Some(name)
208 {
209 break;
210 }
211 name = parent;
212 }
213 let Some(declarator) = name.parent() else {
214 return false;
215 };
216 if declarator.kind() != "function_declarator"
217 || declarator.child_by_field_name("declarator") != Some(name)
218 {
219 return false;
220 }
221 let Some(owner) = declarator.parent() else {
222 return false;
223 };
224 matches!(owner.kind(), "declaration" | "function_definition")
225 && owner.child_by_field_name("declarator") == Some(declarator)
226 && owner.child_by_field_name("type").is_none()
227}
228
229fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
247 if node.kind() != "identifier" {
248 return false;
249 }
250 let Some(call) = node.parent() else {
251 return false;
252 };
253 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
254 return false;
255 }
256 let mut current = call.parent();
257 while let Some(ancestor) = current {
258 if ancestor.kind() == "function_definition" {
259 return ancestor
260 .child_by_field_name("declarator")
261 .is_some_and(|declarator| declarator.kind() == "identifier")
262 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
263 }
264 current = ancestor.parent();
265 }
266 false
267}
268
269fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
272 let header_end = definition
273 .child_by_field_name("body")
274 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
275 let mut stack = vec![definition];
276 while let Some(node) = stack.pop() {
277 if node.start_byte() >= header_end {
278 continue;
279 }
280 if matches!(
281 node.kind(),
282 "identifier" | "type_identifier" | "namespace_identifier"
283 ) && node_text(node, source) == name
284 {
285 return true;
286 }
287 let mut cursor = node.walk();
288 for child in node.named_children(&mut cursor) {
289 stack.push(child);
290 }
291 }
292 false
293}
294
295fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
296 let mut pending = vec![declarator];
297 while let Some(candidate) = pending.pop() {
298 match candidate.kind() {
299 "identifier" | "field_identifier" => {
300 if cpp_same_node(candidate, target) {
301 return true;
302 }
303 }
304 "structured_binding_declarator" => {
305 let mut cursor = candidate.walk();
306 if candidate
307 .named_children(&mut cursor)
308 .any(|name| cpp_same_node(name, target))
309 {
310 return true;
311 }
312 }
313 "pointer_declarator"
314 | "reference_declarator"
315 | "array_declarator"
316 | "attributed_declarator"
317 | "parenthesized_declarator"
318 | "function_declarator"
319 | "init_declarator" => {
320 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
321 pending.push(inner);
322 }
323 }
324 _ => {}
325 }
326 }
327 false
328}
329
330fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
331 node.child_by_field_name("declarator").or_else(|| {
332 let mut cursor = node.walk();
333 node.named_children(&mut cursor).find(|child| {
334 matches!(
335 child.kind(),
336 "identifier"
337 | "field_identifier"
338 | "structured_binding_declarator"
339 | "pointer_declarator"
340 | "reference_declarator"
341 | "array_declarator"
342 | "attributed_declarator"
343 | "parenthesized_declarator"
344 | "function_declarator"
345 | "init_declarator"
346 )
347 })
348 })
349}
350
351fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
352 left.id() == right.id()
353 && left.start_byte() == right.start_byte()
354 && left.end_byte() == right.end_byte()
355}
356
357pub fn cpp_header_body_files_are_related(
364 left: &ProjectFile,
365 right: &ProjectFile,
366 implementation_imports: &[String],
367 include_targets: &IncludeTargetIndex,
368) -> bool {
369 let (header, implementation) = if cpp_source_path_is_header(left) {
370 (left, right)
371 } else if cpp_source_path_is_header(right) {
372 (right, left)
373 } else {
374 return false;
375 };
376 if cpp_source_path_is_header(implementation) {
377 return false;
378 }
379 implementation_imports
380 .iter()
381 .flat_map(|import| include_paths(std::slice::from_ref(import)))
382 .any(|include| {
383 let targets =
384 resolve_include_targets_with_index(implementation, &include, include_targets);
385 targets.len() == 1 && targets.first() == Some(header)
386 })
387}
388
389pub fn cpp_header_body_implementation_file<'a>(
393 left: &'a ProjectFile,
394 right: &'a ProjectFile,
395) -> Option<&'a ProjectFile> {
396 let implementation = if cpp_source_path_is_header(left) {
397 right
398 } else if cpp_source_path_is_header(right) {
399 left
400 } else {
401 return None;
402 };
403 (!cpp_source_path_is_header(implementation)).then_some(implementation)
404}
405
406pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
407 let path = rel_path_string(source).to_ascii_lowercase();
408 matches!(path.rsplit('.').next(), Some("h" | "hh" | "hpp" | "hxx"))
409}
410
411pub fn cpp_occurrence_role_for_range(
412 root: Node<'_>,
413 candidate: &CodeUnit,
414 range: &Range,
415) -> CppOccurrenceRole {
416 if !candidate.is_callable() && !candidate.is_class() {
417 return CppOccurrenceRole::Both;
418 }
419 let Some(node) = cpp_declaration_node_for_range(root, range) else {
420 return CppOccurrenceRole::Unknown;
421 };
422 if candidate.is_callable() {
423 return if subtree_contains(node, |descendant| {
424 descendant.kind() == "function_definition"
425 && descendant.child_by_field_name("body").is_some()
426 }) {
427 CppOccurrenceRole::Definition
428 } else {
429 CppOccurrenceRole::DeclarationOnly
430 };
431 }
432 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
433 return CppOccurrenceRole::Definition;
434 }
435 if !subtree_contains(node, |descendant| {
436 matches!(
437 descendant.kind(),
438 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
439 )
440 }) {
441 return CppOccurrenceRole::Both;
442 }
443 if subtree_contains(node, |descendant| {
444 matches!(
445 descendant.kind(),
446 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
447 ) && descendant.child_by_field_name("body").is_some()
448 }) {
449 CppOccurrenceRole::Definition
450 } else {
451 CppOccurrenceRole::DeclarationOnly
452 }
453}
454
455fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
456 node_for_exact_range(root, range).or_else(|| {
457 root.descendant_for_byte_range(range.start_byte, range.end_byte)
458 .and_then(|mut node| {
459 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
460 node = node.parent()?;
461 }
462 Some(node)
463 })
464 })
465}
466
467#[derive(Default)]
479pub struct CppReconciledDefinitionIndex {
480 pub rekeyed: Vec<CodeUnit>,
482 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, Hash)]
502pub struct CppReconcileGroupKey {
503 pub member_identifier: String,
506 pub owner_terminal: Option<String>,
510}
511
512pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
519 let interner = segment_interner();
520 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
521 let (member_identifier, _) = interner.resolve(query_fq.last()?);
522 if member_identifier.is_empty() {
523 return None;
524 }
525 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
535 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
536 text.rsplit_once('$')
541 .map_or(text, |(_, tail)| tail)
542 .to_string()
543 });
544 Some(CppReconcileGroupKey {
545 member_identifier: member_identifier.to_string(),
546 owner_terminal,
547 })
548}
549
550pub struct CppReconcileCandidates {
556 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
557 all: Vec<CodeUnit>,
561}
562
563impl CppReconcileCandidates {
564 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
567 match &key.owner_terminal {
568 Some(owner_terminal) => self
569 .by_owner_terminal
570 .get(owner_terminal)
571 .map_or(&[][..], Vec::as_slice),
572 None => &self.all,
573 }
574 }
575
576 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
578 self.all.iter()
579 }
580
581 pub fn bucketed_len(&self) -> usize {
584 self.by_owner_terminal.values().map(Vec::len).sum()
585 }
586}
587
588pub fn cpp_reconcile_candidates(
596 cpp: &dyn CppSource,
597 member_identifier: &str,
598 keep_going: &dyn Fn() -> bool,
599) -> Option<CppReconcileCandidates> {
600 let candidates: BTreeSet<CodeUnit> = {
601 let _lookup =
602 profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
603 cpp.lookup_candidates_by_identifier(member_identifier)
604 };
605 profiling::note_with(|| {
606 format!(
607 "cpp.reconcile.candidates[{member_identifier}] n={}",
608 candidates.len()
609 )
610 });
611
612 let interner = segment_interner();
613 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
614 let mut all = Vec::new();
615 for (index, unit) in candidates.into_iter().enumerate() {
616 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
620 return None;
621 }
622 if !unit.is_callable() {
623 continue;
624 }
625 let owner_terminal = unit
626 .fq()
627 .segments()
628 .iter()
629 .filter_map(|&segment| {
630 let (text, kind) = interner.resolve(segment);
631 matches!(
635 kind,
636 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
637 )
638 .then_some(text)
639 })
640 .last();
641 if let Some(owner_terminal) = owner_terminal {
642 by_owner_terminal
643 .entry(owner_terminal.to_string())
644 .or_default()
645 .push(unit.clone());
646 }
647 all.push(unit);
648 }
649 Some(CppReconcileCandidates {
650 by_owner_terminal,
651 all,
652 })
653}
654
655const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
657
658pub fn cpp_reconcile_group(
675 cpp: &dyn CppSource,
676 key: &CppReconcileGroupKey,
677 candidates: &CppReconcileCandidates,
678 keep_going: &dyn Fn() -> bool,
679 on_candidate: &dyn Fn(),
680) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
681 let _scope = profiling::scope_with(|| {
682 format!(
683 "cpp.reconciled.build[{}#{}]",
684 key.member_identifier,
685 key.owner_terminal.as_deref().unwrap_or("*")
686 )
687 });
688 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
689 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
690 for unit in candidates.for_group(key) {
691 if !keep_going() {
696 return None;
697 }
698 on_candidate();
699 let _candidate =
702 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
703 let role = {
704 let _role = profiling::scope("cpp.reconcile.role");
705 cpp_callable_unit_role(cpp, unit)
706 };
707 if !matches!(
708 role,
709 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
710 ) {
711 continue;
712 }
713 let Some(reconciled) = cpp_reconcile_definition_identity(cpp, unit, &mut using_by_file)
714 else {
715 continue;
716 };
717 let canonical_fq = reconciled.fq_name();
718 if unit.fq_name() == canonical_fq {
725 continue;
726 }
727 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
736 let fq = cpp_member_fq(&reconciled.package, &short_name);
737 let rekeyed = CodeUnit::with_signature_and_fq(
738 unit.source().clone(),
739 unit.kind(),
740 reconciled.package,
741 short_name,
742 unit.signature().map(str::to_string),
743 unit.is_synthetic(),
744 fq,
745 );
746 let index = groups.entry(canonical_fq).or_default();
747 index.rekeyed.push(rekeyed.clone());
748 index.provisional_of.insert(rekeyed, unit.clone());
749 }
750 Some(
751 groups
752 .into_iter()
753 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
754 .collect(),
755 )
756}
757
758fn cpp_reconcile_definition_identity(
763 cpp: &dyn CppSource,
764 unit: &CodeUnit,
765 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
766) -> Option<ReconciledIdentity> {
767 let interner = segment_interner();
777 let mut owner_segments: Vec<&str> = Vec::new();
778 let mut member: Option<&str> = None;
779 for &segment in unit.fq().segments() {
780 let (text, kind) = interner.resolve(segment);
781 match kind {
782 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
783 if member.is_some() {
787 return None;
788 }
789 if !text.is_empty() {
790 owner_segments.push(text);
791 }
792 }
793 SegmentKind::Member => member = Some(text),
794 _ => return None,
795 }
796 }
797 let member = member?;
798 if owner_segments.len() < 2 {
799 return None;
800 }
801
802 let using = using_by_file
803 .entry(unit.source().clone())
804 .or_insert_with(|| {
805 Arc::new(
806 cpp.file_source(unit.source())
807 .map(|source| cpp_file_using_namespaces(&source))
808 .unwrap_or_default(),
809 )
810 })
811 .clone();
812 let mut namespace_candidates: Vec<&str> = vec![""];
813 namespace_candidates.extend(using.iter().map(String::as_str));
814
815 let visible = {
816 let _visible = profiling::scope_with(|| {
817 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
818 });
819 cpp.visible_type_units(unit.source())
820 };
821 let class_table: Vec<VisibleClass> = visible
822 .iter()
823 .filter(|candidate| candidate.is_class())
824 .map(|candidate| VisibleClass {
825 package: candidate.package_name(),
826 nested_short_name: candidate.short_name(),
827 })
828 .collect();
829
830 reconcile_out_of_line_member_identity(
831 &owner_segments,
832 member,
833 &namespace_candidates,
834 &class_table,
835 )
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841
842 fn parse_cpp(source: &str) -> Tree {
843 let mut parser = Parser::new();
844 parser
845 .set_language(&tree_sitter_cpp::LANGUAGE.into())
846 .expect("cpp language");
847 parser.parse(source, None).expect("cpp tree")
848 }
849
850 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
851 let end = start + text.len();
852 assert_eq!(&source[start..end], text, "the probe must name the token");
853 let node = tree
854 .root_node()
855 .named_descendant_for_byte_range(start, end)
856 .expect("a node spans the probed range");
857 assert_eq!(
858 (node.start_byte(), node.end_byte()),
859 (start, end),
860 "the probed range must be exactly one node: {}",
861 node.to_sexp()
862 );
863 cpp_is_constructor_or_destructor_declarator_name(node, source)
864 }
865
866 #[test]
871 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
872 let source = concat!(
873 "class Foo {\n",
874 "public:\n",
875 " Foo();\n",
876 " Foo(const Foo&);\n",
877 " ~Foo();\n",
878 " void m();\n",
879 "};\n",
880 "Foo::Foo() {}\n",
881 "Foo::~Foo() {}\n",
882 "void Foo::m() {}\n",
883 );
884 let tree = parse_cpp(source);
885
886 for (label, start, text) in [
887 (
888 "constructor declaration",
889 source.find("Foo();").expect("ctor"),
890 "Foo",
891 ),
892 (
893 "copy constructor declaration",
894 source.find("Foo(const Foo&);").expect("copy ctor"),
895 "Foo",
896 ),
897 (
898 "destructor name",
899 source.find("~Foo();").expect("dtor"),
900 "~Foo",
901 ),
902 (
903 "identifier inside the destructor name",
904 source.find("~Foo();").expect("dtor") + "~".len(),
905 "Foo",
906 ),
907 (
908 "out-of-line constructor definition name",
909 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
910 "Foo",
911 ),
912 (
913 "out-of-line destructor definition name",
914 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
915 "~Foo",
916 ),
917 ] {
918 assert!(
919 is_declarator_name(&tree, source, start, text),
920 "the {label} at byte {start} is a declaration occurrence"
921 );
922 }
923
924 for (label, start, text) in [
925 (
926 "class name",
927 source.find("class Foo {").expect("class") + "class ".len(),
928 "Foo",
929 ),
930 (
931 "parameter type",
932 source.find("const Foo&").expect("parameter type") + "const ".len(),
933 "Foo",
934 ),
935 (
936 "owning scope of an out-of-line constructor",
937 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
938 "Foo",
939 ),
940 (
941 "owning scope of an out-of-line destructor",
942 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
943 "Foo",
944 ),
945 (
946 "out-of-line method name",
947 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
948 "m",
949 ),
950 ] {
951 assert!(
952 !is_declarator_name(&tree, source, start, text),
953 "the {label} at byte {start} stays a reference"
954 );
955 }
956 }
957
958 #[test]
962 fn constructor_call_sites_stay_references() {
963 let source = concat!(
964 "struct B { B(int); };\n",
965 "struct D : B {\n",
966 " D(int x) : B(x), base_(x) {}\n",
967 " int base_;\n",
968 "};\n",
969 "void g() {\n",
970 " D* p = new D(1);\n",
971 " D x(2);\n",
972 " D(3);\n",
973 " g();\n",
974 "}\n",
975 );
976 let tree = parse_cpp(source);
977
978 let inline_declarator = source.find("D(int x)").expect("inline constructor");
979 assert!(
980 is_declarator_name(&tree, source, inline_declarator, "D"),
981 "an inline constructor definition name is still a declarator"
982 );
983
984 for (label, start, text) in [
985 (
986 "base member initializer",
987 source.find(": B(x)").expect("base initializer") + ": ".len(),
988 "B",
989 ),
990 (
991 "field member initializer",
992 source.find("base_(x) {}").expect("field initializer"),
993 "base_",
994 ),
995 (
996 "new expression type",
997 source.find("new D(1)").expect("new expression") + "new ".len(),
998 "D",
999 ),
1000 (
1001 "direct initialization type",
1002 source.find("D x(2)").expect("direct initialization"),
1003 "D",
1004 ),
1005 (
1006 "temporary construction statement",
1007 source.find("D(3)").expect("temporary"),
1008 "D",
1009 ),
1010 (
1011 "recursive call in a real body",
1012 source.find("g();").expect("recursive call"),
1013 "g",
1014 ),
1015 ] {
1016 assert!(
1017 !is_declarator_name(&tree, source, start, text),
1018 "the {label} at byte {start} is a reference"
1019 );
1020 }
1021 }
1022
1023 #[test]
1028 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1029 let source = concat!(
1030 "class SAMPLE_EXPORT Properties {\n",
1031 " public:\n",
1032 " Properties();\n",
1033 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1034 " int size() const;\n",
1035 " int total() { return size(); }\n",
1036 "};\n",
1037 );
1038 let tree = parse_cpp(source);
1039
1040 let recovered = source.find("Properties();").expect("recovered constructor");
1041 assert!(
1042 is_declarator_name(&tree, source, recovered, "Properties"),
1043 "a constructor declaration the parse read as a call is still a declarator"
1044 );
1045
1046 for (label, start, text) in [
1047 (
1048 "class name in the recovered header",
1049 source
1050 .find("class SAMPLE_EXPORT Properties")
1051 .expect("class")
1052 + "class SAMPLE_EXPORT ".len(),
1053 "Properties",
1054 ),
1055 (
1056 "macro invocation in the recovered body",
1057 source
1058 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1059 .expect("macro invocation"),
1060 "DISALLOW_COPY_AND_ASSIGN",
1061 ),
1062 (
1063 "call inside a method body the recovery kept",
1064 source.find("return size();").expect("member call") + "return ".len(),
1065 "size",
1066 ),
1067 ] {
1068 assert!(
1069 !is_declarator_name(&tree, source, start, text),
1070 "the {label} at byte {start} stays a reference"
1071 );
1072 }
1073 }
1074}