1use crate::declarations::{cpp_file_using_namespaces, cpp_member_fq};
18use crate::graph_support::CppSource;
19use crate::imports::{IncludeTargetIndex, include_paths, resolve_include_targets_with_index};
20use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
21use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
22use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range};
23use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
24use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
25use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
26use brokk_bifrost_core::hash::HashMap;
27use brokk_bifrost_core::path_utils::rel_path_string;
28use brokk_bifrost_core::profiling;
29use std::collections::BTreeSet;
30use std::sync::Arc;
31use tree_sitter::{Node, Parser, Tree};
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum CppCallableUnitRole {
35 DeclarationOnly,
36 Definition,
37 Both,
38 Unknown,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CppOccurrenceRole {
43 DeclarationOnly,
44 Definition,
45 Both,
46 Unknown,
47}
48
49impl CppOccurrenceRole {
50 pub fn api_label(self) -> Option<&'static str> {
51 match self {
52 Self::DeclarationOnly => Some("declaration"),
53 Self::Definition => Some("definition"),
54 Self::Both | Self::Unknown => None,
55 }
56 }
57}
58
59pub struct CppOccurrenceClassifier {
60 tree: Tree,
61}
62
63impl CppOccurrenceClassifier {
64 pub fn new(source: &str) -> Option<Self> {
65 let mut parser = Parser::new();
66 parser
67 .set_language(&tree_sitter_cpp::LANGUAGE.into())
68 .ok()?;
69 parser.parse(source, None).map(|tree| Self { tree })
70 }
71
72 pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
73 cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
74 }
75}
76
77pub fn cpp_callable_unit_role(
78 index: &dyn CodeUnitIndex,
79 callable: &CodeUnit,
80) -> CppCallableUnitRole {
81 if !callable.is_callable() {
82 return CppCallableUnitRole::Unknown;
83 }
84 let mut declaration = false;
85 let mut definition = false;
86 for metadata in index.signature_metadata(callable) {
87 if metadata.is_declaration_only() {
88 declaration = true;
89 } else {
90 definition = true;
91 }
92 }
93 match (declaration, definition) {
94 (true, false) => CppCallableUnitRole::DeclarationOnly,
95 (false, true) => CppCallableUnitRole::Definition,
96 (true, true) => CppCallableUnitRole::Both,
97 (false, false) => CppCallableUnitRole::Unknown,
98 }
99}
100
101pub fn cpp_indexed_callable_linkage(
102 index: &dyn CodeUnitIndex,
103 callable: &CodeUnit,
104) -> Option<CallableLinkage> {
105 let mut external = false;
106 for metadata in index.signature_metadata(callable) {
107 match metadata.callable_linkage() {
108 Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
109 Some(CallableLinkage::External) => external = true,
110 None => {}
111 }
112 }
113 external.then_some(CallableLinkage::External)
114}
115
116pub fn cpp_callable_definitions_share_identity_evidence(
122 index: &dyn CodeUnitIndex,
123 left: &CodeUnit,
124 right: &CodeUnit,
125 header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
126) -> bool {
127 left.source() == right.source()
128 || (left.fq_name() == right.fq_name()
129 && left.signature() == right.signature()
130 && matches!(
131 cpp_indexed_callable_linkage(index, left),
132 Some(CallableLinkage::External)
133 )
134 && matches!(
135 cpp_indexed_callable_linkage(index, right),
136 Some(CallableLinkage::External)
137 )
138 && header_body_related(left.source(), right.source()))
139}
140
141pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
145 let mut current = Some(node);
146 while let Some(candidate) = current {
147 let Some(parent) = candidate.parent() else {
148 return false;
149 };
150 if parent.kind() == "for_range_loop" {
151 return parent
152 .child_by_field_name("declarator")
153 .is_some_and(|declarator| {
154 cpp_range_for_declarator_contains_name(declarator, node)
155 });
156 }
157 current = Some(parent);
158 }
159 false
160}
161
162fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
163 let mut pending = vec![declarator];
164 while let Some(candidate) = pending.pop() {
165 match candidate.kind() {
166 "identifier" | "field_identifier" => {
167 if cpp_same_node(candidate, target) {
168 return true;
169 }
170 }
171 "structured_binding_declarator" => {
172 let mut cursor = candidate.walk();
173 if candidate
174 .named_children(&mut cursor)
175 .any(|name| cpp_same_node(name, target))
176 {
177 return true;
178 }
179 }
180 "pointer_declarator"
181 | "reference_declarator"
182 | "array_declarator"
183 | "attributed_declarator"
184 | "parenthesized_declarator"
185 | "function_declarator"
186 | "init_declarator" => {
187 if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
188 pending.push(inner);
189 }
190 }
191 _ => {}
192 }
193 }
194 false
195}
196
197fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
198 node.child_by_field_name("declarator").or_else(|| {
199 let mut cursor = node.walk();
200 node.named_children(&mut cursor).find(|child| {
201 matches!(
202 child.kind(),
203 "identifier"
204 | "field_identifier"
205 | "structured_binding_declarator"
206 | "pointer_declarator"
207 | "reference_declarator"
208 | "array_declarator"
209 | "attributed_declarator"
210 | "parenthesized_declarator"
211 | "function_declarator"
212 | "init_declarator"
213 )
214 })
215 })
216}
217
218fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
219 left.id() == right.id()
220 && left.start_byte() == right.start_byte()
221 && left.end_byte() == right.end_byte()
222}
223
224pub fn cpp_header_body_files_are_related(
231 left: &ProjectFile,
232 right: &ProjectFile,
233 implementation_imports: &[String],
234 include_targets: &IncludeTargetIndex,
235) -> bool {
236 let (header, implementation) = if cpp_source_path_is_header(left) {
237 (left, right)
238 } else if cpp_source_path_is_header(right) {
239 (right, left)
240 } else {
241 return false;
242 };
243 if cpp_source_path_is_header(implementation) {
244 return false;
245 }
246 implementation_imports
247 .iter()
248 .flat_map(|import| include_paths(std::slice::from_ref(import)))
249 .any(|include| {
250 let targets =
251 resolve_include_targets_with_index(implementation, &include, include_targets);
252 targets.len() == 1 && targets.first() == Some(header)
253 })
254}
255
256pub fn cpp_header_body_implementation_file<'a>(
260 left: &'a ProjectFile,
261 right: &'a ProjectFile,
262) -> Option<&'a ProjectFile> {
263 let implementation = if cpp_source_path_is_header(left) {
264 right
265 } else if cpp_source_path_is_header(right) {
266 left
267 } else {
268 return None;
269 };
270 (!cpp_source_path_is_header(implementation)).then_some(implementation)
271}
272
273pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
274 let path = rel_path_string(source).to_ascii_lowercase();
275 matches!(path.rsplit('.').next(), Some("h" | "hh" | "hpp" | "hxx"))
276}
277
278pub fn cpp_occurrence_role_for_range(
279 root: Node<'_>,
280 candidate: &CodeUnit,
281 range: &Range,
282) -> CppOccurrenceRole {
283 if !candidate.is_callable() && !candidate.is_class() {
284 return CppOccurrenceRole::Both;
285 }
286 let Some(node) = cpp_declaration_node_for_range(root, range) else {
287 return CppOccurrenceRole::Unknown;
288 };
289 if candidate.is_callable() {
290 return if subtree_contains(node, |descendant| {
291 descendant.kind() == "function_definition"
292 && descendant.child_by_field_name("body").is_some()
293 }) {
294 CppOccurrenceRole::Definition
295 } else {
296 CppOccurrenceRole::DeclarationOnly
297 };
298 }
299 if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
300 return CppOccurrenceRole::Definition;
301 }
302 if !subtree_contains(node, |descendant| {
303 matches!(
304 descendant.kind(),
305 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
306 )
307 }) {
308 return CppOccurrenceRole::Both;
309 }
310 if subtree_contains(node, |descendant| {
311 matches!(
312 descendant.kind(),
313 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
314 ) && descendant.child_by_field_name("body").is_some()
315 }) {
316 CppOccurrenceRole::Definition
317 } else {
318 CppOccurrenceRole::DeclarationOnly
319 }
320}
321
322fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
323 node_for_exact_range(root, range).or_else(|| {
324 root.descendant_for_byte_range(range.start_byte, range.end_byte)
325 .and_then(|mut node| {
326 while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
327 node = node.parent()?;
328 }
329 Some(node)
330 })
331 })
332}
333
334#[derive(Default)]
346pub struct CppReconciledDefinitionIndex {
347 pub rekeyed: Vec<CodeUnit>,
349 pub provisional_of: HashMap<CodeUnit, CodeUnit>,
351}
352
353pub fn cpp_reconciled_definitions(
368 cpp: &dyn CppSource,
369 fq_name: &str,
370) -> CppReconciledDefinitionIndex {
371 let _scope = profiling::scope_with(|| format!("cpp.reconciled.build[{fq_name}]"));
372 let mut index = CppReconciledDefinitionIndex::default();
373 let interner = segment_interner();
374 let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
380 let Some(member_segment) = query_fq.last() else {
381 return index;
382 };
383 let (member_identifier, _) = interner.resolve(member_segment);
384 if member_identifier.is_empty() {
385 return index;
386 }
387
388 let query_owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
400 let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
401 text.rsplit_once('$').map_or(text, |(_, tail)| tail)
406 });
407
408 let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
409 let candidates: BTreeSet<CodeUnit> = {
410 let _lookup =
411 profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
412 cpp.lookup_candidates_by_identifier(member_identifier)
413 };
414 profiling::note_with(|| {
415 format!(
416 "cpp.reconcile.candidates[{member_identifier}] n={}",
417 candidates.len()
418 )
419 });
420 for unit in candidates {
421 let _candidate =
424 profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
425 let candidate_owner_terminal = unit
426 .fq()
427 .segments()
428 .iter()
429 .filter_map(|&segment| {
430 let (text, kind) = interner.resolve(segment);
431 matches!(
435 kind,
436 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
437 )
438 .then_some(text)
439 })
440 .last();
441 if let Some(query_terminal) = query_owner_terminal
442 && candidate_owner_terminal != Some(query_terminal)
443 {
444 continue;
445 }
446 if !unit.is_callable() || unit.fq_name() == fq_name {
447 continue;
448 }
449 let role = {
450 let _role = profiling::scope("cpp.reconcile.role");
451 cpp_callable_unit_role(cpp, &unit)
452 };
453 if !matches!(
454 role,
455 CppCallableUnitRole::Definition | CppCallableUnitRole::Both
456 ) {
457 continue;
458 }
459 let Some(reconciled) = cpp_reconcile_definition_identity(cpp, &unit, &mut using_by_file)
460 else {
461 continue;
462 };
463 let canonical_fq = reconciled.fq_name();
464 if canonical_fq != fq_name {
465 continue;
466 }
467 let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
476 let fq = cpp_member_fq(&reconciled.package, &short_name);
477 let rekeyed = CodeUnit::with_signature_and_fq(
478 unit.source().clone(),
479 unit.kind(),
480 reconciled.package,
481 short_name,
482 unit.signature().map(str::to_string),
483 unit.is_synthetic(),
484 fq,
485 );
486 index.rekeyed.push(rekeyed.clone());
487 index.provisional_of.insert(rekeyed, unit);
488 }
489 index
490}
491
492fn cpp_reconcile_definition_identity(
497 cpp: &dyn CppSource,
498 unit: &CodeUnit,
499 using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
500) -> Option<ReconciledIdentity> {
501 let interner = segment_interner();
511 let mut owner_segments: Vec<&str> = Vec::new();
512 let mut member: Option<&str> = None;
513 for &segment in unit.fq().segments() {
514 let (text, kind) = interner.resolve(segment);
515 match kind {
516 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
517 if member.is_some() {
521 return None;
522 }
523 if !text.is_empty() {
524 owner_segments.push(text);
525 }
526 }
527 SegmentKind::Member => member = Some(text),
528 _ => return None,
529 }
530 }
531 let member = member?;
532 if owner_segments.len() < 2 {
533 return None;
534 }
535
536 let using = using_by_file
537 .entry(unit.source().clone())
538 .or_insert_with(|| {
539 Arc::new(
540 cpp.file_source(unit.source())
541 .map(|source| cpp_file_using_namespaces(&source))
542 .unwrap_or_default(),
543 )
544 })
545 .clone();
546 let mut namespace_candidates: Vec<&str> = vec![""];
547 namespace_candidates.extend(using.iter().map(String::as_str));
548
549 let visible = {
550 let _visible = profiling::scope_with(|| {
551 format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
552 });
553 cpp.visible_type_units(unit.source())
554 };
555 let class_table: Vec<VisibleClass> = visible
556 .iter()
557 .filter(|candidate| candidate.is_class())
558 .map(|candidate| VisibleClass {
559 package: candidate.package_name(),
560 nested_short_name: candidate.short_name(),
561 })
562 .collect();
563
564 reconcile_out_of_line_member_identity(
565 &owner_segments,
566 member,
567 &namespace_candidates,
568 &class_table,
569 )
570}