Skip to main content

brokk_bifrost_cpp/
external_declarations.rs

1//! Structured declaration extraction for external C++ headers.
2//!
3//! This module is the language-only scanner shared by dependency-pack
4//! production and external-boundary evidence. It emits neutral records so this
5//! crate stays below `brokk-bifrost-analysis` in the dependency graph.
6
7use crate::adapter::parse_cpp_file_with_callable_observer;
8use crate::declarations::{
9    CppCallableObserver, CppComparableNode, CppComparableSlot, CppParameterType,
10    cpp_active_template_type_parameter, cpp_callable_parameter_names,
11    cpp_callable_parameter_type_identities, cpp_callable_return_type_identity,
12    cpp_comparable_parameter_shapes, cpp_macro_swallowed_access_label, node_text,
13};
14use crate::graph::resolver::cpp_name_for;
15use crate::structural::is_cpp_preprocessor_conditional;
16use brokk_bifrost_core::analyzer::ProjectFile;
17use brokk_bifrost_core::analyzer::model::{
18    CallableArity, CodeUnit, CodeUnitType, CppTemplateMetadata, SignatureMetadata,
19    StructuredTypeIdentity, StructuredTypeNodeView,
20};
21use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, collect_parse_errors};
22use brokk_bifrost_core::hash::HashMap;
23use std::cell::RefCell;
24use std::path::{Path, PathBuf};
25use tree_sitter::{Node, Parser};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct CppExternalDeclarationLimits {
29    pub max_records: usize,
30}
31
32impl Default for CppExternalDeclarationLimits {
33    fn default() -> Self {
34        Self {
35            max_records: 250_000,
36        }
37    }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CppExternalDeclarationCompleteness {
42    Complete,
43    Partial,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum CppExternalMemberKind {
48    Function,
49    Field,
50    Macro,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum CppExternalVisibility {
55    Public,
56    Protected,
57    Private,
58}
59
60/// What a reached external declaration set indexed at one name.
61///
62/// A namespace is kept because it is the declaration owner of every free
63/// function written inside it. Dropping it left those declarations owner-less,
64/// and a consumer that publishes a declaration model has no place to put a
65/// member whose owner it never saw.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CppExternalTypeKind {
68    Class,
69    Namespace,
70    TypeAlias,
71}
72
73/// The nearest indexed declaration that scopes one external member.
74///
75/// A type owner and a namespace owner are deliberately distinct: only a type
76/// owner gives its members a receiver and value semantics, while a namespace
77/// scopes a declaration without either.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum CppExternalOwner {
80    Type(String),
81    Namespace(String),
82}
83
84impl CppExternalOwner {
85    pub fn name(&self) -> &str {
86        match self {
87            Self::Type(name) | Self::Namespace(name) => name,
88        }
89    }
90
91    pub fn type_name(&self) -> Option<&str> {
92        match self {
93            Self::Type(name) => Some(name),
94            Self::Namespace(_) => None,
95        }
96    }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct CppExternalType {
101    pub name: String,
102    pub source_name: String,
103    pub kind: CppExternalTypeKind,
104    pub underlying_type: Option<StructuredTypeIdentity>,
105    /// Structured template declaration metadata, when the class declaration
106    /// was reached through a template declaration.  Keeping this alongside
107    /// the neutral record lets a consumer prove a primary template's exact
108    /// arity/defaults without re-reading source text.
109    pub template_metadata: Option<CppTemplateMetadata>,
110    pub visibility: CppExternalVisibility,
111    pub source_path: PathBuf,
112    pub direct_bases: Vec<String>,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct CppExternalMember {
117    pub owner: Option<CppExternalOwner>,
118    pub name: String,
119    pub qualified_name: String,
120    pub kind: CppExternalMemberKind,
121    pub visibility: CppExternalVisibility,
122    pub is_constructor: bool,
123    pub signature: Option<String>,
124    /// The structured type of each invocation parameter, or `None` when the
125    /// declaration is not a callable or its parameter list was not reached.
126    ///
127    /// These are structured identities rather than rendered spellings because
128    /// the consumer publishes them into a type model, and a spelling such as
129    /// `const T&` is a source text, not a type name.
130    pub parameter_types: Option<Vec<CppParameterType>>,
131    /// Parser-derived parameter shapes retaining C++ cv-qualification.
132    pub parameter_shapes: Option<Vec<CppComparableSlot>>,
133    /// Invocation arity, including default and repeated parameters, when the
134    /// declaration is callable and its parameter list was reached.
135    pub callable_arity: Option<CallableArity>,
136    /// The written name of each invocation parameter, when one exists. The
137    /// positions match `parameter_types`; unnamed C parameters remain `None`.
138    pub parameter_names: Option<Vec<Option<String>>>,
139    /// Whether a constructor is available to implicit conversion.
140    pub explicitness: Option<CppCallableExplicitness>,
141    pub return_type: Option<StructuredTypeIdentity>,
142    /// The sole forwarding-reference parameter's type is returned as an
143    /// rvalue reference, directly or through `remove_reference<T>::type`.
144    /// This preserves a declaration relationship, not a function-body claim.
145    pub returns_forwarding_reference: bool,
146    pub source_path: PathBuf,
147}
148
149/// Owned facts from the exact physical callable occurrence visited by the C++
150/// declaration walk. This is local to one external header request.
151#[derive(Debug, Clone, PartialEq, Eq)]
152struct ObservedCallable {
153    metadata: SignatureMetadata,
154    parameter_types: Option<Vec<CppParameterType>>,
155    parameter_names: Option<Vec<Option<String>>>,
156    parameter_shapes: Option<Vec<CppComparableSlot>>,
157    explicitness: Option<CppCallableExplicitness>,
158    return_type: Option<StructuredTypeIdentity>,
159    returns_forwarding_reference: bool,
160}
161
162#[derive(Default)]
163struct ExternalCallableCollector {
164    // None is a sticky same-key conflict, never an arbitrary winner.
165    by_occurrence: RefCell<HashMap<(CodeUnit, usize), Option<ObservedCallable>>>,
166}
167
168impl CppCallableObserver for ExternalCallableCollector {
169    fn observe(
170        &self,
171        unit: &CodeUnit,
172        declaration_start: usize,
173        metadata: &SignatureMetadata,
174        declarator: Node<'_>,
175        source: &str,
176        ancestry: &ParentIndex<'_>,
177    ) {
178        let parameter_types = cpp_callable_parameter_type_identities(declarator, source, ancestry);
179        let parameter_names = declarator
180            .child_by_field_name("parameters")
181            .and_then(|parameters| cpp_callable_parameter_names(parameters, source));
182        let parameter_shapes = cpp_comparable_parameter_shapes(declarator, source, ancestry);
183        let return_type = metadata
184            .return_type_identity()
185            .cloned()
186            .or_else(|| cpp_callable_return_type_identity(declarator, source, ancestry));
187        let returns_forwarding_reference = cpp_returns_forwarding_reference(
188            declarator,
189            source,
190            ancestry,
191            parameter_types.as_deref(),
192            parameter_shapes.as_deref(),
193            return_type.as_ref(),
194        );
195        let observed = ObservedCallable {
196            metadata: metadata.clone(),
197            parameter_types,
198            parameter_names,
199            parameter_shapes,
200            explicitness: cpp_callable_explicitness(declarator),
201            return_type,
202            returns_forwarding_reference,
203        };
204        let mut by_occurrence = self.by_occurrence.borrow_mut();
205        let key = (unit.clone(), declaration_start);
206        if let Some(existing) = by_occurrence.get_mut(&key) {
207            if existing.as_ref() != Some(&observed) {
208                *existing = None;
209            }
210        } else {
211            by_occurrence.insert(key, Some(observed));
212        }
213    }
214
215    fn observe_duplicate(
216        &self,
217        unit: &CodeUnit,
218        declaration_start: usize,
219        declarator: Node<'_>,
220        source: &str,
221        ancestry: &ParentIndex<'_>,
222        metadata: &dyn Fn() -> SignatureMetadata,
223    ) {
224        self.observe(
225            unit,
226            declaration_start,
227            &metadata(),
228            declarator,
229            source,
230            ancestry,
231        );
232    }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum CppCallableExplicitness {
237    Implicit,
238    Explicit,
239    Conditional,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct CppExternalDeclarationDiagnostic {
244    pub code: &'static str,
245    pub message: String,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct CppExternalDeclarationSet {
250    pub types: Vec<CppExternalType>,
251    pub members: Vec<CppExternalMember>,
252    pub completeness: CppExternalDeclarationCompleteness,
253    /// Whether every declaration and callable formal the header's own syntax
254    /// states was extracted: the parse needed no error recovery, every formal
255    /// role was classified, and no record was dropped by the limit.
256    ///
257    /// This is narrower than `completeness`, which also turns partial for any
258    /// preprocessing. Conditional compilation does not hide a written
259    /// declaration from this extractor: every branch is parsed and extracted,
260    /// and a macro definition is extracted under its own name. A declaration
261    /// that only a macro expansion writes is not in the header's syntax at all
262    /// and is outside what this reports.
263    pub syntax_complete: bool,
264    pub diagnostics: Vec<CppExternalDeclarationDiagnostic>,
265}
266
267/// Return literal include paths and their delimiter kind from one C++ source.
268///
269/// The boolean is `true` for a quoted include and `false` for an angle include.
270/// Computed and conditional includes are not external-root evidence; pack
271/// completeness records preprocessor uncertainty separately.
272pub fn external_include_paths(source: &str) -> Vec<(PathBuf, bool)> {
273    let mut parser = Parser::new();
274    parser
275        .set_language(&tree_sitter_cpp::LANGUAGE.into())
276        .expect("the linked tree-sitter-cpp grammar matches this tree-sitter version");
277    let tree = parser.parse(source, None).expect("uncancelled C++ parse");
278    external_include_paths_from_root(source, tree.root_node())
279}
280
281/// Return literal include paths and their delimiter kind from an existing C++
282/// syntax tree.
283pub fn external_include_paths_from_root(source: &str, root: Node<'_>) -> Vec<(PathBuf, bool)> {
284    let mut paths = Vec::new();
285    let mut stack = vec![root];
286    while let Some(node) = stack.pop() {
287        if node.kind() == "preproc_include"
288            && !has_conditional_preprocessor_ancestor(node)
289            && let Some(path) = node.child_by_field_name("path")
290            && let Some((path, quoted)) = match path.kind() {
291                "string_literal" => node_text(path, source)
292                    .strip_prefix('"')
293                    .and_then(|path| path.strip_suffix('"'))
294                    .filter(|path| !path.is_empty())
295                    .map(|path| (PathBuf::from(path), true)),
296                "system_lib_string" => node_text(path, source)
297                    .strip_prefix('<')
298                    .and_then(|path| path.strip_suffix('>'))
299                    .filter(|path| !path.is_empty())
300                    .map(|path| (PathBuf::from(path), false)),
301                _ => None,
302            }
303        {
304            paths.push((path, quoted));
305        }
306        let mut cursor = node.walk();
307        stack.extend(node.named_children(&mut cursor));
308    }
309    paths.sort();
310    paths.dedup();
311    paths
312}
313
314/// Return the literal paths from angle-bracket includes in one C++ source.
315///
316/// Quoted includes are intentionally omitted from this compatibility helper;
317/// callers that resolve compiler search semantics should use
318/// [`external_include_paths`] instead.
319pub fn external_angle_include_paths(source: &str) -> Vec<PathBuf> {
320    external_include_paths(source)
321        .into_iter()
322        .filter_map(|(path, quoted)| (!quoted).then_some(path))
323        .collect()
324}
325
326/// Return literal unconditional angle includes from an existing C++ syntax tree.
327///
328/// The source and root must describe the same immutable snapshot. Analyzer
329/// callers use this form to reuse prepared workspace syntax; standalone
330/// external headers use [`external_angle_include_paths`].
331pub fn external_angle_include_paths_from_root(source: &str, root: Node<'_>) -> Vec<PathBuf> {
332    external_include_paths_from_root(source, root)
333        .into_iter()
334        .filter_map(|(path, quoted)| (!quoted).then_some(path))
335        .collect()
336}
337
338fn has_conditional_preprocessor_ancestor(mut node: Node<'_>) -> bool {
339    while let Some(parent) = node.parent() {
340        if is_cpp_preprocessor_conditional(parent) {
341            return true;
342        }
343        node = parent;
344    }
345    false
346}
347
348/// Extract declarations from one exact header source entry.
349///
350/// `source_path` is relative to `source_set_root`. The caller owns source-set
351/// containment, byte limits, cancellation, and stable hashing. This function
352/// owns only C++ syntax interpretation and its record limit.
353pub fn extract_external_declarations(
354    source_set_root: &Path,
355    source_path: &Path,
356    source: &str,
357    limits: CppExternalDeclarationLimits,
358) -> CppExternalDeclarationSet {
359    let file = ProjectFile::new(source_set_root.to_path_buf(), source_path.to_path_buf());
360    let mut parser = Parser::new();
361    parser
362        .set_language(&tree_sitter_cpp::LANGUAGE.into())
363        .expect("the linked tree-sitter-cpp grammar matches this tree-sitter version");
364    let tree = parser.parse(source, None).expect("uncancelled C++ parse");
365
366    let mut diagnostics = Vec::new();
367    let mut completeness = CppExternalDeclarationCompleteness::Complete;
368    let mut parse_errors = Vec::new();
369    collect_parse_errors(tree.root_node(), &mut parse_errors);
370    if !parse_errors.is_empty() {
371        completeness = CppExternalDeclarationCompleteness::Partial;
372        diagnostics.push(CppExternalDeclarationDiagnostic {
373            code: "cpp.external.parse_error",
374            message: format!(
375                "external header `{}` has parse errors",
376                source_path.display()
377            ),
378        });
379    }
380    if has_unsupported_preprocessing(tree.root_node()) {
381        completeness = CppExternalDeclarationCompleteness::Partial;
382        diagnostics.push(CppExternalDeclarationDiagnostic {
383            code: "cpp.external.preprocessor_partial",
384            message: format!(
385                "external header `{}` has conditional or generated declarations",
386                source_path.display()
387            ),
388        });
389    }
390
391    let mut syntax_complete = parse_errors.is_empty();
392
393    let callable_collector = ExternalCallableCollector::default();
394    let parsed = parse_cpp_file_with_callable_observer(&file, source, &tree, &callable_collector);
395    let mut parent_by_child = HashMap::default();
396    for (parent, children) in &parsed.children {
397        for child in children {
398            // An out-of-line member has both a lexical namespace placement
399            // and a class declaration edge. Its semantic owner is the class,
400            // regardless of HashMap iteration order over those placements.
401            parent_by_child
402                .entry(child.clone())
403                .and_modify(|owner: &mut CodeUnit| {
404                    if parent.is_class() && !owner.is_class() {
405                        *owner = parent.clone();
406                    }
407                })
408                .or_insert_with(|| parent.clone());
409        }
410    }
411
412    let mut declarations = parsed.declarations().iter().cloned().collect::<Vec<_>>();
413    declarations.sort_by_key(|declaration| {
414        (
415            declaration.fq_name(),
416            declaration.kind(),
417            declaration.signature().map(str::to_owned),
418        )
419    });
420
421    let mut types = Vec::new();
422    let mut members = Vec::new();
423    for declaration in declarations {
424        if types.len().saturating_add(members.len()) >= limits.max_records {
425            completeness = CppExternalDeclarationCompleteness::Partial;
426            syntax_complete = false;
427            diagnostics.push(CppExternalDeclarationDiagnostic {
428                code: "cpp.external.record_limit",
429                message: format!(
430                    "external header `{}` exceeded the declaration record limit",
431                    source_path.display()
432                ),
433            });
434            break;
435        }
436        match declaration.kind() {
437            CodeUnitType::Module => types.push(CppExternalType {
438                name: declaration.fq_name(),
439                source_name: cpp_name_for(&declaration),
440                kind: CppExternalTypeKind::Namespace,
441                underlying_type: None,
442                template_metadata: None,
443                visibility: CppExternalVisibility::Public,
444                source_path: source_path.to_path_buf(),
445                direct_bases: Vec::new(),
446            }),
447            CodeUnitType::Class => types.push(CppExternalType {
448                name: declaration.fq_name(),
449                source_name: cpp_name_for(&declaration),
450                kind: if parsed.type_aliases.contains(&declaration) {
451                    CppExternalTypeKind::TypeAlias
452                } else {
453                    CppExternalTypeKind::Class
454                },
455                underlying_type: parsed
456                    .signature_metadata
457                    .get(&declaration)
458                    .and_then(|records| records.first())
459                    .and_then(|metadata| metadata.underlying_type_identity())
460                    .cloned(),
461                template_metadata: parsed.cpp_template_metadata.get(&declaration).cloned(),
462                visibility: parsed
463                    .ranges
464                    .get(&declaration)
465                    .and_then(|ranges| ranges.iter().map(|range| range.start_byte).min())
466                    .map(|start| cpp_member_visibility(tree.root_node(), source, start))
467                    .unwrap_or(CppExternalVisibility::Private),
468                source_path: source_path.to_path_buf(),
469                direct_bases: parsed
470                    .raw_supertypes
471                    .get(&declaration)
472                    .cloned()
473                    .unwrap_or_default(),
474            }),
475            CodeUnitType::Function | CodeUnitType::Field | CodeUnitType::Macro => {
476                let declaration_start = parsed
477                    .ranges
478                    .get(&declaration)
479                    .and_then(|ranges| ranges.iter().map(|range| range.start_byte).min());
480                let observed = (declaration.kind() == CodeUnitType::Function)
481                    .then(|| {
482                        declaration_start.and_then(|start| {
483                            callable_collector
484                                .by_occurrence
485                                .borrow()
486                                .get(&(declaration.clone(), start))
487                                .cloned()
488                                .flatten()
489                        })
490                    })
491                    .flatten();
492                // Never join one physical occurrence's formals with another
493                // occurrence's preferred or first metadata row.
494                let metadata = if declaration.kind() == CodeUnitType::Function {
495                    observed.as_ref().map(|record| &record.metadata)
496                } else {
497                    parsed
498                        .signature_metadata
499                        .get(&declaration)
500                        .and_then(|records| records.first())
501                };
502                let parameter_types = observed
503                    .as_ref()
504                    .and_then(|record| record.parameter_types.clone());
505                let parameter_names = observed
506                    .as_ref()
507                    .and_then(|record| record.parameter_names.clone());
508                let parameter_shapes = observed
509                    .as_ref()
510                    .and_then(|record| record.parameter_shapes.clone());
511                if declaration.kind() == CodeUnitType::Function
512                    && (parameter_types.is_none()
513                        || parameter_names.is_none()
514                        || parameter_shapes.is_none())
515                {
516                    completeness = CppExternalDeclarationCompleteness::Partial;
517                    syntax_complete = false;
518                    diagnostics.push(CppExternalDeclarationDiagnostic {
519                        code: "cpp.external.formal_roles_unavailable",
520                        message: format!(
521                            "external header `{}` has unavailable formal roles for `{}`",
522                            source_path.display(),
523                            declaration.fq_name()
524                        ),
525                    });
526                }
527                let return_type = metadata
528                    .and_then(|metadata| metadata.return_type_identity())
529                    .cloned()
530                    .or_else(|| {
531                        observed
532                            .as_ref()
533                            .and_then(|record| record.return_type.clone())
534                    });
535                let returns_forwarding_reference = observed
536                    .as_ref()
537                    .is_some_and(|record| record.returns_forwarding_reference);
538                members.push(CppExternalMember {
539                    owner: nearest_declaration_owner(&declaration, &parent_by_child),
540                    name: declaration.terminal_name().to_owned(),
541                    qualified_name: cpp_name_for(&declaration),
542                    kind: match declaration.kind() {
543                        CodeUnitType::Function => CppExternalMemberKind::Function,
544                        CodeUnitType::Field => CppExternalMemberKind::Field,
545                        CodeUnitType::Macro => CppExternalMemberKind::Macro,
546                        _ => unreachable!("the outer match admits exactly member kinds"),
547                    },
548                    visibility: declaration_start
549                        .map(|start| cpp_member_visibility(tree.root_node(), source, start))
550                        .unwrap_or(CppExternalVisibility::Private),
551                    is_constructor: metadata
552                        .is_some_and(|metadata| metadata.callable_is_constructor()),
553                    signature: declaration.signature().map(str::to_owned),
554                    parameter_types,
555                    parameter_shapes,
556                    callable_arity: metadata.and_then(SignatureMetadata::callable_arity),
557                    parameter_names,
558                    explicitness: observed.as_ref().and_then(|record| record.explicitness),
559                    return_type,
560                    returns_forwarding_reference,
561                    source_path: source_path.to_path_buf(),
562                });
563            }
564            CodeUnitType::FileScope => {}
565        }
566    }
567
568    CppExternalDeclarationSet {
569        types,
570        members,
571        completeness,
572        syntax_complete,
573        diagnostics,
574    }
575}
576
577fn cpp_member_visibility(root: Node<'_>, source: &str, start_byte: usize) -> CppExternalVisibility {
578    let mut current = root.descendant_for_byte_range(start_byte, start_byte);
579    while let Some(node) = current {
580        let Some(parent) = node.parent() else {
581            break;
582        };
583        if parent.kind() == "field_declaration_list" {
584            let default = match parent.parent().map(|owner| owner.kind()) {
585                Some("struct_specifier" | "union_specifier") => CppExternalVisibility::Public,
586                _ => CppExternalVisibility::Private,
587            };
588            let mut visibility = default;
589            let mut cursor = parent.walk();
590            for child in parent.named_children(&mut cursor) {
591                if child.start_byte() > start_byte {
592                    break;
593                }
594                let access = if child.kind() == "access_specifier" {
595                    Some(node_text(child, source).trim_end_matches(':').trim())
596                } else {
597                    cpp_macro_swallowed_access_label(child, source).map(|label| label.access)
598                };
599                if let Some(access) = access {
600                    visibility = match access {
601                        "public" => CppExternalVisibility::Public,
602                        "protected" => CppExternalVisibility::Protected,
603                        "private" => CppExternalVisibility::Private,
604                        _ => CppExternalVisibility::Private,
605                    };
606                }
607            }
608            return visibility;
609        }
610        current = Some(parent);
611    }
612    CppExternalVisibility::Public
613}
614
615fn cpp_callable_explicitness(mut declarator: Node<'_>) -> Option<CppCallableExplicitness> {
616    while !matches!(
617        declarator.kind(),
618        "declaration" | "field_declaration" | "function_definition"
619    ) {
620        declarator = declarator.parent()?;
621    }
622    if declarator.has_error() {
623        return None;
624    }
625    let mut stack = vec![declarator];
626    let mut explicit = None;
627    while let Some(node) = stack.pop() {
628        if node.kind() == "explicit_function_specifier" {
629            if explicit.is_some() {
630                return None;
631            }
632            explicit = Some(if node.named_child_count() == 0 {
633                CppCallableExplicitness::Explicit
634            } else {
635                CppCallableExplicitness::Conditional
636            });
637            continue;
638        }
639        let mut cursor = node.walk();
640        stack.extend(node.named_children(&mut cursor));
641    }
642    Some(explicit.unwrap_or(CppCallableExplicitness::Implicit))
643}
644
645fn cpp_returns_forwarding_reference<'tree>(
646    declarator: Node<'tree>,
647    source: &str,
648    ancestry: &ParentIndex<'tree>,
649    parameters: Option<&[CppParameterType]>,
650    shapes: Option<&[CppComparableSlot]>,
651    returns: Option<&StructuredTypeIdentity>,
652) -> bool {
653    let (
654        Some([CppParameterType::Structured(parameter)]),
655        Some([CppComparableSlot::Shape(shape)]),
656        Some(returns),
657    ) = (parameters, shapes, returns)
658    else {
659        return false;
660    };
661    let Some(StructuredTypeNodeView::RvalueReference(inner)) = parameter.view(parameter.root_id())
662    else {
663        return false;
664    };
665    let Some(StructuredTypeNodeView::Named(name)) = parameter.view(inner) else {
666        return false;
667    };
668    let [parameter_name] = name.path() else {
669        return false;
670    };
671    let CppComparableNode::Reference { inner } = shape.node(shape.root()) else {
672        return false;
673    };
674    if !matches!(
675        shape.node(*inner),
676        CppComparableNode::Named {
677            konst: false,
678            volatil: false,
679            ..
680        }
681    ) || name.is_absolute()
682        || !cpp_active_template_type_parameter(declarator, parameter_name, source, ancestry)
683        || !matches!(
684            returns.view(returns.root_id()),
685            Some(StructuredTypeNodeView::RvalueReference(_))
686        )
687    {
688        return false;
689    }
690
691    // Keep the dependent return's template argument. Its nominal structured
692    // name alone flattens `remove_reference<T>::type` and loses the `T`.
693    let mut current = declarator;
694    while let Some(parent) = ancestry.parent(current) {
695        if matches!(
696            parent.kind(),
697            "function_definition" | "declaration" | "field_declaration"
698        ) {
699            let mut cursor = parent.walk();
700            if parent.named_children(&mut cursor).any(|child| {
701                child.kind() == "type_qualifier"
702                    && matches!(node_text(child, source), "const" | "volatile")
703            }) {
704                return false;
705            }
706            return parent
707                .child_by_field_name("type")
708                .is_some_and(|return_node| {
709                    cpp_forwarded_return_type(return_node, source, parameter_name)
710                });
711        }
712        current = parent;
713    }
714    false
715}
716
717fn cpp_forwarded_return_type(node: Node<'_>, source: &str, parameter: &str) -> bool {
718    let mut components = Vec::new();
719    let mut removals = 0;
720    let mut stack = vec![node];
721    while let Some(current) = stack.pop() {
722        match current.kind() {
723            "identifier" | "type_identifier" | "namespace_identifier" => {
724                components.push(node_text(current, source));
725            }
726            "dependent_type" => {
727                if current.named_child_count() != 1 {
728                    return false;
729                }
730                stack.push(current.named_child(0).expect("one named child"));
731            }
732            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
733                let Some(name) = current.child_by_field_name("name") else {
734                    return false;
735                };
736                stack.push(name);
737                if let Some(scope) = current.child_by_field_name("scope") {
738                    stack.push(scope);
739                }
740            }
741            "template_type" => {
742                let (Some(name), Some(arguments)) = (
743                    current.child_by_field_name("name"),
744                    current.child_by_field_name("arguments"),
745                ) else {
746                    return false;
747                };
748                if !matches!(name.kind(), "identifier" | "type_identifier")
749                    || node_text(name, source) != "remove_reference"
750                    || arguments.named_child_count() != 1
751                {
752                    return false;
753                }
754                let mut argument = arguments.named_child(0).expect("one named argument");
755                if argument.kind() == "type_descriptor" && argument.named_child_count() == 1 {
756                    argument = argument.named_child(0).expect("one named type");
757                }
758                if !matches!(argument.kind(), "identifier" | "type_identifier")
759                    || node_text(argument, source) != parameter
760                {
761                    return false;
762                }
763                removals += 1;
764                stack.push(name);
765            }
766            _ => return false,
767        }
768    }
769    (removals == 0 && components == [parameter])
770        || (removals == 1
771            && (components == ["std", "remove_reference", "type"]
772                || components == ["remove_reference", "type"]))
773}
774
775fn nearest_declaration_owner(
776    declaration: &CodeUnit,
777    parent_by_child: &HashMap<CodeUnit, CodeUnit>,
778) -> Option<CppExternalOwner> {
779    let mut current = declaration;
780    while let Some(parent) = parent_by_child.get(current) {
781        match parent.kind() {
782            CodeUnitType::Class => return Some(CppExternalOwner::Type(parent.fq_name())),
783            CodeUnitType::Module => return Some(CppExternalOwner::Namespace(parent.fq_name())),
784            _ => {}
785        }
786        current = parent;
787    }
788    None
789}
790
791fn has_unsupported_preprocessing(root: Node<'_>) -> bool {
792    let mut stack = vec![root];
793    while let Some(node) = stack.pop() {
794        if matches!(
795            node.kind(),
796            "preproc_def"
797                | "preproc_function_def"
798                | "preproc_if"
799                | "preproc_ifdef"
800                | "preproc_ifndef"
801                | "preproc_elif"
802                | "preproc_else"
803        ) {
804            return true;
805        }
806        let mut cursor = node.walk();
807        stack.extend(node.named_children(&mut cursor));
808    }
809    false
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815    use brokk_bifrost_core::analyzer::model::CallableArity;
816    use brokk_bifrost_core::analyzer::model::StructuredTypeNodeView;
817    use brokk_bifrost_test_macros::hardwon;
818
819    fn extract(source: &str) -> CppExternalDeclarationSet {
820        let temp = tempfile::tempdir().expect("temp root");
821        extract_external_declarations(
822            temp.path(),
823            Path::new("vector"),
824            source,
825            CppExternalDeclarationLimits::default(),
826        )
827    }
828
829    #[test]
830    fn extracts_literal_include_paths_with_delimiters() {
831        let source = "#include <vector>\n#include \"local.hpp\"\n#include HEADER\n#if FEATURE\n#include <conditional.hpp>\n#endif\n";
832        assert_eq!(
833            vec![
834                (PathBuf::from("local.hpp"), true),
835                (PathBuf::from("vector"), false),
836            ],
837            external_include_paths(source)
838        );
839        assert_eq!(
840            vec![PathBuf::from("vector")],
841            external_angle_include_paths(source)
842        );
843        let mut parser = Parser::new();
844        parser
845            .set_language(&tree_sitter_cpp::LANGUAGE.into())
846            .expect("C++ grammar");
847        let tree = parser.parse(source, None).expect("tree");
848        assert_eq!(
849            vec![PathBuf::from("vector")],
850            external_angle_include_paths_from_root(source, tree.root_node())
851        );
852    }
853
854    #[test]
855    fn extracts_namespaced_template_type_and_owned_members() {
856        let declarations = extract(
857            r#"
858            namespace std {
859            template <typename T> class vector : public sequence<T> {
860            public:
861                vector();
862                void push_back(const T& value);
863                T size;
864            };
865            }
866            "#,
867        );
868
869        assert_eq!(
870            CppExternalDeclarationCompleteness::Complete,
871            declarations.completeness
872        );
873        assert!(
874            declarations.types.iter().any(|record| {
875                record.name == "std.vector" && record.direct_bases == ["sequence<T>"]
876            }),
877            "{declarations:#?}"
878        );
879        assert!(
880            declarations.members.iter().any(|record| {
881                record.owner.as_ref().and_then(CppExternalOwner::type_name) == Some("std.vector")
882                    && record.name == "push_back"
883                    && record.visibility == CppExternalVisibility::Public
884            }),
885            "{declarations:#?}"
886        );
887        assert!(
888            declarations.members.iter().any(|record| {
889                record.owner.as_ref().and_then(CppExternalOwner::type_name) == Some("std.vector")
890                    && record.name == "size"
891            }),
892            "{declarations:#?}"
893        );
894    }
895
896    #[test]
897    fn preserves_copy_and_move_reference_parameter_shapes() {
898        let source = r#"
899            class Widget {
900            public:
901                Widget(const Widget&);
902                Widget(Widget&&);
903            };
904        "#;
905        let declarations = extract(source);
906        let constructors = declarations
907            .members
908            .iter()
909            .filter(|member| {
910                member.owner.as_ref().and_then(CppExternalOwner::type_name) == Some("Widget")
911                    && member.name == "Widget"
912            })
913            .collect::<Vec<_>>();
914
915        assert_eq!(constructors.len(), 2, "{declarations:#?}");
916        let shapes = constructors
917            .iter()
918            .map(|constructor| {
919                let parameters = constructor
920                    .parameter_types
921                    .as_ref()
922                    .expect("constructor parameters");
923                assert_eq!(parameters.len(), 1, "{constructor:#?}");
924                let CppParameterType::Structured(identity) = &parameters[0] else {
925                    panic!("constructor parameter shape: {constructor:#?}");
926                };
927                (
928                    identity.clone(),
929                    matches!(
930                        identity.view(identity.root_id()),
931                        Some(StructuredTypeNodeView::Reference(_))
932                    ),
933                    matches!(
934                        identity.view(identity.root_id()),
935                        Some(StructuredTypeNodeView::RvalueReference(_))
936                    ),
937                )
938            })
939            .collect::<Vec<_>>();
940
941        assert_ne!(shapes[0].0, shapes[1].0);
942        assert!(shapes.iter().any(|(_, is_lvalue, _)| *is_lvalue));
943        assert!(shapes.iter().any(|(_, _, is_rvalue)| *is_rvalue));
944        assert_eq!(declarations, extract(source));
945    }
946
947    #[test]
948    fn preserves_callable_arities_for_defaulted_and_required_parameters() {
949        let declarations = extract(
950            r#"
951            namespace std {
952            template <class C, class Alloc> class basic_string {
953            public:
954                basic_string(const C*, const Alloc& = Alloc());
955                basic_string(const C&, const Alloc&);
956            };
957            }
958            "#,
959        );
960        let constructors = declarations
961            .members
962            .iter()
963            .filter(|member| {
964                member.owner.as_ref().and_then(CppExternalOwner::type_name)
965                    == Some("std.basic_string")
966                    && member.name == "basic_string"
967            })
968            .collect::<Vec<_>>();
969
970        assert_eq!(constructors.len(), 2, "{declarations:#?}");
971        assert!(
972            constructors
973                .iter()
974                .any(|member| { member.callable_arity == Some(CallableArity::new(1, 2, false)) }),
975            "defaulted constructor arity must be required=1,total=2: {constructors:#?}"
976        );
977        assert!(
978            constructors
979                .iter()
980                .any(|member| { member.callable_arity == Some(CallableArity::new(2, 2, false)) }),
981            "non-defaulted constructor arity must be required=2,total=2: {constructors:#?}"
982        );
983    }
984
985    // This manifest checks the neutral header records consumed by model export,
986    // independently of the workspace query layer's declaration projection.
987    #[test]
988    #[hardwon(
989        "6ec52f232835: quicksilver round 4 survivor review: lexical namespaces stole out-of-line exported member owners"
990    )]
991    fn recovered_external_api_manifest() {
992        let declarations = extract(
993            r#"
994#define EXPORT_MARKER
995namespace n { class Widget; int damaged = ; }
996class EXPORT_MARKER Widget { public: void first(); };
997class EXPORT(2,0) Attributed final {
998public:
999    ATTRIBUTE("old") int value();
1000    void following();
1001};
1002namespace a::a {
1003struct C { void f(); void f()const; void g()&; void g()&&; };
1004void ::a::a::C::f() {}
1005void ::a::a::C::f()const {}
1006void C::g()& {}
1007void C::g()&& {}
1008}
1009DEPRECATED /* between name and arguments */ (
1010    EXPORT_MARKER int old(int value), "hint"
1011);
1012EXPORT_MARKER int next();
1013void after() {}
1014"#,
1015        );
1016        let mut types = declarations
1017            .types
1018            .iter()
1019            .map(|record| record.name.as_str())
1020            .collect::<Vec<_>>();
1021        types.sort_unstable();
1022        assert_eq!(
1023            types,
1024            ["Attributed", "a", "a::a", "a::a.C", "n", "n.Widget"]
1025        );
1026        let mut callables = declarations
1027            .members
1028            .iter()
1029            .filter(|record| record.kind == CppExternalMemberKind::Function)
1030            .map(|record| {
1031                (
1032                    record.owner.as_ref().map(CppExternalOwner::name),
1033                    record.name.as_str(),
1034                    record.signature.as_deref(),
1035                )
1036            })
1037            .collect::<Vec<_>>();
1038        callables.sort_unstable();
1039        let mut expected = vec![
1040            (None, "after", Some("()")),
1041            (None, "next", Some("()")),
1042            (None, "old", Some("(int)")),
1043            (Some("Attributed"), "following", Some("()")),
1044            (Some("Attributed"), "value", Some("()")),
1045            (Some("a::a.C"), "f", Some("()")),
1046            (Some("a::a.C"), "f", Some("() const")),
1047            (Some("a::a.C"), "g", Some("() &")),
1048            (Some("a::a.C"), "g", Some("() &&")),
1049            (Some("n.Widget"), "first", Some("()")),
1050        ];
1051        expected.sort_unstable();
1052        assert_eq!(callables, expected);
1053    }
1054
1055    // The external-declaration producer feeds a separately versioned pack model.
1056    // Names belong to the outer formal, never to a nested callback's formal or
1057    // an array bound. This manifest comes from the header's API declarations.
1058    #[test]
1059    fn preserves_callable_parameter_names() {
1060        let declarations = extract(
1061            "struct Owner {};\n\
1062             void connect(int fd, const char *address, unsigned length);\n\
1063             void callbacks(void (*callback)(int argument), int array[extent], int (Owner::*member)(int nested));\n",
1064        );
1065        for (name, expected) in [
1066            ("connect", vec![Some("fd"), Some("address"), Some("length")]),
1067            (
1068                "callbacks",
1069                vec![Some("callback"), Some("array"), Some("member")],
1070            ),
1071        ] {
1072            let candidates = declarations
1073                .members
1074                .iter()
1075                .filter(|member| member.name == name)
1076                .collect::<Vec<_>>();
1077            let [member] = candidates.as_slice() else {
1078                panic!("{name} declaration: {declarations:#?}");
1079            };
1080            assert_eq!(
1081                member.parameter_names,
1082                Some(
1083                    expected
1084                        .into_iter()
1085                        .map(|name| name.map(str::to_owned))
1086                        .collect()
1087                ),
1088                "{name}: outer parameter slots must agree with the API manifest"
1089            );
1090        }
1091    }
1092
1093    #[test]
1094    #[brokk_bifrost_test_macros::hardwon(
1095        "5e79db920891: quicksilver round 4 survivor tests: unnamed formals inherit nested names"
1096    )]
1097    fn unnamed_callable_parameter_names() {
1098        let declarations = extract("void unnamed(void (*)(int argument), int [extent]);\n");
1099        let [member] = declarations.members.as_slice() else {
1100            panic!("one exported callable: {declarations:#?}");
1101        };
1102        assert_eq!(member.parameter_names, Some(vec![None, None]));
1103    }
1104
1105    #[test]
1106    fn preserves_callable_explicitness_for_implicit_binding() {
1107        for (declaration, expected) in [
1108            ("Widget(const char*);", CppCallableExplicitness::Implicit),
1109            (
1110                "explicit Widget(const char*);",
1111                CppCallableExplicitness::Explicit,
1112            ),
1113            (
1114                "explicit(true) Widget(const char*);",
1115                CppCallableExplicitness::Conditional,
1116            ),
1117        ] {
1118            let declarations = extract(&format!("class Widget {{ public: {declaration} }};"));
1119            let member = declarations
1120                .members
1121                .first()
1122                .unwrap_or_else(|| panic!("constructor declaration: {declarations:#?}"));
1123            assert_eq!(Some(expected), member.explicitness, "{member:#?}");
1124        }
1125    }
1126
1127    #[test]
1128    fn preserves_callable_return_reference_shapes() {
1129        let declarations = extract(
1130            r#"
1131            namespace std {
1132            class basic_string {
1133            public:
1134                basic_string& operator=(const basic_string&);
1135                basic_string&& operator=(basic_string&&);
1136                void operator=(int);
1137            };
1138            }
1139            "#,
1140        );
1141        let assignments = declarations
1142            .members
1143            .iter()
1144            .filter(|member| member.name == "operator=")
1145            .collect::<Vec<_>>();
1146        assert_eq!(assignments.len(), 3, "{declarations:#?}");
1147
1148        let copy = assignments
1149            .iter()
1150            .find(|member| member.signature.as_deref() == Some("(const basic_string &)"))
1151            .unwrap_or_else(|| panic!("copy assignment: {declarations:#?}"));
1152        let Some(StructuredTypeNodeView::Reference(inner)) = copy
1153            .return_type
1154            .as_ref()
1155            .and_then(|identity| identity.view(identity.root_id()))
1156        else {
1157            panic!("copy return type: {copy:#?}");
1158        };
1159        let Some(StructuredTypeNodeView::Named(name)) = copy
1160            .return_type
1161            .as_ref()
1162            .and_then(|identity| identity.view(inner))
1163        else {
1164            panic!("copy return target: {copy:#?}");
1165        };
1166        assert_eq!(["basic_string"], name.path());
1167        assert_eq!(["std", "basic_string"], name.lexical_scope());
1168
1169        let move_assignment = declarations
1170            .members
1171            .iter()
1172            .find(|member| member.signature.as_deref() == Some("(basic_string &&)"))
1173            .unwrap_or_else(|| panic!("move assignment: {declarations:#?}"));
1174        assert!(matches!(
1175            move_assignment
1176                .return_type
1177                .as_ref()
1178                .and_then(|identity| identity.view(identity.root_id())),
1179            Some(StructuredTypeNodeView::RvalueReference(_))
1180        ));
1181
1182        let void_assignment = declarations
1183            .members
1184            .iter()
1185            .find(|member| member.signature.as_deref() == Some("(int)"))
1186            .unwrap_or_else(|| panic!("void assignment: {declarations:#?}"));
1187        let Some(StructuredTypeNodeView::Named(name)) = void_assignment
1188            .return_type
1189            .as_ref()
1190            .and_then(|identity| identity.view(identity.root_id()))
1191        else {
1192            panic!("void return type: {void_assignment:#?}");
1193        };
1194        assert_eq!(["void"], name.path());
1195    }
1196
1197    #[test]
1198    fn preserves_type_alias_underlying_structured_identity() {
1199        let declarations = extract(
1200            r#"
1201            namespace std {
1202            template<class C, class Traits, class Alloc> class basic_string;
1203            using string = basic_string<char, char_traits<char>, allocator<char>>;
1204            }
1205            "#,
1206        );
1207        let alias = declarations
1208            .types
1209            .iter()
1210            .find(|record| record.name == "std.string")
1211            .unwrap_or_else(|| panic!("string alias: {declarations:#?}"));
1212        assert_eq!(CppExternalTypeKind::TypeAlias, alias.kind, "{alias:#?}");
1213        let identity = alias
1214            .underlying_type
1215            .as_ref()
1216            .unwrap_or_else(|| panic!("structured alias target: {alias:#?}"));
1217        let Some(StructuredTypeNodeView::Generic { base, arguments }) =
1218            identity.view(identity.root_id())
1219        else {
1220            panic!("generic basic_string alias target: {identity:#?}");
1221        };
1222        assert_eq!(3, arguments.len());
1223        let Some(StructuredTypeNodeView::Named(name)) = identity.view(base) else {
1224            panic!("named basic_string alias base: {identity:#?}");
1225        };
1226        assert_eq!(["basic_string"], name.path());
1227        assert_eq!(["std"], name.lexical_scope());
1228    }
1229
1230    #[test]
1231    fn keeps_same_short_names_under_distinct_owners() {
1232        let declarations = extract(
1233            "namespace first { class box { void add(int); }; }\nnamespace second { class box { void add(int); }; }",
1234        );
1235        let mut owners = declarations
1236            .members
1237            .iter()
1238            .filter(|member| member.name == "add")
1239            .filter_map(|member| member.owner.as_ref().map(|owner| owner.name().to_owned()))
1240            .collect::<Vec<_>>();
1241        owners.sort();
1242
1243        assert_eq!(vec!["first.box", "second.box"], owners);
1244        assert!(
1245            declarations
1246                .members
1247                .iter()
1248                .filter(|member| member.name == "add")
1249                .all(|member| member.visibility == CppExternalVisibility::Private)
1250        );
1251    }
1252
1253    #[test]
1254    fn nested_type_visibility_follows_the_enclosing_access_section() {
1255        let declarations = extract(
1256            "class Outer { class Hidden {}; public: struct Visible {}; protected: class Guarded {}; };",
1257        );
1258        assert!(declarations.types.iter().any(|record| {
1259            record.name == "Outer$Hidden" && record.visibility == CppExternalVisibility::Private
1260        }));
1261        assert!(declarations.types.iter().any(|record| {
1262            record.name == "Outer$Visible" && record.visibility == CppExternalVisibility::Public
1263        }));
1264        assert!(declarations.types.iter().any(|record| {
1265            record.name == "Outer$Guarded" && record.visibility == CppExternalVisibility::Protected
1266        }));
1267    }
1268
1269    #[test]
1270    fn preprocessor_and_record_limits_make_the_surface_partial() {
1271        let temp = tempfile::tempdir().expect("temp root");
1272        let declarations = extract_external_declarations(
1273            temp.path(),
1274            Path::new("limited.hpp"),
1275            "#ifdef FEATURE\nclass Conditional {};\n#endif\nclass Always {};",
1276            CppExternalDeclarationLimits { max_records: 1 },
1277        );
1278
1279        assert_eq!(
1280            CppExternalDeclarationCompleteness::Partial,
1281            declarations.completeness
1282        );
1283        assert!(
1284            declarations
1285                .diagnostics
1286                .iter()
1287                .any(|diagnostic| diagnostic.code == "cpp.external.preprocessor_partial")
1288        );
1289        assert!(
1290            declarations
1291                .diagnostics
1292                .iter()
1293                .any(|diagnostic| diagnostic.code == "cpp.external.record_limit")
1294        );
1295    }
1296}