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