use crate::declarations::{cpp_file_using_namespaces, cpp_member_fq};
use crate::graph_support::CppSource;
use crate::imports::{IncludeTargetIndex, include_paths, resolve_include_targets_with_index};
use crate::reconcile::{ReconciledIdentity, VisibleClass, reconcile_out_of_line_member_identity};
use brokk_bifrost_core::analyzer::fq_name::{SegmentKind, segment_interner};
use brokk_bifrost_core::analyzer::model::{CallableLinkage, Range};
use brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path_fq;
use brokk_bifrost_core::analyzer::tree_walk::{node_for_exact_range, subtree_contains};
use brokk_bifrost_core::analyzer::{CodeUnit, CodeUnitIndex, Language, ProjectFile};
use brokk_bifrost_core::hash::HashMap;
use brokk_bifrost_core::path_utils::rel_path_string;
use brokk_bifrost_core::profiling;
use std::collections::BTreeSet;
use std::sync::Arc;
use tree_sitter::{Node, Parser, Tree};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CppCallableUnitRole {
DeclarationOnly,
Definition,
Both,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CppOccurrenceRole {
DeclarationOnly,
Definition,
Both,
Unknown,
}
impl CppOccurrenceRole {
pub fn api_label(self) -> Option<&'static str> {
match self {
Self::DeclarationOnly => Some("declaration"),
Self::Definition => Some("definition"),
Self::Both | Self::Unknown => None,
}
}
}
pub struct CppOccurrenceClassifier {
tree: Tree,
}
impl CppOccurrenceClassifier {
pub fn new(source: &str) -> Option<Self> {
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_cpp::LANGUAGE.into())
.ok()?;
parser.parse(source, None).map(|tree| Self { tree })
}
pub fn classify(&self, candidate: &CodeUnit, range: &Range) -> CppOccurrenceRole {
cpp_occurrence_role_for_range(self.tree.root_node(), candidate, range)
}
}
pub fn cpp_callable_unit_role(
index: &dyn CodeUnitIndex,
callable: &CodeUnit,
) -> CppCallableUnitRole {
if !callable.is_callable() {
return CppCallableUnitRole::Unknown;
}
let mut declaration = false;
let mut definition = false;
for metadata in index.signature_metadata(callable) {
if metadata.is_declaration_only() {
declaration = true;
} else {
definition = true;
}
}
match (declaration, definition) {
(true, false) => CppCallableUnitRole::DeclarationOnly,
(false, true) => CppCallableUnitRole::Definition,
(true, true) => CppCallableUnitRole::Both,
(false, false) => CppCallableUnitRole::Unknown,
}
}
pub fn cpp_indexed_callable_linkage(
index: &dyn CodeUnitIndex,
callable: &CodeUnit,
) -> Option<CallableLinkage> {
let mut external = false;
for metadata in index.signature_metadata(callable) {
match metadata.callable_linkage() {
Some(CallableLinkage::Internal) => return Some(CallableLinkage::Internal),
Some(CallableLinkage::External) => external = true,
None => {}
}
}
external.then_some(CallableLinkage::External)
}
pub fn cpp_callable_definitions_share_identity_evidence(
index: &dyn CodeUnitIndex,
left: &CodeUnit,
right: &CodeUnit,
header_body_related: impl Fn(&ProjectFile, &ProjectFile) -> bool,
) -> bool {
left.source() == right.source()
|| (left.fq_name() == right.fq_name()
&& left.signature() == right.signature()
&& matches!(
cpp_indexed_callable_linkage(index, left),
Some(CallableLinkage::External)
)
&& matches!(
cpp_indexed_callable_linkage(index, right),
Some(CallableLinkage::External)
)
&& header_body_related(left.source(), right.source()))
}
pub fn cpp_is_range_for_binding_name(node: Node<'_>) -> bool {
let mut current = Some(node);
while let Some(candidate) = current {
let Some(parent) = candidate.parent() else {
return false;
};
if parent.kind() == "for_range_loop" {
return parent
.child_by_field_name("declarator")
.is_some_and(|declarator| {
cpp_range_for_declarator_contains_name(declarator, node)
});
}
current = Some(parent);
}
false
}
fn cpp_range_for_declarator_contains_name(declarator: Node<'_>, target: Node<'_>) -> bool {
let mut pending = vec![declarator];
while let Some(candidate) = pending.pop() {
match candidate.kind() {
"identifier" | "field_identifier" => {
if cpp_same_node(candidate, target) {
return true;
}
}
"structured_binding_declarator" => {
let mut cursor = candidate.walk();
if candidate
.named_children(&mut cursor)
.any(|name| cpp_same_node(name, target))
{
return true;
}
}
"pointer_declarator"
| "reference_declarator"
| "array_declarator"
| "attributed_declarator"
| "parenthesized_declarator"
| "function_declarator"
| "init_declarator" => {
if let Some(inner) = cpp_range_for_inner_declarator(candidate) {
pending.push(inner);
}
}
_ => {}
}
}
false
}
fn cpp_range_for_inner_declarator(node: Node<'_>) -> Option<Node<'_>> {
node.child_by_field_name("declarator").or_else(|| {
let mut cursor = node.walk();
node.named_children(&mut cursor).find(|child| {
matches!(
child.kind(),
"identifier"
| "field_identifier"
| "structured_binding_declarator"
| "pointer_declarator"
| "reference_declarator"
| "array_declarator"
| "attributed_declarator"
| "parenthesized_declarator"
| "function_declarator"
| "init_declarator"
)
})
})
}
fn cpp_same_node(left: Node<'_>, right: Node<'_>) -> bool {
left.id() == right.id()
&& left.start_byte() == right.start_byte()
&& left.end_byte() == right.end_byte()
}
pub fn cpp_header_body_files_are_related(
left: &ProjectFile,
right: &ProjectFile,
implementation_imports: &[String],
include_targets: &IncludeTargetIndex,
) -> bool {
let (header, implementation) = if cpp_source_path_is_header(left) {
(left, right)
} else if cpp_source_path_is_header(right) {
(right, left)
} else {
return false;
};
if cpp_source_path_is_header(implementation) {
return false;
}
implementation_imports
.iter()
.flat_map(|import| include_paths(std::slice::from_ref(import)))
.any(|include| {
let targets =
resolve_include_targets_with_index(implementation, &include, include_targets);
targets.len() == 1 && targets.first() == Some(header)
})
}
pub fn cpp_header_body_implementation_file<'a>(
left: &'a ProjectFile,
right: &'a ProjectFile,
) -> Option<&'a ProjectFile> {
let implementation = if cpp_source_path_is_header(left) {
right
} else if cpp_source_path_is_header(right) {
left
} else {
return None;
};
(!cpp_source_path_is_header(implementation)).then_some(implementation)
}
pub fn cpp_source_path_is_header(source: &ProjectFile) -> bool {
let path = rel_path_string(source).to_ascii_lowercase();
matches!(path.rsplit('.').next(), Some("h" | "hh" | "hpp" | "hxx"))
}
pub fn cpp_occurrence_role_for_range(
root: Node<'_>,
candidate: &CodeUnit,
range: &Range,
) -> CppOccurrenceRole {
if !candidate.is_callable() && !candidate.is_class() {
return CppOccurrenceRole::Both;
}
let Some(node) = cpp_declaration_node_for_range(root, range) else {
return CppOccurrenceRole::Unknown;
};
if candidate.is_callable() {
return if subtree_contains(node, |descendant| {
descendant.kind() == "function_definition"
&& descendant.child_by_field_name("body").is_some()
}) {
CppOccurrenceRole::Definition
} else {
CppOccurrenceRole::DeclarationOnly
};
}
if node.kind() == "function_definition" && node.child_by_field_name("body").is_some() {
return CppOccurrenceRole::Definition;
}
if !subtree_contains(node, |descendant| {
matches!(
descendant.kind(),
"class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
)
}) {
return CppOccurrenceRole::Both;
}
if subtree_contains(node, |descendant| {
matches!(
descendant.kind(),
"class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
) && descendant.child_by_field_name("body").is_some()
}) {
CppOccurrenceRole::Definition
} else {
CppOccurrenceRole::DeclarationOnly
}
}
fn cpp_declaration_node_for_range<'tree>(root: Node<'tree>, range: &Range) -> Option<Node<'tree>> {
node_for_exact_range(root, range).or_else(|| {
root.descendant_for_byte_range(range.start_byte, range.end_byte)
.and_then(|mut node| {
while node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
node = node.parent()?;
}
Some(node)
})
})
}
#[derive(Default)]
pub struct CppReconciledDefinitionIndex {
pub rekeyed: Vec<CodeUnit>,
pub provisional_of: HashMap<CodeUnit, CodeUnit>,
}
pub fn cpp_reconciled_definitions(
cpp: &dyn CppSource,
fq_name: &str,
) -> CppReconciledDefinitionIndex {
let _scope = profiling::scope_with(|| format!("cpp.reconciled.build[{fq_name}]"));
let mut index = CppReconciledDefinitionIndex::default();
let interner = segment_interner();
let query_fq = parse_symbol_path_fq(Language::Cpp, fq_name, interner);
let Some(member_segment) = query_fq.last() else {
return index;
};
let (member_identifier, _) = interner.resolve(member_segment);
if member_identifier.is_empty() {
return index;
}
let query_owner_terminal = query_fq.segments().len().checked_sub(2).map(|penultimate| {
let (text, _) = interner.resolve(query_fq.segments()[penultimate]);
text.rsplit_once('$').map_or(text, |(_, tail)| tail)
});
let mut using_by_file: HashMap<ProjectFile, Arc<Vec<String>>> = HashMap::default();
let candidates: BTreeSet<CodeUnit> = {
let _lookup =
profiling::scope_with(|| format!("cpp.reconcile.lookup[{member_identifier}]"));
cpp.lookup_candidates_by_identifier(member_identifier)
};
profiling::note_with(|| {
format!(
"cpp.reconcile.candidates[{member_identifier}] n={}",
candidates.len()
)
});
for unit in candidates {
let _candidate =
profiling::scope_with(|| format!("cpp.reconcile.candidate[{}]", unit.fq_name()));
let candidate_owner_terminal = unit
.fq()
.segments()
.iter()
.filter_map(|&segment| {
let (text, kind) = interner.resolve(segment);
matches!(
kind,
SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested
)
.then_some(text)
})
.last();
if let Some(query_terminal) = query_owner_terminal
&& candidate_owner_terminal != Some(query_terminal)
{
continue;
}
if !unit.is_callable() || unit.fq_name() == fq_name {
continue;
}
let role = {
let _role = profiling::scope("cpp.reconcile.role");
cpp_callable_unit_role(cpp, &unit)
};
if !matches!(
role,
CppCallableUnitRole::Definition | CppCallableUnitRole::Both
) {
continue;
}
let Some(reconciled) = cpp_reconcile_definition_identity(cpp, &unit, &mut using_by_file)
else {
continue;
};
let canonical_fq = reconciled.fq_name();
if canonical_fq != fq_name {
continue;
}
let short_name = format!("{}.{}", reconciled.owner_chain, reconciled.member);
let fq = cpp_member_fq(&reconciled.package, &short_name);
let rekeyed = CodeUnit::with_signature_and_fq(
unit.source().clone(),
unit.kind(),
reconciled.package,
short_name,
unit.signature().map(str::to_string),
unit.is_synthetic(),
fq,
);
index.rekeyed.push(rekeyed.clone());
index.provisional_of.insert(rekeyed, unit);
}
index
}
fn cpp_reconcile_definition_identity(
cpp: &dyn CppSource,
unit: &CodeUnit,
using_by_file: &mut HashMap<ProjectFile, Arc<Vec<String>>>,
) -> Option<ReconciledIdentity> {
let interner = segment_interner();
let mut owner_segments: Vec<&str> = Vec::new();
let mut member: Option<&str> = None;
for &segment in unit.fq().segments() {
let (text, kind) = interner.resolve(segment);
match kind {
SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => {
if member.is_some() {
return None;
}
if !text.is_empty() {
owner_segments.push(text);
}
}
SegmentKind::Member => member = Some(text),
_ => return None,
}
}
let member = member?;
if owner_segments.len() < 2 {
return None;
}
let using = using_by_file
.entry(unit.source().clone())
.or_insert_with(|| {
Arc::new(
cpp.file_source(unit.source())
.map(|source| cpp_file_using_namespaces(&source))
.unwrap_or_default(),
)
})
.clone();
let mut namespace_candidates: Vec<&str> = vec![""];
namespace_candidates.extend(using.iter().map(String::as_str));
let visible = {
let _visible = profiling::scope_with(|| {
format!("cpp.reconcile.visible[{}]", rel_path_string(unit.source()))
});
cpp.visible_type_units(unit.source())
};
let class_table: Vec<VisibleClass> = visible
.iter()
.filter(|candidate| candidate.is_class())
.map(|candidate| VisibleClass {
package: candidate.package_name(),
nested_short_name: candidate.short_name(),
})
.collect();
reconcile_out_of_line_member_identity(
&owner_segments,
member,
&namespace_candidates,
&class_table,
)
}