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_constructor_or_destructor_declarator_name(node: Node<'_>, source: &str) -> bool {
189 cpp_is_declared_constructor_or_destructor_name(node)
190 || cpp_is_recovered_constructor_or_destructor_name(node, source)
191}
192
193fn cpp_is_declared_constructor_or_destructor_name(node: Node<'_>) -> bool {
199 let mut name = node;
200 if let Some(parent) = name.parent()
201 && parent.kind() == "destructor_name"
202 {
203 name = parent;
204 }
205 while let Some(parent) = name.parent() {
209 if parent.kind() != "qualified_identifier"
210 || parent.child_by_field_name("name") != Some(name)
211 {
212 break;
213 }
214 name = parent;
215 }
216 let Some(declarator) = name.parent() else {
217 return false;
218 };
219 if declarator.kind() != "function_declarator"
220 || declarator.child_by_field_name("declarator") != Some(name)
221 {
222 return false;
223 }
224 let Some(owner) = declarator.parent() else {
225 return false;
226 };
227 matches!(owner.kind(), "declaration" | "function_definition")
228 && owner.child_by_field_name("declarator") == Some(declarator)
229 && owner.child_by_field_name("type").is_none()
230}
231
232fn cpp_is_recovered_constructor_or_destructor_name(node: Node<'_>, source: &str) -> bool {
250 if node.kind() != "identifier" {
251 return false;
252 }
253 let Some(call) = node.parent() else {
254 return false;
255 };
256 if call.kind() != "call_expression" || call.child_by_field_name("function") != Some(node) {
257 return false;
258 }
259 let mut current = call.parent();
260 while let Some(ancestor) = current {
261 if ancestor.kind() == "function_definition" {
262 return ancestor
263 .child_by_field_name("declarator")
264 .is_some_and(|declarator| declarator.kind() == "identifier")
265 && cpp_recovered_class_header_names(ancestor, node_text(node, source), source);
266 }
267 current = ancestor.parent();
268 }
269 false
270}
271
272fn cpp_recovered_class_header_names(definition: Node<'_>, name: &str, source: &str) -> bool {
275 let header_end = definition
276 .child_by_field_name("body")
277 .map_or_else(|| definition.end_byte(), |body| body.start_byte());
278 let mut stack = vec![definition];
279 while let Some(node) = stack.pop() {
280 if node.start_byte() >= header_end {
281 continue;
282 }
283 if matches!(
284 node.kind(),
285 "identifier" | "type_identifier" | "namespace_identifier"
286 ) && node_text(node, source) == name
287 {
288 return true;
289 }
290 let mut cursor = node.walk();
291 for child in node.named_children(&mut cursor) {
292 stack.push(child);
293 }
294 }
295 false
296}
297
298fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
299 let mut pending = vec![declarator];
300 while let Some(candidate) = pending.pop() {
301 match candidate.kind() {
302 "identifier" | "field_identifier" => {
303 if cpp_same_node(candidate, target) {
304 return true;
305 }
306 }
307 "structured_binding_declarator" => {
308 let mut cursor = candidate.walk();
309 if candidate
310 .named_children(&mut cursor)
311 .any(|name| cpp_same_node(name, target))
312 {
313 return true;
314 }
315 }
316 "pointer_declarator"
317 | "reference_declarator"
318 | "array_declarator"
319 | "attributed_declarator"
320 | "parenthesized_declarator"
321 | "function_declarator"
322 | "init_declarator" => {
323 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
324 pending.push(inner);
325 }
326 }
327 _ => {}
328 }
329 }
330 false
331}
332
333fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
334 node.child_by_field_name("declarator").or_else(|| {
335 let mut cursor = node.walk();
336 node.named_children(&mut cursor).find(|child| {
337 matches!(
338 child.kind(),
339 "identifier"
340 | "field_identifier"
341 | "structured_binding_declarator"
342 | "pointer_declarator"
343 | "reference_declarator"
344 | "array_declarator"
345 | "attributed_declarator"
346 | "parenthesized_declarator"
347 | "function_declarator"
348 | "init_declarator"
349 )
350 })
351 })
352}
353
354fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
355 left.id() == right.id()
356 && left.start_byte() == right.start_byte()
357 && left.end_byte() == right.end_byte()
358}
359
360pub fn cpp_header_body_files_are_related(
367 left: &ProjectFile,
368 right: &ProjectFile,
369 implementation_imports: &[String],
370 include_targets: &IncludeTargetIndex,
371) -> bool {
372 let (header, implementation) = if cpp_source_path_is_header(left) {
373 (left, right)
374 } else if cpp_source_path_is_header(right) {
375 (right, left)
376 } else {
377 return false;
378 };
379 if cpp_source_path_is_header(implementation) {
380 return false;
381 }
382 implementation_imports
383 .iter()
384 .flat_map(|import| include_paths(std::slice::from_ref(import)))
385 .any(|include| {
386 let targets =
387 resolve_include_targets_with_index(implementation, &include, include_targets);
388 targets.len() == 1 && targets.first() == Some(header)
389 })
390}
391
392pub fn cpp_header_body_implementation_file<'a>(
396 left: &'a ProjectFile,
397 right: &'a ProjectFile,
398) -> Option<&'a ProjectFile> {
399 let implementation = if cpp_source_path_is_header(left) {
400 right
401 } else if cpp_source_path_is_header(right) {
402 left
403 } else {
404 return None;
405 };
406 (!cpp_source_path_is_header(implementation)).then_some(implementation)
407}
408
409pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
410 let path = rel_path_string(source).to_ascii_lowercase();
411 matches!(path.rsplit('.').next(), Some("h" | "hh" | "hpp" | "hxx"))
412}
413
414pub fn cpp_occurrence_role_for_range(
415 root: Node<'_>,
416 candidate: &CodeUnit,
417 range: &Range,
418) -> CppOccurrenceRole {
419 if !candidate.is_callable() && !candidate.is_class() {
420 return CppOccurrenceRole::Both;
421 }
422 let Some(node) = cpp_declaration_node_for_range(root, range) else {
423 return CppOccurrenceRole::Unknown;
424 };
425 if candidate.is_callable() {
426 return if subtree_contains(node, |descendant| {
427 descendant.kind() == "function_definition"
428 && descendant.child_by_field_name("body").is_some()
429 }) {
430 CppOccurrenceRole::Definition
431 } else {
432 CppOccurrenceRole::DeclarationOnly
433 };
434 }
435 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
436 return CppOccurrenceRole::Definition;
437 }
438 if !subtree_contains(node, |descendant| {
439 matches!(
440 descendant.kind(),
441 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
442 )
443 }) {
444 return CppOccurrenceRole::Both;
445 }
446 if subtree_contains(node, |descendant| {
447 matches!(
448 descendant.kind(),
449 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
450 ) && descendant.child_by_field_name("body").is_some()
451 }) {
452 CppOccurrenceRole::Definition
453 } else {
454 CppOccurrenceRole::DeclarationOnly
455 }
456}
457
458fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
459 node_for_exact_range(root, range).or_else(|| {
460 root.descendant_for_byte_range(range.start_byte, range.end_byte)
461 .and_then(|mut node| {
462 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
463 node = node.parent()?;
464 }
465 Some(node)
466 })
467 })
468}
469
470#[derive(Default)]
482pub struct CppReconciledDefinitionIndex {
483 pub rekeyed: Vec<CodeUnit>,
485 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
487}
488
489#[derive(Debug, Clone, PartialEq, Eq, Hash)]
505pub struct CppReconcileGroupKey {
506 pub member_identifier: String,
509 pub owner_terminal: Option<String>,
513}
514
515pub fn cpp_reconcile_group_key(fq_name: &str) -> Option<CppReconcileGroupKey> {
522 let interner = segment_interner();
523 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
524 let (member_identifier, _) = interner.resolve(query_fq.last()?);
525 if member_identifier.is_empty() {
526 return None;
527 }
528 let owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
538 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
539 text.rsplit_once('$')
544 .map_or(text, |(_, tail)| tail)
545 .to_string()
546 });
547 Some(CppReconcileGroupKey {
548 member_identifier: member_identifier.to_string(),
549 owner_terminal,
550 })
551}
552
553pub struct CppReconcileCandidates {
559 by_owner_terminal: HashMap<String, Vec<CodeUnit>>,
560 all: Vec<CodeUnit>,
564}
565
566impl CppReconcileCandidates {
567 fn for_group(&self, key: &CppReconcileGroupKey) -> &[CodeUnit] {
570 match &key.owner_terminal {
571 Some(owner_terminal) => self
572 .by_owner_terminal
573 .get(owner_terminal)
574 .map_or(&[][..], Vec::as_slice),
575 None => &self.all,
576 }
577 }
578
579 pub fn iter(&self) -> impl Iterator<Item = &CodeUnit> {
581 self.all.iter()
582 }
583
584 pub fn bucketed_len(&self) -> usize {
587 self.by_owner_terminal.values().map(Vec::len).sum()
588 }
589}
590
591pub fn cpp_reconcile_candidates(
599 cpp: &dyn CppSource,
600 member_identifier: &str,
601 keep_going: &dyn Fn() -> bool,
602) -> Option<CppReconcileCandidates> {
603 let candidates: BTreeSet<CodeUnit> = {
604 let _lookup =
605 profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
606 cpp.lookup_candidates_by_identifier(member_identifier)
607 };
608 profiling::note_with(|| {
609 format!(
610 "cpp.reconcile.candidates[{member_identifier}] n={}",
611 candidates.len()
612 )
613 });
614
615 let interner = segment_interner();
616 let mut by_owner_terminal: HashMap<String, Vec<CodeUnit>> = HashMap::default();
617 let mut all = Vec::new();
618 for (index, unit) in candidates.into_iter().enumerate() {
619 if index % CANDIDATE_BUCKETING_POLL_STRIDE == 0 && !keep_going() {
623 return None;
624 }
625 if !unit.is_callable() {
626 continue;
627 }
628 let owner_terminal = unit
629 .fq()
630 .segments()
631 .iter()
632 .filter_map(|&segment| {
633 let (text, kind) = interner.resolve(segment);
634 matches!(
638 kind,
639 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
640 )
641 .then_some(text)
642 })
643 .last();
644 if let Some(owner_terminal) = owner_terminal {
645 by_owner_terminal
646 .entry(owner_terminal.to_string())
647 .or_default()
648 .push(unit.clone());
649 }
650 all.push(unit);
651 }
652 Some(CppReconcileCandidates {
653 by_owner_terminal,
654 all,
655 })
656}
657
658const CANDIDATE_BUCKETING_POLL_STRIDE: usize = 256;
660
661pub fn cpp_reconcile_group(
678 cpp: &dyn CppSource,
679 key: &CppReconcileGroupKey,
680 candidates: &CppReconcileCandidates,
681 keep_going: &dyn Fn() -> bool,
682 on_candidate: &dyn Fn(),
683) -> Option<HashMap<String, Arc<CppReconciledDefinitionIndex>>> {
684 let _scope = profiling::scope_with(|| {
685 format!(
686 "cpp.reconciled.build[{}#{}]",
687 key.member_identifier,
688 key.owner_terminal.as_deref().unwrap_or("*")
689 )
690 });
691 let mut groups: HashMap<String, CppReconciledDefinitionIndex> = HashMap::default();
692 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
693 for unit in candidates.for_group(key) {
694 if !keep_going() {
699 return None;
700 }
701 on_candidate();
702 let _candidate =
705 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
706 let role = {
707 let _role = profiling::scope("cpp.reconcile.role");
708 cpp_callable_unit_role(cpp, unit)
709 };
710 if !matches!(
711 role,
712 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
713 ) {
714 continue;
715 }
716 let Some(reconciled) = cpp_reconcile_definition_identity(cpp, unit, &mut using_by_file)
717 else {
718 continue;
719 };
720 let canonical_fq = reconciled.fq_name();
721 if unit.fq_name() == canonical_fq {
728 continue;
729 }
730 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
739 let fq = cpp_member_fq(&reconciled.package, &short_name);
740 let rekeyed = CodeUnit::with_signature_and_fq(
741 unit.source().clone(),
742 unit.kind(),
743 reconciled.package,
744 short_name,
745 unit.signature().map(str::to_string),
746 unit.is_synthetic(),
747 fq,
748 );
749 let index = groups.entry(canonical_fq).or_default();
750 index.rekeyed.push(rekeyed.clone());
751 index.provisional_of.insert(rekeyed, unit.clone());
752 }
753 Some(
754 groups
755 .into_iter()
756 .map(|(canonical_fq, index)| (canonical_fq, Arc::new(index)))
757 .collect(),
758 )
759}
760
761fn cpp_reconcile_definition_identity(
767 cpp: &dyn CppSource,
768 unit: &CodeUnit,
769 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
770) -> Option<ReconciledIdentity> {
771 let interner = segment_interner();
781 let mut provisional_owner_segments: Vec<&str> = Vec::new();
782 let mut member: Option<&str> = None;
783 for &segment in unit.fq().segments() {
784 let (text, kind) = interner.resolve(segment);
785 match kind {
786 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
787 if member.is_some() {
791 return None;
792 }
793 if !text.is_empty() {
794 provisional_owner_segments.push(text);
795 }
796 }
797 SegmentKind::Member => member = Some(text),
798 _ => return None,
799 }
800 }
801 let member = member?;
802 let structured_owner_segments =
807 cpp_structured_out_of_line_owner_segments(cpp, unit).filter(|segments| segments.len() == 1);
808 let owner_segments = structured_owner_segments.as_ref().map_or_else(
809 || provisional_owner_segments,
810 |segments| segments.iter().map(String::as_str).collect(),
811 );
812 if owner_segments.is_empty() {
813 return None;
814 }
815
816 let using = using_by_file
817 .entry(unit.source().clone())
818 .or_insert_with(|| {
819 Arc::new(
820 cpp.file_source(unit.source())
821 .map(|source| cpp_file_using_namespaces(&source))
822 .unwrap_or_default(),
823 )
824 })
825 .clone();
826 let mut namespace_candidates: Vec<&str> = vec![""];
827 namespace_candidates.extend(using.iter().map(String::as_str));
828
829 let visible = {
830 let _visible = profiling::scope_with(|| {
831 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
832 });
833 cpp.visible_type_units(unit.source())
834 };
835 let class_table: Vec<VisibleClass> = visible
836 .iter()
837 .filter(|candidate| candidate.is_class())
838 .map(|candidate| VisibleClass {
839 package: candidate.package_name(),
840 nested_short_name: candidate.short_name(),
841 })
842 .collect();
843
844 reconcile_out_of_line_member_identity(
845 &owner_segments,
846 member,
847 &namespace_candidates,
848 &class_table,
849 )
850}
851
852fn cpp_structured_out_of_line_owner_segments(
859 cpp: &dyn CppSource,
860 unit: &CodeUnit,
861) -> Option<Vec<String>> {
862 let prepared = cpp.prepared_syntax(unit.source())?;
863 let root = prepared.tree().root_node();
864 for range in cpp.ranges(unit) {
865 let mut current = cpp_declaration_node_for_range(root, &range)?;
866 let function = loop {
867 if current.kind() == "function_definition" {
868 break current;
869 }
870 current = current.parent()?;
871 };
872 if function.child_by_field_name("body").is_none() {
873 continue;
874 }
875 let declarator = function.child_by_field_name("declarator")?;
876 let name = declarator_name_node(declarator)?;
877 if !qualified_name_has_concrete_scope_separators(name) {
878 continue;
879 }
880 let mut components = cpp_type_name_components(name, prepared.source())?;
881 components.pop()?;
882 if !components.is_empty() {
883 return Some(components);
884 }
885 }
886 None
887}
888
889#[cfg(test)]
890mod tests {
891 use super::*;
892
893 fn parse_cpp(source: &str) -> Tree {
894 let mut parser = Parser::new();
895 parser
896 .set_language(&tree_sitter_cpp::LANGUAGE.into())
897 .expect("cpp language");
898 parser.parse(source, None).expect("cpp tree")
899 }
900
901 fn is_declarator_name(tree: &Tree, source: &str, start: usize, text: &str) -> bool {
902 let end = start + text.len();
903 assert_eq!(&source[start..end], text, "the probe must name the token");
904 let node = tree
905 .root_node()
906 .named_descendant_for_byte_range(start, end)
907 .expect("a node spans the probed range");
908 assert_eq!(
909 (node.start_byte(), node.end_byte()),
910 (start, end),
911 "the probed range must be exactly one node: {}",
912 node.to_sexp()
913 );
914 cpp_is_constructor_or_destructor_declarator_name(node, source)
915 }
916
917 #[test]
922 fn declared_constructor_and_destructor_declarator_names_are_not_references() {
923 let source = concat!(
924 "class Foo {\n",
925 "public:\n",
926 " Foo();\n",
927 " Foo(const Foo&);\n",
928 " ~Foo();\n",
929 " void m();\n",
930 "};\n",
931 "Foo::Foo() {}\n",
932 "Foo::~Foo() {}\n",
933 "void Foo::m() {}\n",
934 );
935 let tree = parse_cpp(source);
936
937 for (label, start, text) in [
938 (
939 "constructor declaration",
940 source.find("Foo();").expect("ctor"),
941 "Foo",
942 ),
943 (
944 "copy constructor declaration",
945 source.find("Foo(const Foo&);").expect("copy ctor"),
946 "Foo",
947 ),
948 (
949 "destructor name",
950 source.find("~Foo();").expect("dtor"),
951 "~Foo",
952 ),
953 (
954 "identifier inside the destructor name",
955 source.find("~Foo();").expect("dtor") + "~".len(),
956 "Foo",
957 ),
958 (
959 "out-of-line constructor definition name",
960 source.find("Foo::Foo() {}").expect("out-of-line ctor") + "Foo::".len(),
961 "Foo",
962 ),
963 (
964 "out-of-line destructor definition name",
965 source.find("Foo::~Foo() {}").expect("out-of-line dtor") + "Foo::".len(),
966 "~Foo",
967 ),
968 ] {
969 assert!(
970 is_declarator_name(&tree, source, start, text),
971 "the {label} at byte {start} is a declaration occurrence"
972 );
973 }
974
975 for (label, start, text) in [
976 (
977 "class name",
978 source.find("class Foo {").expect("class") + "class ".len(),
979 "Foo",
980 ),
981 (
982 "parameter type",
983 source.find("const Foo&").expect("parameter type") + "const ".len(),
984 "Foo",
985 ),
986 (
987 "owning scope of an out-of-line constructor",
988 source.find("Foo::Foo() {}").expect("out-of-line ctor"),
989 "Foo",
990 ),
991 (
992 "owning scope of an out-of-line destructor",
993 source.find("Foo::~Foo() {}").expect("out-of-line dtor"),
994 "Foo",
995 ),
996 (
997 "out-of-line method name",
998 source.find("void Foo::m() {}").expect("out-of-line method") + "void Foo::".len(),
999 "m",
1000 ),
1001 ] {
1002 assert!(
1003 !is_declarator_name(&tree, source, start, text),
1004 "the {label} at byte {start} stays a reference"
1005 );
1006 }
1007 }
1008
1009 #[test]
1013 fn constructor_call_sites_stay_references() {
1014 let source = concat!(
1015 "struct B { B(int); };\n",
1016 "struct D : B {\n",
1017 " D(int x) : B(x), base_(x) {}\n",
1018 " int base_;\n",
1019 "};\n",
1020 "void g() {\n",
1021 " D* p = new D(1);\n",
1022 " D x(2);\n",
1023 " D(3);\n",
1024 " g();\n",
1025 "}\n",
1026 );
1027 let tree = parse_cpp(source);
1028
1029 let inline_declarator = source.find("D(int x)").expect("inline constructor");
1030 assert!(
1031 is_declarator_name(&tree, source, inline_declarator, "D"),
1032 "an inline constructor definition name is still a declarator"
1033 );
1034
1035 for (label, start, text) in [
1036 (
1037 "base member initializer",
1038 source.find(": B(x)").expect("base initializer") + ": ".len(),
1039 "B",
1040 ),
1041 (
1042 "field member initializer",
1043 source.find("base_(x) {}").expect("field initializer"),
1044 "base_",
1045 ),
1046 (
1047 "new expression type",
1048 source.find("new D(1)").expect("new expression") + "new ".len(),
1049 "D",
1050 ),
1051 (
1052 "direct initialization type",
1053 source.find("D x(2)").expect("direct initialization"),
1054 "D",
1055 ),
1056 (
1057 "temporary construction statement",
1058 source.find("D(3)").expect("temporary"),
1059 "D",
1060 ),
1061 (
1062 "recursive call in a real body",
1063 source.find("g();").expect("recursive call"),
1064 "g",
1065 ),
1066 ] {
1067 assert!(
1068 !is_declarator_name(&tree, source, start, text),
1069 "the {label} at byte {start} is a reference"
1070 );
1071 }
1072 }
1073
1074 #[test]
1079 fn a_constructor_declarator_the_parse_read_as_a_call_is_not_a_reference() {
1080 let source = concat!(
1081 "class SAMPLE_EXPORT Properties {\n",
1082 " public:\n",
1083 " Properties();\n",
1084 " DISALLOW_COPY_AND_ASSIGN(Properties);\n",
1085 " int size() const;\n",
1086 " int total() { return size(); }\n",
1087 "};\n",
1088 );
1089 let tree = parse_cpp(source);
1090
1091 let recovered = source.find("Properties();").expect("recovered constructor");
1092 assert!(
1093 is_declarator_name(&tree, source, recovered, "Properties"),
1094 "a constructor declaration the parse read as a call is still a declarator"
1095 );
1096
1097 for (label, start, text) in [
1098 (
1099 "class name in the recovered header",
1100 source
1101 .find("class SAMPLE_EXPORT Properties")
1102 .expect("class")
1103 + "class SAMPLE_EXPORT ".len(),
1104 "Properties",
1105 ),
1106 (
1107 "macro invocation in the recovered body",
1108 source
1109 .find("DISALLOW_COPY_AND_ASSIGN(Properties);")
1110 .expect("macro invocation"),
1111 "DISALLOW_COPY_AND_ASSIGN",
1112 ),
1113 (
1114 "call inside a method body the recovery kept",
1115 source.find("return size();").expect("member call") + "return ".len(),
1116 "size",
1117 ),
1118 ] {
1119 assert!(
1120 !is_declarator_name(&tree, source, start, text),
1121 "the {label} at byte {start} stays a reference"
1122 );
1123 }
1124 }
1125}