use crate::declarations::{
cpp_template_parameter_kind, cpp_template_parameter_name, cpp_template_term, node_text,
};
use crate::graph::resolver::{
cpp_name_component_nodes, cpp_substitute_template_term, cpp_template_terms_equal,
};
use brokk_bifrost_core::analyzer::model::{CppTemplateParameterKind, CppTemplateTerm};
use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
use brokk_bifrost_core::hash::{HashMap, HashSet};
use tree_sitter::Node;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CppTemplateParameterShape {
Type,
Value {
type_term: CppTemplateTerm,
declarator: Option<CppTemplateTerm>,
},
Template(Vec<(CppTemplateParameterKind, bool)>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CppTemplateParameter {
pub name: String,
pub variadic: bool,
pub shape: CppTemplateParameterShape,
}
impl CppTemplateParameter {
fn injected_argument(&self) -> CppTemplateTerm {
let parameter = CppTemplateTerm::Parameter(self.name.clone());
if self.variadic {
CppTemplateTerm::Node {
kind: "parameter_pack_expansion".to_owned(),
children: vec![
parameter,
CppTemplateTerm::Atom {
kind: "...".to_owned(),
text: "...".to_owned(),
},
],
}
} else {
parameter
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CppTemplateHead {
pub parameters: Vec<CppTemplateParameter>,
pub requires: Option<CppTemplateTerm>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CppTemplateLevel {
pub name: String,
pub head: Option<CppTemplateHead>,
pub arguments: Option<Vec<CppTemplateTerm>>,
}
impl CppTemplateLevel {
fn is_bare(&self) -> bool {
self.head.is_none() && self.arguments.is_none()
}
fn effective_arguments(&self) -> Option<Vec<CppTemplateTerm>> {
self.arguments.clone().or_else(|| {
self.head.as_ref().map(|head| {
head.parameters
.iter()
.map(CppTemplateParameter::injected_argument)
.collect()
})
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CppTemplateContext {
pub levels: Vec<CppTemplateLevel>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CppTemplateBinding {
right_to_left: HashMap<String, CppTemplateTerm>,
left: HashSet<String>,
}
impl CppTemplateBinding {
pub fn is_empty(&self) -> bool {
self.left.is_empty()
}
pub fn is_left_parameter(&self, name: &str) -> bool {
self.left.contains(name)
}
pub fn is_right_parameter(&self, name: &str) -> bool {
self.right_to_left.contains_key(name)
}
pub fn binds(&self, right: &str, left: &str) -> bool {
matches!(
self.right_to_left.get(right),
Some(CppTemplateTerm::Parameter(bound)) if bound == left
)
}
pub fn rename(&self, term: &CppTemplateTerm) -> Option<CppTemplateTerm> {
cpp_substitute_template_term(term, &self.right_to_left)
}
pub fn terms_agree(&self, left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
self.rename(right)
.is_some_and(|renamed| cpp_template_terms_equal(left, &renamed))
}
fn bind(&mut self, right: &str, left: &str) {
self.left.insert(left.to_owned());
self.right_to_left.insert(
right.to_owned(),
CppTemplateTerm::Parameter(left.to_owned()),
);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CppTemplateReconciliation {
Equivalent(CppTemplateBinding),
Distinct,
Unsupported,
}
impl CppTemplateContext {
pub fn is_templated(&self) -> bool {
self.levels.iter().any(|level| !level.is_bare())
}
pub fn reconcile(&self, other: &CppTemplateContext) -> CppTemplateReconciliation {
let shared = self.levels.len().min(other.levels.len());
let (left_extra, left) = self.levels.split_at(self.levels.len() - shared);
let (right_extra, right) = other.levels.split_at(other.levels.len() - shared);
if !left_extra
.iter()
.chain(right_extra)
.all(CppTemplateLevel::is_bare)
{
return CppTemplateReconciliation::Unsupported;
}
let mut binding = CppTemplateBinding::default();
for (left_level, right_level) in left.iter().zip(right) {
if left_level.name != right_level.name {
return CppTemplateReconciliation::Unsupported;
}
match (&left_level.head, &right_level.head) {
(None, None) => {}
(Some(left_head), Some(right_head)) => {
if !heads_agree(left_head, right_head, &mut binding) {
return CppTemplateReconciliation::Distinct;
}
}
_ => return CppTemplateReconciliation::Distinct,
}
let agree = match (
left_level.effective_arguments(),
right_level.effective_arguments(),
) {
(None, None) => true,
(Some(left_arguments), Some(right_arguments)) => {
left_arguments.len() == right_arguments.len()
&& left_arguments
.iter()
.zip(&right_arguments)
.all(|(left, right)| binding.terms_agree(left, right))
}
_ => false,
};
if !agree {
return CppTemplateReconciliation::Distinct;
}
}
CppTemplateReconciliation::Equivalent(binding)
}
}
fn heads_agree(
left: &CppTemplateHead,
right: &CppTemplateHead,
binding: &mut CppTemplateBinding,
) -> bool {
if left.parameters.len() != right.parameters.len() {
return false;
}
for (left_parameter, right_parameter) in left.parameters.iter().zip(&right.parameters) {
if left_parameter.variadic != right_parameter.variadic {
return false;
}
binding.bind(&right_parameter.name, &left_parameter.name);
let agree = match (&left_parameter.shape, &right_parameter.shape) {
(CppTemplateParameterShape::Type, CppTemplateParameterShape::Type) => true,
(
CppTemplateParameterShape::Value {
type_term: left_type,
declarator: left_declarator,
},
CppTemplateParameterShape::Value {
type_term: right_type,
declarator: right_declarator,
},
) => {
binding.terms_agree(left_type, right_type)
&& match (left_declarator, right_declarator) {
(None, None) => true,
(Some(left), Some(right)) => binding.terms_agree(left, right),
_ => false,
}
}
(
CppTemplateParameterShape::Template(left_kinds),
CppTemplateParameterShape::Template(right_kinds),
) => left_kinds == right_kinds,
_ => false,
};
if !agree {
return false;
}
}
match (&left.requires, &right.requires) {
(None, None) => true,
(Some(left), Some(right)) => binding.terms_agree(left, right),
_ => false,
}
}
pub fn cpp_declaration_template_context(
node: Node<'_>,
source: &str,
) -> Option<CppTemplateContext> {
let statement = declaration_statement(node)?;
let (own_heads, top) = direct_template_heads(statement);
let qualified = declaration_qualifier(statement);
let mut enclosing = Vec::new();
let mut current = top;
while let Some(parent) = current.parent() {
match parent.kind() {
"field_declaration_list" => {
let class = parent.parent()?;
if !is_class_specifier(class) {
return None;
}
enclosing.push(class);
current = class;
}
"template_declaration"
| "declaration"
| "field_declaration"
| "declaration_list"
| "namespace_definition"
| "linkage_specification"
| "preproc_if"
| "preproc_ifdef"
| "preproc_else"
| "preproc_elif"
| "preproc_elifdef" => current = parent,
"translation_unit" => break,
_ => return None,
}
}
let mut builder = ContextBuilder {
source,
ancestry: ParentIndex::unindexed(),
names: Vec::new(),
levels: Vec::new(),
};
for class in enclosing.into_iter().rev() {
let (heads, _) = direct_template_heads(class);
match heads.as_slice() {
[] => builder.push_class(class, None)?,
[head] => builder.push_class(class, Some(*head))?,
_ => return None,
}
}
if is_class_specifier(statement) {
match own_heads.as_slice() {
[] => builder.push_class(statement, None)?,
[head] => builder.push_class(statement, Some(*head))?,
_ => return None,
}
return Some(builder.finish());
}
match qualified {
Some(_) if !builder.levels.is_empty() => return None,
Some(qualifier) => builder.push_out_of_line(qualifier, &own_heads)?,
None => {
let head = match own_heads.as_slice() {
[] => None,
[head] => builder.head(*head)?,
_ => return None,
};
builder.levels.push(CppTemplateLevel {
name: String::new(),
head,
arguments: None,
});
}
}
if let Some(arguments) = declaration_terminal(statement)
.filter(|terminal| terminal.kind() == "template_function")
.and_then(|terminal| terminal.child_by_field_name("arguments"))
{
let arguments = builder.arguments(arguments);
builder.levels.last_mut()?.arguments = Some(arguments);
}
Some(builder.finish())
}
struct ContextBuilder<'a, 'tree> {
source: &'a str,
ancestry: ParentIndex<'tree>,
names: Vec<String>,
levels: Vec<CppTemplateLevel>,
}
impl<'tree> ContextBuilder<'_, 'tree> {
fn finish(self) -> CppTemplateContext {
CppTemplateContext {
levels: self.levels,
}
}
fn push_class(&mut self, class: Node<'tree>, head: Option<Node<'tree>>) -> Option<()> {
let head = match head {
Some(head) => self.head(head)?,
None => None,
};
let (name, arguments) = match class.child_by_field_name("name") {
None => (String::new(), None),
Some(name) if name.kind() == "template_type" => (
node_text(name.child_by_field_name("name")?, self.source).to_owned(),
Some(self.arguments(name.child_by_field_name("arguments")?)),
),
Some(name) if name.kind() == "type_identifier" => {
(node_text(name, self.source).to_owned(), None)
}
Some(_) => return None,
};
self.levels.push(CppTemplateLevel {
name,
head,
arguments,
});
Some(())
}
fn push_out_of_line(&mut self, qualifier: Node<'tree>, heads: &[Node<'tree>]) -> Option<()> {
let mut components = cpp_name_component_nodes(qualifier)?;
components.pop()?;
let mut heads = heads.iter().copied().peekable();
for component in components {
let arguments = component
.parent()
.filter(|parent| {
matches!(parent.kind(), "template_type" | "template_function")
&& parent.child_by_field_name("name") == Some(component)
})
.map(|template| template.child_by_field_name("arguments"));
let name = node_text(component, self.source).to_owned();
let Some(arguments) = arguments else {
self.levels.push(CppTemplateLevel {
name,
head: None,
arguments: None,
});
continue;
};
let arguments = arguments?;
let scope = self.names.len();
let head = match heads.peek().copied() {
Some(head) => {
let parsed = self.head(head)?;
let consumed = match &parsed {
None => true,
Some(parsed) => {
let introduced = parsed
.parameters
.iter()
.map(|parameter| parameter.name.as_str())
.collect::<HashSet<_>>();
self.arguments(arguments)
.iter()
.any(|term| term_names_any(term, &introduced))
}
};
if consumed {
heads.next();
parsed
} else {
self.names.truncate(scope);
None
}
}
None => None,
};
let arguments = Some(self.arguments(arguments));
self.levels.push(CppTemplateLevel {
name,
head,
arguments,
});
}
let own = match heads.next() {
Some(head) => self.head(head)?,
None => None,
};
if heads.next().is_some() {
return None;
}
self.levels.push(CppTemplateLevel {
name: String::new(),
head: own,
arguments: None,
});
Some(())
}
fn head(&mut self, template: Node<'tree>) -> Option<Option<CppTemplateHead>> {
let list = template.child_by_field_name("parameters")?;
let mut parameters = Vec::new();
let mut cursor = list.walk();
for (index, parameter) in list
.named_children(&mut cursor)
.filter(|child| !child.is_extra())
.enumerate()
{
if !matches!(
parameter.kind(),
"type_parameter_declaration"
| "optional_type_parameter_declaration"
| "variadic_type_parameter_declaration"
| "template_template_parameter_declaration"
| "parameter_declaration"
| "optional_parameter_declaration"
| "variadic_parameter_declaration"
) {
return None;
}
let name = cpp_template_parameter_name(parameter, self.source)
.unwrap_or_else(|| format!("<anonymous:{}:{index}>", self.names.len()));
self.names.push(name.clone());
let variadic = matches!(
parameter.kind(),
"variadic_type_parameter_declaration" | "variadic_parameter_declaration"
);
let shape = match cpp_template_parameter_kind(parameter) {
CppTemplateParameterKind::Type => CppTemplateParameterShape::Type,
CppTemplateParameterKind::Template => {
let nested = parameter.child_by_field_name("parameters")?;
let mut cursor = nested.walk();
CppTemplateParameterShape::Template(
nested
.named_children(&mut cursor)
.filter(|child| !child.is_extra())
.map(|child| {
(
cpp_template_parameter_kind(child),
matches!(
child.kind(),
"variadic_type_parameter_declaration"
| "variadic_parameter_declaration"
),
)
})
.collect(),
)
}
CppTemplateParameterKind::Value => CppTemplateParameterShape::Value {
type_term: self.term(parameter.child_by_field_name("type")?),
declarator: parameter
.child_by_field_name("declarator")
.filter(|declarator| {
!matches!(declarator.kind(), "identifier" | "variadic_declarator")
})
.map(|declarator| self.term(declarator)),
},
};
parameters.push(CppTemplateParameter {
name,
variadic,
shape,
});
}
let mut cursor = template.walk();
let requires = template
.named_children(&mut cursor)
.find(|child| child.kind() == "requires_clause")
.map(|clause| self.term(clause));
Some((!parameters.is_empty()).then_some(CppTemplateHead {
parameters,
requires,
}))
}
fn arguments(&self, list: Node<'tree>) -> Vec<CppTemplateTerm> {
let mut cursor = list.walk();
list.named_children(&mut cursor)
.filter(|argument| !argument.is_extra() && argument.kind() != "comment")
.map(|argument| self.term(argument))
.collect()
}
fn term(&self, node: Node<'tree>) -> CppTemplateTerm {
cpp_template_term(node, self.source, &self.names, &self.ancestry)
}
}
fn term_names_any(term: &CppTemplateTerm, names: &HashSet<&str>) -> bool {
let mut work = vec![term];
while let Some(term) = work.pop() {
match term {
CppTemplateTerm::Parameter(name) if names.contains(name.as_str()) => return true,
CppTemplateTerm::Node { children, .. } => work.extend(children),
CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
}
}
false
}
fn is_class_specifier(node: Node<'_>) -> bool {
matches!(
node.kind(),
"class_specifier" | "struct_specifier" | "union_specifier"
)
}
fn declaration_statement(node: Node<'_>) -> Option<Node<'_>> {
let mut current = node;
loop {
match current.kind() {
"function_definition" | "declaration" | "field_declaration" => return Some(current),
_ if is_class_specifier(current) => return Some(current),
"function_declarator"
| "pointer_declarator"
| "reference_declarator"
| "parenthesized_declarator"
| "attributed_declarator"
| "init_declarator" => current = current.parent()?,
_ => return None,
}
}
}
fn class_statement(class: Node<'_>) -> Node<'_> {
class
.parent()
.filter(|parent| {
matches!(parent.kind(), "declaration" | "field_declaration")
&& parent.child_by_field_name("type") == Some(class)
})
.unwrap_or(class)
}
fn direct_template_heads(statement: Node<'_>) -> (Vec<Node<'_>>, Node<'_>) {
let statement = if is_class_specifier(statement) {
class_statement(statement)
} else {
statement
};
let mut heads = Vec::new();
let mut current = statement;
while let Some(parent) = current.parent() {
if parent.kind() != "template_declaration"
|| parent.child_by_field_name("parameters") == Some(current)
{
break;
}
heads.push(parent);
current = parent;
}
heads.reverse();
(heads, current)
}
fn declaration_qualifier(statement: Node<'_>) -> Option<Node<'_>> {
if is_class_specifier(statement) {
return None;
}
let mut current = statement.child_by_field_name("declarator")?;
loop {
match current.kind() {
"qualified_identifier" => return Some(current),
"identifier" | "field_identifier" | "operator_name" | "destructor_name"
| "template_function" => return None,
_ => {
current = current
.child_by_field_name("declarator")
.or_else(|| current.named_child(0))?;
}
}
}
}
fn declaration_terminal(statement: Node<'_>) -> Option<Node<'_>> {
if is_class_specifier(statement) {
return None;
}
let mut current = statement.child_by_field_name("declarator")?;
loop {
match current.kind() {
"qualified_identifier" => current = current.child_by_field_name("name")?,
"identifier" | "field_identifier" | "operator_name" | "destructor_name"
| "template_function" => return Some(current),
_ => {
current = current
.child_by_field_name("declarator")
.or_else(|| current.named_child(0))?;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const DECLARATIONS: &str = r#"namespace n {
template<class T> struct Box {
void put(T);
template<class U> void take(U);
};
template<class T> struct Box<T*> {
void put(T*);
};
template<> struct Box<int> {
void exact();
};
template<class T, class U> struct Pair {
void pair();
};
template<int N> struct Fixed {
void reset();
};
template<class... Ts> struct Pack {
void pack();
};
template<class T> requires Ready<T> struct Gate {
void open();
};
void make();
}
"#;
const DEFINITIONS: &str = r#"namespace n {
template<typename U> void Box<U>::put(U) {}
template<class T> template<class W> void Box<T>::take(W) {}
template<class T> void Box<T*>::put(T*) {}
void Box<int>::exact() {}
template<class T> void Pair<T>::pair() {}
template<long N> void Fixed<N>::reset() {}
template<int M> void Fixed<M>::reset() {}
template<class... Us> void Pack<Us...>::pack() {}
template<class T> void Pack<T>::pack() {}
template<class U> requires Ready<U> void Gate<U>::open() {}
template<class U> void Gate<U>::open() {}
template<> void make<int>() {}
using Exact = Box<int>;
void Exact::exact() {}
}
"#;
fn context_of(source: &str, needle: &str) -> CppTemplateContext {
let tree = crate::diagnostics::parse_cpp_tree(source);
assert!(!tree.root_node().has_error(), "fixture must parse cleanly");
let start = source.find(needle).expect("needle");
let mut node = tree
.root_node()
.descendant_for_byte_range(start, start + needle.len())
.expect("needle node");
while !matches!(
node.kind(),
"function_definition" | "field_declaration" | "declaration" | "template_declaration"
) {
node = node.parent().expect("declaration ancestor");
}
while node.kind() == "template_declaration" {
node = node
.named_child(node.named_child_count() - 1)
.expect("templated declaration");
}
cpp_declaration_template_context(node, source)
.unwrap_or_else(|| panic!("no template context for {needle}"))
}
#[test]
fn reconcile_matches_heads_and_owner_arguments_structurally() {
use CppTemplateReconciliation::{Distinct, Unsupported};
let cases = [
("put(T);", "Box<U>::put(U)", Some(true)),
("take(U)", "Box<T>::take(W)", Some(true)),
("put(T*)", "Box<T*>::put(T*)", Some(true)),
("put(T);", "Box<T*>::put(T*)", Some(false)),
("exact();", "Box<int>::exact()", Some(true)),
("put(T);", "Box<int>::exact()", Some(false)),
("pair();", "Pair<T>::pair()", Some(false)),
("reset();", "Fixed<N>::reset()", Some(false)),
("reset();", "Fixed<M>::reset()", Some(true)),
("pack();", "Pack<Us...>::pack()", Some(true)),
("pack();", "Pack<T>::pack()", Some(false)),
("open();", "Ready<U> void Gate<U>::open()", Some(true)),
("open();", "<class U> void Gate<U>::open()", Some(false)),
("make();", "make<int>()", Some(false)),
("exact();", "Exact::exact()", None),
];
let mut mismatches = Vec::new();
for (declaration, definition, expected) in cases {
let left = context_of(DECLARATIONS, declaration);
let right = context_of(DEFINITIONS, definition);
let forward = left.reconcile(&right);
let inverse = right.reconcile(&left);
let verdict = |reconciliation: &CppTemplateReconciliation| match reconciliation {
CppTemplateReconciliation::Equivalent(_) => Some(true),
Distinct => Some(false),
Unsupported => None,
};
if verdict(&forward) != expected || verdict(&inverse) != expected {
mismatches.push((declaration, definition, forward, inverse));
}
}
assert!(mismatches.is_empty(), "{mismatches:#?}");
}
}