Skip to main content

brokk_bifrost_cpp/
source_facts.rs

1//! Declaration properties interpreted while the coordinated producer owns syntax.
2
3use crate::declarations::{
4    cpp_callable_identity_suffix, cpp_callable_is_structural_constructor,
5    cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection, node_text,
6};
7use crate::graph::resolver::{
8    cpp_template_reference_arguments, cpp_type_name_components, declarator_name_node,
9    declarator_names_function_type, declared_name_binding, structured_alias_type_target,
10};
11use brokk_bifrost_core::analyzer::CodeUnit;
12use brokk_bifrost_core::analyzer::cpp_facts::*;
13use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
14use brokk_bifrost_core::analyzer::tree_walk::ParentIndex;
15use tree_sitter::Node;
16
17pub(crate) fn base_specifier_from_node(
18    node: Node<'_>,
19    source: &str,
20    is_virtual: bool,
21) -> Option<CppBaseSpecifierFact> {
22    let components = cpp_type_name_components(node, source)?;
23    (!components.is_empty()).then_some(CppBaseSpecifierFact {
24        absolute: node.child(0).is_some_and(|token| token.kind() == "::"),
25        components,
26        is_virtual,
27    })
28}
29
30pub(crate) fn declaration_name<'tree>(
31    node: Node<'tree>,
32    unit: &CodeUnit,
33    source: &str,
34) -> Option<Node<'tree>> {
35    if let Some(name) = node.child_by_field_name("name") {
36        return crate::structural::declarator_name_node(name);
37    }
38    let mut cursor = node.walk();
39    node.children_by_field_name("declarator", &mut cursor)
40        .find_map(|declarator| {
41            let name = crate::structural::declarator_name_node(declarator)?;
42            (node_text(name, source) == unit.identifier()).then_some(name)
43        })
44}
45
46/// Capture the structured type properties of one field declaration while its
47/// declaration AST is still available.
48pub(crate) fn capture_field_type(
49    node: Node<'_>,
50    field_name: &str,
51    source: &str,
52) -> (Option<CppDeclaredFieldTypeFact>, bool) {
53    if let Some(recovered) = crate::declarations::recovered_pyobject_head_field(node, source)
54        && node_text(recovered.name, source) == field_name
55    {
56        return (
57            Some(CppDeclaredFieldTypeFact {
58                type_text: node_text(recovered.type_node, source).to_owned(),
59                indirection: recovered.pointer_depth(),
60                binds_indirectly: recovered.pointer_depth() > 0,
61                template_arguments: None,
62            }),
63            false,
64        );
65    }
66    let Some(type_node) = node.child_by_field_name("type") else {
67        return (None, false);
68    };
69    let mut cursor = node.walk();
70    let declarator = node
71        .children_by_field_name("declarator", &mut cursor)
72        .find(|declarator| {
73            crate::structural::declarator_name_node(*declarator)
74                .is_some_and(|name| node_text(name, source) == field_name)
75        });
76    let names_function_type = declarator.is_some_and(declarator_names_function_type);
77    let field_type = declared_name_binding(node, type_node, field_name, source).map(|binding| {
78        let ty = if matches!(
79            type_node.kind(),
80            "class_specifier" | "struct_specifier" | "union_specifier"
81        ) {
82            type_node.child_by_field_name("name")
83        } else {
84            Some(type_node)
85        };
86        CppDeclaredFieldTypeFact {
87            type_text: ty.map_or_else(
88                || field_name.to_owned(),
89                |ty| node_text(ty, source).to_owned(),
90            ),
91            indirection: binding.pointer_depth,
92            binds_indirectly: binding.indirect,
93            template_arguments: ty.and_then(|ty| cpp_template_reference_arguments(ty, source)),
94        }
95    });
96    (field_type, names_function_type)
97}
98
99pub(crate) fn capture_declaration(
100    declaration: SourceDeclarationId,
101    node: Node<'_>,
102    unit: &CodeUnit,
103    source: &str,
104    ancestry: &ParentIndex<'_>,
105) -> CppDeclarationSourceFact {
106    let mut fact = CppDeclarationSourceFact::new(declaration);
107    if matches!(node.kind(), "alias_declaration" | "type_definition")
108        && crate::graph::resolver::alias_has_visible_file_scope(node)
109        && let Some(name) = declaration_name(node, unit, source)
110        && let Some(name) =
111            crate::graph::resolver::normalize_reference_name(node_text(name, source))
112        && let Some(target) = node.child_by_field_name("type")
113        && let Some(target) =
114            crate::graph::resolver::normalize_reference_name(node_text(target, source))
115    {
116        fact.file_scope_alias = Some(CppFileScopeAliasFact {
117            name,
118            target,
119            namespace: crate::graph::resolver::enclosing_namespace_context(node, source),
120        });
121    }
122    let mut owners = Vec::new();
123    let mut current = ancestry.parent(node);
124    while let Some(parent) = current {
125        if matches!(
126            parent.kind(),
127            "namespace_definition"
128                | "class_specifier"
129                | "struct_specifier"
130                | "union_specifier"
131                | "enum_specifier"
132        ) && let Some(name) = parent.child_by_field_name("name")
133        {
134            owners.push(node_text(name, source).to_owned());
135        }
136        current = ancestry.parent(parent);
137    }
138    owners.reverse();
139    owners.push(unit.identifier().to_owned());
140    fact.lexical_path = owners;
141    if matches!(
142        node.kind(),
143        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
144    ) {
145        fact.class_strength = if node.child_by_field_name("body").is_some() {
146            CppClassDeclarationStrength::Full
147        } else {
148            CppClassDeclarationStrength::Forward
149        };
150        if node.kind() == "enum_specifier" {
151            let mut cursor = node.walk();
152            fact.enum_kind = if node
153                .children(&mut cursor)
154                .any(|child| matches!(child.kind(), "class" | "struct"))
155            {
156                CppEnumOwnerKind::Scoped
157            } else {
158                CppEnumOwnerKind::Unscoped
159            };
160        }
161        let mut cursor = node.walk();
162        for base_clause in node
163            .named_children(&mut cursor)
164            .filter(|child| child.kind() == "base_class_clause")
165        {
166            let mut is_virtual = false;
167            let mut cursor = base_clause.walk();
168            for child in base_clause.children(&mut cursor) {
169                match child.kind() {
170                    "," => is_virtual = false,
171                    "virtual" => is_virtual = true,
172                    "type_identifier"
173                    | "qualified_identifier"
174                    | "scoped_type_identifier"
175                    | "template_type" => {
176                        if let Some(base) = base_specifier_from_node(child, source, is_virtual) {
177                            fact.bases.push(base);
178                        }
179                        is_virtual = false;
180                    }
181                    _ => {}
182                }
183            }
184        }
185    }
186    let mut cursor = node.walk();
187    let declarator = node
188        .children_by_field_name("declarator", &mut cursor)
189        .find(|declarator| {
190            crate::structural::declarator_name_node(*declarator)
191                .is_some_and(|name| node_text(name, source) == unit.identifier())
192        });
193    if unit.is_function() {
194        let callable = if node.kind() == "function_declarator" {
195            Some(node)
196        } else {
197            node.child_by_field_name("declarator")
198        };
199        if let Some(declarator) = callable {
200            if let Some(name) = declarator_name_node(declarator)
201                && let Some(mut components) = cpp_type_name_components(name, source)
202            {
203                components.pop();
204                fact.written_owner = components;
205            }
206            let mut chain = Some(declarator);
207            while let Some(current) = chain {
208                if current.kind() == "function_declarator" {
209                    fact.callable_comparable_shapes =
210                        Some(cpp_comparable_parameter_shapes(current, source, ancestry));
211                    fact.callable_identity_suffix = cpp_callable_identity_suffix(current, source);
212                    fact.callable_is_constructor =
213                        cpp_callable_is_structural_constructor(current, source, ancestry);
214                    fact.callable_is_deduction_guide =
215                        cpp_callable_is_deduction_guide(node, current, unit, source);
216                    fact.callable_is_template = ancestry.parent(node).is_some_and(|parent| {
217                        parent.kind() == "template_declaration"
218                            && parent
219                                .named_child(parent.named_child_count().saturating_sub(1))
220                                .is_some_and(|declaration| declaration.id() == node.id())
221                    });
222                    // Only identity qualifiers participate in overload hiding.
223                    // The declarator can also contain override/final specifiers.
224                    fact.trailing_qualifiers =
225                        fact.callable_identity_suffix.clone().unwrap_or_default();
226                    break;
227                }
228                chain = current.child_by_field_name("declarator");
229            }
230        }
231    }
232    if unit.is_field() {
233        let (field_type, names_function_type) = capture_field_type(node, unit.identifier(), source);
234        fact.field_type = field_type;
235        fact.names_function_type = names_function_type;
236    }
237    let alias_type = if matches!(node.kind(), "alias_declaration" | "type_definition") {
238        node.child_by_field_name("type")
239    } else if unit.is_class() {
240        crate::declarations::recovered_alias_type_node(node, unit.identifier(), source)
241    } else {
242        None
243    };
244    if let Some(alias_type) = alias_type {
245        let alias_declarator = if node.kind() == "type_definition" {
246            declarator
247        } else {
248            alias_type.child_by_field_name("declarator")
249        };
250        fact.names_function_type = alias_declarator.is_some_and(declarator_names_function_type);
251        fact.adds_indirection = alias_declarator.is_some_and(cpp_declarator_adds_indirection);
252        if !fact.names_function_type {
253            fact.alias_target = structured_alias_type_target(alias_type, source);
254            let mut target = alias_type;
255            while target.kind() == "type_descriptor" {
256                let Some(inner) = target.child_by_field_name("type") else {
257                    break;
258                };
259                target = inner;
260            }
261            if matches!(
262                target.kind(),
263                "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
264            ) && let Some(name) = target.child_by_field_name("name")
265            {
266                target = name;
267            }
268            fact.alias_target_text = Some(node_text(target, source).to_owned());
269        }
270    }
271    fact
272}
273
274fn cpp_callable_is_deduction_guide(
275    declaration: Node<'_>,
276    function_declarator: Node<'_>,
277    unit: &CodeUnit,
278    source: &str,
279) -> bool {
280    if declaration.kind() != "declaration"
281        || declaration.child_by_field_name("type").is_some()
282        || function_declarator.kind() != "function_declarator"
283    {
284        return false;
285    }
286    let mut cursor = function_declarator.walk();
287    function_declarator
288        .named_children(&mut cursor)
289        .any(|child| child.kind() == "trailing_return_type")
290        && declarator_name_node(function_declarator)
291            .is_some_and(|name| node_text(name, source) == unit.identifier())
292}
293
294/// One mounted, generation-selected canonical declaration family.
295#[derive(Debug, Clone)]
296pub struct CppFileSourceFacts {
297    pub source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
298    pub facts: CppSourceFacts,
299    pub declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
300    by_unit: brokk_bifrost_core::hash::HashMap<CodeUnit, Vec<usize>>,
301}
302
303impl CppFileSourceFacts {
304    pub fn new(
305        source: brokk_bifrost_core::analyzer::source_facts::SourceFactRows,
306        facts: CppSourceFacts,
307        declaration_units: brokk_bifrost_core::hash::HashMap<SourceDeclarationId, Vec<CodeUnit>>,
308    ) -> Self {
309        assert!(
310            facts.valid_links(&source),
311            "invalid C++ canonical source links"
312        );
313        let mut by_unit: brokk_bifrost_core::hash::HashMap<_, Vec<_>> = Default::default();
314        for (index, fact) in facts.declarations.iter().enumerate() {
315            if let Some(units) = declaration_units.get(&fact.declaration) {
316                for unit in units {
317                    by_unit.entry(unit.clone()).or_default().push(index);
318                }
319            }
320        }
321        Self {
322            source,
323            facts,
324            declaration_units,
325            by_unit,
326        }
327    }
328
329    pub fn for_unit(&self, unit: &CodeUnit) -> impl Iterator<Item = &CppDeclarationSourceFact> {
330        self.by_unit
331            .get(unit)
332            .into_iter()
333            .flatten()
334            .map(|index| &self.facts.declarations[*index])
335    }
336
337    pub fn estimated_retained_bytes(&self) -> usize {
338        self.source.estimated_bytes()
339            + self.facts.estimated_retained_bytes()
340            + self
341                .declaration_units
342                .values()
343                .map(|units| units.capacity() * std::mem::size_of::<CodeUnit>())
344                .sum::<usize>()
345            + self
346                .by_unit
347                .values()
348                .map(|indices| indices.capacity() * std::mem::size_of::<usize>())
349                .sum::<usize>()
350    }
351}
352
353/// One source-backed navigation alternative for a mounted declaration.
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub struct CppNavigationOccurrence {
356    pub range: brokk_bifrost_core::analyzer::Range,
357    pub role: CppOccurrenceRole,
358    pub conditional_family: Option<(usize, usize)>,
359}
360
361/// Canonical declaration context with IDs resolved in its own dialect arena.
362#[derive(Debug, Clone, PartialEq, Eq)]
363pub struct CppDeclarationOccurrence {
364    pub fact: CppDeclarationSourceFact,
365    pub range: brokk_bifrost_core::analyzer::Range,
366    pub name_range: Option<brokk_bifrost_core::analyzer::Range>,
367    pub exhaustive_family: Option<(usize, usize)>,
368    pub displaced_namespace_closing_brace: Option<usize>,
369}
370
371impl CppFileSourceFacts {
372    pub fn declaration_occurrences(&self, unit: &CodeUnit) -> Vec<CppDeclarationOccurrence> {
373        self.for_unit(unit)
374            .map(|fact| {
375                let declaration = self.source.declaration(fact.declaration);
376                CppDeclarationOccurrence {
377                    range: self.source.occurrence(declaration.occurrence).range,
378                    name_range: declaration.name.map(|id| self.source.occurrence(id).range),
379                    exhaustive_family: fact.exhaustive_conditional_family.map(|id| {
380                        let range = self.source.occurrence(id).range;
381                        (range.start_byte, range.end_byte)
382                    }),
383                    displaced_namespace_closing_brace: fact
384                        .displaced_namespace_closing_brace
385                        .map(|id| self.source.occurrence(id).range.start_byte),
386                    fact: fact.clone(),
387                }
388            })
389            .collect()
390    }
391}