Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3use crate::declarations::cpp_displaced_preprocessor_branch;
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7    CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
8    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
9    enclosing_cpp_namespace_nodes, macro_modifier_identifier, node_text, normalize_cpp_whitespace,
10    recovered_class_body_at, recovered_function_like_field_declarator,
11    recovered_pyobject_head_field,
12};
13use crate::graph::CppGraphSource;
14use crate::graph::extractor::ScanCtx;
15use crate::graph::syntax::{
16    function_macro_replacement_span, normalize_macro_continuations,
17    object_macro_replacement_type_references, qualified_name_parts,
18};
19use crate::graph_support::CppSource;
20use crate::imports::{
21    IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
22};
23use crate::source_context::cpp_guard_set_to_runtime;
24use crate::structural::is_cpp_preprocessor_conditional;
25pub use brokk_bifrost_core::analyzer::cpp_facts::{
26    CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
27};
28use brokk_bifrost_core::analyzer::cpp_facts::{
29    CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
30    CppStructuredAliasTarget as StructuredAliasTarget,
31};
32use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
33use brokk_bifrost_core::analyzer::model::{
34    CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
35    CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
36    SignatureMetadata, StructuredTypeName,
37};
38use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
39use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
40#[cfg(test)]
41use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
42use brokk_bifrost_core::analyzer::query_token::QueryToken;
43use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
44use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
45use brokk_bifrost_core::analyzer::tree_walk::{
46    ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
47    named_children_iter, node_for_exact_range, push_named_children_reversed,
48};
49use brokk_bifrost_core::analyzer::usages::common::same_node;
50use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
51use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
52use brokk_bifrost_core::cancellation::CancellationToken;
53use brokk_bifrost_core::hash::{HashMap, HashSet};
54#[cfg(test)]
55use brokk_bifrost_core::text_utils::compute_line_starts;
56use std::borrow::Cow;
57#[cfg(any(test, feature = "test-support"))]
58use std::cell::Cell;
59use std::cell::OnceCell;
60use std::cmp::Ordering as CmpOrdering;
61use std::collections::BTreeSet;
62use std::hash::Hash;
63use std::sync::atomic::{AtomicUsize, Ordering};
64use std::sync::{Arc, Mutex, OnceLock, RwLock};
65use std::time::{Duration, Instant};
66use tree_sitter::{Node, Parser, Tree};
67
68const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
69const CALLABLE_METADATA_CACHE_CAP: usize = 512;
70const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
71
72#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
73struct PreprocessorContextNodeKey {
74    id: usize,
75    start_byte: usize,
76    end_byte: usize,
77}
78
79/// Query-local memo for malformed-preprocessor boundary discovery.
80///
81/// The boundary is a property of one conditional AST node, but the resolver
82/// asks for it once per declaration below that node and once in each of the
83/// guard/family passes. Keeping this memo on the C++ visitor avoids repeating
84/// the bounded subtree scans without retaining anything after one translation
85/// unit is parsed. A full cache falls back to the uncached computation, so its
86/// memory is explicitly capped and its admission cannot affect answers.
87#[derive(Default)]
88pub(crate) struct PreprocessorContextCache {
89    displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
90    displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
91}
92
93/// The end byte of a [`cpp_displaced_preprocessor_branch`] directive and
94/// whether it is an `#else` (otherwise an `#elif` form).
95#[derive(Clone, Copy)]
96struct DisplacedBranchDirective {
97    end_byte: usize,
98    is_else: bool,
99}
100
101impl PreprocessorContextCache {
102    fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
103        let key = PreprocessorContextNodeKey {
104            id: conditional.id(),
105            start_byte: conditional.start_byte(),
106            end_byte: conditional.end_byte(),
107        };
108        if let Some(directive) = self.displaced_branches.get(&key) {
109            return *directive;
110        }
111        let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
112            DisplacedBranchDirective {
113                end_byte: directive.end_byte(),
114                is_else: directive.kind() == "#else",
115            }
116        });
117        if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
118            self.displaced_branches.insert(key, directive);
119        }
120        directive
121    }
122
123    fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
124        let key = PreprocessorContextNodeKey {
125            id: conditional.id(),
126            start_byte: conditional.start_byte(),
127            end_byte: conditional.end_byte(),
128        };
129        if let Some(boundary) = self.displaced_boundaries.get(&key) {
130            return *boundary;
131        }
132        let boundary =
133            cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
134        if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
135            self.displaced_boundaries.insert(key, boundary);
136        }
137        boundary
138    }
139}
140
141#[cfg(any(test, feature = "test-support"))]
142thread_local! {
143    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
144    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
145}
146
147#[derive(Clone, Copy, PartialEq, Eq)]
148pub enum TargetKind {
149    Type,
150    Constructor,
151    FreeFunction,
152    Method,
153    GlobalField,
154    MemberField,
155    Macro,
156}
157
158pub enum LexicalTypeResolution {
159    Resolved {
160        unit: CodeUnit,
161        components: Vec<String>,
162        candidates: Vec<CodeUnit>,
163    },
164    Ambiguous,
165    Missing,
166}
167
168#[derive(Clone, Copy)]
169enum TypeCandidateResolution<'a> {
170    Canonical,
171    PreserveAlias,
172    PreserveTarget(&'a CodeUnit),
173}
174
175/// Why a name did not reduce to one indexed type declaration.
176///
177/// The two answers are not interchangeable. `Ambiguous` means the index holds
178/// several declarations and the caller must choose; `Unresolvable` means the
179/// index holds none, which is a boundary the workspace cannot see past. A
180/// `using`/`typedef` alias to a template parameter or to a standard-library
181/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
182/// answer with an empty candidate list (#1828).
183#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184enum TypeCandidateFailure {
185    Ambiguous,
186    Unresolvable,
187}
188
189impl TypeCandidateFailure {
190    fn lexical_resolution(self) -> LexicalTypeResolution {
191        match self {
192            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
193            Self::Unresolvable => LexicalTypeResolution::Missing,
194        }
195    }
196}
197
198pub enum LexicalCallableValueResolution {
199    Type(CodeUnit),
200    FreeFunction(CodeUnit),
201    Ambiguous,
202    Missing,
203}
204
205pub enum UsingEnumMemberResolution {
206    Resolved { owner: CodeUnit, member: CodeUnit },
207    Ambiguous,
208    Missing,
209}
210
211pub enum NamespaceValueResolution {
212    Resolved,
213    Ambiguous,
214    Missing,
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub enum OrdinaryMacroReferenceResolution {
219    Resolved(CodeUnit),
220    Ambiguous,
221    Missing,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
225pub enum RecoveredCReferenceRanges {
226    Complete(Vec<Range>),
227    LimitExceeded,
228}
229
230pub fn resolve_namespace_value(
231    analyzer: &CppGraphSource<'_>,
232    visibility: &VisibilityIndex<'_>,
233    file: &ProjectFile,
234    namespace: &str,
235    name: &str,
236    before_byte: usize,
237) -> NamespaceValueResolution {
238    let mut matches = Vec::new();
239    for candidate in visibility.visible_identifier_candidates(file, name) {
240        if type_owner_of(analyzer, candidate).is_some()
241            || candidate.package_name() != namespace
242            || (candidate.source() == file
243                && !analyzer
244                    .ranges(candidate)
245                    .iter()
246                    .any(|range| range.start_byte < before_byte))
247            || matches
248                .iter()
249                .any(|existing| same_visible_symbol(existing, candidate))
250        {
251            continue;
252        }
253        matches.push(candidate.clone());
254        if matches.len() > 1 {
255            return NamespaceValueResolution::Ambiguous;
256        }
257    }
258    matches
259        .pop()
260        .map(|_| NamespaceValueResolution::Resolved)
261        .unwrap_or(NamespaceValueResolution::Missing)
262}
263
264pub(crate) struct ScopedUsingEnumOwners {
265    scopes: Vec<Vec<CodeUnit>>,
266}
267
268/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
269/// Cross-file and inherited class imports are deliberately not inferred without persisted
270/// evidence; a missing imported enumerator therefore remains unproven rather than being
271/// misresolved.
272pub(crate) struct SemanticUsingEnumOwners {
273    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
274    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
275}
276
277pub(crate) enum SemanticUsingEnumMemberResolution {
278    Class(UsingEnumMemberResolution),
279    Namespace(UsingEnumMemberResolution),
280    Missing,
281}
282
283impl SemanticUsingEnumOwners {
284    pub(crate) fn new() -> Self {
285        Self {
286            class_imports: HashMap::default(),
287            namespace_imports: HashMap::default(),
288        }
289    }
290
291    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
292        let imports = self.class_imports.entry(class).or_default();
293        if !imports
294            .iter()
295            .any(|existing| same_visible_symbol(existing, &enum_owner))
296        {
297            imports.push(enum_owner);
298        }
299    }
300
301    pub fn import_namespace(
302        &mut self,
303        namespace: Vec<String>,
304        declaration_byte: usize,
305        enum_owner: CodeUnit,
306    ) {
307        let imports = self.namespace_imports.entry(namespace).or_default();
308        if !imports
309            .iter()
310            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
311        {
312            imports.push((declaration_byte, enum_owner));
313        }
314    }
315
316    pub fn resolve_member(
317        &self,
318        visibility: &VisibilityIndex<'_>,
319        file: &ProjectFile,
320        class: Option<&CodeUnit>,
321        namespace: &[String],
322        before_byte: usize,
323        name: &str,
324    ) -> SemanticUsingEnumMemberResolution {
325        if let Some(class) = class
326            && let Some((_, imports)) = self
327                .class_imports
328                .iter()
329                .find(|(owner, _)| same_visible_symbol(owner, class))
330        {
331            let resolution =
332                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
333            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
334                return SemanticUsingEnumMemberResolution::Class(resolution);
335            }
336        }
337        for prefix_len in (0..=namespace.len()).rev() {
338            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
339                continue;
340            };
341            let owners = imports
342                .iter()
343                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
344                .map(|(_, owner)| owner);
345            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
346            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
347                return SemanticUsingEnumMemberResolution::Namespace(resolution);
348            }
349        }
350        SemanticUsingEnumMemberResolution::Missing
351    }
352}
353
354fn resolve_using_enum_member_for_owners<'a>(
355    visibility: &VisibilityIndex<'_>,
356    file: &ProjectFile,
357    owners: impl IntoIterator<Item = &'a CodeUnit>,
358    name: &str,
359) -> UsingEnumMemberResolution {
360    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
361    for owner in owners {
362        for member in visibility.visible_members_for_owner_name(file, owner, name) {
363            if !member.is_field()
364                || matches.iter().any(|(existing_owner, existing_member)| {
365                    same_visible_symbol(existing_owner, owner)
366                        && same_visible_symbol(existing_member, member)
367                })
368            {
369                continue;
370            }
371            matches.push((owner.clone(), member.clone()));
372        }
373    }
374    match matches.len() {
375        0 => UsingEnumMemberResolution::Missing,
376        1 => {
377            let (owner, member) = matches.pop().expect("one using-enum match");
378            UsingEnumMemberResolution::Resolved { owner, member }
379        }
380        _ => UsingEnumMemberResolution::Ambiguous,
381    }
382}
383
384impl ScopedUsingEnumOwners {
385    pub(crate) fn new() -> Self {
386        Self {
387            scopes: vec![Vec::new()],
388        }
389    }
390
391    pub fn enter_scope(&mut self) {
392        self.scopes.push(Vec::new());
393    }
394
395    pub fn exit_scope(&mut self) {
396        brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
397    }
398
399    pub fn import(&mut self, owner: CodeUnit) {
400        let scope = self
401            .scopes
402            .last_mut()
403            .expect("using-enum scope stack is never empty");
404        if !scope
405            .iter()
406            .any(|existing| same_visible_symbol(existing, &owner))
407        {
408            scope.push(owner);
409        }
410    }
411
412    pub fn resolve_member(
413        &self,
414        visibility: &VisibilityIndex<'_>,
415        file: &ProjectFile,
416        name: &str,
417    ) -> UsingEnumMemberResolution {
418        for scope in self.scopes.iter().rev() {
419            let resolution =
420                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
421            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
422                return resolution;
423            }
424        }
425        UsingEnumMemberResolution::Missing
426    }
427}
428
429#[derive(Clone)]
430pub struct TargetSpec {
431    pub target: CodeUnit,
432    pub kind: TargetKind,
433    pub owner: Option<CodeUnit>,
434    pub member_name: String,
435    pub callable_arity: Option<CallableArity>,
436    pub activated_callable_arities: Vec<ActivatedCallableArity>,
437    pub param_types: Option<Vec<String>>,
438    pub enum_owner_kind: EnumOwnerKind,
439    pub owner_is_forward_declaration: bool,
440    pub callable_has_definition_body: bool,
441}
442
443#[derive(Clone, Copy)]
444pub struct ActivatedCallableArity {
445    pub activation_byte: usize,
446    pub arity: CallableArity,
447}
448
449#[derive(Debug, PartialEq, Eq, Hash)]
450pub struct TypeScanKey {
451    target: LogicalSymbolKey,
452    member_name: String,
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Hash)]
456struct LogicalSymbolKey {
457    kind: CodeUnitType,
458    fq_name: String,
459    signature: Option<String>,
460}
461
462struct ResolvedTypeOwner {
463    unit: CodeUnit,
464    is_forward_declaration: bool,
465}
466
467impl TargetSpec {
468    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
469        (self.kind == TargetKind::Type).then(|| TypeScanKey {
470            target: logical_symbol_key(&self.target),
471            member_name: self.member_name.clone(),
472        })
473    }
474
475    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
476        if target.is_class() {
477            return Some(Self::new(
478                target.clone(),
479                TargetKind::Type,
480                Some(target.clone()),
481                target.identifier().to_string(),
482                None,
483                None,
484            ));
485        }
486
487        if target.is_field() {
488            // A namespace (module) is not a receiver: a namespace-scoped constant such as
489            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
490            // qualified from outside, exactly like a global. Treating a module owner as a
491            // member-field owner makes the receiver/owner-context match reject every valid
492            // reference, so resolve it as a global field instead.
493            let owner = type_owner_of(analyzer, target);
494            let kind = if owner.is_some() {
495                TargetKind::MemberField
496            } else {
497                TargetKind::GlobalField
498            };
499            let enum_owner_kind = owner
500                .as_ref()
501                .map(|owner| classify_enum_owner(analyzer, owner))
502                .unwrap_or(EnumOwnerKind::NonEnum);
503            let mut spec = Self::new(
504                target.clone(),
505                kind,
506                owner,
507                target.identifier().to_string(),
508                None,
509                None,
510            );
511            spec.enum_owner_kind = enum_owner_kind;
512            return Some(spec);
513        }
514
515        if target.is_function() {
516            // Free functions declared inside a namespace have a module owner; that namespace is
517            // not a call receiver, so resolve them as free functions rather than methods.
518            let owner_resolution = target_type_owner_resolution(analyzer, target);
519            let owner_is_forward_declaration = owner_resolution
520                .as_ref()
521                .is_some_and(|owner| owner.is_forward_declaration);
522            let owner = owner_resolution.map(|owner| owner.unit);
523            let kind = if owner.as_ref().is_some_and(|owner| {
524                target.identifier() == owner.identifier()
525                    || analyzer
526                        .cpp
527                        .and_then(|cpp| cpp.template_metadata(owner))
528                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
529            }) {
530                TargetKind::Constructor
531            } else if owner.is_some() {
532                TargetKind::Method
533            } else {
534                TargetKind::FreeFunction
535            };
536            let mut spec = Self::new(
537                target.clone(),
538                kind,
539                owner,
540                target.identifier().to_string(),
541                cpp_callable_arity(analyzer, target),
542                cpp_callable_parameter_types(analyzer, target),
543            );
544            spec.owner_is_forward_declaration = owner_is_forward_declaration;
545            spec.callable_has_definition_body =
546                callable_target_has_definition_body(analyzer, target);
547            return Some(spec);
548        }
549
550        if target.is_macro() {
551            return Some(Self::new(
552                target.clone(),
553                TargetKind::Macro,
554                None,
555                target.identifier().to_string(),
556                None,
557                None,
558            ));
559        }
560
561        None
562    }
563
564    pub fn with_visible_callable_arities<'a>(
565        &'a self,
566        analyzer: &CppGraphSource<'_>,
567        cpp: &dyn CppSource,
568        visibility: &VisibilityIndex<'_>,
569        file: &ProjectFile,
570        prepared: &PreparedSyntaxTree,
571    ) -> Cow<'a, Self> {
572        let macro_parameter_arity =
573            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
574        let activated_callable_arities =
575            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
576        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
577            return Cow::Borrowed(self);
578        }
579        let mut effective = self.clone();
580        if let Some(macro_parameter_arity) = macro_parameter_arity {
581            effective.callable_arity = Some(macro_parameter_arity);
582        }
583        effective.activated_callable_arities = activated_callable_arities;
584        Cow::Owned(effective)
585    }
586
587    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
588        let base = self.callable_arity?;
589        Some(
590            self.activated_callable_arities
591                .iter()
592                .filter(|candidate| candidate.activation_byte <= byte)
593                .fold(base, |arity, candidate| {
594                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
595                }),
596        )
597    }
598
599    pub fn new(
600        target: CodeUnit,
601        kind: TargetKind,
602        owner: Option<CodeUnit>,
603        member_name: String,
604        callable_arity: Option<CallableArity>,
605        param_types: Option<Vec<String>>,
606    ) -> Self {
607        Self {
608            target,
609            kind,
610            owner,
611            member_name,
612            callable_arity,
613            activated_callable_arities: Vec::new(),
614            param_types,
615            enum_owner_kind: EnumOwnerKind::NonEnum,
616            owner_is_forward_declaration: false,
617            callable_has_definition_body: false,
618        }
619    }
620}
621
622fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
623    analyzer
624        .signature_metadata(target)
625        .iter()
626        .any(|metadata| !metadata.is_declaration_only())
627}
628
629fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
630    LogicalSymbolKey {
631        kind: unit.kind(),
632        fq_name: unit.fq_name(),
633        signature: unit.signature().map(str::to_string),
634    }
635}
636
637fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
638    let Some(facts) = analyzer.declaration_source_properties(owner) else {
639        return EnumOwnerKind::NonEnum;
640    };
641    let Some(first) = facts.first() else {
642        return EnumOwnerKind::NonEnum;
643    };
644    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
645        first.enum_kind
646    } else {
647        EnumOwnerKind::NonEnum
648    }
649}
650
651#[derive(Clone, PartialEq, Eq, Hash)]
652pub struct CppScanBinding {
653    pub unit: Option<CodeUnit>,
654    pub type_name: Option<String>,
655    pub indirection: i32,
656}
657
658impl CppScanBinding {
659    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
660        Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
661    }
662
663    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
664        Self {
665            type_name: Some(type_name),
666            unit,
667            indirection,
668        }
669    }
670
671    pub fn as_arg_type(&self) -> Option<CppArgType> {
672        let name = self
673            .type_name
674            .clone()
675            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
676        Some(CppArgType {
677            name,
678            unit: self.unit.clone(),
679            indirection: self.indirection,
680            pointee_const: false,
681        })
682    }
683}
684
685type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
686pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
687pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
688type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
689type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
690type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
691type MacroLexicalTemplateCache =
692    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
693
694type MacroLocalBindingTemplateCache =
695    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
696type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
697type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
698type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
699
700struct StructuredIncludeFact {
701    start_byte: usize,
702    end_byte: usize,
703    path: String,
704}
705
706#[derive(Clone, Default)]
707pub struct MacroEnvironment {
708    bindings: HashMap<String, MacroBinding>,
709    known_undefined_names: HashSet<String>,
710    /// Names the translation unit's compile command proves defined (#2011):
711    /// the `-D`s that survive command ordering, intersected across every
712    /// configuration naming the TU. Seeded once at TU start. An explicit
713    /// `#undef` seen later lands in `known_undefined_names` and wins.
714    build_proven_defines: HashSet<String>,
715    unknown_names: bool,
716    applied_pragma_once_files: HashSet<ProjectFile>,
717    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
718}
719
720/// How many macro events one checkpoint window may cover.
721///
722/// A request for an environment replays only the events between the nearest
723/// earlier checkpoint and its own frontier, so one file's whole scan costs its
724/// event count (the checkpoint build) plus this many applications per request,
725/// whatever order the requests arrive in. The forward cursor this replaced was
726/// optimal for one worker reading one file in byte order and quadratic for the
727/// inverse, which asks many workers for positions that move backwards (#1496).
728pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
729
730/// One event prefix of a file whose environment the index keeps.
731struct MacroEnvironmentCheckpoint {
732    /// How many of the file's events this environment has applied.
733    frontier: usize,
734    environment: Arc<MacroEnvironment>,
735}
736
737/// The replay checkpoints for one file's macro events, ascending by frontier
738/// and always starting at frontier zero (the compile-proven defines alone).
739///
740/// Checkpoints use an adaptive stride no greater than
741/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
742/// `#include` event. Applying an include event replays the included file's
743/// complete event list, so keeping one there holds that unbounded cost out of
744/// every later replay window.
745struct MacroEnvironmentCheckpoints {
746    checkpoints: Vec<MacroEnvironmentCheckpoint>,
747}
748
749impl MacroEnvironmentCheckpoints {
750    /// The latest checkpoint at or before `frontier`.
751    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
752        let index = self
753            .checkpoints
754            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
755        assert!(
756            index > 0,
757            "a checkpoint vector starts at frontier zero, which precedes every request"
758        );
759        &self.checkpoints[index - 1]
760    }
761}
762
763fn clone_or_default_file_cell<T: Default + Clone>(
764    cells: &Mutex<HashMap<ProjectFile, T>>,
765    file: &ProjectFile,
766    poison_message: &str,
767) -> T {
768    cells
769        .lock()
770        .expect(poison_message)
771        .entry(file.clone())
772        .or_default()
773        .clone()
774}
775
776impl MacroEnvironment {
777    fn binding(&self, name: &str) -> Option<&MacroBinding> {
778        self.bindings.get(name)
779    }
780
781    fn may_bind(&self, name: &str) -> bool {
782        self.bindings.contains_key(name) || self.unknown_names
783    }
784
785    fn insert(&mut self, name: String, binding: MacroBinding) {
786        self.known_undefined_names.remove(&name);
787        self.bindings.insert(name, binding);
788    }
789
790    fn remove(&mut self, name: &str) {
791        self.bindings.remove(name);
792        self.known_undefined_names.insert(name.to_string());
793    }
794
795    fn remove_known_undefined(&mut self, name: &str) {
796        self.known_undefined_names.remove(name);
797    }
798
799    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
800        for binding in self.bindings.values_mut() {
801            *binding = MacroBinding::uncertain_from(binding, source, byte);
802        }
803        self.known_undefined_names.clear();
804        // An untracked include could `#undef` a command-line define, so the
805        // may-hold filter must stop treating the build facts as decisive from
806        // here on. The additive proof path keeps its facts: they still hold at
807        // the include chain's activation point.
808        self.build_proven_defines.clear();
809        self.unknown_names = true;
810    }
811
812    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
813        guards.iter().all(|guard| self.guard_may_hold(guard))
814    }
815
816    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
817        let Some(expression) = guard.as_boolean_expression() else {
818            return true;
819        };
820        self.boolean_guard_may_hold(&expression)
821    }
822
823    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
824        match expression {
825            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
826            BooleanGuardExpression::Undefined(name) => {
827                self.bindings
828                    .get(name)
829                    .is_none_or(|binding| !binding.is_exact())
830                    && (!self.build_proven_defines.contains(name)
831                        || self.known_undefined_names.contains(name))
832            }
833            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
834            BooleanGuardExpression::Opaque(_)
835            | BooleanGuardExpression::NegatedOpaque(_)
836            | BooleanGuardExpression::Constant(true) => true,
837            BooleanGuardExpression::Constant(false) => false,
838            BooleanGuardExpression::All(expressions) => expressions
839                .iter()
840                .all(|expression| self.boolean_guard_may_hold(expression)),
841            BooleanGuardExpression::Any(expressions) => expressions
842                .iter()
843                .any(|expression| self.boolean_guard_may_hold(expression)),
844        }
845    }
846}
847
848fn any_visible_source_file_matches(
849    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
850    file: &ProjectFile,
851    mut predicate: impl FnMut(&ProjectFile) -> bool,
852) -> bool {
853    match visible_source_files_by_root.get(file) {
854        Some(files) => files.iter().any(predicate),
855        None => predicate(file),
856    }
857}
858
859#[derive(Clone)]
860pub enum EffectiveUsingTarget {
861    Ordinary {
862        name: String,
863        target_components: Vec<String>,
864        global: bool,
865    },
866    Namespace {
867        namespace_components: Vec<String>,
868        global: bool,
869    },
870}
871
872#[derive(Clone)]
873pub struct OrdinaryTypeImport {
874    pub target: EffectiveUsingTarget,
875    pub source: ProjectFile,
876    pub declaration_byte: usize,
877    pub scope_start: usize,
878    pub scope_end: usize,
879    pub scope_depth: usize,
880    pub block_scope: bool,
881    pub lexical_depth: usize,
882    pub declaration_namespace: Vec<String>,
883    pub namespace_scope: Option<Vec<String>>,
884    pub resolved_target_components: Option<Vec<String>>,
885    pub required_guards: HashSet<PreprocessorGuard>,
886}
887
888#[derive(Clone)]
889pub struct ConditionalIncludeProjection {
890    pub activation_byte: usize,
891    pub required_guards: HashSet<PreprocessorGuard>,
892    /// The subset of `required_guards` a lone `#if` contributes: a branch no
893    /// sibling `#else` completes, so no configuration is obliged to take it.
894    /// [`IncludePathAdmission::Compatible`] still demands those.
895    pub partial_guards: HashSet<PreprocessorGuard>,
896}
897
898#[derive(Default)]
899pub struct SourceUsingIndex {
900    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
901    pub directives: Vec<OrdinaryTypeImport>,
902}
903
904#[derive(Default)]
905pub struct ProjectUsingIndex {
906    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
907    pub directives: Vec<OrdinaryTypeImport>,
908}
909
910type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
911
912pub struct EffectiveUsingIndex {
913    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
914}
915
916impl EffectiveUsingIndex {
917    fn new(_root: ProjectFile) -> Self {
918        Self {
919            projected_by_name: Mutex::new(HashMap::default()),
920        }
921    }
922
923    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
924        self.projected_by_name
925            .lock()
926            .expect("C++ effective-using projection cache poisoned")
927            .entry(name.to_string())
928            .or_default()
929            .clone()
930    }
931}
932
933pub enum OrdinaryTypeImportResolution {
934    Resolved {
935        target: CodeUnit,
936        target_components: Vec<String>,
937        lexical_depth: usize,
938        is_direct: bool,
939    },
940    Ambiguous {
941        lexical_depth: usize,
942    },
943    Missing,
944}
945
946type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
947type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
948type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
949type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
950type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
951type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
952type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
953type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
954
955/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
956/// read from its declaration syntax rather than from its persisted signature
957/// string: the comparable shape of each parameter, and the trailing identity
958/// suffix that shape does not carry.
959struct ExtractedComparable {
960    shapes: Vec<CppComparableSlot>,
961    suffix: String,
962}
963
964/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
965/// before giving up on a written type name. A visited set already stops a
966/// cycle; this stops an adversarially long chain from costing a lookup per hop.
967const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
968
969/// Which comparison a conditional `#include` path's guards face before the
970/// header it reaches counts as visible at a reference.
971///
972/// `Proven` is the build's rule: `compile_commands.json` covers the
973/// reference's translation unit, so the reference's active guards plus the
974/// defines the build proves must imply every guard on the path, and a
975/// platform macro the build does not prove keeps the header invisible
976/// (#2011).
977///
978/// `Compatible` is the rule for a translation unit no build describes. A
979/// complete `#if`/`#else` family is a case analysis every configuration takes
980/// one branch of, so a branch of one is admitted unless its guards contradict
981/// the reference's own -- the rule the resolver already applies to a
982/// declaration's own guards (#2988). A lone `#if` is a different claim: no
983/// configuration is obliged to take it, so its guards stay strictly required
984/// and an unprovable one still reports `missing_compile_context` (#2011).
985///
986/// Without the first half, libuv's `uv/threadpool.h`, which `uv.h` includes
987/// from both arms of `#if defined(_WIN32)`, and `uv/unix.h`, which it includes
988/// from the `#else` arm, are invisible to every unguarded reference in the
989/// project (#3088).
990#[derive(Clone, Copy, Debug, PartialEq, Eq)]
991enum IncludePathAdmission {
992    Proven,
993    Compatible,
994}
995
996impl IncludePathAdmission {
997    fn admits(
998        self,
999        required: &HashSet<PreprocessorGuard>,
1000        partial: &HashSet<PreprocessorGuard>,
1001        reference_guards: Option<&HashSet<PreprocessorGuard>>,
1002    ) -> bool {
1003        match self {
1004            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1005            Self::Compatible => {
1006                guard_requirements_hold_at_reference(partial, reference_guards)
1007                    && guards_compatible_at_reference(required, reference_guards)
1008            }
1009        }
1010    }
1011}
1012
1013/// Per-query C++ visibility facts with a borrowed source and scope proof.
1014/// Cache only `data()` across calls, and reattach the current source and token
1015/// with `from_data`; cloning the analyzer would lose its query read cache.
1016pub struct VisibilityIndex<'a> {
1017    cpp: &'a dyn CppSource,
1018    token: QueryToken<'a>,
1019    data: Arc<VisibilityIndexData>,
1020}
1021
1022impl std::ops::Deref for VisibilityIndex<'_> {
1023    type Target = VisibilityIndexData;
1024
1025    fn deref(&self) -> &Self::Target {
1026        &self.data
1027    }
1028}
1029
1030/// Owned visibility facts. They retain no source or request-scope borrow.
1031/// The caller keeps these facts inside the query that built them.
1032pub struct VisibilityIndexData {
1033    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1034    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1035    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1036    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1037    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1038    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1039    parser_alias_target_matches:
1040        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1041    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1042    project_using_index: OnceLock<ProjectUsingIndex>,
1043    callable_reference_specs:
1044        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1045    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1046    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1047    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1048    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1049    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1050    #[cfg(any(test, feature = "test-support"))]
1051    conditional_include_projection_index_build_count: AtomicUsize,
1052    #[cfg(any(test, feature = "test-support"))]
1053    conditional_include_projection_state_count: AtomicUsize,
1054    #[cfg(any(test, feature = "test-support"))]
1055    conditional_include_target_state_count: AtomicUsize,
1056    #[cfg(any(test, feature = "test-support"))]
1057    include_activation_build_count: AtomicUsize,
1058    #[cfg(any(test, feature = "test-support"))]
1059    using_donor_activation_count: AtomicUsize,
1060    #[cfg(any(test, feature = "test-support"))]
1061    using_namespace_lookup_count: AtomicUsize,
1062    #[cfg(any(test, feature = "test-support"))]
1063    using_name_candidate_inspection_count: AtomicUsize,
1064    #[cfg(any(test, feature = "test-support"))]
1065    callable_reference_spec_build_count: AtomicUsize,
1066    #[cfg(any(test, feature = "test-support"))]
1067    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1068    #[cfg(any(test, feature = "test-support"))]
1069    visible_parser_alias_name_set_build_count: AtomicUsize,
1070    parser_alias_fallback_calls: AtomicUsize,
1071    parser_alias_fallback_files: AtomicUsize,
1072    parser_alias_source_parses: AtomicUsize,
1073    parser_alias_fallback_elapsed_micros: AtomicUsize,
1074    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1075    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1076    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1077    /// Complete callable metadata is expensive for reconciled C++ members:
1078    /// each read can rebuild the declaration/definition evidence. Reuse it
1079    /// across the member, arity, identity, and navigation phases of this
1080    /// visibility query, with an explicit cap so the query never retains a
1081    /// workspace-sized unit table.
1082    callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1083    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1084    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1085    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1086    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1087    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1088    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1089    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1090    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1091    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1092    // The environment at selected event prefixes of each file, built once and read by every
1093    // worker. The authoritative differential shares this index across target workers whose
1094    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1095    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1096    // with a binary search and at most one stride of replay, and being immutable they need no
1097    // per-worker copy (#1496).
1098    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1099    macro_replacements: Mutex<MacroReplacementCache>,
1100    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1101    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1102    macro_lexical_indexes:
1103        Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1104    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1105    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1106    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1107    #[cfg(any(test, feature = "test-support"))]
1108    pub macro_replacement_parse_count: AtomicUsize,
1109    #[cfg(any(test, feature = "test-support"))]
1110    pub macro_event_application_count: AtomicUsize,
1111    /// How many files had their checkpoint vector built. One build per file
1112    /// per query even when workers race for the same file.
1113    #[cfg(any(test, feature = "test-support"))]
1114    pub macro_environment_checkpoint_build_count: AtomicUsize,
1115    /// How many requests landed off a checkpoint and so had to copy one and
1116    /// replay the events after it.
1117    #[cfg(any(test, feature = "test-support"))]
1118    pub macro_environment_copy_count: AtomicUsize,
1119    #[cfg(any(test, feature = "test-support"))]
1120    pub macro_environment_request_count: AtomicUsize,
1121    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1122    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1123    #[cfg(any(test, feature = "test-support"))]
1124    qualified_candidate_inspections: AtomicUsize,
1125    #[cfg(any(test, feature = "test-support"))]
1126    target_preserving_type_resolution_count: AtomicUsize,
1127    #[cfg(any(test, feature = "test-support"))]
1128    visibility_identifier_lookup_count: usize,
1129    #[cfg(any(test, feature = "test-support"))]
1130    visibility_identifier_batch_count: usize,
1131}
1132
1133impl Drop for VisibilityIndexData {
1134    fn drop(&mut self) {
1135        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1136            return;
1137        }
1138        #[cfg(any(test, feature = "test-support"))]
1139        eprintln!(
1140            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1141            self.macro_environment_request_count.load(Ordering::Relaxed),
1142            self.macro_environment_copy_count.load(Ordering::Relaxed),
1143            self.macro_environment_checkpoint_build_count
1144                .load(Ordering::Relaxed),
1145            self.macro_event_application_count.load(Ordering::Relaxed),
1146        );
1147        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1148        if calls == 0 {
1149            return;
1150        }
1151        eprintln!(
1152            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1153            calls,
1154            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1155            self.parser_alias_source_parses.load(Ordering::Relaxed),
1156            self.parser_alias_fallback_elapsed_micros
1157                .load(Ordering::Relaxed)
1158                / 1_000,
1159        );
1160    }
1161}
1162
1163#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1164pub enum PreprocessorGuard {
1165    Defined(String),
1166    Undefined(String),
1167    Boolean(BooleanGuardExpression),
1168    Expression(String),
1169    NegatedExpression(String),
1170    Constant(bool),
1171}
1172
1173#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1174pub enum BooleanGuardExpression {
1175    Defined(String),
1176    Undefined(String),
1177    Truthy(String),
1178    Falsy(String),
1179    Opaque(String),
1180    NegatedOpaque(String),
1181    All(Vec<BooleanGuardExpression>),
1182    Any(Vec<BooleanGuardExpression>),
1183    Constant(bool),
1184}
1185
1186impl BooleanGuardExpression {
1187    fn negated(&self) -> Self {
1188        match self {
1189            Self::Defined(name) => Self::Undefined(name.clone()),
1190            Self::Undefined(name) => Self::Defined(name.clone()),
1191            Self::Truthy(name) => Self::Falsy(name.clone()),
1192            Self::Falsy(name) => Self::Truthy(name.clone()),
1193            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1194            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1195            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1196            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1197            Self::Constant(value) => Self::Constant(!value),
1198        }
1199    }
1200
1201    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1202        Self::normalized(expressions, true)
1203    }
1204
1205    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1206        Self::normalized(expressions, false)
1207    }
1208
1209    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1210        let mut normalized = Vec::new();
1211        for expression in expressions {
1212            match expression {
1213                Self::All(nested) if conjunction => normalized.extend(nested),
1214                Self::Any(nested) if !conjunction => normalized.extend(nested),
1215                Self::Constant(value) if value == conjunction => {}
1216                Self::Constant(value) => return Self::Constant(value),
1217                expression => normalized.push(expression),
1218            }
1219        }
1220        normalized.sort_unstable();
1221        normalized.dedup();
1222        match normalized.len() {
1223            0 => Self::Constant(conjunction),
1224            1 => normalized.pop().expect("one Boolean guard expression"),
1225            _ if conjunction => Self::All(normalized),
1226            _ => Self::Any(normalized),
1227        }
1228    }
1229
1230    fn implies(&self, required: &Self) -> bool {
1231        if self == required
1232            || matches!(self, Self::Constant(false))
1233            || matches!(required, Self::Constant(true))
1234        {
1235            return true;
1236        }
1237        if matches!(
1238            (self, required),
1239            (Self::Truthy(active), Self::Defined(required))
1240                | (Self::Undefined(active), Self::Falsy(required))
1241                if active == required
1242        ) {
1243            return true;
1244        }
1245        match self {
1246            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1247            Self::All(active) => match required {
1248                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1249                _ => active.iter().any(|expression| expression.implies(required)),
1250            },
1251            _ => match required {
1252                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1253                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1254                _ => false,
1255            },
1256        }
1257    }
1258
1259    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1260        match self {
1261            Self::Defined(name)
1262            | Self::Undefined(name)
1263            | Self::Truthy(name)
1264            | Self::Falsy(name) => name == macro_name,
1265            // Opaque expressions have structured conditional ownership but no
1266            // structured macro operands, so any mutation may change them.
1267            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1268            Self::All(expressions) | Self::Any(expressions) => expressions
1269                .iter()
1270                .any(|expression| expression.may_depend_on_macro(macro_name)),
1271            Self::Constant(_) => false,
1272        }
1273    }
1274
1275    pub fn heap_size(&self) -> usize {
1276        match self {
1277            Self::Defined(value)
1278            | Self::Undefined(value)
1279            | Self::Truthy(value)
1280            | Self::Falsy(value)
1281            | Self::Opaque(value)
1282            | Self::NegatedOpaque(value) => value.len(),
1283            Self::All(expressions) | Self::Any(expressions) => {
1284                expressions
1285                    .iter()
1286                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1287                        size.saturating_add(std::mem::size_of::<Self>())
1288                            .saturating_add(expression.heap_size())
1289                    })
1290            }
1291            Self::Constant(_) => 0,
1292        }
1293    }
1294}
1295
1296impl PreprocessorGuard {
1297    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1298        match self {
1299            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1300            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1301            Self::Boolean(expression) => Some(expression.clone()),
1302            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1303            Self::Expression(_) | Self::NegatedExpression(_) => None,
1304        }
1305    }
1306
1307    fn negated(&self) -> Self {
1308        match self {
1309            Self::Defined(name) => Self::Undefined(name.clone()),
1310            Self::Undefined(name) => Self::Defined(name.clone()),
1311            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1312            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1313            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1314            Self::Constant(value) => Self::Constant(!value),
1315        }
1316    }
1317
1318    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1319        match self {
1320            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1321            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1322            // These expressions could not be lowered to a Boolean operand
1323            // tree, so their dependencies remain unknown.
1324            Self::Expression(_) | Self::NegatedExpression(_) => true,
1325            Self::Constant(_) => false,
1326        }
1327    }
1328}
1329
1330#[derive(Clone, PartialEq, Eq)]
1331pub enum MacroDefinition {
1332    Object {
1333        replacement: String,
1334    },
1335    Function {
1336        parameters: Vec<String>,
1337        replacement: String,
1338    },
1339    VariadicFunction {
1340        parameters: Vec<String>,
1341        replacement: String,
1342    },
1343    Unsupported,
1344}
1345
1346#[derive(Clone, Debug, PartialEq, Eq)]
1347pub enum MacroIncludeProtection {
1348    MacroGuard(String),
1349    PragmaOnce,
1350    None,
1351}
1352
1353enum ParsedMacroReplacement {
1354    Parsed { source: String, tree: Tree },
1355    Unsupported,
1356}
1357
1358/// The sentinel that gives a function-like macro replacement a parseable
1359/// statement context. The replacement text is copied in verbatim, so the only
1360/// bytes ahead of it are this prefix.
1361const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1362
1363/// A function-like macro replacement parsed inside a sentinel function body.
1364///
1365/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1366/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1367/// its statements, declarations, and member calls as ordinary C++ structure.
1368/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1369/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1370/// that offset.
1371pub struct ParsedReplacementBody {
1372    pub source: String,
1373    pub tree: Tree,
1374    pub body_offset: usize,
1375    pub parameters: Vec<String>,
1376    /// For a normal parse this is an identity map. A constrained statement
1377    /// recovery may insert separators after a macro formal; each boundary in
1378    /// this map points back to the corresponding byte in the original
1379    /// replacement, keeping source-backed ranges exact after substitution.
1380    original_offsets: Box<[usize]>,
1381}
1382
1383impl ParsedReplacementBody {
1384    /// The sentinel function body holding the replacement's statements.
1385    pub fn statements(&self) -> Option<Node<'_>> {
1386        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1387            .child_by_field_name("body")
1388    }
1389
1390    /// The byte range `node` occupies in the file that defines the macro.
1391    ///
1392    /// `replacement_start` is the logical replacement span's start byte.
1393    /// The origin map accounts for continuation whitespace and any synthetic
1394    /// statement separators inserted while parsing the replacement.
1395    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1396        assert!(
1397            node.start_byte() >= self.body_offset,
1398            "synthetic sentinel node cannot be mapped to a macro replacement"
1399        );
1400        assert!(
1401            node.end_byte() >= self.body_offset,
1402            "synthetic sentinel node cannot be mapped to a macro replacement"
1403        );
1404        let start_offset = node.start_byte() - self.body_offset;
1405        let end_offset = node.end_byte() - self.body_offset;
1406        assert!(start_offset <= end_offset);
1407        let start_origin = *self
1408            .original_offsets
1409            .get(start_offset)
1410            .expect("replacement node start must have a source mapping");
1411        let end_origin = *self
1412            .original_offsets
1413            .get(end_offset)
1414            .expect("replacement node end must have a source mapping");
1415        assert!(start_origin <= end_origin);
1416        let start = replacement_start + start_origin;
1417        let end = replacement_start + end_origin;
1418        start..end
1419    }
1420
1421    /// Whether the replacement names the variadic argument pack.
1422    ///
1423    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1424    /// gives no error for it even though the expansion it stands for is
1425    /// unknown at the definition. Reject it from the parsed tree rather than
1426    /// by scanning the replacement text.
1427    fn expands_variadic_arguments(&self) -> bool {
1428        let mut stack = vec![self.tree.root_node()];
1429        while let Some(node) = stack.pop() {
1430            if matches!(
1431                node.kind(),
1432                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1433            ) && node_text(node, &self.source) == "__VA_ARGS__"
1434            {
1435                return true;
1436            }
1437            push_named_children_reversed(node, &mut stack);
1438        }
1439        false
1440    }
1441}
1442
1443/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1444pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1445    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1446    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1447        if compact.starts_with("0x") || compact.starts_with("0X") {
1448            (16, 2, |ch| ch.is_ascii_hexdigit())
1449        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1450            (2, 2, |ch| matches!(ch, '0' | '1'))
1451        } else if compact.starts_with('0') && compact.len() > 1 {
1452            (8, 0, |ch| matches!(ch, '0'..='7'))
1453        } else {
1454            (10, 0, |ch| ch.is_ascii_digit())
1455        };
1456    let digit_len = compact[digits_start..]
1457        .chars()
1458        .take_while(|ch| digit_matches(*ch))
1459        .map(char::len_utf8)
1460        .sum::<usize>();
1461    if digit_len == 0 {
1462        return None;
1463    }
1464    let digits_end = digits_start + digit_len;
1465    if !compact[digits_end..]
1466        .chars()
1467        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1468    {
1469        return None;
1470    }
1471    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1472}
1473
1474#[derive(Clone)]
1475enum MacroLocalBindingTypeTemplate {
1476    Parameter(usize),
1477    Fixed(String),
1478}
1479
1480#[derive(Clone)]
1481struct MacroLocalBindingTemplate {
1482    name: String,
1483    declared_type: MacroLocalBindingTypeTemplate,
1484    pointer_depth: i32,
1485}
1486
1487/// A local declaration contributed by one structurally known function-like macro.
1488///
1489/// `type_node` points into the invocation syntax when the replacement's type
1490/// is one of the macro parameters. Consumers can therefore use their normal
1491/// lexical type resolver without parsing replacement text themselves.
1492/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1493/// an explicit `struct` or `union` argument across recovery nodes.
1494pub struct MacroLocalBinding<'tree> {
1495    pub name: String,
1496    pub type_name: String,
1497    pub type_node: Option<Node<'tree>>,
1498    pub pointer_depth: i32,
1499    pub proven_unit: Option<CodeUnit>,
1500}
1501
1502/// The source-backed declaration introduced by a function-like macro. The
1503/// declaration belongs to the macro definition, while the reference range
1504/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1505/// definition body, invocation argument, or caller scope where substitution
1506/// makes the binding visible.
1507#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1508pub enum MacroLexicalBindingKind {
1509    Parameter,
1510    Local,
1511}
1512
1513#[derive(Clone, Debug, PartialEq, Eq)]
1514pub struct MacroLexicalBinding {
1515    pub definition: ProjectFile,
1516    pub kind: MacroLexicalBindingKind,
1517    pub name: String,
1518    pub name_range: std::ops::Range<usize>,
1519    pub declaration_range: std::ops::Range<usize>,
1520}
1521
1522/// Lexical macro references are query-local facts. A cancelled or bounded
1523/// walk is kept distinct from a complete empty enumeration so authoritative
1524/// inverse callers can preserve their completeness status.
1525#[derive(Clone, Debug, Default, PartialEq, Eq)]
1526pub struct MacroLexicalReferences {
1527    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1528    pub truncated: bool,
1529    pub cancelled: bool,
1530}
1531
1532fn macro_replacement_type_parameters(
1533    body: &ParsedReplacementBody,
1534    parameters: &[String],
1535) -> Option<Vec<usize>> {
1536    let mut found = Vec::new();
1537    let mut stack = vec![body.tree.root_node()];
1538    while let Some(node) = stack.pop() {
1539        let type_position = node.kind() == "type_identifier"
1540            || (node.kind() == "identifier"
1541                && node.parent().is_some_and(|parent| {
1542                    parent.kind() == "type_descriptor"
1543                        && parent.child_by_field_name("type") == Some(node)
1544                }));
1545        let offsetof_type_position = node.kind() == "identifier"
1546            && node
1547                .parent()
1548                .filter(|parent| parent.kind() == "argument_list")
1549                .and_then(|arguments| arguments.parent())
1550                .is_some_and(|call| {
1551                    call.kind() == "call_expression"
1552                        && call
1553                            .child_by_field_name("function")
1554                            .is_some_and(|function| {
1555                                function.kind() == "identifier"
1556                                    && node_text(function, &body.source) == "offsetof"
1557                            })
1558                        && call
1559                            .child_by_field_name("arguments")
1560                            .is_some_and(|arguments| {
1561                                argument_children(arguments).next() == Some(node)
1562                            })
1563                });
1564        if (type_position || offsetof_type_position)
1565            && let Some(index) = parameters
1566                .iter()
1567                .position(|parameter| parameter == node_text(node, &body.source))
1568            && !found.contains(&index)
1569        {
1570            found.push(index);
1571        }
1572        push_named_children_reversed(node, &mut stack);
1573    }
1574    (!found.is_empty()).then_some(found)
1575}
1576
1577fn macro_replacement_type_parameter(
1578    body: &ParsedReplacementBody,
1579    parameters: &[String],
1580) -> Option<usize> {
1581    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1582    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1583}
1584
1585pub(crate) fn macro_type_argument_node<'tree>(
1586    node: Node<'tree>,
1587    source: &str,
1588) -> Option<Node<'tree>> {
1589    match node.kind() {
1590        "type_descriptor" => {
1591            let type_child = node
1592                .child_by_field_name("type")
1593                .or_else(|| first_type_child(node))?;
1594            for index in (0..node.named_child_count()).rev() {
1595                let child = node.named_child(index)?;
1596                if child != type_child
1597                    && matches!(
1598                        child.kind(),
1599                        "identifier"
1600                            | "type_identifier"
1601                            | "qualified_identifier"
1602                            | "scoped_type_identifier"
1603                    )
1604                {
1605                    return Some(child);
1606                }
1607            }
1608            macro_type_argument_node(type_child, source)
1609        }
1610        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1611            node.child_by_field_name("name")
1612        }
1613        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1614            .next_named_sibling()
1615            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1616            .and_then(|error| error.named_child(0))
1617            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1618        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1619    }
1620}
1621
1622fn c_function_macro_argument<'tree>(
1623    node: Node<'tree>,
1624) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1625    let mut current = node;
1626    while let Some(parent) = current.parent() {
1627        if parent.kind() == "argument_list" {
1628            let call = parent.parent().filter(|call| {
1629                call.kind() == "call_expression"
1630                    && call.child_by_field_name("arguments") == Some(parent)
1631                    && call
1632                        .child_by_field_name("function")
1633                        .is_some_and(|function| {
1634                            function.kind() == "identifier"
1635                                && !node_range_contains(function, current)
1636                        })
1637            })?;
1638            let mut actuals = argument_children(parent).enumerate();
1639            let (index, argument) = actuals.find(|(_, argument)| {
1640                node_range_contains(*argument, node)
1641                    && (argument.start_byte() == current.start_byte()
1642                        || argument.end_byte() == current.end_byte())
1643            })?;
1644            return Some((call, index, argument));
1645        }
1646        current = parent;
1647    }
1648    None
1649}
1650
1651/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1652/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1653/// invocation as the assignment's left operand and the declared name as one
1654/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1655/// The macro-declaration grammar reads the same line as a clean declaration
1656/// whose type is a `macro_type_specifier`; the `g_autoptr` name gate is what
1657/// keeps an arbitrary function-like type macro from becoming a binding.
1658fn recognized_c_macro_declarator_binding<'tree>(
1659    statement: Node<'tree>,
1660    source: &str,
1661) -> Option<MacroLocalBinding<'tree>> {
1662    if statement.kind() == "declaration" {
1663        let specifier = statement
1664            .child_by_field_name("type")
1665            .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1666        let macro_name = specifier
1667            .child_by_field_name("name")
1668            .filter(|name| name.kind() == "identifier")?;
1669        if node_text(macro_name, source) != "g_autoptr" {
1670            return None;
1671        }
1672        let type_node = specifier
1673            .child_by_field_name("type")
1674            .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1675        let declarator = statement
1676            .child_by_field_name("declarator")
1677            .filter(|declarator| declarator.kind() == "init_declarator")?;
1678        let name_node = declarator
1679            .child_by_field_name("declarator")
1680            .filter(|name| name.kind() == "identifier")?;
1681        declarator.child_by_field_name("value")?;
1682        let name = node_text(name_node, source).trim();
1683        let type_name = node_text(type_node, source).trim();
1684        if name.is_empty() || type_name.is_empty() {
1685            return None;
1686        }
1687        return Some(MacroLocalBinding {
1688            name: name.to_string(),
1689            type_name: type_name.to_string(),
1690            type_node: Some(type_node),
1691            pointer_depth: 1,
1692            proven_unit: None,
1693        });
1694    }
1695    let assignment = match statement.kind() {
1696        "assignment_expression" => statement,
1697        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1698        _ => return None,
1699    };
1700    if assignment.kind() != "assignment_expression" {
1701        return None;
1702    }
1703    let call = assignment.child_by_field_name("left")?;
1704    if call.kind() != "call_expression" {
1705        return None;
1706    }
1707    let function = call.child_by_field_name("function")?;
1708    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1709        return None;
1710    }
1711    let arguments = call.child_by_field_name("arguments")?;
1712    let mut actuals = argument_children(arguments);
1713    let type_node = actuals.next()?;
1714    if actuals.next().is_some()
1715        || !matches!(
1716            type_node.kind(),
1717            "identifier"
1718                | "type_identifier"
1719                | "qualified_identifier"
1720                | "scoped_type_identifier"
1721                | "template_type"
1722        )
1723    {
1724        return None;
1725    }
1726    let name_node = (0..assignment.named_child_count())
1727        .filter_map(|index| assignment.named_child(index))
1728        .filter(|child| child.kind() == "ERROR")
1729        .filter_map(|error| {
1730            (error.named_child_count() == 1)
1731                .then(|| error.named_child(0))
1732                .flatten()
1733        })
1734        .find(|node| node.kind() == "identifier")?;
1735    let name = node_text(name_node, source).trim();
1736    let type_name = node_text(type_node, source).trim();
1737    if name.is_empty() || type_name.is_empty() {
1738        return None;
1739    }
1740    Some(MacroLocalBinding {
1741        name: name.to_string(),
1742        type_name: type_name.to_string(),
1743        type_node: Some(type_node),
1744        pointer_depth: 1,
1745        proven_unit: None,
1746    })
1747}
1748
1749#[derive(Clone, PartialEq, Eq)]
1750pub struct MacroBinding {
1751    source: ProjectFile,
1752    declaration_byte: usize,
1753    definition: MacroDefinition,
1754    exact: bool,
1755}
1756
1757impl MacroBinding {
1758    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1759        Self {
1760            source: source.clone(),
1761            declaration_byte,
1762            definition: MacroDefinition::Unsupported,
1763            exact: false,
1764        }
1765    }
1766
1767    fn is_exact(&self) -> bool {
1768        self.exact
1769    }
1770
1771    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1772        Self {
1773            source: source.clone(),
1774            declaration_byte,
1775            definition: current.definition.clone(),
1776            exact: false,
1777        }
1778    }
1779}
1780
1781/// The preprocessor conditionals whose truth decides whether one macro event
1782/// applies, by the start byte of each conditional node. Empty means the event
1783/// is unconditional.
1784///
1785/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1786/// conditional ancestors that structurally contain the event, and deciding
1787/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1788/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1789/// conditional's whole subtree. That answer is a fact about the tree alone, so
1790/// it is settled once, when the events are collected, instead of on every
1791/// replay of the event (#1496).
1792type OwningPreprocessorConditionals = Box<[usize]>;
1793
1794#[derive(Clone)]
1795pub enum MacroEvent {
1796    Define {
1797        name: String,
1798        binding: MacroBinding,
1799        byte: usize,
1800        conditionals: OwningPreprocessorConditionals,
1801    },
1802    Undef {
1803        name: String,
1804        byte: usize,
1805        conditionals: OwningPreprocessorConditionals,
1806    },
1807    Include {
1808        targets: Vec<ProjectFile>,
1809        byte: usize,
1810        conditionals: OwningPreprocessorConditionals,
1811    },
1812    Invalidate {
1813        byte: usize,
1814    },
1815}
1816
1817impl MacroEvent {
1818    pub fn byte(&self) -> usize {
1819        match self {
1820            Self::Define { byte, .. }
1821            | Self::Undef { byte, .. }
1822            | Self::Include { byte, .. }
1823            | Self::Invalidate { byte } => *byte,
1824        }
1825    }
1826}
1827
1828#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1829pub enum CallArityEvidence {
1830    Exact(usize),
1831    Unknown,
1832}
1833
1834impl CallArityEvidence {
1835    pub fn exact(self) -> Option<usize> {
1836        match self {
1837            Self::Exact(arity) => Some(arity),
1838            Self::Unknown => None,
1839        }
1840    }
1841
1842    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1843        self.exact().map(|arity| expected.accepts(arity))
1844    }
1845}
1846
1847struct CppAlias {
1848    name: String,
1849    target: String,
1850    namespace: Option<String>,
1851}
1852
1853type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1854
1855/// Why template-argument resolution failed. Definition diagnostics render
1856/// each mode differently; graph scans only care that the resolution is
1857/// unproven and match `Err(_)`.
1858#[derive(Debug, Clone, PartialEq, Eq)]
1859pub enum CppTemplateResolutionError {
1860    /// A template alias expansion revisited `alias`.
1861    AliasCycle { alias: CodeUnit },
1862    /// The explicit arguments do not bind to the declared template parameters.
1863    ArgumentBinding,
1864    /// Bound arguments do not substitute into the alias target's arguments.
1865    Substitution,
1866    /// No visible primary template declaration could be selected and
1867    /// reconciled for the specialization family.
1868    PrimarySelection,
1869    /// More than one applicable specialization remains and none is strictly
1870    /// more specialized than every other candidate.
1871    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1872}
1873
1874/// The ambiguity candidates, deduplicated to one representative per visible
1875/// symbol so a diagnostic lists each contender once.
1876fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1877    let mut distinct: Vec<CodeUnit> = Vec::new();
1878    for unit in units {
1879        if !distinct
1880            .iter()
1881            .any(|existing| same_visible_symbol(existing, unit))
1882        {
1883            distinct.push(unit.clone());
1884        }
1885    }
1886    distinct
1887}
1888
1889/// Enumerate macro lexical references while constructing visibility only if
1890/// the bounded source walk finds a definition or identifier call candidate.
1891pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1892    visibility: Factory,
1893    file: &ProjectFile,
1894    root: Node<'_>,
1895    source: &str,
1896    max_references: usize,
1897    cancelled: impl FnMut() -> bool,
1898) -> MacroLexicalReferences
1899where
1900    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1901{
1902    crate::graph::macro_lexical::all_references(
1903        visibility,
1904        file,
1905        root,
1906        source,
1907        max_references,
1908        cancelled,
1909    )
1910}
1911
1912impl<'a> VisibilityIndex<'a> {
1913    pub fn data(&self) -> Arc<VisibilityIndexData> {
1914        Arc::clone(&self.data)
1915    }
1916
1917    pub fn from_data(
1918        cpp: &'a dyn CppSource,
1919        token: QueryToken<'a>,
1920        data: Arc<VisibilityIndexData>,
1921    ) -> Self {
1922        Self { cpp, token, data }
1923    }
1924
1925    pub fn cpp(&self) -> &'a dyn CppSource {
1926        self.cpp
1927    }
1928
1929    /// The request-scope proof this index was built with (issue #2414 step 3).
1930    pub fn token(&self) -> QueryToken<'a> {
1931        self.token
1932    }
1933
1934    /// Build the source-backed macro lexical index once per file in this
1935    /// visibility query. The entry cap keeps the query from retaining a
1936    /// workspace-sized collection when a batch visits many translation units.
1937    pub(crate) fn macro_lexical_index(
1938        &self,
1939        file: &ProjectFile,
1940        root: Node<'_>,
1941        source: &str,
1942    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1943        if let Some(index) = self
1944            .macro_lexical_indexes
1945            .lock()
1946            .expect("macro lexical index cache poisoned")
1947            .get(file)
1948            .cloned()
1949        {
1950            debug_assert_eq!(
1951                index.source_len,
1952                source.len(),
1953                "macro lexical index for {file:?} was built from a different source"
1954            );
1955            return index;
1956        }
1957        let index = Arc::new(crate::graph::macro_lexical::build_index(
1958            self, file, root, source,
1959        ));
1960        let mut cache = self
1961            .macro_lexical_indexes
1962            .lock()
1963            .expect("macro lexical index cache poisoned");
1964        if let Some(existing) = cache.get(file).cloned() {
1965            return existing;
1966        }
1967        if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1968            cache.insert(file.clone(), Arc::clone(&index));
1969        }
1970        index
1971    }
1972
1973    /// The complete signature rows for one callable, shared by every
1974    /// candidate fact that the visibility query asks for. C++ reconciled
1975    /// members make this read substantially more expensive than an ordinary
1976    /// metadata lookup, so keep the answer on this per-query index and cap it
1977    /// explicitly.
1978    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1979        if let Some(metadata) = self
1980            .callable_metadata
1981            .lock()
1982            .expect("C++ callable metadata cache poisoned")
1983            .get(unit)
1984            .cloned()
1985        {
1986            return metadata;
1987        }
1988        let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1989        let mut cache = self
1990            .callable_metadata
1991            .lock()
1992            .expect("C++ callable metadata cache poisoned");
1993        if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1994            cache.insert(unit.clone(), Arc::clone(&metadata));
1995        }
1996        metadata
1997    }
1998
1999    /// Callable linkage backed by the query-local metadata memo.
2000    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2001        crate::identity::cpp_callable_linkage_from_metadata(
2002            self.callable_metadata(unit).iter().cloned(),
2003        )
2004    }
2005
2006    /// Callable arity backed by the same query-local metadata memo.
2007    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2008        brokk_bifrost_core::analyzer::model::first_callable_arity(
2009            self.callable_metadata(unit).iter().cloned(),
2010        )
2011    }
2012
2013    /// Callable parameter types backed by the same stored-metadata memo.
2014    /// Parameter spellings are source-owned facts and do not depend on the
2015    /// modifier joins that full `signature_metadata` performs for an
2016    /// out-of-line member.
2017    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2018        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2019            self.callable_metadata(unit).iter().cloned(),
2020        )
2021    }
2022
2023    /// Whether `file` has a structured unresolved include that is active before
2024    /// the reference at `before_byte`.
2025    ///
2026    /// Include facts are collected from the prepared tree once per visibility
2027    /// query. The reference position is still evaluated for each call because
2028    /// preprocessor visibility depends on the reference's own conditional
2029    /// context.
2030    pub fn has_unresolved_include_visible_before(
2031        &self,
2032        file: &ProjectFile,
2033        before_byte: usize,
2034    ) -> bool {
2035        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2036            return false;
2037        };
2038        let facts = self.structured_include_facts(file, prepared.as_ref());
2039        has_unresolved_include_visible_before_in_prepared(
2040            file,
2041            prepared.as_ref(),
2042            self.cpp.include_target_index(),
2043            &facts,
2044            before_byte,
2045        )
2046    }
2047
2048    fn structured_include_facts(
2049        &self,
2050        file: &ProjectFile,
2051        prepared: &PreparedSyntaxTree,
2052    ) -> Arc<[StructuredIncludeFact]> {
2053        let cell = self
2054            .structured_include_fact_cells
2055            .lock()
2056            .expect("C++ structured include-fact cache poisoned")
2057            .entry(file.clone())
2058            .or_default()
2059            .clone();
2060        Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2061    }
2062
2063    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
2064    /// bypassing the include-closure walk [`Self::build`] performs.
2065    ///
2066    /// The resolver's own unit tests drive the type-resolution paths against a
2067    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
2068    /// because they need a real `CppAnalyzer`, so the struct literal they used
2069    /// to write inline is here instead of thirty-three public fields.
2070    #[cfg(any(test, feature = "test-support"))]
2071    pub fn from_visible_files_for_test(
2072        cpp: &'a dyn CppSource,
2073        token: QueryToken<'a>,
2074        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2075    ) -> Self {
2076        let visible_source_files_by_root = visible_by_file
2077            .iter()
2078            .map(|(file, visible)| {
2079                (
2080                    file.clone(),
2081                    visible
2082                        .iter()
2083                        .map(|unit| unit.source().clone())
2084                        .chain(std::iter::once(file.clone()))
2085                        .collect(),
2086                )
2087            })
2088            .collect();
2089        let mut global_field_internal_linkage = HashMap::default();
2090        Self {
2091            cpp,
2092            token,
2093            data: Arc::new(VisibilityIndexData {
2094                visible_by_identifier: build_visible_identifier_index(
2095                    &CppGraphSource::from_source(cpp, token),
2096                    &visible_by_file,
2097                    &visible_source_files_by_root,
2098                    &mut global_field_internal_linkage,
2099                ),
2100                global_field_internal_linkage,
2101                visible_by_file,
2102                visible_source_files_by_root,
2103                alias_cells: Mutex::new(HashMap::default()),
2104                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2105                parser_alias_target_matches: RwLock::new(HashMap::default()),
2106                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2107                project_using_index: OnceLock::new(),
2108                callable_reference_specs: Mutex::new(HashMap::default()),
2109                structured_include_fact_cells: Mutex::new(HashMap::default()),
2110                include_activation_cells: Mutex::new(HashMap::default()),
2111                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2112                include_path_admission_cells: Mutex::new(HashMap::default()),
2113                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2114                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2115                conditional_include_projection_state_count: AtomicUsize::new(0),
2116                conditional_include_target_state_count: AtomicUsize::new(0),
2117                include_activation_build_count: AtomicUsize::new(0),
2118                using_donor_activation_count: AtomicUsize::new(0),
2119                using_namespace_lookup_count: AtomicUsize::new(0),
2120                using_name_candidate_inspection_count: AtomicUsize::new(0),
2121                callable_reference_spec_build_count: AtomicUsize::new(0),
2122                alias_source_parse_counts: Mutex::new(HashMap::default()),
2123                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2124                parser_alias_fallback_calls: AtomicUsize::new(0),
2125                parser_alias_fallback_files: AtomicUsize::new(0),
2126                parser_alias_source_parses: AtomicUsize::new(0),
2127                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2128                field_type_facts: Mutex::new(HashMap::default()),
2129                structured_alias_targets: Mutex::new(HashMap::default()),
2130                callable_comparables: Mutex::new(HashMap::default()),
2131                callable_metadata: Mutex::new(HashMap::default()),
2132                comparable_name_declarations: Mutex::new(HashMap::default()),
2133                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2134                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2135                precise_parent_cache: Mutex::new(HashMap::default()),
2136                c_tag_kind_cache: Mutex::new(HashMap::default()),
2137                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2138                macro_event_cells: Mutex::new(HashMap::default()),
2139                macro_event_name_sets: Mutex::new(HashMap::default()),
2140                macro_include_protection_cells: Mutex::new(HashMap::default()),
2141                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2142                macro_replacements: Mutex::new(HashMap::default()),
2143                macro_local_binding_templates: Mutex::new(HashMap::default()),
2144                macro_lexical_templates: Mutex::new(HashMap::default()),
2145                macro_lexical_indexes: Mutex::new(HashMap::default()),
2146                macro_replacement_bodies: Mutex::new(HashMap::default()),
2147                macro_type_parameters: Mutex::new(HashMap::default()),
2148                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2149                macro_replacement_parse_count: AtomicUsize::new(0),
2150                macro_event_application_count: AtomicUsize::new(0),
2151                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2152                macro_environment_copy_count: AtomicUsize::new(0),
2153                macro_environment_request_count: AtomicUsize::new(0),
2154                cpp_template_metadata: HashMap::default(),
2155                cpp_template_families: HashMap::default(),
2156                qualified_candidate_inspections: AtomicUsize::new(0),
2157                target_preserving_type_resolution_count: AtomicUsize::new(0),
2158                visibility_identifier_lookup_count: 0,
2159                visibility_identifier_batch_count: 0,
2160            }),
2161        }
2162    }
2163
2164    /// The index's own C++ source, in the dispatching-analyzer shape.
2165    ///
2166    /// Four resolution paths reach the workspace through the C++ analyzer they
2167    /// already hold rather than through the analyzer the query was issued
2168    /// against; before the move they passed `&CppAnalyzer` straight into a
2169    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2170    fn cpp_source(&self) -> CppGraphSource<'a> {
2171        CppGraphSource::from_source(self.cpp, self.token)
2172    }
2173
2174    pub fn build(
2175        cpp: &'a dyn CppSource,
2176        token: QueryToken<'a>,
2177        analyzer: &CppGraphSource<'_>,
2178        roots: &HashSet<ProjectFile>,
2179    ) -> Self {
2180        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2181    }
2182
2183    pub fn build_with_cancellation(
2184        cpp: &'a dyn CppSource,
2185        token: QueryToken<'a>,
2186        analyzer: &CppGraphSource<'_>,
2187        roots: &HashSet<ProjectFile>,
2188        cancellation: Option<&CancellationToken>,
2189    ) -> Self {
2190        let visibility_started = Instant::now();
2191        let include_targets = cpp.include_target_index();
2192        let includes_started = Instant::now();
2193        let mut include_graph = IncludeGraph::default();
2194        for root in roots {
2195            include_graph.extend_with(root, cancellation, &mut |file| {
2196                cpp.canonical_include_paths(token, file)
2197                    .unwrap_or_default()
2198                    .into_iter()
2199                    .flat_map(|include| {
2200                        resolve_include_targets_with_index(file, &include, include_targets)
2201                    })
2202                    .collect()
2203            });
2204        }
2205        let include_elapsed = includes_started.elapsed();
2206        let include_file_count = include_graph.files().count();
2207        let visible_source_files_by_root = roots
2208            .iter()
2209            .map(|root| {
2210                (
2211                    root.clone(),
2212                    include_graph.reachable_files(root, cancellation),
2213                )
2214            })
2215            .collect::<HashMap<_, _>>();
2216        let mut visibility_stats = BoundedVisibilityStats::default();
2217        let mut visible_by_file = build_bounded_visible_declarations(
2218            cpp,
2219            token,
2220            analyzer,
2221            roots,
2222            &visible_source_files_by_root,
2223            cancellation,
2224            &mut visibility_stats,
2225        );
2226        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2227            eprintln!(
2228                "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} identifier_batches={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_source_names={} dependency_names={} lookup_ms={} declaration_ms={} dependency_source_ms={}",
2229                visibility_started.elapsed().as_millis(),
2230                include_elapsed.as_millis(),
2231                include_file_count,
2232                visibility_stats.rounds,
2233                visibility_stats.root_names,
2234                visibility_stats.identifier_lookups,
2235                visibility_stats.identifier_batches,
2236                visibility_stats.candidate_units,
2237                visibility_stats.candidate_sources,
2238                visibility_stats.declaration_reads,
2239                visibility_stats.declaration_units,
2240                visibility_stats.selected_units,
2241                visibility_stats.dependency_source_names,
2242                visibility_stats.dependency_names,
2243                visibility_stats.lookup_elapsed.as_millis(),
2244                visibility_stats.declaration_elapsed.as_millis(),
2245                visibility_stats.dependency_source_elapsed.as_millis(),
2246            );
2247        }
2248        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2249        let finalize_started = Instant::now();
2250        if report_stats {
2251            eprintln!(
2252                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2253                visible_by_file.len(),
2254                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2255            );
2256        }
2257        let owner_started = Instant::now();
2258        if report_stats {
2259            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2260        }
2261        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2262        if report_stats {
2263            eprintln!(
2264                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2265                owner_stats.unseen_owners,
2266                owner_stats.definition_lookups,
2267                owner_stats.admitted,
2268                owner_started.elapsed().as_millis(),
2269            );
2270        }
2271        let mut global_field_internal_linkage = HashMap::default();
2272        let identifier_started = Instant::now();
2273        if report_stats {
2274            eprintln!(
2275                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2276            );
2277        }
2278        let visible_by_identifier = build_visible_identifier_index(
2279            analyzer,
2280            &visible_by_file,
2281            &visible_source_files_by_root,
2282            &mut global_field_internal_linkage,
2283        );
2284        if report_stats {
2285            eprintln!(
2286                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2287                visible_by_identifier.len(),
2288                visible_by_identifier
2289                    .values()
2290                    .map(HashMap::len)
2291                    .sum::<usize>(),
2292                visible_by_identifier
2293                    .values()
2294                    .flat_map(HashMap::values)
2295                    .map(Vec::len)
2296                    .sum::<usize>(),
2297                identifier_started.elapsed().as_millis(),
2298            );
2299        }
2300        let mut cpp_template_metadata = HashMap::default();
2301        let metadata_started = Instant::now();
2302        let mut template_classes = 0usize;
2303        if report_stats {
2304            eprintln!(
2305                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2306            );
2307        }
2308        for unit in visible_by_file
2309            .values()
2310            .flatten()
2311            .filter(|unit| unit.is_class())
2312        {
2313            template_classes += 1;
2314            if cpp_template_metadata.contains_key(unit) {
2315                continue;
2316            }
2317            if let Some(metadata) = cpp.template_metadata(unit) {
2318                cpp_template_metadata.insert(unit.clone(), metadata);
2319            }
2320        }
2321        if report_stats {
2322            eprintln!(
2323                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2324                template_classes,
2325                cpp_template_metadata.len(),
2326                metadata_started.elapsed().as_millis(),
2327            );
2328        }
2329        let families_started = Instant::now();
2330        if report_stats {
2331            eprintln!(
2332                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2333            );
2334        }
2335        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2336        for (unit, metadata) in &cpp_template_metadata {
2337            cpp_template_families
2338                .entry(metadata.primary_fq_name.clone())
2339                .or_default()
2340                .push(unit.clone());
2341        }
2342        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2343        // order above is a function of those hashes. Two mirrored headers can
2344        // declare one specialization; `select_template_specialization` treats
2345        // them as interchangeable and returns the family's first entry, so an
2346        // unsorted family made the reported declaration depend on the
2347        // workspace's absolute path and on unrelated files (#1836). Order the
2348        // family exactly as `build_visible_identifier_index` orders its
2349        // per-identifier candidate lists.
2350        for family in cpp_template_families.values_mut() {
2351            sort_lookup_units(family);
2352        }
2353        if report_stats {
2354            eprintln!(
2355                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2356                cpp_template_families.len(),
2357                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2358                families_started.elapsed().as_millis(),
2359            );
2360            eprintln!(
2361                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2362                visible_by_file.len(),
2363                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2364                finalize_started.elapsed().as_millis(),
2365                visibility_started.elapsed().as_millis(),
2366            );
2367        }
2368        Self {
2369            cpp,
2370            token,
2371            data: Arc::new(VisibilityIndexData {
2372                visible_by_file,
2373                visible_by_identifier,
2374                global_field_internal_linkage,
2375                visible_source_files_by_root,
2376                alias_cells: Mutex::new(HashMap::default()),
2377                visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2378                parser_alias_target_matches: RwLock::new(HashMap::default()),
2379                ordinary_type_import_cells: Mutex::new(HashMap::default()),
2380                project_using_index: OnceLock::new(),
2381                callable_reference_specs: Mutex::new(HashMap::default()),
2382                structured_include_fact_cells: Mutex::new(HashMap::default()),
2383                include_activation_cells: Mutex::new(HashMap::default()),
2384                compile_proven_guard_cells: Mutex::new(HashMap::default()),
2385                include_path_admission_cells: Mutex::new(HashMap::default()),
2386                conditional_include_projection_cells: Mutex::new(HashMap::default()),
2387                #[cfg(any(test, feature = "test-support"))]
2388                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2389                #[cfg(any(test, feature = "test-support"))]
2390                conditional_include_projection_state_count: AtomicUsize::new(0),
2391                #[cfg(any(test, feature = "test-support"))]
2392                conditional_include_target_state_count: AtomicUsize::new(0),
2393                #[cfg(any(test, feature = "test-support"))]
2394                include_activation_build_count: AtomicUsize::new(0),
2395                #[cfg(any(test, feature = "test-support"))]
2396                using_donor_activation_count: AtomicUsize::new(0),
2397                #[cfg(any(test, feature = "test-support"))]
2398                using_namespace_lookup_count: AtomicUsize::new(0),
2399                #[cfg(any(test, feature = "test-support"))]
2400                using_name_candidate_inspection_count: AtomicUsize::new(0),
2401                #[cfg(any(test, feature = "test-support"))]
2402                callable_reference_spec_build_count: AtomicUsize::new(0),
2403                #[cfg(any(test, feature = "test-support"))]
2404                alias_source_parse_counts: Mutex::new(HashMap::default()),
2405                #[cfg(any(test, feature = "test-support"))]
2406                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2407                parser_alias_fallback_calls: AtomicUsize::new(0),
2408                parser_alias_fallback_files: AtomicUsize::new(0),
2409                parser_alias_source_parses: AtomicUsize::new(0),
2410                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2411                field_type_facts: Mutex::new(HashMap::default()),
2412                structured_alias_targets: Mutex::new(HashMap::default()),
2413                callable_comparables: Mutex::new(HashMap::default()),
2414                callable_metadata: Mutex::new(HashMap::default()),
2415                comparable_name_declarations: Mutex::new(HashMap::default()),
2416                indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2417                indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2418                precise_parent_cache: Mutex::new(HashMap::default()),
2419                c_tag_kind_cache: Mutex::new(HashMap::default()),
2420                c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2421                macro_event_cells: Mutex::new(HashMap::default()),
2422                macro_event_name_sets: Mutex::new(HashMap::default()),
2423                macro_include_protection_cells: Mutex::new(HashMap::default()),
2424                macro_environment_checkpoints: Mutex::new(HashMap::default()),
2425                macro_replacements: Mutex::new(HashMap::default()),
2426                macro_local_binding_templates: Mutex::new(HashMap::default()),
2427                macro_lexical_templates: Mutex::new(HashMap::default()),
2428                macro_lexical_indexes: Mutex::new(HashMap::default()),
2429                macro_replacement_bodies: Mutex::new(HashMap::default()),
2430                macro_type_parameters: Mutex::new(HashMap::default()),
2431                callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2432                #[cfg(any(test, feature = "test-support"))]
2433                macro_replacement_parse_count: AtomicUsize::new(0),
2434                #[cfg(any(test, feature = "test-support"))]
2435                macro_event_application_count: AtomicUsize::new(0),
2436                #[cfg(any(test, feature = "test-support"))]
2437                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2438                #[cfg(any(test, feature = "test-support"))]
2439                macro_environment_copy_count: AtomicUsize::new(0),
2440                #[cfg(any(test, feature = "test-support"))]
2441                macro_environment_request_count: AtomicUsize::new(0),
2442                cpp_template_metadata,
2443                cpp_template_families,
2444                #[cfg(any(test, feature = "test-support"))]
2445                qualified_candidate_inspections: AtomicUsize::new(0),
2446                #[cfg(any(test, feature = "test-support"))]
2447                target_preserving_type_resolution_count: AtomicUsize::new(0),
2448                #[cfg(any(test, feature = "test-support"))]
2449                visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2450                #[cfg(any(test, feature = "test-support"))]
2451                visibility_identifier_batch_count: visibility_stats.identifier_batches,
2452            }),
2453        }
2454    }
2455
2456    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2457        if file == target.source() {
2458            return true;
2459        }
2460        if self.global_field_has_internal_linkage(target) {
2461            return self
2462                .visible_source_files_by_root
2463                .get(file)
2464                .is_some_and(|sources| sources.contains(target.source()));
2465        }
2466        self.visible_by_file
2467            .get(file)
2468            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2469    }
2470
2471    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2472        self.global_field_internal_linkage
2473            .get(unit)
2474            .copied()
2475            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2476    }
2477
2478    pub fn call_arity_evidence(
2479        &self,
2480        file: &ProjectFile,
2481        call: Node<'_>,
2482        source: &str,
2483    ) -> CallArityEvidence {
2484        self.call_arity_evidence_at(file, call, source, call.start_byte())
2485    }
2486
2487    /// Argument-count evidence for a call whose macro environment is not the
2488    /// one at its own byte offset.
2489    ///
2490    /// A call recovered from a macro replacement lives in a sentinel parse of
2491    /// its own, so its node offsets say nothing about which macros are active.
2492    /// `environment_byte` names the position in `file` whose macro environment
2493    /// governs the call: the macro definition site for a replacement body.
2494    pub fn call_arity_evidence_at(
2495        &self,
2496        file: &ProjectFile,
2497        call: Node<'_>,
2498        source: &str,
2499        environment_byte: usize,
2500    ) -> CallArityEvidence {
2501        let Some(arguments) = call
2502            .child_by_field_name("arguments")
2503            .or_else(|| call.child_by_field_name("parameters"))
2504            .or_else(|| call.child_by_field_name("value"))
2505            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2506            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2507        else {
2508            return CallArityEvidence::Exact(0);
2509        };
2510        let recovered_c_keyword_arguments =
2511            recovered_c_keyword_argument_count(file, call, arguments, source);
2512        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2513        let arguments = argument_children(arguments)
2514            .flat_map(|argument| {
2515                recovered_c_new_expression_arguments(argument, c_semantics)
2516                    .map(Vec::from)
2517                    .unwrap_or_else(|| vec![argument])
2518            })
2519            .collect::<Vec<_>>();
2520        if arguments
2521            .iter()
2522            .all(|argument| !argument_shape_may_change_arity(*argument))
2523        {
2524            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2525        }
2526        let environment = self.macro_environment(file, environment_byte);
2527        let mut stack = Vec::new();
2528        let mut total = recovered_c_keyword_arguments;
2529        for argument in arguments {
2530            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2531                return CallArityEvidence::Unknown;
2532            }
2533            let CallArityEvidence::Exact(spread) =
2534                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2535            else {
2536                return CallArityEvidence::Unknown;
2537            };
2538            total += spread;
2539        }
2540        CallArityEvidence::Exact(total)
2541    }
2542
2543    fn argument_arity_evidence(
2544        &self,
2545        argument: Node<'_>,
2546        source: &str,
2547        environment: &MacroEnvironment,
2548        stack: &mut Vec<(ProjectFile, usize)>,
2549    ) -> CallArityEvidence {
2550        let (name, invocation_arguments, function_like) = match argument.kind() {
2551            "identifier" => (node_text(argument, source), None, false),
2552            "call_expression" => {
2553                let Some(function) = argument.child_by_field_name("function") else {
2554                    return CallArityEvidence::Exact(1);
2555                };
2556                if function.kind() != "identifier" {
2557                    return CallArityEvidence::Exact(1);
2558                }
2559                let Some(arguments) = argument.child_by_field_name("arguments") else {
2560                    return CallArityEvidence::Exact(1);
2561                };
2562                (node_text(function, source), Some(arguments), true)
2563            }
2564            _ => return CallArityEvidence::Exact(1),
2565        };
2566        let Some(binding) = environment.binding(name) else {
2567            return if environment.unknown_names {
2568                CallArityEvidence::Unknown
2569            } else {
2570                CallArityEvidence::Exact(1)
2571            };
2572        };
2573        if !binding.is_exact() {
2574            return CallArityEvidence::Unknown;
2575        }
2576        match (&binding.definition, invocation_arguments, function_like) {
2577            (MacroDefinition::Object { replacement }, None, false) => self
2578                .replacement_arity_evidence(
2579                    replacement,
2580                    &[],
2581                    &[],
2582                    source,
2583                    environment,
2584                    stack,
2585                    binding,
2586                ),
2587            (
2588                MacroDefinition::Function {
2589                    parameters,
2590                    replacement,
2591                },
2592                Some(arguments),
2593                true,
2594            ) => {
2595                let actuals = argument_children(arguments).collect::<Vec<_>>();
2596                if actuals.len() != parameters.len() {
2597                    CallArityEvidence::Unknown
2598                } else {
2599                    self.replacement_arity_evidence(
2600                        replacement,
2601                        parameters,
2602                        &actuals,
2603                        source,
2604                        environment,
2605                        stack,
2606                        binding,
2607                    )
2608                }
2609            }
2610            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2611            _ => CallArityEvidence::Unknown,
2612        }
2613    }
2614
2615    #[allow(clippy::too_many_arguments)]
2616    fn replacement_arity_evidence(
2617        &self,
2618        replacement: &str,
2619        parameters: &[String],
2620        actuals: &[Node<'_>],
2621        actual_source: &str,
2622        environment: &MacroEnvironment,
2623        stack: &mut Vec<(ProjectFile, usize)>,
2624        binding: &MacroBinding,
2625    ) -> CallArityEvidence {
2626        let identity = (binding.source.clone(), binding.declaration_byte);
2627        if stack.contains(&identity) || replacement.trim().is_empty() {
2628            return CallArityEvidence::Unknown;
2629        }
2630        stack.push(identity);
2631        let parsed = self.parsed_macro_replacement(binding, replacement);
2632        let evidence = (|| {
2633            let ParsedMacroReplacement::Parsed {
2634                source: sentinel,
2635                tree,
2636            } = parsed.as_ref()
2637            else {
2638                return None;
2639            };
2640            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2641            let arguments = call.child_by_field_name("arguments")?;
2642            let mut total = 0usize;
2643            for argument in argument_children(arguments) {
2644                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2645                    return None;
2646                }
2647                if argument.kind() == "identifier"
2648                    && let Some(parameter_index) = parameters
2649                        .iter()
2650                        .position(|parameter| parameter == node_text(argument, sentinel))
2651                {
2652                    if !macro_expansion_shape_is_safe(
2653                        actuals[parameter_index],
2654                        actual_source,
2655                        &[],
2656                        environment,
2657                    ) {
2658                        return None;
2659                    }
2660                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2661                        actuals[parameter_index],
2662                        actual_source,
2663                        environment,
2664                        stack,
2665                    ) else {
2666                        return None;
2667                    };
2668                    total += spread;
2669                    continue;
2670                }
2671                let CallArityEvidence::Exact(spread) =
2672                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2673                else {
2674                    return None;
2675                };
2676                total += spread;
2677            }
2678            Some(CallArityEvidence::Exact(total))
2679        })()
2680        .unwrap_or(CallArityEvidence::Unknown);
2681        stack.pop();
2682        evidence
2683    }
2684
2685    fn parsed_macro_replacement(
2686        &self,
2687        binding: &MacroBinding,
2688        replacement: &str,
2689    ) -> Arc<ParsedMacroReplacement> {
2690        let key = (binding.source.clone(), binding.declaration_byte);
2691        let mut cache = self
2692            .macro_replacements
2693            .lock()
2694            .expect("C++ macro replacement cache poisoned");
2695        if let Some(parsed) = cache.get(&key) {
2696            return Arc::clone(parsed);
2697        }
2698        #[cfg(any(test, feature = "test-support"))]
2699        self.macro_replacement_parse_count
2700            .fetch_add(1, Ordering::Relaxed);
2701        let source =
2702            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2703        let mut parser = Parser::new();
2704        let parsed = parser
2705            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2706            .ok()
2707            .and_then(|()| parser.parse(&source, None))
2708            .filter(|tree| !tree.root_node().has_error())
2709            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2710                ParsedMacroReplacement::Parsed { source, tree }
2711            });
2712        let parsed = Arc::new(parsed);
2713        cache.insert(key, Arc::clone(&parsed));
2714        parsed
2715    }
2716
2717    /// Recover a typed local declared by an active C function-like macro.
2718    ///
2719    /// This is intentionally narrower than macro expansion. The replacement
2720    /// must parse as one declaration, and the invocation must bind every
2721    /// formal parameter to one structured argument. That is sufficient for
2722    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2723    /// can make the binding provisional without erasing its last known
2724    /// definition; an explicit conflicting definition still replaces it with
2725    /// Unsupported. Malformed and statement-producing macros also fail closed.
2726    pub fn function_macro_local_binding<'tree>(
2727        &self,
2728        file: &ProjectFile,
2729        statement: Node<'tree>,
2730        source: &str,
2731    ) -> Option<MacroLocalBinding<'tree>> {
2732        if !is_c_source_file(file) {
2733            return None;
2734        }
2735        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2736            return Some(binding);
2737        }
2738        let call = match statement.kind() {
2739            "call_expression" => statement,
2740            "expression_statement" if statement.named_child_count() == 1 => {
2741                statement.named_child(0)?
2742            }
2743            _ => return None,
2744        };
2745        if call.kind() != "call_expression" {
2746            return None;
2747        }
2748        let function = call.child_by_field_name("function")?;
2749        if function.kind() != "identifier" {
2750            return None;
2751        }
2752        let arguments = call.child_by_field_name("arguments")?;
2753        let actuals = argument_children(arguments).collect::<Vec<_>>();
2754        let environment = self.macro_environment(file, call.start_byte());
2755        let function_name = node_text(function, source);
2756        let binding = environment.binding(function_name)?;
2757        let MacroDefinition::Function {
2758            parameters,
2759            replacement,
2760        } = &binding.definition
2761        else {
2762            return None;
2763        };
2764        if actuals.len() != parameters.len() {
2765            return None;
2766        }
2767        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2768        let (type_name, type_node) = match &template.declared_type {
2769            MacroLocalBindingTypeTemplate::Parameter(index) => {
2770                let actual = *actuals.get(*index)?;
2771                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2772                    return None;
2773                }
2774                (node_text(actual, source).trim().to_string(), Some(actual))
2775            }
2776            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2777        };
2778        if type_name.is_empty() {
2779            return None;
2780        }
2781        Some(MacroLocalBinding {
2782            name: template.name.clone(),
2783            type_name,
2784            type_node,
2785            pointer_depth: template.pointer_depth,
2786            proven_unit: None,
2787        })
2788    }
2789
2790    /// Recover the typed receiver established by a C container macro
2791    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2792    ///
2793    /// The macro definition's parsed replacement identifies the one formal
2794    /// parameter used in type position.  The invocation supplies the actual
2795    /// type node, which is then resolved through the ordinary visibility
2796    /// index.  Calls with no unique type-position parameter, an unresolved
2797    /// type, or an uncertain macro environment remain unproven.
2798    pub fn function_macro_container_binding<'tree>(
2799        &self,
2800        analyzer: &CppGraphSource<'_>,
2801        file: &ProjectFile,
2802        assignment: Node<'tree>,
2803        source: &str,
2804    ) -> Option<MacroLocalBinding<'tree>> {
2805        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2806            return None;
2807        }
2808        let name_node = assignment.child_by_field_name("left")?;
2809        if name_node.kind() != "identifier" {
2810            return None;
2811        }
2812        let call = assignment.child_by_field_name("right")?;
2813        if call.kind() != "call_expression" {
2814            return None;
2815        }
2816        let function = call.child_by_field_name("function")?;
2817        if function.kind() != "identifier" {
2818            return None;
2819        }
2820        let arguments = call.child_by_field_name("arguments")?;
2821        let actuals = argument_children(arguments).collect::<Vec<_>>();
2822        let function_name = node_text(function, source);
2823        let environment = self.macro_environment(file, call.start_byte());
2824        let binding = environment.binding(function_name)?;
2825        if !binding.is_exact() {
2826            return None;
2827        }
2828        let MacroDefinition::Function {
2829            parameters,
2830            replacement,
2831        } = &binding.definition
2832        else {
2833            return None;
2834        };
2835        if actuals.len() != parameters.len() {
2836            return None;
2837        }
2838        let body = self.parsed_macro_replacement_body(
2839            &(binding.source.clone(), binding.declaration_byte),
2840            parameters,
2841            replacement,
2842        )?;
2843        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2844        let type_argument = *actuals.get(type_parameter)?;
2845        let type_node = macro_type_argument_node(type_argument, source)?;
2846        let type_name = node_text(type_node, source).trim().to_string();
2847        if type_name.is_empty() {
2848            return None;
2849        }
2850        let explicit_tag = match node_text(type_argument, source) {
2851            "struct" => Some(CppCTagKind::Struct),
2852            "union" => Some(CppCTagKind::Union),
2853            _ => None,
2854        };
2855        let resolved_type = if let Some(tag) = explicit_tag {
2856            let candidates = self
2857                .visible_identifier_candidates(file, &type_name)
2858                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2859                .collect::<Vec<_>>();
2860            self.resolve_type_candidates(
2861                analyzer,
2862                file,
2863                &candidates,
2864                TypeCandidateResolution::Canonical,
2865            )
2866            .ok()
2867        } else {
2868            self.resolve_type_node_result(file, type_node, source)
2869                .ok()
2870                .flatten()
2871        };
2872        let proven_unit = resolved_type?;
2873        Some(MacroLocalBinding {
2874            name: node_text(name_node, source).to_string(),
2875            type_name,
2876            type_node: Some(type_node),
2877            pointer_depth: 1,
2878            proven_unit: Some(proven_unit),
2879        })
2880    }
2881
2882    /// Recover the invocation-specific type of a replacement local visible
2883    /// at the selected source token.
2884    pub fn macro_local_binding_at<'tree>(
2885        &self,
2886        file: &ProjectFile,
2887        root: Node<'tree>,
2888        source: &str,
2889        start_byte: usize,
2890        end_byte: usize,
2891    ) -> Option<MacroLocalBinding<'tree>> {
2892        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2893    }
2894
2895    /// Resolve the source-backed lexical macro declaration for one source
2896    /// range. The range may be a point range (`start..start + 1`) inside an
2897    /// identifier, which is the form used by goto-definition queries.
2898    pub fn macro_lexical_binding(
2899        &self,
2900        file: &ProjectFile,
2901        root: Node<'_>,
2902        source: &str,
2903        start_byte: usize,
2904        end_byte: usize,
2905    ) -> Option<MacroLexicalBinding> {
2906        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2907    }
2908
2909    /// Enumerate source ranges whose lexical meaning comes from a
2910    /// function-like macro formal or replacement local. Definition spellings
2911    /// themselves are omitted; callers needing goto-definition on a spelling
2912    /// use [`Self::macro_lexical_binding`].
2913    pub fn macro_lexical_references(
2914        &self,
2915        file: &ProjectFile,
2916        root: Node<'_>,
2917        source: &str,
2918        max_references: usize,
2919        cancelled: impl FnMut() -> bool,
2920    ) -> MacroLexicalReferences {
2921        crate::graph::macro_lexical::all_references(
2922            || self,
2923            file,
2924            root,
2925            source,
2926            max_references,
2927            cancelled,
2928        )
2929    }
2930
2931    /// Return the source identity of the exact active function-like macro
2932    /// binding. The definition AST is hydrated by the lexical helper from
2933    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2934    /// the prepared source borrow.
2935    pub(crate) fn function_macro_binding_at(
2936        &self,
2937        file: &ProjectFile,
2938        name: &str,
2939        before_byte: usize,
2940    ) -> Option<(ProjectFile, usize)> {
2941        let environment = self.macro_environment(file, before_byte);
2942        let binding = environment.binding(name)?;
2943        if binding.is_exact()
2944            && matches!(
2945                binding.definition,
2946                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2947            )
2948        {
2949            return Some((binding.source.clone(), binding.declaration_byte));
2950        }
2951        // A definition under an unknown condition is still certain at a use
2952        // within that same branch. The checkpoint retains the event that made
2953        // the binding uncertain. Require that exact defining event, so an
2954        // intervening undef, include, or conditional redefinition cannot be
2955        // bypassed by a lexical search for an older macro.
2956        if binding.source != *file {
2957            return None;
2958        }
2959        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2960        let root = prepared.tree().root_node();
2961        let source = prepared.source();
2962        let reference = root.descendant_for_byte_range(
2963            before_byte,
2964            before_byte.saturating_add(1).min(source.len()),
2965        )?;
2966        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2967        let cell = self.macro_event_cell(file);
2968        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2969        let event = events
2970            .iter()
2971            .find(|event| event.byte() == binding.declaration_byte)?;
2972        let MacroEvent::Define {
2973            name: defined_name,
2974            binding: definition,
2975            conditionals,
2976            ..
2977        } = event
2978        else {
2979            return None;
2980        };
2981        if defined_name != name
2982            || conditionals.is_empty()
2983            || !conditionals
2984                .iter()
2985                .all(|condition| reference_conditions.contains(condition))
2986            || !matches!(
2987                definition.definition,
2988                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2989            )
2990        {
2991            return None;
2992        }
2993        Some((definition.source.clone(), definition.declaration_byte))
2994    }
2995
2996    /// Return the type argument at a C function-like macro invocation.
2997    ///
2998    /// Exact local macro definitions identify type formals from their parsed
2999    /// replacement. An unavailable definition is deliberately not inferred:
3000    /// C has no AST distinction between an external macro call and an
3001    /// ordinary call to an unindexed function.
3002    pub fn function_macro_type_argument<'tree>(
3003        &self,
3004        file: &ProjectFile,
3005        node: Node<'tree>,
3006        source: &str,
3007    ) -> Option<Node<'tree>> {
3008        if !is_c_source_file(file) {
3009            return None;
3010        }
3011        let (call, argument_index, argument) = c_function_macro_argument(node)?;
3012        let function = call.child_by_field_name("function")?;
3013        let function_name = node_text(function, source);
3014        if function_name.is_empty() {
3015            return None;
3016        }
3017        if !self.file_defines_macro_name(file, function_name)
3018            && !self
3019                .visible_identifier_candidates(file, function_name)
3020                .any(|candidate| candidate.is_macro())
3021        {
3022            return None;
3023        }
3024        let environment = self.macro_environment(file, call.start_byte());
3025        let binding = environment.binding(function_name)?;
3026        if !binding.is_exact() {
3027            return None;
3028        }
3029        let (parameters, replacement, variadic) = match &binding.definition {
3030            MacroDefinition::Function {
3031                parameters,
3032                replacement,
3033            } => (parameters, replacement, false),
3034            MacroDefinition::VariadicFunction {
3035                parameters,
3036                replacement,
3037            } => (parameters, replacement, true),
3038            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3039        };
3040        let actuals = call
3041            .child_by_field_name("arguments")
3042            .map(argument_children)
3043            .into_iter()
3044            .flatten()
3045            .collect::<Vec<_>>();
3046        let arity_matches = if variadic {
3047            actuals.len() >= parameters.len()
3048        } else {
3049            actuals.len() == parameters.len()
3050        };
3051        let type_parameters = self.macro_type_parameter_indices(
3052            &(binding.source.clone(), binding.declaration_byte),
3053            parameters,
3054            replacement,
3055        )?;
3056        if !arity_matches || !type_parameters.contains(&argument_index) {
3057            return None;
3058        }
3059        let type_node = macro_type_argument_node(argument, source)?;
3060        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3061            return None;
3062        }
3063        Some(type_node)
3064    }
3065
3066    fn macro_local_binding_template(
3067        &self,
3068        binding: &MacroBinding,
3069        parameters: &[String],
3070        replacement: &str,
3071    ) -> Option<Arc<MacroLocalBindingTemplate>> {
3072        let key = (binding.source.clone(), binding.declaration_byte);
3073        if let Some(template) = self
3074            .macro_local_binding_templates
3075            .lock()
3076            .expect("C++ macro local-binding cache poisoned")
3077            .get(&key)
3078        {
3079            return template.clone();
3080        }
3081        let template = (|| {
3082            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3083            let sentinel = body.source.as_str();
3084            let statements = body.statements()?;
3085            if statements.named_child_count() != 1 {
3086                return None;
3087            }
3088            let declaration = statements.named_child(0)?;
3089            if declaration.kind() != "declaration" {
3090                return None;
3091            }
3092            let type_node = declaration
3093                .child_by_field_name("type")
3094                .or_else(|| first_type_child(declaration))?;
3095            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3096                let mut cursor = declaration.walk();
3097                declaration.named_children(&mut cursor).find_map(|child| {
3098                    if child.kind() == "init_declarator" {
3099                        child.child_by_field_name("declarator")
3100                    } else {
3101                        is_declarator_node(child).then_some(child)
3102                    }
3103                })
3104            })?;
3105            let name = extract_variable_name(declarator, sentinel)?;
3106            let pointer_depth =
3107                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3108            let type_text = node_text(type_node, sentinel).trim();
3109            let declared_type = parameters
3110                .iter()
3111                .position(|parameter| parameter == type_text)
3112                .map(MacroLocalBindingTypeTemplate::Parameter)
3113                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3114            Some(Arc::new(MacroLocalBindingTemplate {
3115                name,
3116                declared_type,
3117                pointer_depth,
3118            }))
3119        })();
3120        self.macro_local_binding_templates
3121            .lock()
3122            .expect("C++ macro local-binding cache poisoned")
3123            .insert(key, template.clone());
3124        template
3125    }
3126
3127    /// The parsed replacement body of the function-like macro `definition`
3128    /// defines, or `None` when the replacement cannot be recovered exactly.
3129    ///
3130    /// `definition` is the defining `preproc_function_def` node in `file`, so
3131    /// the result describes that definition rather than whichever same-named
3132    /// macro a later reference resolves to.
3133    pub fn function_macro_replacement_body(
3134        &self,
3135        file: &ProjectFile,
3136        definition: Node<'_>,
3137        source: &str,
3138    ) -> Option<Arc<ParsedReplacementBody>> {
3139        debug_assert_eq!(definition.kind(), "preproc_function_def");
3140        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3141            MacroDefinition::Function {
3142                parameters,
3143                replacement,
3144            }
3145            | MacroDefinition::VariadicFunction {
3146                parameters,
3147                replacement,
3148            } => (parameters, replacement),
3149            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3150        };
3151        self.parsed_macro_replacement_body(
3152            &(file.clone(), definition.start_byte()),
3153            &parameters,
3154            &replacement,
3155        )
3156    }
3157
3158    /// Parse one function-like macro replacement inside the shared sentinel.
3159    ///
3160    /// The parse fails closed, and the failure is cached, whenever the
3161    /// sentinel tree carries an error or the replacement uses preprocessor
3162    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3163    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3164    /// therefore rejected from the parsed tree instead of the source text.
3165    fn parsed_macro_replacement_body(
3166        &self,
3167        key: &(ProjectFile, usize),
3168        parameters: &[String],
3169        replacement: &str,
3170    ) -> Option<Arc<ParsedReplacementBody>> {
3171        if let Some(body) = self
3172            .macro_replacement_bodies
3173            .lock()
3174            .expect("C++ macro replacement body cache poisoned")
3175            .get(key)
3176        {
3177            return body.clone();
3178        }
3179        let body = (|| {
3180            if replacement.trim().is_empty() {
3181                return None;
3182            }
3183            let (source, tree, original_offsets) =
3184                Self::parse_macro_replacement_body(replacement, parameters)?;
3185            let body = ParsedReplacementBody {
3186                source,
3187                tree,
3188                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3189                parameters: parameters.to_vec(),
3190                original_offsets,
3191            };
3192            body.statements()?;
3193            if body.expands_variadic_arguments() {
3194                return None;
3195            }
3196            Some(Arc::new(body))
3197        })();
3198        self.macro_replacement_bodies
3199            .lock()
3200            .expect("C++ macro replacement body cache poisoned")
3201            .insert(key.clone(), body.clone());
3202        body
3203    }
3204
3205    /// Parse a replacement while preserving a map from the parse buffer back
3206    /// to the original logical-line bytes. A block formal can occur where C
3207    /// requires a statement terminator (`if (condition) block`); the caller's
3208    /// actual block supplies that terminator only after macro substitution.
3209    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3210    /// semicolon is inserted at that AST-derived boundary for the fallback
3211    /// parse. No identifier text is scanned to find the insertion point.
3212    fn parse_macro_replacement_body(
3213        replacement: &str,
3214        parameters: &[String],
3215    ) -> Option<(String, Tree, Box<[usize]>)> {
3216        let normalized = normalize_macro_continuations(replacement);
3217        let parse = |replacement: &str| {
3218            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3219            let mut parser = Parser::new();
3220            parser
3221                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3222                .ok()?;
3223            let tree = parser.parse(&source, None)?;
3224            Some((source, tree))
3225        };
3226        let (source, tree) = parse(&normalized)?;
3227        if !tree.root_node().has_error() {
3228            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3229            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3230            return Some((source, tree, original_offsets.into_boxed_slice()));
3231        }
3232
3233        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3234        let mut insertion_points = Vec::new();
3235        let mut stack = vec![tree.root_node()];
3236        while let Some(node) = stack.pop() {
3237            if matches!(
3238                node.kind(),
3239                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3240            ) && parameters
3241                .iter()
3242                .any(|parameter| parameter.as_str() == node_text(node, &source))
3243                && Self::macro_formal_needs_statement_separator(node)
3244            {
3245                let point = node.end_byte().saturating_sub(body_offset);
3246                if point <= normalized.len() && !insertion_points.contains(&point) {
3247                    insertion_points.push(point);
3248                }
3249            }
3250            push_named_children_reversed(node, &mut stack);
3251        }
3252        if insertion_points.is_empty() {
3253            return None;
3254        }
3255        insertion_points.sort_unstable();
3256        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3257        let mut original_offsets =
3258            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3259        let mut next_insertion = 0;
3260        for (index, byte) in normalized.bytes().enumerate() {
3261            recovered.push(byte);
3262            original_offsets.push(index);
3263            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3264                recovered.push(b';');
3265                original_offsets.push(index + 1);
3266                next_insertion += 1;
3267            }
3268        }
3269        original_offsets.push(normalized.len());
3270        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3271        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3272        let (source, tree) = parse(&recovered)?;
3273        if tree.root_node().has_error() {
3274            return None;
3275        }
3276        Some((source, tree, original_offsets.into_boxed_slice()))
3277    }
3278
3279    /// Extend a replacement-origin map over the `; }` suffix appended by the
3280    /// sentinel parser. Nodes such as a declaration whose source omits its
3281    /// terminator include that synthetic semicolon; every suffix boundary
3282    /// still maps to the replacement's real end.
3283    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3284        offsets.extend([replacement_end; 3]);
3285    }
3286
3287    /// A formal is statement-shaped when the recovered tree places it directly
3288    /// at a statement boundary. This is the grammar recovery produced for
3289    /// replacement forms such as `if (condition) block`; an identifier inside
3290    /// an expression or call argument must not receive an artificial `;`.
3291    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3292        let mut current = node;
3293        let mut crossed_recovery = false;
3294        while let Some(parent) = current.parent() {
3295            if parent.is_error() {
3296                crossed_recovery = true;
3297                current = parent;
3298                continue;
3299            }
3300            if matches!(
3301                parent.kind(),
3302                "call_expression"
3303                    | "argument_list"
3304                    | "field_expression"
3305                    | "binary_expression"
3306                    | "unary_expression"
3307                    | "assignment_expression"
3308                    | "conditional_expression"
3309                    | "parenthesized_expression"
3310                    | "subscript_expression"
3311            ) {
3312                return false;
3313            }
3314            if parent.kind() == "expression_statement" {
3315                return parent.named_child_count() == 1;
3316            }
3317            if parent.kind() == "compound_statement" && current == node {
3318                return true;
3319            }
3320            if matches!(
3321                parent.kind(),
3322                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3323            ) {
3324                return crossed_recovery
3325                    || parent.child_by_field_name("consequence") == Some(current);
3326            }
3327            if matches!(parent.kind(), "declaration" | "init_declarator") {
3328                return false;
3329            }
3330            current = parent;
3331        }
3332        false
3333    }
3334
3335    /// Identify fixed macro formals that retain an unambiguous type role even
3336    /// when an unrelated part of the replacement needs parser recovery.
3337    ///
3338    /// Variadic production macros commonly contain token pasting elsewhere in
3339    /// the body. The strict shared replacement parser must keep rejecting that
3340    /// body for expansion modeling, but a type declaration such as `TY col`
3341    /// remains usable when the node itself retains a structured type role.
3342    fn macro_type_parameter_indices(
3343        &self,
3344        key: &(ProjectFile, usize),
3345        parameters: &[String],
3346        replacement: &str,
3347    ) -> Option<Arc<[usize]>> {
3348        if let Some(indices) = self
3349            .macro_type_parameters
3350            .lock()
3351            .expect("C++ macro type-parameter cache poisoned")
3352            .get(key)
3353        {
3354            return indices.clone();
3355        }
3356        #[cfg(any(test, feature = "test-support"))]
3357        self.macro_replacement_parse_count
3358            .fetch_add(1, Ordering::Relaxed);
3359        let indices = (|| {
3360            if replacement.trim().is_empty() {
3361                return None;
3362            }
3363            // Keep this parser permissive. Type-role extraction predates the
3364            // replacement-body model and deliberately survives unrelated
3365            // token-pasting or recovery elsewhere in a production macro.
3366            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3367            let mut parser = Parser::new();
3368            parser
3369                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3370                .ok()?;
3371            let tree = parser.parse(&source, None)?;
3372            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3373            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3374            let body = ParsedReplacementBody {
3375                source,
3376                tree,
3377                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3378                parameters: parameters.to_vec(),
3379                original_offsets: original_offsets.into_boxed_slice(),
3380            };
3381            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3382        })();
3383        self.macro_type_parameters
3384            .lock()
3385            .expect("C++ macro type-parameter cache poisoned")
3386            .insert(key.clone(), indices.clone());
3387        indices
3388    }
3389
3390    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3391        let replacement = if node.kind() == "preproc_function_def" {
3392            function_macro_replacement_span(node, source)
3393                .and_then(|span| source.get(span))
3394                .map(str::to_owned)
3395                .or_else(|| {
3396                    node.child_by_field_name("value")
3397                        .map(|value| node_text(value, source).to_string())
3398                })
3399                .unwrap_or_default()
3400        } else {
3401            node.child_by_field_name("value")
3402                .map(|value| node_text(value, source).to_string())
3403                .unwrap_or_default()
3404        };
3405        if node.kind() == "preproc_def" {
3406            return MacroDefinition::Object { replacement };
3407        }
3408        let Some(parameters) = node.child_by_field_name("parameters") else {
3409            return MacroDefinition::Unsupported;
3410        };
3411        let variadic = (0..parameters.child_count()).any(|index| {
3412            parameters
3413                .child(index)
3414                .is_some_and(|child| child.kind() == "...")
3415        });
3416        let parameters = (0..parameters.named_child_count())
3417            .filter_map(|index| parameters.named_child(index))
3418            .map(|parameter| node_text(parameter, source).to_string())
3419            .collect::<Vec<_>>();
3420        if variadic {
3421            MacroDefinition::VariadicFunction {
3422                parameters,
3423                replacement,
3424            }
3425        } else {
3426            MacroDefinition::Function {
3427                parameters,
3428                replacement,
3429            }
3430        }
3431    }
3432
3433    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3434        clone_or_default_file_cell(
3435            &self.macro_event_cells,
3436            file,
3437            "C++ macro event cache poisoned",
3438        )
3439    }
3440
3441    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3442        if let Some(names) = self
3443            .macro_event_name_sets
3444            .lock()
3445            .expect("C++ macro event-name cache poisoned")
3446            .get(file)
3447            .cloned()
3448        {
3449            return names.contains(name);
3450        }
3451        let cell = self.macro_event_cell(file);
3452        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3453        let names = Arc::new(
3454            events
3455                .iter()
3456                .filter_map(|event| match event {
3457                    MacroEvent::Define { name, .. } => Some(name.clone()),
3458                    MacroEvent::Undef { .. }
3459                    | MacroEvent::Include { .. }
3460                    | MacroEvent::Invalidate { .. } => None,
3461                })
3462                .collect(),
3463        );
3464        self.macro_event_name_sets
3465            .lock()
3466            .expect("C++ macro event-name cache poisoned")
3467            .insert(file.clone(), Arc::clone(&names));
3468        names.contains(name)
3469    }
3470
3471    fn macro_environment_checkpoint_cell(
3472        &self,
3473        file: &ProjectFile,
3474    ) -> MacroEnvironmentCheckpointCell {
3475        clone_or_default_file_cell(
3476            &self.macro_environment_checkpoints,
3477            file,
3478            "C++ macro environment checkpoint cache poisoned",
3479        )
3480    }
3481
3482    /// The environment of `file`'s macro events applied up to `before_byte`.
3483    pub fn macro_environment(
3484        &self,
3485        file: &ProjectFile,
3486        before_byte: usize,
3487    ) -> Arc<MacroEnvironment> {
3488        #[cfg(any(test, feature = "test-support"))]
3489        self.macro_environment_request_count
3490            .fetch_add(1, Ordering::Relaxed);
3491        let cell = self.macro_event_cell(file);
3492        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3493        let frontier = events.partition_point(|event| event.byte() < before_byte);
3494        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3495        let checkpoints =
3496            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3497        let checkpoint = checkpoints.at_or_before(frontier);
3498        if checkpoint.frontier == frontier {
3499            return Arc::clone(&checkpoint.environment);
3500        }
3501        #[cfg(any(test, feature = "test-support"))]
3502        self.macro_environment_copy_count
3503            .fetch_add(1, Ordering::Relaxed);
3504        let mut environment = checkpoint.environment.as_ref().clone();
3505        let mut include_stack = HashSet::from_iter([file.clone()]);
3506        for event in &events[checkpoint.frontier..frontier] {
3507            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3508        }
3509        Arc::new(environment)
3510    }
3511
3512    /// Apply `file`'s events once, keeping the environment at the prefixes
3513    /// [`MacroEnvironmentCheckpoints`] describes.
3514    fn build_macro_environment_checkpoints(
3515        &self,
3516        file: &ProjectFile,
3517        events: &[MacroEvent],
3518    ) -> MacroEnvironmentCheckpoints {
3519        #[cfg(any(test, feature = "test-support"))]
3520        self.macro_environment_checkpoint_build_count
3521            .fetch_add(1, Ordering::Relaxed);
3522        // The TU's build-proven defines hold from the first byte (#2011): they
3523        // are facts of the whole compile, so they seed the frontier-zero
3524        // checkpoint. A later explicit #undef event still overrides them
3525        // through `known_undefined_names`.
3526        let mut environment = MacroEnvironment {
3527            build_proven_defines: self
3528                .compile_proven_guards(file)
3529                .iter()
3530                .filter_map(|guard| match guard {
3531                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3532                    _ => None,
3533                })
3534                .collect(),
3535            ..MacroEnvironment::default()
3536        };
3537        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3538            frontier: 0,
3539            environment: Arc::new(environment.clone()),
3540        }];
3541        // Keep roughly one fixed stride's worth of checkpoints, while taking
3542        // a checkpoint at every event for event sets no larger than the fixed
3543        // stride. Generated C tables commonly have thousands of uses between
3544        // one #define and a trailing #undef; with a fixed stride those
3545        // identical frontier requests would all copy and replay the same
3546        // prefix.
3547        let checkpoint_stride = events
3548            .len()
3549            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3550            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3551        let mut include_stack = HashSet::from_iter([file.clone()]);
3552        for (index, event) in events.iter().enumerate() {
3553            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3554            let frontier = index + 1;
3555            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3556                checkpoints.push(MacroEnvironmentCheckpoint {
3557                    frontier,
3558                    environment: Arc::new(environment.clone()),
3559                });
3560            }
3561        }
3562        MacroEnvironmentCheckpoints { checkpoints }
3563    }
3564
3565    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3566    /// including a binding this environment cannot pin to one replacement
3567    /// (a conditional `#define`, or a function-like macro).
3568    ///
3569    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3570    /// `None`, which is indistinguishable from "not a macro at all". A caller
3571    /// that must not read a macro token as an ordinary type name needs the two
3572    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3573    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3574        self.macro_environment(file, before_byte)
3575            .binding(name)
3576            .is_some()
3577    }
3578
3579    pub fn macro_name_may_be_bound_at(
3580        &self,
3581        file: &ProjectFile,
3582        name: &str,
3583        before_byte: usize,
3584    ) -> bool {
3585        self.macro_environment(file, before_byte).may_bind(name)
3586    }
3587
3588    /// Whether the active macro binding at this reference is the requested
3589    /// indexed definition. Name equality alone is not enough because two
3590    /// headers can define the same macro for different translation units.
3591    pub fn macro_binding_matches_target_at(
3592        &self,
3593        analyzer: &CppGraphSource<'_>,
3594        file: &ProjectFile,
3595        name: &str,
3596        before_byte: usize,
3597        target: &CodeUnit,
3598    ) -> bool {
3599        let ranges = analyzer.ranges(target);
3600        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3601        self.macro_binding_matches_target_declaration_at(
3602            file,
3603            name,
3604            before_byte,
3605            target.source(),
3606            &declaration_bytes,
3607        )
3608    }
3609
3610    /// Resolve declaration ranges once for inverse scans that test many call
3611    /// sites against one macro target. Walking a large generated syntax tree
3612    /// back to the same `#define` for every call site is otherwise quadratic
3613    /// in the number of top-level declarations.
3614    pub(crate) fn macro_declaration_bytes(
3615        &self,
3616        target: &CodeUnit,
3617        ranges: &[Range],
3618    ) -> Vec<usize> {
3619        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3620            return Vec::new();
3621        };
3622        ranges
3623            .iter()
3624            .filter_map(|range| {
3625                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3626                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3627                    node = node.parent()?;
3628                }
3629                Some(node.start_byte())
3630            })
3631            .collect()
3632    }
3633
3634    pub(crate) fn macro_binding_matches_target_declaration_at(
3635        &self,
3636        file: &ProjectFile,
3637        name: &str,
3638        before_byte: usize,
3639        target_source: &ProjectFile,
3640        target_declaration_bytes: &[usize],
3641    ) -> bool {
3642        let environment = self.macro_environment(file, before_byte);
3643        let Some(binding) = environment.binding(name) else {
3644            return false;
3645        };
3646        if binding.definition == MacroDefinition::Unsupported {
3647            return false;
3648        }
3649        // A normal header guard makes the replacement text conditional, but
3650        // it does not erase the definition site's source and byte identity.
3651        // Keep that identity even when expansion details are not exact.
3652        if binding.source != *target_source {
3653            return false;
3654        }
3655        target_declaration_bytes.contains(&binding.declaration_byte)
3656    }
3657
3658    /// Resolve an ordinary expression-position macro token at its exact byte.
3659    ///
3660    /// Calls and preprocessor-condition tokens have separate resolution
3661    /// surfaces. Declaration names, macro parameters, and labels are not
3662    /// references. Keeping that role policy here makes forward and both
3663    /// inverse graph builders consume the same activation verdict (#2093).
3664    pub fn resolve_ordinary_macro_reference(
3665        &self,
3666        analyzer: &CppGraphSource<'_>,
3667        file: &ProjectFile,
3668        node: Node<'_>,
3669        source: &str,
3670    ) -> OrdinaryMacroReferenceResolution {
3671        if !is_ordinary_macro_reference_node(node) {
3672            return OrdinaryMacroReferenceResolution::Missing;
3673        }
3674        let name = node_text(node, source);
3675        if name.is_empty() {
3676            return OrdinaryMacroReferenceResolution::Missing;
3677        }
3678        let visible = self
3679            .visible_identifier_candidates(file, name)
3680            .filter(|candidate| candidate.is_macro())
3681            .cloned()
3682            .collect::<Vec<_>>();
3683        let mut exact = Vec::new();
3684        for candidate in &visible {
3685            if self.macro_binding_matches_target_at(
3686                analyzer,
3687                file,
3688                name,
3689                node.start_byte(),
3690                candidate,
3691            ) && !exact
3692                .iter()
3693                .any(|existing| same_visible_symbol(existing, candidate))
3694            {
3695                exact.push(candidate.clone());
3696            }
3697        }
3698        match exact.len() {
3699            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3700            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3701            0 if !visible.is_empty()
3702                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3703            {
3704                OrdinaryMacroReferenceResolution::Ambiguous
3705            }
3706            0 => OrdinaryMacroReferenceResolution::Missing,
3707        }
3708    }
3709
3710    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3711    ///
3712    /// The ordinary census deliberately skips every `ERROR` subtree. This
3713    /// separate, precision-only frontier admits only roles that retain enough
3714    /// structure for the C usage graph to interpret independently (#2089).
3715    /// Macro evidence comes from this visibility index at the exact byte; no
3716    /// source-text parsing or terminal-name fallback is used.
3717    pub fn recovered_c_reference_ranges(
3718        &self,
3719        file: &ProjectFile,
3720        root: Node<'_>,
3721        source: &str,
3722        limit: usize,
3723    ) -> RecoveredCReferenceRanges {
3724        if !is_c_source_file(file) {
3725            return RecoveredCReferenceRanges::Complete(Vec::new());
3726        }
3727        let mut ranges = Vec::new();
3728        let mut seen = HashSet::default();
3729        let mut stack = vec![(root, root.is_error())];
3730        while let Some((node, inside_error)) = stack.pop() {
3731            // A `macro_type_specifier` is the macro-declaration grammar's
3732            // structured form of the same evidence an `ERROR` carried: it is
3733            // admitted only where the parse would otherwise fail, so the type
3734            // it wraps keeps its structured references.
3735            let inside_error =
3736                inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3737            if node.kind() == "preproc_arg" {
3738                // Tree-sitter keeps an object-like replacement opaque. The
3739                // inverse extractor uses the same parsed replacement helper;
3740                // retain its exact macro/type leaves for precision membership
3741                // even when the preprocessor node is outside ERROR recovery.
3742                let macro_value_kind = node.parent().and_then(|parent| {
3743                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3744                });
3745                if matches!(
3746                    macro_value_kind,
3747                    Some("preproc_def" | "preproc_function_def")
3748                ) {
3749                    let name = node_text(node, source);
3750                    if !name.is_empty()
3751                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3752                        && !push_recovered_c_range(
3753                            &mut ranges,
3754                            &mut seen,
3755                            node.start_byte(),
3756                            node.end_byte(),
3757                            node,
3758                            limit,
3759                        )
3760                    {
3761                        return RecoveredCReferenceRanges::LimitExceeded;
3762                    }
3763                }
3764                if macro_value_kind == Some("preproc_def") {
3765                    for reference in object_macro_replacement_type_references(node, source) {
3766                        for range in reference.component_ranges {
3767                            let visible = self
3768                                .visible_identifier_candidates(file, &source[range.clone()])
3769                                .any(|candidate| candidate.is_class() || candidate.is_module());
3770                            if visible
3771                                && !push_recovered_c_range(
3772                                    &mut ranges,
3773                                    &mut seen,
3774                                    range.start,
3775                                    range.end,
3776                                    node,
3777                                    limit,
3778                                )
3779                            {
3780                                return RecoveredCReferenceRanges::LimitExceeded;
3781                            }
3782                        }
3783                    }
3784                }
3785            }
3786            if inside_error
3787                && recovered_c_reference_node(self, file, node, source)
3788                && !push_recovered_c_range(
3789                    &mut ranges,
3790                    &mut seen,
3791                    node.start_byte(),
3792                    node.end_byte(),
3793                    node,
3794                    limit,
3795                )
3796            {
3797                return RecoveredCReferenceRanges::LimitExceeded;
3798            }
3799            let mut cursor = node.walk();
3800            for child in node.named_children(&mut cursor) {
3801                stack.push((child, inside_error));
3802            }
3803        }
3804        ranges.sort_unstable();
3805        RecoveredCReferenceRanges::Complete(ranges)
3806    }
3807
3808    /// Whether this target is an indexed macro visible from this file.
3809    ///
3810    /// An unresolved conditional can make more than one same-name macro a
3811    /// possible active binding. Each possible target can keep the site as an
3812    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3813    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3814        self.visible_identifier_candidates(file, target.identifier())
3815            .filter(|candidate| candidate.is_macro())
3816            .any(|candidate| {
3817                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3818            })
3819    }
3820
3821    pub fn object_macro_replacement_at(
3822        &self,
3823        file: &ProjectFile,
3824        name: &str,
3825        before_byte: usize,
3826    ) -> Option<String> {
3827        let environment = self.macro_environment(file, before_byte);
3828        let binding = environment.binding(name)?;
3829        if !binding.exact {
3830            return None;
3831        }
3832        match &binding.definition {
3833            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3834            MacroDefinition::Function { .. }
3835            | MacroDefinition::VariadicFunction { .. }
3836            | MacroDefinition::Unsupported => None,
3837        }
3838    }
3839
3840    fn apply_macro_events(
3841        &self,
3842        file: &ProjectFile,
3843        before_byte: Option<usize>,
3844        environment: &mut MacroEnvironment,
3845        include_stack: &mut HashSet<ProjectFile>,
3846    ) {
3847        if !include_stack.insert(file.clone()) {
3848            return;
3849        }
3850        if self.cpp.prepared_syntax(self.token, file).is_none() {
3851            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3852            include_stack.remove(file);
3853            return;
3854        }
3855        match self.macro_include_protection(file) {
3856            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3857                Some(binding) if binding.is_exact() => {
3858                    include_stack.remove(file);
3859                    return;
3860                }
3861                Some(_) | None if environment.unknown_names => {
3862                    let mut ambiguous_seen = HashSet::default();
3863                    self.mark_macro_events_ambiguous(
3864                        file,
3865                        environment,
3866                        &mut ambiguous_seen,
3867                        file,
3868                        before_byte.unwrap_or_default(),
3869                    );
3870                    include_stack.remove(file);
3871                    return;
3872                }
3873                Some(_) => {
3874                    let mut ambiguous_seen = HashSet::default();
3875                    self.mark_macro_events_ambiguous(
3876                        file,
3877                        environment,
3878                        &mut ambiguous_seen,
3879                        file,
3880                        before_byte.unwrap_or_default(),
3881                    );
3882                    include_stack.remove(file);
3883                    return;
3884                }
3885                None => {}
3886            },
3887            MacroIncludeProtection::PragmaOnce => {
3888                if !environment.applied_pragma_once_files.insert(file.clone()) {
3889                    include_stack.remove(file);
3890                    return;
3891                }
3892                if environment.maybe_applied_pragma_once_files.remove(file) {
3893                    // A prior conditional include may already have consumed the pragma-once
3894                    // header. This unconditional include guarantees it is consumed now, but
3895                    // cannot prove whether its events occur before or after intervening local
3896                    // macro changes, so preserve the union as ambiguous.
3897                    let mut ambiguous_seen = HashSet::default();
3898                    environment.applied_pragma_once_files.remove(file);
3899                    self.mark_macro_events_ambiguous(
3900                        file,
3901                        environment,
3902                        &mut ambiguous_seen,
3903                        file,
3904                        before_byte.unwrap_or_default(),
3905                    );
3906                    environment.maybe_applied_pragma_once_files.remove(file);
3907                    environment.applied_pragma_once_files.insert(file.clone());
3908                    include_stack.remove(file);
3909                    return;
3910                }
3911            }
3912            MacroIncludeProtection::None => {}
3913        }
3914        let cell = self.macro_event_cell(file);
3915        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3916        for event in events {
3917            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3918                break;
3919            }
3920            self.apply_macro_event(file, event, environment, include_stack);
3921        }
3922        include_stack.remove(file);
3923    }
3924
3925    fn apply_macro_event(
3926        &self,
3927        file: &ProjectFile,
3928        event: &MacroEvent,
3929        environment: &mut MacroEnvironment,
3930        include_stack: &mut HashSet<ProjectFile>,
3931    ) {
3932        #[cfg(any(test, feature = "test-support"))]
3933        self.macro_event_application_count
3934            .fetch_add(1, Ordering::Relaxed);
3935        match event {
3936            MacroEvent::Define {
3937                name,
3938                binding,
3939                conditionals,
3940                byte,
3941            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3942                Some(true) => environment.insert(name.clone(), binding.clone()),
3943                Some(false) => {}
3944                None => Self::merge_conditional_macro_definition(
3945                    environment,
3946                    name,
3947                    binding,
3948                    file,
3949                    *byte,
3950                ),
3951            },
3952            MacroEvent::Undef {
3953                name,
3954                conditionals,
3955                byte,
3956            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3957                Some(true) => environment.remove(name),
3958                Some(false) => {}
3959                None => {
3960                    if environment.binding(name).is_some() {
3961                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3962                    }
3963                }
3964            },
3965            MacroEvent::Include {
3966                targets,
3967                conditionals,
3968                byte,
3969            } => {
3970                let condition =
3971                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3972                if condition == Some(false) {
3973                    return;
3974                }
3975                if targets.is_empty() {
3976                    environment.mark_unknown_names(file, *byte);
3977                    return;
3978                }
3979                if condition.is_none() || targets.len() > 1 {
3980                    let mut ambiguous_seen = HashSet::default();
3981                    for target in targets {
3982                        self.mark_macro_events_ambiguous(
3983                            target,
3984                            environment,
3985                            &mut ambiguous_seen,
3986                            file,
3987                            *byte,
3988                        );
3989                    }
3990                } else if let Some(target) = targets.first() {
3991                    self.apply_macro_events(target, None, environment, include_stack);
3992                }
3993            }
3994            MacroEvent::Invalidate { byte } => {
3995                for binding in environment.bindings.values_mut() {
3996                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3997                }
3998            }
3999        }
4000    }
4001
4002    /// Evaluate the structured conditional path that owns one macro event.
4003    ///
4004    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
4005    /// this source byte. `None` preserves the old conditional merge when a
4006    /// build/configuration input or an unsupported expression is involved.
4007    ///
4008    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
4009    /// which ancestors structurally own the event was decided when the events
4010    /// were collected, so all this walk does is find those nodes again and ask
4011    /// the environment what their conditions are worth here.
4012    fn macro_event_condition_value(
4013        &self,
4014        file: &ProjectFile,
4015        event_byte: usize,
4016        environment: &MacroEnvironment,
4017        conditionals: &OwningPreprocessorConditionals,
4018    ) -> Option<bool> {
4019        if conditionals.is_empty() {
4020            return Some(true);
4021        }
4022        let prepared = self.cpp.prepared_syntax(self.token, file)?;
4023        let source = prepared.source();
4024        let root = prepared.tree().root_node();
4025        let descendant = root.descendant_for_byte_range(
4026            event_byte,
4027            event_byte.saturating_add(1).min(source.len()),
4028        )?;
4029        let mut unknown = false;
4030        let mut current = descendant.parent();
4031        while let Some(conditional) = current {
4032            if matches!(
4033                conditional.kind(),
4034                "preproc_if" | "preproc_ifdef" | "preproc_elif"
4035            ) && conditionals.contains(&conditional.start_byte())
4036            {
4037                let mut value = match conditional.kind() {
4038                    "preproc_ifdef" => {
4039                        let name = conditional.child_by_field_name("name")?;
4040                        let defined =
4041                            self.macro_name_defined_value(environment, node_text(name, source));
4042                        match conditional.child(0)?.kind() {
4043                            "#ifdef" => defined,
4044                            "#ifndef" => defined.map(|defined| !defined),
4045                            _ => None,
4046                        }
4047                    }
4048                    "preproc_if" | "preproc_elif" => conditional
4049                        .child_by_field_name("condition")
4050                        .and_then(|condition| {
4051                            self.preprocessor_integer_value(
4052                                condition,
4053                                source,
4054                                environment,
4055                                &mut Vec::new(),
4056                                0,
4057                            )
4058                        })
4059                        .map(|value| value != 0),
4060                    _ => unreachable!(),
4061                };
4062                match preprocessor_branch_of_descendant(
4063                    conditional,
4064                    descendant,
4065                    &mut PreprocessorContextCache::default(),
4066                ) {
4067                    PreprocessorBranch::Consequence => {}
4068                    PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4069                        value = value.map(|value| !value);
4070                    }
4071                    PreprocessorBranch::Unreadable => value = None,
4072                }
4073                match value {
4074                    Some(true) => {}
4075                    Some(false) => return Some(false),
4076                    None => unknown = true,
4077                }
4078            }
4079            current = conditional.parent();
4080        }
4081        (!unknown).then_some(true)
4082    }
4083
4084    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4085        if environment.known_undefined_names.contains(name) {
4086            return Some(false);
4087        }
4088        if let Some(binding) = environment.binding(name) {
4089            return binding.is_exact().then_some(true);
4090        }
4091        environment
4092            .build_proven_defines
4093            .contains(name)
4094            .then_some(true)
4095    }
4096
4097    fn preprocessor_integer_value(
4098        &self,
4099        expression: Node<'_>,
4100        source: &str,
4101        environment: &MacroEnvironment,
4102        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4103        depth: usize,
4104    ) -> Option<i128> {
4105        // Macro replacement graphs can cycle. This explicit bound makes the
4106        // otherwise recursive AST evaluation stack-safe for hostile input.
4107        if depth >= 64 {
4108            return None;
4109        }
4110        match expression.kind() {
4111            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4112            "identifier" | "type_identifier" => {
4113                let binding = environment.binding(node_text(expression, source))?;
4114                if !binding.is_exact() {
4115                    return None;
4116                }
4117                let MacroDefinition::Object { replacement } = &binding.definition else {
4118                    return None;
4119                };
4120                let identity = (binding.source.clone(), binding.declaration_byte);
4121                if expansion_stack.contains(&identity) {
4122                    return None;
4123                }
4124                expansion_stack.push(identity);
4125                let parsed = self.parsed_macro_replacement(binding, replacement);
4126                let value = match parsed.as_ref() {
4127                    ParsedMacroReplacement::Parsed {
4128                        source: replacement_source,
4129                        tree,
4130                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4131                        .and_then(|call| call.child_by_field_name("arguments"))
4132                        .and_then(|arguments| argument_children(arguments).next())
4133                        .and_then(|argument| {
4134                            self.preprocessor_integer_value(
4135                                argument,
4136                                replacement_source,
4137                                environment,
4138                                expansion_stack,
4139                                depth + 1,
4140                            )
4141                        }),
4142                    ParsedMacroReplacement::Unsupported => None,
4143                };
4144                expansion_stack.pop();
4145                value
4146            }
4147            "preproc_defined" => {
4148                let mut cursor = expression.walk();
4149                let name = expression
4150                    .named_children(&mut cursor)
4151                    .find(|child| child.kind() == "identifier")?;
4152                self.macro_name_defined_value(environment, node_text(name, source))
4153                    .map(i128::from)
4154            }
4155            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4156                self.preprocessor_integer_value(
4157                    child,
4158                    source,
4159                    environment,
4160                    expansion_stack,
4161                    depth + 1,
4162                )
4163            }),
4164            "unary_expression" => {
4165                let operator = expression.child_by_field_name("operator")?.kind();
4166                let argument = expression.child_by_field_name("argument")?;
4167                let value = self.preprocessor_integer_value(
4168                    argument,
4169                    source,
4170                    environment,
4171                    expansion_stack,
4172                    depth + 1,
4173                )?;
4174                match operator {
4175                    "+" => Some(value),
4176                    "-" => value.checked_neg(),
4177                    "!" => Some(i128::from(value == 0)),
4178                    "~" => Some(!value),
4179                    _ => None,
4180                }
4181            }
4182            "binary_expression" => {
4183                let left = self.preprocessor_integer_value(
4184                    expression.child_by_field_name("left")?,
4185                    source,
4186                    environment,
4187                    expansion_stack,
4188                    depth + 1,
4189                )?;
4190                let right = self.preprocessor_integer_value(
4191                    expression.child_by_field_name("right")?,
4192                    source,
4193                    environment,
4194                    expansion_stack,
4195                    depth + 1,
4196                )?;
4197                match expression.child_by_field_name("operator")?.kind() {
4198                    "+" => left.checked_add(right),
4199                    "-" => left.checked_sub(right),
4200                    "*" => left.checked_mul(right),
4201                    "/" => left.checked_div(right),
4202                    "%" => left.checked_rem(right),
4203                    "<<" => u32::try_from(right)
4204                        .ok()
4205                        .and_then(|shift| left.checked_shl(shift)),
4206                    ">>" => u32::try_from(right)
4207                        .ok()
4208                        .and_then(|shift| left.checked_shr(shift)),
4209                    "<" => Some(i128::from(left < right)),
4210                    "<=" => Some(i128::from(left <= right)),
4211                    ">" => Some(i128::from(left > right)),
4212                    ">=" => Some(i128::from(left >= right)),
4213                    "==" => Some(i128::from(left == right)),
4214                    "!=" => Some(i128::from(left != right)),
4215                    "&" => Some(left & right),
4216                    "|" => Some(left | right),
4217                    "^" => Some(left ^ right),
4218                    "&&" => Some(i128::from(left != 0 && right != 0)),
4219                    "||" => Some(i128::from(left != 0 || right != 0)),
4220                    _ => None,
4221                }
4222            }
4223            _ => None,
4224        }
4225    }
4226
4227    fn mark_macro_events_ambiguous(
4228        &self,
4229        file: &ProjectFile,
4230        environment: &mut MacroEnvironment,
4231        include_stack: &mut HashSet<ProjectFile>,
4232        conditional_file: &ProjectFile,
4233        conditional_byte: usize,
4234    ) {
4235        if !include_stack.insert(file.clone()) {
4236            return;
4237        }
4238        if self.cpp.prepared_syntax(self.token, file).is_none() {
4239            environment.mark_unknown_names(conditional_file, conditional_byte);
4240            return;
4241        }
4242        match self.macro_include_protection(file) {
4243            MacroIncludeProtection::MacroGuard(guard) => {
4244                if environment
4245                    .binding(&guard)
4246                    .is_some_and(MacroBinding::is_exact)
4247                {
4248                    return;
4249                }
4250            }
4251            MacroIncludeProtection::PragmaOnce => {
4252                if environment.applied_pragma_once_files.contains(file) {
4253                    return;
4254                }
4255                environment
4256                    .maybe_applied_pragma_once_files
4257                    .insert(file.clone());
4258            }
4259            MacroIncludeProtection::None => {}
4260        }
4261        let cell = self.macro_event_cell(file);
4262        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4263        for event in events {
4264            #[cfg(any(test, feature = "test-support"))]
4265            self.macro_event_application_count
4266                .fetch_add(1, Ordering::Relaxed);
4267            match event {
4268                MacroEvent::Define { name, binding, .. } => {
4269                    Self::merge_conditional_macro_definition(
4270                        environment,
4271                        name,
4272                        binding,
4273                        conditional_file,
4274                        conditional_byte,
4275                    );
4276                }
4277                MacroEvent::Undef { name, .. } => {
4278                    if environment.binding(name).is_some() {
4279                        environment.insert(
4280                            name.clone(),
4281                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4282                        );
4283                    } else {
4284                        environment.remove_known_undefined(name);
4285                    }
4286                }
4287                MacroEvent::Include { targets, .. } => {
4288                    if targets.is_empty() {
4289                        environment.mark_unknown_names(conditional_file, conditional_byte);
4290                        continue;
4291                    }
4292                    for target in targets {
4293                        self.mark_macro_events_ambiguous(
4294                            target,
4295                            environment,
4296                            include_stack,
4297                            conditional_file,
4298                            conditional_byte,
4299                        );
4300                    }
4301                }
4302                MacroEvent::Invalidate { .. } => {
4303                    for binding in environment.bindings.values_mut() {
4304                        *binding = MacroBinding::uncertain_from(
4305                            binding,
4306                            conditional_file,
4307                            conditional_byte,
4308                        );
4309                    }
4310                }
4311            }
4312        }
4313    }
4314
4315    fn merge_conditional_macro_definition(
4316        environment: &mut MacroEnvironment,
4317        name: &str,
4318        possible_binding: &MacroBinding,
4319        conditional_file: &ProjectFile,
4320        conditional_byte: usize,
4321    ) {
4322        // A conditional include can revisit an already-active guarded header.
4323        // If the possible branch defines the exact same macro, both outcomes
4324        // leave the binding unchanged; degrading it to Unknown would discard
4325        // proof because of an unrelated unresolved macro name (#2092).
4326        if environment.binding(name).is_some_and(|current| {
4327            current.definition != MacroDefinition::Unsupported
4328                && current.definition == possible_binding.definition
4329        }) {
4330            return;
4331        }
4332        environment.insert(
4333            name.to_string(),
4334            MacroBinding::ambiguous(conditional_file, conditional_byte),
4335        );
4336    }
4337
4338    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4339        let cell = self
4340            .macro_include_protection_cells
4341            .lock()
4342            .expect("C++ include protection cache poisoned")
4343            .entry(file.clone())
4344            .or_default()
4345            .clone();
4346        cell.get_or_init(|| {
4347            self.cpp.prepared_syntax(self.token, file).map_or(
4348                MacroIncludeProtection::None,
4349                |prepared| {
4350                    top_level_macro_include_protection(
4351                        prepared.tree().root_node(),
4352                        prepared.source(),
4353                    )
4354                },
4355            )
4356        })
4357        .clone()
4358    }
4359
4360    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4361        collect_macro_events(self.cpp, self.token, file)
4362    }
4363
4364    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4365        self.ordinary_type_import_cells
4366            .lock()
4367            .expect("C++ ordinary type import cache poisoned")
4368            .entry(file.clone())
4369            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4370            .clone()
4371    }
4372
4373    pub fn project_using_index(
4374        &self,
4375        build: impl FnOnce() -> ProjectUsingIndex,
4376    ) -> &ProjectUsingIndex {
4377        self.project_using_index.get_or_init(build)
4378    }
4379
4380    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4381        let mut files = self
4382            .visible_source_files_by_root
4383            .values()
4384            .flatten()
4385            .cloned()
4386            .collect::<HashSet<_>>()
4387            .into_iter()
4388            .collect::<Vec<_>>();
4389        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4390        files
4391    }
4392
4393    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4394        self.visible_source_files_by_root
4395            .get(root)
4396            .is_some_and(|files| files.contains(source))
4397    }
4398
4399    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4400        let cached = self
4401            .visible_parser_alias_name_sets
4402            .read()
4403            .expect("visible parser alias-name cache poisoned")
4404            .get(file)
4405            .cloned();
4406        let cell = if let Some(cached) = cached {
4407            cached
4408        } else {
4409            let mut cells = self
4410                .visible_parser_alias_name_sets
4411                .write()
4412                .expect("visible parser alias-name cache poisoned");
4413            Arc::clone(
4414                cells
4415                    .entry(file.clone())
4416                    .or_insert_with(|| Arc::new(OnceLock::new())),
4417            )
4418        };
4419        if let Some(names) = cell.get() {
4420            return names.contains(name);
4421        }
4422        let mut names = HashSet::default();
4423        let visible_files = self
4424            .visible_source_files_by_root
4425            .get(file)
4426            .cloned()
4427            .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4428        for visible_file in visible_files {
4429            let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4430                return false;
4431            };
4432            for alias in aliases.get().expect("published canonical aliases").iter() {
4433                names.insert(alias.name.clone());
4434            }
4435        }
4436        #[cfg(any(test, feature = "test-support"))]
4437        self.visible_parser_alias_name_set_build_count
4438            .fetch_add(1, Ordering::Relaxed);
4439        cell.get_or_init(|| names).contains(name)
4440    }
4441
4442    pub fn parser_alias_name_may_resolve_to_target(
4443        &self,
4444        file: &ProjectFile,
4445        alias_name: &str,
4446        target: &CodeUnit,
4447    ) -> bool {
4448        let started = std::time::Instant::now();
4449        self.parser_alias_fallback_calls
4450            .fetch_add(1, Ordering::Relaxed);
4451        let key = (
4452            file.clone(),
4453            alias_name.to_string(),
4454            logical_symbol_key(target),
4455        );
4456        let cached = self
4457            .parser_alias_target_matches
4458            .read()
4459            .expect("parser alias target-match cache poisoned")
4460            .get(&key)
4461            .cloned();
4462        let cell = if let Some(cached) = cached {
4463            cached
4464        } else {
4465            let mut cells = self
4466                .parser_alias_target_matches
4467                .write()
4468                .expect("parser alias target-match cache poisoned");
4469            Arc::clone(
4470                cells
4471                    .entry(key)
4472                    .or_insert_with(|| Arc::new(OnceLock::new())),
4473            )
4474        };
4475        let matched = *cell.get_or_init(|| {
4476            any_visible_source_file_matches(
4477                &self.visible_source_files_by_root,
4478                file,
4479                |visible_file| {
4480                    self.parser_alias_fallback_files
4481                        .fetch_add(1, Ordering::Relaxed);
4482                    self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4483                },
4484            )
4485        });
4486        self.parser_alias_fallback_elapsed_micros.fetch_add(
4487            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4488            Ordering::Relaxed,
4489        );
4490        matched
4491    }
4492
4493    fn callable_arities_for_target(
4494        &self,
4495        analyzer: &CppGraphSource<'_>,
4496        cpp: &dyn CppSource,
4497        file: &ProjectFile,
4498        prepared: &PreparedSyntaxTree,
4499        spec: &TargetSpec,
4500    ) -> Vec<ActivatedCallableArity> {
4501        let Some(signature) = spec.target.signature() else {
4502            return Vec::new();
4503        };
4504        let Some(candidates) = self
4505            .visible_by_identifier
4506            .get(file)
4507            .and_then(|by_name| by_name.get(&spec.member_name))
4508        else {
4509            return Vec::new();
4510        };
4511        let differing_candidates = candidates
4512            .iter()
4513            .filter(|candidate| {
4514                candidate.is_function()
4515                    && candidate.fq_name() == spec.target.fq_name()
4516                    && candidate.signature() == Some(signature)
4517            })
4518            .filter_map(|candidate| {
4519                analyzer
4520                    .signature_metadata(candidate)
4521                    .into_iter()
4522                    .find_map(|metadata| metadata.callable_arity())
4523                    .filter(|arity| Some(*arity) != spec.callable_arity)
4524                    .map(|arity| (candidate, arity))
4525            })
4526            .collect::<Vec<_>>();
4527        if differing_candidates.is_empty() {
4528            return Vec::new();
4529        }
4530        let mut arities = Vec::with_capacity(differing_candidates.len());
4531        // The activation ranges here describe the whole file rather than one
4532        // reference, so there is no reference guard environment to consult.
4533        let reference = CallableReferenceContext {
4534            file,
4535            position: None,
4536        };
4537        for (candidate, candidate_arity) in differing_candidates {
4538            let declaration_activation =
4539                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4540            let Some(declaration_activation) = declaration_activation else {
4541                continue;
4542            };
4543            let activation_byte = if candidate.source() == file {
4544                Some(declaration_activation)
4545            } else {
4546                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4547            };
4548            if let Some(activation_byte) = activation_byte {
4549                arities.push(ActivatedCallableArity {
4550                    activation_byte,
4551                    arity: candidate_arity,
4552                });
4553            }
4554        }
4555        arities
4556    }
4557
4558    fn callable_parameter_macro_arity(
4559        &self,
4560        target: &CodeUnit,
4561        parameter_types: Option<&[String]>,
4562    ) -> Option<CallableArity> {
4563        let parameter_types = parameter_types?;
4564        let [macro_name] = parameter_types else {
4565            return None;
4566        };
4567        if macro_name.is_empty()
4568            || !macro_name
4569                .chars()
4570                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4571        {
4572            return None;
4573        }
4574        let cache_key = (target.source().clone(), macro_name.clone());
4575        if let Some(cached) = self
4576            .callable_parameter_macro_arities
4577            .lock()
4578            .expect("C++ callable parameter-macro arity cache poisoned")
4579            .get(&cache_key)
4580            .copied()
4581        {
4582            return cached;
4583        }
4584        let mut visible_files = HashSet::default();
4585        collect_include_closure(
4586            &self.cpp_source(),
4587            self.cpp.include_target_index(),
4588            target.source(),
4589            &mut visible_files,
4590            None,
4591        );
4592        let mut arities = Vec::new();
4593        for visible_file in visible_files {
4594            let cell = self.macro_event_cell(&visible_file);
4595            for event in
4596                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4597            {
4598                let MacroEvent::Define { name, binding, .. } = event else {
4599                    continue;
4600                };
4601                if name != macro_name {
4602                    continue;
4603                }
4604                let MacroDefinition::Object { replacement } = &binding.definition else {
4605                    continue;
4606                };
4607                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4608                    continue;
4609                };
4610                if !arities.contains(&arity) {
4611                    arities.push(arity);
4612                }
4613            }
4614        }
4615        let resolved = (|| {
4616            let required = arities
4617                .iter()
4618                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4619                .min()?;
4620            let total = arities.iter().map(|arity| arity.total()).max()?;
4621            let repeated = arities
4622                .iter()
4623                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4624            // Preprocessor conditions can leave more than one object-like parameter
4625            // bundle active in the target header's include closure. Preserve their
4626            // conservative callable envelope instead of choosing whichever definition
4627            // happened to be visited first.
4628            Some(CallableArity::new(required, total, repeated))
4629        })();
4630        self.callable_parameter_macro_arities
4631            .lock()
4632            .expect("C++ callable parameter-macro arity cache poisoned")
4633            .insert(cache_key, resolved);
4634        resolved
4635    }
4636
4637    pub fn include_activation_for_source(
4638        &self,
4639        cpp: &dyn CppSource,
4640        file: &ProjectFile,
4641        prepared: &PreparedSyntaxTree,
4642        donor_source: &ProjectFile,
4643    ) -> Option<usize> {
4644        let key = (file.clone(), donor_source.clone());
4645        if let Some(cached) = self
4646            .include_activation_cells
4647            .lock()
4648            .expect("C++ include activation cache poisoned")
4649            .get(&key)
4650            .copied()
4651        {
4652            return cached;
4653        }
4654        #[cfg(any(test, feature = "test-support"))]
4655        self.include_activation_build_count
4656            .fetch_add(1, Ordering::Relaxed);
4657        let language_guard_stable = OnceCell::new();
4658        let activation =
4659            find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4660                *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4661            });
4662        let mut cells = self
4663            .include_activation_cells
4664            .lock()
4665            .expect("C++ include activation cache poisoned");
4666        *cells.entry(key).or_insert(activation)
4667    }
4668
4669    pub fn conditional_include_projections_for_source(
4670        &self,
4671        file: &ProjectFile,
4672        prepared: &PreparedSyntaxTree,
4673        donor_source: &ProjectFile,
4674    ) -> Arc<[ConditionalIncludeProjection]> {
4675        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4676        let cell = self
4677            .conditional_include_projection_cells
4678            .lock()
4679            .expect("C++ conditional include projection cache poisoned")
4680            .entry(file.clone())
4681            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4682            .clone();
4683        let index = cell.get_or_build_pool_independent(|| {
4684            #[cfg(any(test, feature = "test-support"))]
4685            self.conditional_include_projection_index_build_count
4686                .fetch_add(1, Ordering::Relaxed);
4687            let language_guard_stable = OnceCell::new();
4688            find_conditional_include_projection_index(
4689                self.cpp,
4690                self.token,
4691                file,
4692                prepared,
4693                &|| {
4694                    *language_guard_stable
4695                        .get_or_init(|| self.include_language_guard_is_stable(file))
4696                },
4697                &|| {
4698                    #[cfg(any(test, feature = "test-support"))]
4699                    self.conditional_include_projection_state_count
4700                        .fetch_add(1, Ordering::Relaxed);
4701                },
4702            )
4703        });
4704        index
4705            .get(donor_source)
4706            .cloned()
4707            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4708    }
4709
4710    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4711        let mut macro_events = |file: &ProjectFile| {
4712            let cell = self.macro_event_cell(file);
4713            cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4714                .clone()
4715        };
4716        include_language_guard_is_stable_with(
4717            self.cpp,
4718            self.token,
4719            file,
4720            &mut macro_events,
4721            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4722                self.structured_include_facts(file, prepared)
4723            },
4724        )
4725    }
4726
4727    #[cfg(any(test, feature = "test-support"))]
4728    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4729        (
4730            self.conditional_include_projection_index_build_count
4731                .load(Ordering::Relaxed),
4732            self.conditional_include_projection_state_count
4733                .load(Ordering::Relaxed),
4734        )
4735    }
4736
4737    #[cfg(any(test, feature = "test-support"))]
4738    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4739        self.conditional_include_target_state_count
4740            .load(Ordering::Relaxed)
4741    }
4742
4743    #[cfg(any(test, feature = "test-support"))]
4744    pub fn include_activation_build_count_for_test(&self) -> usize {
4745        self.include_activation_build_count.load(Ordering::Relaxed)
4746    }
4747
4748    #[cfg(any(test, feature = "test-support"))]
4749    pub fn note_using_donor_activation_for_test(&self) {
4750        self.using_donor_activation_count
4751            .fetch_add(1, Ordering::Relaxed);
4752    }
4753
4754    #[cfg(not(any(test, feature = "test-support")))]
4755    pub fn note_using_donor_activation_for_test(&self) {}
4756
4757    #[cfg(any(test, feature = "test-support"))]
4758    pub fn note_using_namespace_lookup_for_test(&self) {
4759        self.using_namespace_lookup_count
4760            .fetch_add(1, Ordering::Relaxed);
4761    }
4762
4763    #[cfg(not(any(test, feature = "test-support")))]
4764    pub fn note_using_namespace_lookup_for_test(&self) {}
4765
4766    #[cfg(any(test, feature = "test-support"))]
4767    pub fn note_using_name_candidate_inspection_for_test(&self) {
4768        self.using_name_candidate_inspection_count
4769            .fetch_add(1, Ordering::Relaxed);
4770    }
4771
4772    #[cfg(not(any(test, feature = "test-support")))]
4773    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4774
4775    #[cfg(any(test, feature = "test-support"))]
4776    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4777        (
4778            self.using_donor_activation_count.load(Ordering::Relaxed),
4779            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4780            self.callable_reference_spec_build_count
4781                .load(Ordering::Relaxed),
4782            self.using_name_candidate_inspection_count
4783                .load(Ordering::Relaxed),
4784        )
4785    }
4786
4787    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4788        file == target.source()
4789            || self
4790                .visible_by_file
4791                .get(file)
4792                .is_some_and(|visible| visible.contains(target))
4793    }
4794
4795    /// Whether some declaration of `declaration`'s logical symbol is visible at
4796    /// `reference_byte` in `file`.
4797    ///
4798    /// The question is asked of the *logical* symbol, not of the physical unit:
4799    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4800    /// and it does not have to be - what makes the call legal is the header
4801    /// declaration that the reference file does include. Reading that relation
4802    /// through `same_logical_callable` rather than through signature strings is
4803    /// the same #2010 correction the gates make, and it matters here because
4804    /// the body and the declaration are exactly the pair that spells one
4805    /// parameter type two ways.
4806    pub fn declaration_visible_at(
4807        &self,
4808        analyzer: &CppGraphSource<'_>,
4809        file: &ProjectFile,
4810        declaration: &CodeUnit,
4811        reference_byte: usize,
4812    ) -> bool {
4813        let reference_guards = OnceCell::new();
4814        self.visible_identifier_candidates(file, declaration.identifier())
4815            .filter(|candidate| {
4816                self.same_logical_callable(analyzer, candidate, declaration)
4817                    || flattened_macro_namespace_declaration_matches(
4818                        analyzer,
4819                        self.cpp,
4820                        file,
4821                        candidate,
4822                        declaration,
4823                        reference_byte,
4824                    )
4825            })
4826            .any(|candidate| {
4827                self.physical_declaration_visible_at(
4828                    analyzer,
4829                    file,
4830                    candidate,
4831                    reference_byte,
4832                    &reference_guards,
4833                )
4834            })
4835    }
4836
4837    /// Whether a physical declaration is visible at an exact structured
4838    /// reference node. Inverse C field references need the reference's own
4839    /// preprocessor environment before using the callable activation path:
4840    /// callable activation can establish that a field declaration is
4841    /// nameable, but it must not admit the opposite branch of that field's
4842    /// conditional family.
4843    pub fn declaration_visible_at_reference(
4844        &self,
4845        analyzer: &CppGraphSource<'_>,
4846        file: &ProjectFile,
4847        declaration: &CodeUnit,
4848        reference: Node<'_>,
4849    ) -> bool {
4850        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4851            return false;
4852        };
4853        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4854        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4855        if !declaration_guards.iter().any(|(_, required)| {
4856            guards_compatible_at_reference(required, reference_guards.as_ref())
4857        }) {
4858            return false;
4859        }
4860        if declaration.source() == file
4861            && !analyzer.reference_uses_c_semantics(file)
4862            && (declaration.is_field() || declaration.is_callable())
4863            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4864                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4865                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4866            })
4867        {
4868            // C++ complete-class context makes members visible throughout the
4869            // class. The guard check above still excludes a declaration from
4870            // an incompatible preprocessor branch.
4871            return true;
4872        }
4873        if declaration.is_field() && declaration.source() == file {
4874            let reference_byte = reference.start_byte();
4875            let reference_function =
4876                real_function_definition_ancestor(reference, prepared.source());
4877            let guards = OnceCell::new();
4878            let field_reference = CallableReferenceContext {
4879                file,
4880                position: Some(CallableReferencePosition {
4881                    prepared: prepared.as_ref(),
4882                    byte: reference_byte,
4883                    guards: &guards,
4884                }),
4885            };
4886            let mut has_local_declaration = false;
4887            let mut local_declaration_visible = false;
4888            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4889            {
4890                let Some(declaration_function) =
4891                    real_function_definition_ancestor(declaration, prepared.source())
4892                else {
4893                    continue;
4894                };
4895                has_local_declaration = true;
4896                if reference_function.is_some_and(|reference_function| {
4897                    reference_function.start_byte() == declaration_function.start_byte()
4898                        && reference_function.end_byte() == declaration_function.end_byte()
4899                }) && callable_preprocessor_context_is_visible_for_reference(
4900                    declaration,
4901                    prepared.source(),
4902                    &field_reference,
4903                ) && declaration.end_byte() < reference_byte
4904                {
4905                    local_declaration_visible = true;
4906                    break;
4907                }
4908            }
4909            if has_local_declaration {
4910                return local_declaration_visible;
4911            }
4912        }
4913        let guards = OnceCell::new();
4914        self.physical_declaration_visible_at(
4915            analyzer,
4916            file,
4917            declaration,
4918            reference.start_byte(),
4919            &guards,
4920        )
4921    }
4922
4923    /// C forward navigation may bind a call to a later same-file definition.
4924    /// There is no earlier source declaration to activate in that legacy C
4925    /// shape, but the call's preprocessor environment must still imply the
4926    /// definition's requirements. Ordinary C++ and inverse visibility retain
4927    /// the declaration-order rule in [`Self::declaration_visible_at`].
4928    pub fn declaration_visible_for_c_forward_call(
4929        &self,
4930        analyzer: &CppGraphSource<'_>,
4931        file: &ProjectFile,
4932        declaration: &CodeUnit,
4933        reference_byte: usize,
4934    ) -> bool {
4935        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4936            return true;
4937        }
4938        if declaration.source() != file {
4939            return false;
4940        }
4941        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4942            return false;
4943        };
4944        let reference_guards = prepared
4945            .tree()
4946            .root_node()
4947            .descendant_for_byte_range(reference_byte, reference_byte)
4948            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4949        declaration_guard_requirements(analyzer, self.cpp, declaration)
4950            .into_iter()
4951            .any(|(_, required)| {
4952                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4953            })
4954    }
4955
4956    pub fn callable_arity_at_reference(
4957        &self,
4958        analyzer: &CppGraphSource<'_>,
4959        file: &ProjectFile,
4960        candidate: &CodeUnit,
4961        reference_byte: usize,
4962    ) -> Option<CallableArity> {
4963        let key = (file.clone(), logical_symbol_key(candidate));
4964        let cell = self
4965            .callable_reference_specs
4966            .lock()
4967            .expect("C++ callable reference-spec cache poisoned")
4968            .entry(key)
4969            .or_default()
4970            .clone();
4971        let spec = cell.get_or_init(|| {
4972            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4973            let spec = TargetSpec::from_target(analyzer, candidate)?;
4974            let spec = spec
4975                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4976                .into_owned();
4977            #[cfg(any(test, feature = "test-support"))]
4978            self.callable_reference_spec_build_count
4979                .fetch_add(1, Ordering::Relaxed);
4980            Some(spec)
4981        });
4982        spec.as_ref()?.callable_arity_at(reference_byte)
4983    }
4984
4985    fn physical_declaration_visible_at(
4986        &self,
4987        analyzer: &CppGraphSource<'_>,
4988        file: &ProjectFile,
4989        declaration: &CodeUnit,
4990        reference_byte: usize,
4991        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4992    ) -> bool {
4993        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4994            return false;
4995        };
4996        let reference = CallableReferenceContext {
4997            file,
4998            position: Some(CallableReferencePosition {
4999                prepared: prepared.as_ref(),
5000                byte: reference_byte,
5001                guards: reference_guards,
5002            }),
5003        };
5004        if declaration.source() == file {
5005            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5006                .or_else(|| {
5007                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5008                })
5009                .is_some_and(|activation| activation < reference_byte);
5010        }
5011        if self
5012            .foreign_callable_declaration_activation(analyzer, declaration, &reference)
5013            .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
5014            .is_none()
5015        {
5016            return false;
5017        }
5018        declaration_guard_requirements(analyzer, self.cpp, declaration)
5019            .into_iter()
5020            .any(|(_, declaration_guards)| {
5021                self.foreign_declaration_reachable_at_reference(
5022                    file,
5023                    prepared.as_ref(),
5024                    declaration.source(),
5025                    &declaration_guards,
5026                    reference.guards(),
5027                    reference_byte,
5028                )
5029            })
5030    }
5031
5032    /// The byte at which `declaration` activates inside the foreign file that
5033    /// its canonical source occurrence describes.
5034    ///
5035    /// Which guard rule applies depends on what decides the reference's
5036    /// configuration. When `compile_commands.json` covers the reference's
5037    /// translation unit, the build is the proof source and the declaration's
5038    /// guards must follow from the reference's active guards plus the proven
5039    /// defines; a platform macro the build does not prove leaves the
5040    /// declaration invisible (#2011). With no compile coverage nothing decides
5041    /// a platform macro, so the cross-file rule stands: the donor resolves its
5042    /// own conditionals and only has to stay free of contradiction with the
5043    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
5044    /// in a shared header is hidden from every unguarded caller in another
5045    /// translation unit (#2988).
5046    fn foreign_callable_declaration_activation(
5047        &self,
5048        analyzer: &CppGraphSource<'_>,
5049        declaration: &CodeUnit,
5050        reference: &CallableReferenceContext<'_>,
5051    ) -> Option<usize> {
5052        let build_decides = !self.compile_context_is_absent(reference.file);
5053        let proven = self.compile_proven_guards(reference.file);
5054        let augmented;
5055        let active = match reference.guards() {
5056            Some(active) if !proven.is_empty() => {
5057                augmented = active.union(&proven).cloned().collect();
5058                Some(&augmented)
5059            }
5060            other => other,
5061        };
5062        analyzer
5063            .declaration_source_occurrences(declaration)?
5064            .into_iter()
5065            .filter_map(|occurrence| {
5066                let activation = occurrence.fact.callable_activation?;
5067                let mut required =
5068                    cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5069                if required.iter().any(|guard| match guard {
5070                    PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
5071                    PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
5072                        !reference.is_c()
5073                    }
5074                    _ => false,
5075                }) {
5076                    return None;
5077                }
5078                required.retain(|guard| {
5079                    !matches!(guard,
5080                    PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5081                        if name == "__cplusplus")
5082                });
5083                let visible = required.is_empty()
5084                    || if build_decides {
5085                        guard_requirements_hold_at_reference(&required, active)
5086                    } else {
5087                        guards_compatible_at_reference(&required, reference.guards())
5088                    };
5089                visible.then_some(activation)
5090            })
5091            .min()
5092    }
5093
5094    pub fn external_type_candidate_visible_at(
5095        &self,
5096        file: &ProjectFile,
5097        candidate: &CodeUnit,
5098        reference_byte: usize,
5099    ) -> bool {
5100        if candidate.source() == file {
5101            return true;
5102        }
5103        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5104            return false;
5105        };
5106        self.visible_identifier_candidates(file, candidate.identifier())
5107            .filter(|peer| same_logical_symbol(candidate, peer))
5108            .any(|peer| {
5109                peer.source() == file
5110                    || self
5111                        .include_activation_for_source(
5112                            self.cpp,
5113                            file,
5114                            prepared.as_ref(),
5115                            peer.source(),
5116                        )
5117                        .is_some_and(|activation| activation <= reference_byte)
5118            })
5119    }
5120
5121    pub fn external_type_declaration_visible_at(
5122        &self,
5123        file: &ProjectFile,
5124        candidate: &CodeUnit,
5125        reference_byte: usize,
5126    ) -> bool {
5127        if candidate.source() == file {
5128            return true;
5129        }
5130        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5131            return false;
5132        };
5133        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5134            .is_some_and(|activation| activation <= reference_byte)
5135    }
5136
5137    /// The preprocessor facts the build proves for a reference sited in
5138    /// `file` (#2011).
5139    ///
5140    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5141    /// `Defined` fact, and a fact holds only when every compile configuration
5142    /// that governs the file agrees on it (intersection). The facts are
5143    /// strictly additive to the reference's active guard set: they can prove a
5144    /// required guard, but the guard check itself is never weakened and no
5145    /// implication is ever inferred from source text.
5146    ///
5147    /// A file with its own database entry answers from that entry alone
5148    /// (phase 1). A header takes its context from the translation units whose
5149    /// include closure reaches it, intersected across all of them (phase 2):
5150    /// the header is compiled once per including TU, so a fact holds for a
5151    /// header-sited reference only when every one of those compilations
5152    /// proves it. A reaching TU the database does not cover proves nothing,
5153    /// which empties the intersection. A file nothing covers or reaches has
5154    /// no facts and every check runs on source structure alone.
5155    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5156        if let Some(cached) = self
5157            .compile_proven_guard_cells
5158            .lock()
5159            .expect("C++ compile-proven guard cache poisoned")
5160            .get(file)
5161        {
5162            return Arc::clone(cached);
5163        }
5164        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5165            Some(names) => names,
5166            None => {
5167                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5168                let seed = translation_units.next().and_then(|translation_unit| {
5169                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5170                });
5171                match seed {
5172                    None => HashSet::default(),
5173                    Some(mut names) => {
5174                        for translation_unit in translation_units {
5175                            let Some(reached) = context_fact_names(
5176                                self.cpp.compile_contexts_for(&translation_unit),
5177                            ) else {
5178                                names.clear();
5179                                break;
5180                            };
5181                            names.retain(|name| reached.contains(name));
5182                            if names.is_empty() {
5183                                break;
5184                            }
5185                        }
5186                        names
5187                    }
5188                }
5189            }
5190        };
5191        let proven = Arc::new(
5192            names
5193                .into_iter()
5194                .map(PreprocessorGuard::Defined)
5195                .collect::<HashSet<_>>(),
5196        );
5197        self.compile_proven_guard_cells
5198            .lock()
5199            .expect("C++ compile-proven guard cache poisoned")
5200            .insert(file.clone(), Arc::clone(&proven));
5201        proven
5202    }
5203
5204    /// The rule a conditional `#include` path's guards face on the way to a
5205    /// reference in `file`.
5206    ///
5207    /// This is the include-edge analogue of the choice
5208    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5209    /// declaration's own guards (#2988), and it is decided by the same thing:
5210    /// what settles the reference's configuration. Memoized per file because
5211    /// the answer depends on nothing else, and the callers ask it once per
5212    /// candidate.
5213    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5214        if let Some(cached) = self
5215            .include_path_admission_cells
5216            .lock()
5217            .expect("C++ include-path admission cache poisoned")
5218            .get(file)
5219            .copied()
5220        {
5221            return cached;
5222        }
5223        let admission = if self.compile_context_is_absent(file) {
5224            IncludePathAdmission::Compatible
5225        } else {
5226            IncludePathAdmission::Proven
5227        };
5228        self.include_path_admission_cells
5229            .lock()
5230            .expect("C++ include-path admission cache poisoned")
5231            .insert(file.clone(), admission);
5232        admission
5233    }
5234
5235    /// Whether no compile data covers the compilations of `file`: it has no
5236    /// database entry of its own, and either nothing reaches it or some
5237    /// translation unit that reaches it has no entry. This is the state a
5238    /// regenerated `compile_commands.json` could decide; data that is present
5239    /// for every governing compilation but does not prove a guard is a
5240    /// decided conservative miss, not this state.
5241    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5242        if !self.cpp.compile_contexts_for(file).is_empty() {
5243            return false;
5244        }
5245        let translation_units = self.cpp.reaching_translation_units(file);
5246        translation_units.is_empty()
5247            || translation_units
5248                .iter()
5249                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5250    }
5251
5252    /// Whether a lookup miss for `identifier` in `file` is explainable by
5253    /// missing compile context (#2011): some same-name declaration is
5254    /// reachable through a conditional include whose required guards neither
5255    /// contradict the reference's active guards nor follow from them, and the
5256    /// translation unit has no compile-commands entry that could decide the
5257    /// question. Callers surface this as an explicit "requires compile
5258    /// context" incompleteness instead of an indistinguishable miss.
5259    ///
5260    /// A structurally disproven declaration (contradicting guards) and a TU
5261    /// whose compile context exists but does not prove the guard both answer
5262    /// `false`: those misses are decided, not incomplete.
5263    pub fn miss_requires_compile_context(
5264        &self,
5265        file: &ProjectFile,
5266        identifier: &str,
5267        reference: Node<'_>,
5268    ) -> bool {
5269        if !self.compile_context_is_absent(file) {
5270            return false;
5271        }
5272        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5273            return false;
5274        };
5275        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5276        let reference_byte = reference.start_byte();
5277        let mut sources = self
5278            .visible_identifier_candidates(file, identifier)
5279            .map(CodeUnit::source)
5280            .filter(|source| *source != file)
5281            .collect::<Vec<_>>();
5282        sources.sort();
5283        sources.dedup();
5284        sources.into_iter().any(|declaration_source| {
5285            self.conditional_include_projections_for_source(
5286                file,
5287                prepared.as_ref(),
5288                declaration_source,
5289            )
5290            .iter()
5291            .any(|projection| {
5292                projection.activation_byte <= reference_byte
5293                    && !guard_requirements_hold_at_reference(
5294                        &projection.required_guards,
5295                        reference_guards.as_ref(),
5296                    )
5297                    && guards_compatible_at_reference(
5298                        &projection.required_guards,
5299                        reference_guards.as_ref(),
5300                    )
5301            })
5302        })
5303    }
5304
5305    /// Decide whether a declaration that lives in another file reaches a
5306    /// reference in `file`.
5307    ///
5308    /// An external header selects its declaration branch before the reference
5309    /// file is parsed. Require compatible reference guards, but do not test
5310    /// the header's guard expression for stability in the reference file: a
5311    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5312    /// wraps every declaration of a portable C header, and demanding it would
5313    /// hide the whole header. Guards that the reference file imposes on its
5314    /// own `#include` still have to be stable, and must satisfy the rule
5315    /// [`IncludePathAdmission`] names for this reference's translation unit.
5316    fn foreign_declaration_reachable_at_reference(
5317        &self,
5318        file: &ProjectFile,
5319        prepared: &PreparedSyntaxTree,
5320        declaration_source: &ProjectFile,
5321        declaration_guards: &HashSet<PreprocessorGuard>,
5322        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5323        reference_byte: usize,
5324    ) -> bool {
5325        // The translation unit's build-proven defines join the reference's
5326        // active guard set (#2011): a conditional include like the nng
5327        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5328        // A reference whose own environment is unknown stays unknown -- the
5329        // facts extend an environment, they never invent one.
5330        let proven = self.compile_proven_guards(file);
5331        let augmented;
5332        let reference_guards = match reference_guards {
5333            Some(active) if !proven.is_empty() => {
5334                augmented = active.union(&proven).cloned().collect();
5335                Some(&augmented)
5336            }
5337            other => other,
5338        };
5339        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5340            eprintln!(
5341                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5342                declaration_source.rel_path().display(),
5343            );
5344        }
5345        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5346            return false;
5347        }
5348        if self
5349            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5350            .is_some_and(|activation| activation <= reference_byte)
5351        {
5352            return true;
5353        }
5354        let projections =
5355            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5356        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5357            eprintln!(
5358                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5359                declaration_source.rel_path().display(),
5360                declaration_guards.len(),
5361                proven.len(),
5362                projections.len(),
5363            );
5364        }
5365        let admission = self.include_path_admission(file);
5366        projections.iter().any(|projection| {
5367            projection.activation_byte <= reference_byte
5368                && admission.admits(
5369                    &projection.required_guards,
5370                    &projection.partial_guards,
5371                    reference_guards,
5372                )
5373                && self.preprocessor_guards_stable_between(
5374                    file,
5375                    projection.activation_byte,
5376                    reference_byte,
5377                    &projection.required_guards,
5378                )
5379        })
5380    }
5381
5382    fn foreign_declaration_may_be_reachable_from_raw_guards(
5383        &self,
5384        file: &ProjectFile,
5385        prepared: &PreparedSyntaxTree,
5386        declaration_source: &ProjectFile,
5387        declaration_guards: &HashSet<PreprocessorGuard>,
5388        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5389        reference_byte: usize,
5390    ) -> bool {
5391        let proven = self.compile_proven_guards(file);
5392        let augmented;
5393        let reference_guards = match reference_guards {
5394            Some(active) if !proven.is_empty() => {
5395                augmented = active.union(&proven).cloned().collect();
5396                Some(&augmented)
5397            }
5398            other => other,
5399        };
5400        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5401            return false;
5402        }
5403        if self
5404            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5405            .is_some_and(|activation| activation <= reference_byte)
5406        {
5407            return true;
5408        }
5409        let language_guard_stable = OnceCell::new();
5410        let reachable = find_conditional_include_projection_for_source(
5411            self.cpp,
5412            self.token,
5413            file,
5414            prepared,
5415            declaration_source,
5416            self.include_path_admission(file),
5417            reference_guards,
5418            reference_byte,
5419            &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5420            &|| {
5421                #[cfg(any(test, feature = "test-support"))]
5422                self.conditional_include_target_state_count
5423                    .fetch_add(1, Ordering::Relaxed);
5424            },
5425        );
5426        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5427            eprintln!(
5428                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5429                declaration_source.rel_path().display(),
5430                declaration_guards.len(),
5431                proven.len(),
5432                reference_guards.map_or(0, HashSet::len),
5433            );
5434        }
5435        reachable
5436    }
5437
5438    fn foreign_declaration_reachable_from_compile_proven_guards(
5439        &self,
5440        file: &ProjectFile,
5441        prepared: &PreparedSyntaxTree,
5442        declaration_source: &ProjectFile,
5443        declaration_guards: &HashSet<PreprocessorGuard>,
5444        reference_byte: usize,
5445    ) -> bool {
5446        let proven = self.compile_proven_guards(file);
5447        if proven.is_empty()
5448            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5449        {
5450            return false;
5451        }
5452        let projections =
5453            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5454        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5455            eprintln!(
5456                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5457                declaration_source.rel_path().display(),
5458                declaration_guards.len(),
5459                proven.len(),
5460                projections.len(),
5461            );
5462        }
5463        projections.iter().any(|projection| {
5464            projection.activation_byte <= reference_byte
5465                    && guard_requirements_hold_at_reference(
5466                        &projection.required_guards,
5467                        Some(proven.as_ref()),
5468                    )
5469                    // Build facts hold at translation-unit entry. A source
5470                    // `#undef` or an earlier include may invalidate one before
5471                    // this conditional include is reached; mutations after the
5472                    // include cannot revoke declarations it already supplied.
5473                    && self.preprocessor_guards_stable_between(
5474                        file,
5475                        0,
5476                        projection.activation_byte,
5477                        &projection.required_guards,
5478                    )
5479        })
5480    }
5481
5482    pub fn external_type_candidate_visible_in_context(
5483        &self,
5484        analyzer: &CppGraphSource<'_>,
5485        file: &ProjectFile,
5486        candidate: &CodeUnit,
5487        reference: Node<'_>,
5488    ) -> bool {
5489        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5490        if report_stats {
5491            eprintln!(
5492                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5493                candidate.fq_name(),
5494                candidate.source().rel_path().display(),
5495                file.rel_path().display(),
5496                reference.start_byte(),
5497            );
5498        }
5499        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5500            return false;
5501        };
5502        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5503        let reference_guards = OnceCell::new();
5504        let reference_guards_at_site = || {
5505            reference_guards.get_or_init(|| {
5506                let started = Instant::now();
5507                if report_stats {
5508                    eprintln!(
5509                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5510                        file.rel_path().display(),
5511                        reference.start_byte(),
5512                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5513                    );
5514                }
5515                let macro_environment = self.macro_environment(file, reference.start_byte());
5516                let filtered = raw_reference_guards
5517                    .clone()
5518                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5519                if report_stats {
5520                    eprintln!(
5521                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5522                        filtered.is_some(),
5523                        started.elapsed().as_millis(),
5524                    );
5525                }
5526                filtered
5527            })
5528        };
5529
5530        let peers = self
5531            .visible_identifier_candidates(file, candidate.identifier())
5532            .filter(|peer| same_logical_symbol(candidate, peer))
5533            .collect::<Vec<_>>();
5534        if report_stats {
5535            let peer_sources = peers
5536                .iter()
5537                .map(|peer| peer.source().rel_path().display().to_string())
5538                .collect::<Vec<_>>();
5539            eprintln!(
5540                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5541                candidate.fq_name(),
5542            );
5543        }
5544        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5545            declaration_guard_requirements(analyzer, self.cpp, peer)
5546                .into_iter()
5547                .any(|(declaration_byte, declaration_guards)| {
5548                    if peer.source() == file {
5549                        let visible = declaration_byte < reference.start_byte()
5550                            && declaration_guards.is_empty();
5551                        if report_stats {
5552                            eprintln!(
5553                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5554                                peer.source().rel_path().display(),
5555                                declaration_guards.len(),
5556                            );
5557                        }
5558                        return visible;
5559                    }
5560                    let direct = declaration_guards.is_empty()
5561                        && self
5562                            .include_activation_for_source(
5563                                self.cpp,
5564                                file,
5565                                prepared.as_ref(),
5566                                peer.source(),
5567                            )
5568                            .is_some_and(|activation| activation <= reference.start_byte());
5569                    let compile_proven = !direct
5570                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5571                            file,
5572                            prepared.as_ref(),
5573                            peer.source(),
5574                            &declaration_guards,
5575                            reference.start_byte(),
5576                        );
5577                    if report_stats {
5578                        eprintln!(
5579                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5580                            peer.source().rel_path().display(),
5581                            declaration_guards.len(),
5582                        );
5583                    }
5584                    direct || compile_proven
5585                })
5586        });
5587        if directly_visible_without_reference_environment {
5588            if report_stats {
5589                eprintln!(
5590                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5591                    candidate.fq_name(),
5592                );
5593            }
5594            return true;
5595        }
5596        let directly_visible = peers.iter().any(|peer| {
5597            declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5598                .into_iter()
5599                .any(|site| {
5600                    let declaration_byte = site.byte;
5601                    let declaration_guards = site.guards;
5602                    if peer.source() == file {
5603                        if declaration_byte >= reference.start_byte() {
5604                            return false;
5605                        }
5606                        // The reference's own conditional context must either
5607                        // prove the declaration's requirements, or follow a
5608                        // completed family that encloses the declaration
5609                        // (#3297); either way it must not contradict them.
5610                        let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5611                            guard_requirements_hold_at_reference(&declaration_guards, guards)
5612                                || (site.completed_family_before_reference
5613                                    && guards_compatible_at_reference(&declaration_guards, guards))
5614                        };
5615                        if !active(raw_reference_guards.as_ref()) {
5616                            return false;
5617                        }
5618                        return active(reference_guards_at_site().as_ref())
5619                            && self.preprocessor_guards_stable_between(
5620                                file,
5621                                declaration_byte,
5622                                reference.start_byte(),
5623                                &declaration_guards,
5624                            );
5625                    }
5626                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5627                        file,
5628                        prepared.as_ref(),
5629                        peer.source(),
5630                        &declaration_guards,
5631                        raw_reference_guards.as_ref(),
5632                        reference.start_byte(),
5633                    );
5634                    if report_stats {
5635                        eprintln!(
5636                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5637                            peer.source().rel_path().display(),
5638                            declaration_guards.len(),
5639                        );
5640                    }
5641                    if !raw_feasible {
5642                        return false;
5643                    }
5644                    self.foreign_declaration_reachable_at_reference(
5645                        file,
5646                        prepared.as_ref(),
5647                        peer.source(),
5648                        &declaration_guards,
5649                        reference_guards_at_site().as_ref(),
5650                        reference.start_byte(),
5651                    )
5652                })
5653        });
5654        if directly_visible {
5655            if report_stats {
5656                eprintln!(
5657                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5658                    candidate.fq_name(),
5659                );
5660            }
5661            return true;
5662        }
5663        let complementary = self
5664            .visible_identifier_candidates(file, candidate.identifier())
5665            .filter(|peer| {
5666                peer.kind() == candidate.kind()
5667                    && peer.fq_name() == candidate.fq_name()
5668                    && peer.source() == candidate.source()
5669            })
5670            .collect::<Vec<_>>();
5671        // A completed #if/#else family declares the shared source-level name
5672        // before this reference. A later macro mutation cannot revoke that
5673        // declaration. The family gate below rejects declarations split across
5674        // separate conditional blocks, where mutation can change coverage.
5675        let complementary_family =
5676            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5677        let raw_candidate_branch_compatible = complementary_family
5678            && raw_reference_guards.as_ref().is_some_and(|active| {
5679                declaration_guard_requirements(analyzer, self.cpp, candidate)
5680                    .iter()
5681                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5682            });
5683        if report_stats {
5684            eprintln!(
5685                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5686                candidate.fq_name(),
5687                complementary.len(),
5688                complementary_family,
5689                raw_candidate_branch_compatible,
5690            );
5691        }
5692        let candidate_branch_compatible = raw_candidate_branch_compatible
5693            && reference_guards_at_site().as_ref().is_some_and(|active| {
5694                declaration_guard_requirements(analyzer, self.cpp, candidate)
5695                    .iter()
5696                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5697            });
5698        let complementary_visible = candidate_branch_compatible
5699            && if candidate.source() == file {
5700                declaration_guard_requirements(analyzer, self.cpp, candidate)
5701                    .iter()
5702                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5703            } else {
5704                self.include_activation_for_source(
5705                    self.cpp,
5706                    file,
5707                    prepared.as_ref(),
5708                    candidate.source(),
5709                )
5710                .is_some_and(|activation| activation <= reference.start_byte())
5711            };
5712        if report_stats {
5713            eprintln!(
5714                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5715                if complementary_visible {
5716                    "complementary"
5717                } else {
5718                    "missing"
5719                },
5720                candidate.fq_name(),
5721            );
5722        }
5723        complementary_visible
5724    }
5725
5726    pub fn is_exhaustive_same_fqn_type_declaration_family(
5727        &self,
5728        analyzer: &CppGraphSource<'_>,
5729        file: &ProjectFile,
5730        candidate: &CodeUnit,
5731    ) -> bool {
5732        let candidates = self
5733            .visible_identifier_candidates(file, candidate.identifier())
5734            .filter(|peer| {
5735                peer.kind() == candidate.kind()
5736                    && peer.fq_name() == candidate.fq_name()
5737                    && peer.source() == candidate.source()
5738            })
5739            .collect::<Vec<_>>();
5740        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5741    }
5742
5743    /// Prove a nested type alias used as a dependent member-pointer owner when
5744    /// its owning class has mutually-exclusive declarations.  A common C++11
5745    /// compatibility shape provides the owning class in one preprocessor
5746    /// branch and aliases it to a standard-library type in the other branch;
5747    /// the nested fallback alias is therefore not itself active in every
5748    /// branch even though the qualified owner API is.
5749    ///
5750    /// This is deliberately narrower than ordinary type visibility.  The
5751    /// caller has already recovered a member-pointer owner path from the CST;
5752    /// this helper additionally requires the target's structured parent to
5753    /// match that path, physical source visibility, and exact preprocessor
5754    /// guard agreement with the parent declaration.  Only then may the
5755    /// parent's direct/complementary same-FQN visibility stand in for the
5756    /// nested terminal's active-branch check.
5757    pub fn dependent_member_pointer_alias_visible_in_context(
5758        &self,
5759        analyzer: &CppGraphSource<'_>,
5760        file: &ProjectFile,
5761        candidate: &CodeUnit,
5762        owner_components: &[String],
5763        reference: Node<'_>,
5764    ) -> bool {
5765        if !analyzer
5766            .type_alias_provider()
5767            .is_some_and(|provider| provider.is_type_alias(candidate))
5768        {
5769            return false;
5770        }
5771        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5772            return false;
5773        };
5774        if terminal != candidate.identifier()
5775            || canonical_cpp_scope_components(candidate) != owner_components
5776        {
5777            return false;
5778        }
5779        let Some(expected_parent_fq_name) =
5780            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5781        else {
5782            return false;
5783        };
5784        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5785            return false;
5786        };
5787        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5788            || parent_anchor.source() != candidate.source()
5789            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5790        {
5791            return false;
5792        }
5793
5794        // The ordinary path already handles unguarded aliases (and preserves
5795        // same-file declaration ordering).  This fallback is only for a
5796        // physically visible declaration whose guard is the owning branch's
5797        // guard, so reject a same-file declaration that appears after the
5798        // reference before considering guard compatibility.
5799        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5800            || candidate.source() == file
5801                && !analyzer
5802                    .ranges(candidate)
5803                    .iter()
5804                    .any(|range| range.start_byte < reference.start_byte())
5805        {
5806            return false;
5807        }
5808
5809        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5810        if candidate_guards.is_empty() {
5811            return false;
5812        }
5813        let same_guard_sets =
5814            |left: &[(usize, HashSet<PreprocessorGuard>)],
5815             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5816                left.iter().all(|(_, left_guards)| {
5817                    right
5818                        .iter()
5819                        .any(|(_, right_guards)| left_guards == right_guards)
5820                })
5821            };
5822        let parent_candidates = self
5823            .visible_identifier_candidates(file, parent_anchor.identifier())
5824            .filter(|peer| {
5825                peer.kind() == parent_anchor.kind()
5826                    && peer.fq_name() == expected_parent_fq_name.as_str()
5827                    && peer.source() == parent_anchor.source()
5828                    && canonical_cpp_scope_components(peer) == owner_prefix
5829            })
5830            .filter_map(|peer| {
5831                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5832                (candidate_guards.len() == parent_guards.len()
5833                    && same_guard_sets(&candidate_guards, &parent_guards)
5834                    && same_guard_sets(&parent_guards, &candidate_guards))
5835                .then(|| (peer.clone(), parent_guards))
5836            })
5837            .collect::<Vec<_>>();
5838        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5839            return false;
5840        };
5841
5842        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5843            return false;
5844        };
5845        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5846        else {
5847            return false;
5848        };
5849        // An external header selects its declaration branch before the
5850        // reference file is parsed. Require compatible reference guards, but
5851        // do not test the header's guard expression for stability in the
5852        // reference file. Same-file aliases still require that stability.
5853        if !candidate_guards.iter().any(|(_, target_guards)| {
5854            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5855                && (candidate.source() != file
5856                    || self.preprocessor_guards_stable_between(
5857                        file,
5858                        0,
5859                        reference.start_byte(),
5860                        target_guards,
5861                    ))
5862        }) {
5863            return false;
5864        }
5865
5866        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5867    }
5868
5869    /// Check a type candidate's preprocessor/import context without imposing
5870    /// ordinary declaration-before-reference ordering for same-file peers.
5871    ///
5872    /// C++ class scope makes member names visible throughout the complete
5873    /// class, including a trailing return type that appears before the member
5874    /// alias declaration in source order. Callers must first prove that the
5875    /// reference is inside the candidate's indexed class owner; this helper
5876    /// only relaxes the byte-order predicate while retaining guard and include
5877    /// activation checks.
5878    pub fn external_type_candidate_guard_compatible_in_context(
5879        &self,
5880        analyzer: &CppGraphSource<'_>,
5881        file: &ProjectFile,
5882        candidate: &CodeUnit,
5883        reference: Node<'_>,
5884    ) -> bool {
5885        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5886            return false;
5887        };
5888        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5889
5890        self.visible_identifier_candidates(file, candidate.identifier())
5891            .filter(|peer| same_logical_symbol(candidate, peer))
5892            .any(|peer| {
5893                declaration_guard_requirements(analyzer, self.cpp, peer)
5894                    .into_iter()
5895                    .any(|(declaration_byte, declaration_guards)| {
5896                        if peer.source() == file {
5897                            let (start, end) = if declaration_byte <= reference.start_byte() {
5898                                (declaration_byte, reference.start_byte())
5899                            } else {
5900                                (reference.start_byte(), declaration_byte)
5901                            };
5902                            return guard_requirements_hold_at_reference(
5903                                &declaration_guards,
5904                                reference_guards.as_ref(),
5905                            ) && self.preprocessor_guards_stable_between(
5906                                file,
5907                                start,
5908                                end,
5909                                &declaration_guards,
5910                            );
5911                        }
5912                        self.foreign_declaration_reachable_at_reference(
5913                            file,
5914                            prepared.as_ref(),
5915                            peer.source(),
5916                            &declaration_guards,
5917                            reference_guards.as_ref(),
5918                            reference.start_byte(),
5919                        )
5920                    })
5921            })
5922    }
5923
5924    /// Whether a same-file callable declaration is nameable from `reference`
5925    /// after deliberately relaxing declaration-before-reference ordering.
5926    ///
5927    /// Ordinary lookup still requires an earlier declaration. Definition
5928    /// navigation for incomplete C translation units may recover a later
5929    /// definition, but only when it is at file scope and its preprocessor
5930    /// requirements hold at the call (#2404).
5931    pub fn same_file_callable_guard_compatible_ignoring_order(
5932        &self,
5933        analyzer: &CppGraphSource<'_>,
5934        file: &ProjectFile,
5935        candidate: &CodeUnit,
5936        reference: Node<'_>,
5937    ) -> bool {
5938        if candidate.source() != file || !candidate.is_callable() {
5939            return false;
5940        }
5941        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5942            return false;
5943        };
5944        let guards = OnceCell::new();
5945        let context = CallableReferenceContext {
5946            file,
5947            position: Some(CallableReferencePosition {
5948                prepared: prepared.as_ref(),
5949                byte: reference.start_byte(),
5950                guards: &guards,
5951            }),
5952        };
5953        analyzer
5954            .declaration_source_occurrences(candidate)
5955            .into_iter()
5956            .flatten()
5957            .filter_map(|occurrence| {
5958                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5959                Some(callable_guard_requirements_are_visible(
5960                    &guards,
5961                    occurrence.fact.callable_guard_completion_byte,
5962                    &context,
5963                ))
5964            })
5965            .any(|visible| visible)
5966    }
5967
5968    pub fn type_candidate_may_be_visible_before_reference(
5969        &self,
5970        analyzer: &CppGraphSource<'_>,
5971        file: &ProjectFile,
5972        candidate: &CodeUnit,
5973        reference_byte: usize,
5974    ) -> bool {
5975        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5976            return false;
5977        };
5978        let root = prepared.tree().root_node();
5979        let end_byte = reference_byte
5980            .saturating_add(1)
5981            .min(prepared.source().len());
5982        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5983            return false;
5984        };
5985        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5986    }
5987
5988    pub fn preprocessor_guards_stable_between(
5989        &self,
5990        file: &ProjectFile,
5991        start_byte: usize,
5992        end_byte: usize,
5993        guards: &HashSet<PreprocessorGuard>,
5994    ) -> bool {
5995        if guards.is_empty() || start_byte >= end_byte {
5996            return true;
5997        }
5998        let cell = self.macro_event_cell(file);
5999        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6000        let mut visited = HashSet::from_iter([file.clone()]);
6001        !events.iter().any(|event| {
6002            event.byte() >= start_byte
6003                && event.byte() < end_byte
6004                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6005        })
6006    }
6007
6008    fn macro_event_may_mutate_guards(
6009        &self,
6010        event: &MacroEvent,
6011        guards: &HashSet<PreprocessorGuard>,
6012        visited: &mut HashSet<ProjectFile>,
6013    ) -> bool {
6014        match event {
6015            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6016                guards.iter().any(|guard| guard.may_depend_on_macro(name))
6017            }
6018            MacroEvent::Include { targets, .. } => {
6019                targets.is_empty()
6020                    || targets
6021                        .iter()
6022                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
6023            }
6024            MacroEvent::Invalidate { .. } => true,
6025        }
6026    }
6027
6028    fn source_may_mutate_guards(
6029        &self,
6030        file: &ProjectFile,
6031        guards: &HashSet<PreprocessorGuard>,
6032        visited: &mut HashSet<ProjectFile>,
6033    ) -> bool {
6034        if !visited.insert(file.clone()) {
6035            return false;
6036        }
6037        let cell = self.macro_event_cell(file);
6038        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6039        events
6040            .iter()
6041            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6042    }
6043
6044    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6045        let normalized = normalize_reference_name(raw_name)?;
6046        self.type_candidates(file, &normalized)
6047            .into_iter()
6048            .next()
6049            .cloned()
6050    }
6051
6052    /// Mirror forward navigation's visible-name fallback for a bare parameter
6053    /// type after lexical owner and inheritance lookup is exhausted.
6054    ///
6055    /// Generated or otherwise unindexed base classes can hide the alias that
6056    /// makes a parameter type valid C++. Accept the fallback only when every
6057    /// include-visible class or alias with that spelling canonicalizes to one
6058    /// logical type. A shadowing local type resolves lexically before this
6059    /// path, while distinct visible types keep the result ambiguous.
6060    pub fn unique_visible_parameter_type_fallback(
6061        &self,
6062        analyzer: &CppGraphSource<'_>,
6063        file: &ProjectFile,
6064        node: Node<'_>,
6065        source: &str,
6066    ) -> Option<CodeUnit> {
6067        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6068            return None;
6069        }
6070        let name = node_text(node, source);
6071        let candidates = self
6072            .visible_identifier_candidates(file, name)
6073            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6074            .filter(|candidate| {
6075                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6076            })
6077            .collect::<Vec<_>>();
6078        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6079    }
6080
6081    pub fn resolve_type_node_result(
6082        &self,
6083        file: &ProjectFile,
6084        node: Node<'_>,
6085        source: &str,
6086    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6087        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6088            return Ok(None);
6089        };
6090        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6091            return Ok(Some(primary));
6092        };
6093        self.resolve_template_arguments(file, primary, &arguments)
6094            .map(Some)
6095    }
6096
6097    pub fn resolve_type_node_primary(
6098        &self,
6099        file: &ProjectFile,
6100        node: Node<'_>,
6101        source: &str,
6102    ) -> Option<CodeUnit> {
6103        let components = cpp_type_name_components(node, source)?;
6104        self.resolve_type(file, &components.join("::"))
6105    }
6106
6107    pub fn resolve_template_arguments(
6108        &self,
6109        file: &ProjectFile,
6110        primary: CodeUnit,
6111        arguments: &[CppTemplateExpression],
6112    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6113        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6114    }
6115
6116    fn resolve_template_arguments_inner(
6117        &self,
6118        file: &ProjectFile,
6119        primary: CodeUnit,
6120        arguments: &[CppTemplateExpression],
6121        seen_aliases: &mut HashSet<CodeUnit>,
6122    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6123        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6124            && let Some(alias_target) = &metadata.alias_target
6125        {
6126            if !seen_aliases.insert(primary.clone()) {
6127                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6128            }
6129            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6130                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6131            let target_name = alias_target.components.join("::");
6132            let target_primary = if alias_target.global {
6133                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6134            } else {
6135                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6136            };
6137            let Some(target_primary) = target_primary else {
6138                // A dependent or external RHS cannot be canonicalized from the
6139                // indexed graph. Preserve the alias's direct identity instead
6140                // of inventing a target from its source spelling.
6141                return Ok(primary);
6142            };
6143            let Some(target_arguments) = &alias_target.arguments else {
6144                return Ok(target_primary);
6145            };
6146            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6147                .ok_or(CppTemplateResolutionError::Substitution)?;
6148            return self.resolve_template_arguments_inner(
6149                file,
6150                target_primary,
6151                &target_arguments,
6152                seen_aliases,
6153            );
6154        }
6155
6156        let primary_fq_name = self
6157            .cpp_template_metadata
6158            .get(&primary)
6159            .map(|metadata| metadata.primary_fq_name.clone())
6160            .unwrap_or_else(|| primary.fq_name());
6161        let has_specialization_metadata = self
6162            .cpp_template_families
6163            .get(&primary_fq_name)
6164            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6165        if !has_specialization_metadata {
6166            return Ok(primary);
6167        }
6168        self.select_template_specialization(file, &primary, arguments)
6169    }
6170
6171    fn select_template_specialization(
6172        &self,
6173        file: &ProjectFile,
6174        resolved: &CodeUnit,
6175        explicit_arguments: &[CppTemplateExpression],
6176    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6177        let primary_fq_name = self
6178            .cpp_template_metadata
6179            .get(resolved)
6180            .map(|metadata| metadata.primary_fq_name.clone())
6181            .unwrap_or_else(|| resolved.fq_name());
6182        let family = self
6183            .cpp_template_families
6184            .get(&primary_fq_name)
6185            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6186        let primary_candidates = family
6187            .iter()
6188            .filter_map(|unit| {
6189                let metadata = self.cpp_template_metadata.get(unit)?;
6190                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6191            })
6192            .collect::<Vec<_>>();
6193        let primary_unit = primary_candidates
6194            .iter()
6195            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6196            .or_else(|| {
6197                primary_candidates
6198                    .iter()
6199                    .map(|(unit, _)| *unit)
6200                    .min_by_key(|unit| {
6201                        (
6202                            unit.source().to_string(),
6203                            unit.signature().unwrap_or_default(),
6204                        )
6205                    })
6206            })
6207            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6208        let primary_parameters =
6209            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6210                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6211        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6212            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6213
6214        let mut applicable = Vec::new();
6215        for unit in family {
6216            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6217                continue;
6218            };
6219            if metadata.is_primary() || !self.is_visible(file, unit) {
6220                continue;
6221            }
6222            if !cpp_specialization_matches(metadata, &expanded) {
6223                continue;
6224            }
6225            applicable.push((unit, metadata));
6226        }
6227        if applicable.is_empty() {
6228            return Ok(primary_unit.clone());
6229        }
6230
6231        // A scalar constraint count cannot represent C++ partial ordering:
6232        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6233        // Select only a logical candidate whose structural pattern is strictly
6234        // more specialized than every other distinct applicable candidate.
6235        let winners = applicable
6236            .iter()
6237            .filter(|(candidate, candidate_metadata)| {
6238                applicable.iter().all(|(other, other_metadata)| {
6239                    same_visible_symbol(candidate, other)
6240                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6241                })
6242            })
6243            .copied()
6244            .collect::<Vec<_>>();
6245        let Some((selected, _)) = winners.first() else {
6246            // Mutually incomparable applicable candidates: every one of them
6247            // is a live contender.
6248            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6249                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6250            });
6251        };
6252        if winners
6253            .iter()
6254            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6255        {
6256            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6257                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6258            });
6259        }
6260        Ok((*selected).clone())
6261    }
6262
6263    pub fn resolve_type_components_lexically(
6264        &self,
6265        analyzer: &CppGraphSource<'_>,
6266        file: &ProjectFile,
6267        components: &[String],
6268        global: bool,
6269        lexical_scope: &[String],
6270    ) -> LexicalTypeResolution {
6271        self.resolve_type_components_lexically_inner(
6272            analyzer,
6273            file,
6274            components,
6275            global,
6276            lexical_scope,
6277            TypeCandidateResolution::Canonical,
6278        )
6279    }
6280
6281    pub fn resolve_type_components_lexically_for_forward(
6282        &self,
6283        analyzer: &CppGraphSource<'_>,
6284        file: &ProjectFile,
6285        components: &[String],
6286        global: bool,
6287        lexical_scope: &[String],
6288    ) -> LexicalTypeResolution {
6289        self.resolve_type_components_lexically_inner(
6290            analyzer,
6291            file,
6292            components,
6293            global,
6294            lexical_scope,
6295            TypeCandidateResolution::PreserveAlias,
6296        )
6297    }
6298
6299    pub fn resolve_type_components_lexically_for_target(
6300        &self,
6301        analyzer: &CppGraphSource<'_>,
6302        file: &ProjectFile,
6303        components: &[String],
6304        global: bool,
6305        lexical_scope: &[String],
6306        target: &CodeUnit,
6307    ) -> LexicalTypeResolution {
6308        #[cfg(any(test, feature = "test-support"))]
6309        self.target_preserving_type_resolution_count
6310            .fetch_add(1, Ordering::Relaxed);
6311        self.resolve_type_components_lexically_inner(
6312            analyzer,
6313            file,
6314            components,
6315            global,
6316            lexical_scope,
6317            TypeCandidateResolution::PreserveTarget(target),
6318        )
6319    }
6320
6321    pub fn coarse_unqualified_type_reference_may_resolve(
6322        &self,
6323        file: &ProjectFile,
6324        name: &str,
6325    ) -> bool {
6326        if name.is_empty() {
6327            return true;
6328        }
6329        self.visible_identifier_candidates(file, name)
6330            .any(|candidate| {
6331                candidate.kind() == CodeUnitType::Class
6332                    || declared_type_alias(&self.cpp_source(), candidate)
6333            })
6334            || self.visible_parser_alias_name_is_visible(file, name)
6335    }
6336
6337    #[allow(clippy::too_many_arguments)]
6338    pub fn structured_type_reference_may_resolve_to_target(
6339        &self,
6340        analyzer: &CppGraphSource<'_>,
6341        file: &ProjectFile,
6342        components: &[String],
6343        global: bool,
6344        lexical_scope: &[String],
6345        target: &CodeUnit,
6346    ) -> bool {
6347        if components.is_empty() {
6348            return true;
6349        }
6350        let Some(terminal) = components.last() else {
6351            return true;
6352        };
6353        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6354            .map(|qualified| qualified.join("::"))
6355            .collect::<Vec<_>>();
6356        let target_name = cpp_name_for(target);
6357        if qualified_tiers
6358            .iter()
6359            .any(|qualified| qualified == &target_name)
6360        {
6361            return true;
6362        }
6363
6364        let mut saw_shape_candidate = false;
6365        for candidate in self.visible_identifier_candidates(file, terminal) {
6366            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6367            {
6368                continue;
6369            }
6370            let candidate_name = cpp_name_for(candidate);
6371            let shape_matches = if global || components.len() > 1 {
6372                qualified_tiers
6373                    .iter()
6374                    .any(|qualified| qualified == &candidate_name)
6375            } else {
6376                true
6377            };
6378            if !shape_matches {
6379                continue;
6380            }
6381            saw_shape_candidate = true;
6382            if same_visible_symbol(candidate, target)
6383                || self.c_tag_declaration_family_matches_target(
6384                    analyzer,
6385                    file,
6386                    std::slice::from_ref(&candidate),
6387                    target,
6388                )
6389                || self.compatible_primary_template_redeclarations(candidate, target)
6390                || (declared_type_alias(analyzer, candidate)
6391                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6392            {
6393                return true;
6394            }
6395        }
6396
6397        !saw_shape_candidate
6398    }
6399
6400    /// A C tag's forward declaration and complete definition are one logical
6401    /// type even when their indexed signatures and source files differ. Keep
6402    /// this identity narrow: the complete declaration must be a top-level C
6403    /// tag, every visible candidate must be its physical forward declaration,
6404    /// and the parsed tag kind must agree. Different FQNs, competing complete
6405    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6406    fn cached_c_tag_kind(
6407        &self,
6408        analyzer: &CppGraphSource<'_>,
6409        candidate: &CodeUnit,
6410    ) -> Option<CppCTagKind> {
6411        if let Some(kind) = self
6412            .c_tag_kind_cache
6413            .lock()
6414            .expect("C tag kind cache poisoned")
6415            .get(candidate)
6416        {
6417            return *kind;
6418        }
6419        let kind = indexed_c_tag_kind(analyzer, candidate);
6420        self.c_tag_kind_cache
6421            .lock()
6422            .expect("C tag kind cache poisoned")
6423            .insert(candidate.clone(), kind);
6424        kind
6425    }
6426
6427    fn cached_unique_c_tag_complete_definition(
6428        &self,
6429        analyzer: &CppGraphSource<'_>,
6430        target: &CodeUnit,
6431        target_tag: CppCTagKind,
6432    ) -> Option<CodeUnit> {
6433        if let Some(definition) = self
6434            .c_tag_complete_definition_cache
6435            .lock()
6436            .expect("C tag complete-definition cache poisoned")
6437            .get(target)
6438        {
6439            return definition.clone();
6440        }
6441        let complete_definitions = analyzer
6442            .definitions(&target.fq_name())
6443            .filter(|candidate| {
6444                candidate.is_class()
6445                    && !declared_type_alias(analyzer, candidate)
6446                    && is_c_source_file(candidate.source())
6447                    && analyzer.parent_of(candidate).is_none()
6448                    && cpp_class_declaration_strength(analyzer, candidate)
6449                        == CppClassDeclarationStrength::Full
6450                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6451            })
6452            .collect::<HashSet<_>>();
6453        let definition = (complete_definitions.len() == 1)
6454            .then(|| complete_definitions.into_iter().next())
6455            .flatten()
6456            .filter(|candidate| same_visible_symbol(candidate, target));
6457        self.c_tag_complete_definition_cache
6458            .lock()
6459            .expect("C tag complete-definition cache poisoned")
6460            .insert(target.clone(), definition.clone());
6461        definition
6462    }
6463
6464    pub fn c_tag_declaration_family_matches_target(
6465        &self,
6466        analyzer: &CppGraphSource<'_>,
6467        visible_from: &ProjectFile,
6468        candidates: &[&CodeUnit],
6469        target: &CodeUnit,
6470    ) -> bool {
6471        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6472            let candidate_evidence = candidates
6473                .iter()
6474                .map(|candidate| {
6475                    (
6476                        candidate.fq_name(),
6477                        candidate.source().rel_path().to_path_buf(),
6478                        cpp_class_declaration_strength(analyzer, candidate),
6479                        indexed_c_tag_kind(analyzer, candidate),
6480                        self.is_physically_visible(visible_from, candidate),
6481                    )
6482                })
6483                .collect::<Vec<_>>();
6484            eprintln!(
6485                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6486                visible_from.rel_path().display(),
6487                target.fq_name(),
6488                target.source().rel_path().display(),
6489                cpp_class_declaration_strength(analyzer, target),
6490                indexed_c_tag_kind(analyzer, target),
6491            );
6492        }
6493        if candidates.is_empty()
6494            || !target.is_class()
6495            || declared_type_alias(analyzer, target)
6496            || !is_c_source_file(target.source())
6497            || analyzer.parent_of(target).is_some()
6498            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6499        {
6500            return false;
6501        }
6502        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6503            return false;
6504        };
6505        if self
6506            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6507            .is_none()
6508        {
6509            return false;
6510        }
6511        let mut saw_visible_forward = false;
6512        for candidate in candidates.iter().copied() {
6513            if candidate == target {
6514                continue;
6515            }
6516            if !candidate.is_class()
6517                || declared_type_alias(analyzer, candidate)
6518                || candidate.fq_name() != target.fq_name()
6519                || analyzer.parent_of(candidate).is_some()
6520                || cpp_class_declaration_strength(analyzer, candidate)
6521                    != CppClassDeclarationStrength::Forward
6522                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6523                || !self.is_physically_visible(visible_from, candidate)
6524            {
6525                return false;
6526            }
6527            saw_visible_forward = true;
6528        }
6529        saw_visible_forward
6530    }
6531
6532    /// Collapse one visible complete C tag and its visible forward declarations
6533    /// before ordinary lexical resolution sees their different signatures as
6534    /// competing types. A second complete definition, a different FQN/tag
6535    /// kind, or an unknown declaration shape remains ambiguous.
6536    fn unique_c_tag_declaration_family(
6537        &self,
6538        analyzer: &CppGraphSource<'_>,
6539        visible_from: &ProjectFile,
6540        candidates: &[&CodeUnit],
6541    ) -> Option<CodeUnit> {
6542        let first = candidates.first()?;
6543        let target_fq_name = first.fq_name();
6544        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6545        let mut full = None;
6546        let mut saw_forward = false;
6547        for candidate in candidates.iter().copied() {
6548            if !candidate.is_class()
6549                || declared_type_alias(analyzer, candidate)
6550                || candidate.fq_name() != target_fq_name
6551                || analyzer.parent_of(candidate).is_some()
6552                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6553            {
6554                return None;
6555            }
6556            match cpp_class_declaration_strength(analyzer, candidate) {
6557                CppClassDeclarationStrength::Full
6558                    if is_c_source_file(candidate.source())
6559                        && full.replace(candidate.clone()).is_none() => {}
6560                CppClassDeclarationStrength::Forward
6561                    if self.is_physically_visible(visible_from, candidate) =>
6562                {
6563                    saw_forward = true
6564                }
6565                _ => return None,
6566            }
6567        }
6568        if saw_forward { full } else { None }
6569    }
6570
6571    pub fn target_preserving_reference_namespace(
6572        &self,
6573        analyzer: &CppGraphSource<'_>,
6574        file: &ProjectFile,
6575        identifier: &str,
6576        target: &CodeUnit,
6577    ) -> Option<Vec<String>> {
6578        let mut namespace = None;
6579        for candidate in self.visible_identifier_candidates(file, identifier) {
6580            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6581            {
6582                continue;
6583            }
6584            if !(same_visible_symbol(candidate, target)
6585                || self.compatible_primary_template_redeclarations(candidate, target)
6586                || declared_type_alias(analyzer, candidate)
6587                    && self.structured_alias_primary_preserves_target(
6588                        analyzer, file, candidate, target,
6589                    ))
6590            {
6591                continue;
6592            }
6593            if namespace
6594                .as_ref()
6595                .is_some_and(|existing| existing != candidate.package_name())
6596            {
6597                return None;
6598            }
6599            namespace = Some(candidate.package_name().to_string());
6600        }
6601        let namespace = namespace?;
6602        Some(
6603            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6604                brokk_bifrost_core::analyzer::Language::Cpp,
6605                &namespace,
6606            ),
6607        )
6608    }
6609
6610    pub fn resolve_imported_type_candidate(
6611        &self,
6612        analyzer: &CppGraphSource<'_>,
6613        file: &ProjectFile,
6614        target: &CodeUnit,
6615        target_components: &[String],
6616        direct_target: Option<&CodeUnit>,
6617        preserve_alias: bool,
6618    ) -> LexicalTypeResolution {
6619        let candidates = [target];
6620        let resolution = if preserve_alias {
6621            TypeCandidateResolution::PreserveAlias
6622        } else {
6623            direct_target.map_or(
6624                TypeCandidateResolution::Canonical,
6625                TypeCandidateResolution::PreserveTarget,
6626            )
6627        };
6628        // One candidate goes in, so a failure here is never "choose one of
6629        // these": it is the alias chain leaving the index, which must answer
6630        // missing rather than ambiguous (#1828).
6631        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6632            Ok(unit) => LexicalTypeResolution::Resolved {
6633                unit,
6634                components: target_components.to_vec(),
6635                candidates: vec![target.clone()],
6636            },
6637            Err(failure) => failure.lexical_resolution(),
6638        }
6639    }
6640
6641    fn resolve_type_components_lexically_inner(
6642        &self,
6643        analyzer: &CppGraphSource<'_>,
6644        file: &ProjectFile,
6645        components: &[String],
6646        global: bool,
6647        lexical_scope: &[String],
6648        resolution: TypeCandidateResolution<'_>,
6649    ) -> LexicalTypeResolution {
6650        if components.is_empty() {
6651            return LexicalTypeResolution::Missing;
6652        }
6653        // A C++ class injects its own name into the class scope.  The indexed
6654        // FqName for that declaration is the class path itself (for example,
6655        // `n::raw_hash_set`), not a synthetic child named
6656        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6657        // requested identifier to every scope component, so they cannot
6658        // represent that injected binding when the enclosing class is the
6659        // closest scope.  Recover the binding from the structured class path
6660        // before allowing lookup to fall through to an outer same-spelled
6661        // declaration.
6662        let mut injected = self.resolve_injected_class_name(
6663            analyzer,
6664            file,
6665            components,
6666            global,
6667            lexical_scope,
6668            resolution,
6669        );
6670        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6671            let prefix_len = qualified.len().saturating_sub(components.len());
6672            if injected
6673                .as_ref()
6674                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6675            {
6676                return injected
6677                    .take()
6678                    .expect("injected class resolution was just present")
6679                    .1;
6680            }
6681            let qualified_name = qualified.join("::");
6682            let candidates = self
6683                .type_candidates(file, &qualified_name)
6684                .into_iter()
6685                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6686                .collect::<Vec<_>>();
6687            if candidates.is_empty() {
6688                if !global && components.len() == 1 {
6689                    match self.resolve_inherited_type_for_lexical_scope(
6690                        analyzer,
6691                        file,
6692                        &qualified[..prefix_len],
6693                        &components[0],
6694                        resolution,
6695                    ) {
6696                        LexicalTypeResolution::Missing => {}
6697                        inherited => return inherited,
6698                    }
6699                }
6700                continue;
6701            }
6702            let candidates =
6703                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6704            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6705                Ok(unit) => unit,
6706                Err(failure) => return failure.lexical_resolution(),
6707            };
6708            return LexicalTypeResolution::Resolved {
6709                unit,
6710                components: qualified,
6711                candidates: candidates.into_iter().cloned().collect(),
6712            };
6713        }
6714        LexicalTypeResolution::Missing
6715    }
6716
6717    fn resolve_injected_class_name(
6718        &self,
6719        analyzer: &CppGraphSource<'_>,
6720        file: &ProjectFile,
6721        components: &[String],
6722        global: bool,
6723        lexical_scope: &[String],
6724        resolution: TypeCandidateResolution<'_>,
6725    ) -> Option<(usize, LexicalTypeResolution)> {
6726        if global
6727            || components.len() != 1
6728            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6729            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6730        {
6731            return None;
6732        }
6733        let name = components.first()?;
6734        let mut matches: Vec<&CodeUnit> = Vec::new();
6735        let mut owner_len = 0;
6736        for candidate in self.visible_identifier_candidates(file, name) {
6737            if !candidate.is_class()
6738                || declared_type_alias(analyzer, candidate)
6739                || candidate.identifier() != name
6740            {
6741                continue;
6742            }
6743            let candidate_scope = canonical_cpp_scope_components(candidate);
6744            if candidate_scope.len() > lexical_scope.len()
6745                || !lexical_scope.starts_with(&candidate_scope)
6746                || candidate_scope.last().is_none_or(|last| last != name)
6747            {
6748                continue;
6749            }
6750            if candidate_scope.len() > owner_len {
6751                owner_len = candidate_scope.len();
6752                matches.clear();
6753            }
6754            if candidate_scope.len() == owner_len
6755                && !matches
6756                    .iter()
6757                    .any(|existing| same_logical_symbol(existing, candidate))
6758            {
6759                matches.push(candidate);
6760            }
6761        }
6762        if matches.is_empty() {
6763            return None;
6764        }
6765        // The exact current class boundary is the most important injected-name
6766        // case. Ordinary lexical lookup appends the requested name and can
6767        // otherwise select a synthetic constructor-shaped child such as
6768        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6769        // The global and single-component gates above keep qualified receiver
6770        // and static-qualifier contexts out of this recovery.
6771        let owner_components = lexical_scope[..owner_len].to_vec();
6772        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6773        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6774            Ok(unit) => LexicalTypeResolution::Resolved {
6775                unit,
6776                components: owner_components,
6777                candidates: matches.into_iter().cloned().collect(),
6778            },
6779            Err(failure) => failure.lexical_resolution(),
6780        };
6781        Some((owner_len, resolution))
6782    }
6783
6784    fn resolve_inherited_type_for_lexical_scope(
6785        &self,
6786        analyzer: &CppGraphSource<'_>,
6787        file: &ProjectFile,
6788        lexical_scope: &[String],
6789        name: &str,
6790        resolution: TypeCandidateResolution<'_>,
6791    ) -> LexicalTypeResolution {
6792        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6793            return LexicalTypeResolution::Missing;
6794        };
6795        let lexical_owner_name = lexical_scope.join("::");
6796        if lexical_owner_name.is_empty() {
6797            return LexicalTypeResolution::Missing;
6798        }
6799        let owner_candidates = self
6800            .type_candidates(file, &lexical_owner_name)
6801            .into_iter()
6802            .filter(|candidate| {
6803                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6804                    && !declared_type_alias(analyzer, candidate)
6805            })
6806            .collect::<Vec<_>>();
6807        if owner_candidates.is_empty() {
6808            return LexicalTypeResolution::Missing;
6809        }
6810        // A visible forward declaration and the physical class definition share
6811        // one FQN, but only the definition owns hierarchy facts. When lookup is
6812        // physically inside that definition, do not let an earlier header
6813        // forward declaration erase its base edges (#2240).
6814        let physical_owner_candidates = owner_candidates
6815            .iter()
6816            .copied()
6817            .filter(|candidate| candidate.source() == file)
6818            .collect::<Vec<_>>();
6819        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6820            owner_candidates
6821        } else {
6822            physical_owner_candidates
6823        };
6824        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6825            return LexicalTypeResolution::Ambiguous;
6826        };
6827
6828        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6829        let mut visited_owners = HashSet::default();
6830        while !frontier.is_empty() {
6831            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6832            let mut next_frontier = Vec::new();
6833            for owner in frontier {
6834                if !visited_owners.insert(owner.fq_name()) {
6835                    continue;
6836                }
6837                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6838                let candidates = self
6839                    .type_candidates(file, &qualified_name)
6840                    .into_iter()
6841                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6842                    .collect::<Vec<_>>();
6843                if candidates.is_empty() {
6844                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6845                        if !next_frontier
6846                            .iter()
6847                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6848                        {
6849                            next_frontier.push(ancestor);
6850                        }
6851                    }
6852                    continue;
6853                }
6854                let candidates =
6855                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6856                let unit =
6857                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6858                        Ok(unit) => unit,
6859                        Err(failure) => return failure.lexical_resolution(),
6860                    };
6861                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6862            }
6863            if let Some((unit, candidates)) = level_matches.first().cloned() {
6864                let Some(first_declaration) = candidates.first() else {
6865                    return LexicalTypeResolution::Ambiguous;
6866                };
6867                if !level_matches.iter().all(|(_, declarations)| {
6868                    declarations
6869                        .iter()
6870                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6871                }) {
6872                    return LexicalTypeResolution::Ambiguous;
6873                }
6874                let mut components = lexical_scope.to_vec();
6875                components.push(name.to_string());
6876                return LexicalTypeResolution::Resolved {
6877                    unit,
6878                    components,
6879                    candidates,
6880                };
6881            }
6882            frontier = next_frontier;
6883        }
6884        LexicalTypeResolution::Missing
6885    }
6886
6887    /// Resolve a base class through its injected class name at the nearest
6888    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6889    ///
6890    /// A base whose canonical full definition cannot be pinned from `file` -
6891    /// a forward declaration the include closure completes with two different
6892    /// full definitions, or an alias chain that leaves the index - stops the
6893    /// walk only when that base is spelled `injected_name`. The mem-initializer
6894    /// names a base by that base's own injected class name, so a base spelled
6895    /// differently can never be the one it names, whichever definition it would
6896    /// have turned out to be; aborting the level on its account instead loses
6897    /// the sibling base that *is* named (#2543). A base that is spelled
6898    /// `injected_name` still fails closed, because choosing a deeper same-named
6899    /// ancestor over it would bind the initializer to the wrong constructor.
6900    /// The skipped base carries its own ancestors out of the walk with it: with
6901    /// no canonical unit, the repeated-base accounting below cannot tell one
6902    /// inherited path through it from two.
6903    pub fn inherited_injected_class_owner(
6904        &self,
6905        analyzer: &CppGraphSource<'_>,
6906        file: &ProjectFile,
6907        enclosing_owner: &CodeUnit,
6908        injected_name: &str,
6909    ) -> Option<CodeUnit> {
6910        let hierarchy = analyzer.type_hierarchy_provider()?;
6911        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6912        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6913        while !frontier.is_empty() {
6914            let mut level_matches = Vec::new();
6915            let mut next_frontier = Vec::new();
6916            for raw_owner in frontier {
6917                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6918                else {
6919                    if raw_owner.identifier() == injected_name {
6920                        return None;
6921                    }
6922                    continue;
6923                };
6924                let propagated = propagated_counts.entry(owner.clone()).or_default();
6925                if *propagated == 2 {
6926                    continue;
6927                }
6928                *propagated += 1;
6929                if owner.identifier() == injected_name {
6930                    level_matches.push(owner.clone());
6931                }
6932                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6933            }
6934            match level_matches.as_slice() {
6935                [owner] => return Some(owner.clone()),
6936                [_, ..] => return None,
6937                [] => {}
6938            }
6939            frontier = next_frontier;
6940        }
6941        None
6942    }
6943
6944    /// The one type the candidates name under `resolution`, or why they do not
6945    /// name one. The two preserving modes only ever reject candidates that
6946    /// disagree with each other, which is ambiguity; canonicalization can also
6947    /// fail because the alias chain leaves the index (#1828).
6948    fn resolve_type_candidates(
6949        &self,
6950        analyzer: &CppGraphSource<'_>,
6951        file: &ProjectFile,
6952        candidates: &[&CodeUnit],
6953        resolution: TypeCandidateResolution<'_>,
6954    ) -> Result<CodeUnit, TypeCandidateFailure> {
6955        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6956            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6957        {
6958            return Ok(unit);
6959        }
6960        match resolution {
6961            TypeCandidateResolution::Canonical => {
6962                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6963            }
6964            TypeCandidateResolution::PreserveAlias => {
6965                // A generated index can retain identical alias spellings from
6966                // mutually exclusive headers. When the reference file
6967                // physically reaches exactly one of those source declarations,
6968                // include closure is the structured evidence that selects it;
6969                // treating the two source spellings as an overload set makes a
6970                // reachable alias appear ambiguous (#1844).
6971                let same_fqn_alias_family = candidates.len() > 1
6972                    && candidates.iter().all(|candidate| {
6973                        declared_type_alias(analyzer, candidate)
6974                            && same_logical_symbol(candidates[0], candidate)
6975                    })
6976                    && candidates
6977                        .iter()
6978                        .any(|candidate| candidate.source() != candidates[0].source());
6979                if same_fqn_alias_family {
6980                    let physically_visible = candidates
6981                        .iter()
6982                        .copied()
6983                        .filter(|candidate| self.is_physically_visible(file, candidate))
6984                        .collect::<Vec<_>>();
6985                    // The family is one logical declaration only when the
6986                    // reachable spellings agree. Two same-FQN aliases whose
6987                    // written targets differ (`using Choice = Canonical;` in
6988                    // one header, `using Choice = ::Canonical;` in another)
6989                    // are a genuine conflict, and choosing the first indexed
6990                    // one silently binds the reference to an arbitrary owner
6991                    // (#2398). Collapse only a single reachable declaration
6992                    // or reachable declarations with one structured target;
6993                    // everything else stays ambiguous below.
6994                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
6995                        &physically_visible,
6996                        |candidate| self.structured_alias_target(analyzer, candidate),
6997                    ) {
6998                        return Ok(alias.clone());
6999                    }
7000                }
7001                unique_type_candidate_preserving_alias(analyzer, file, candidates)
7002                    .ok_or(TypeCandidateFailure::Ambiguous)
7003            }
7004            TypeCandidateResolution::PreserveTarget(target) => self
7005                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7006                .ok_or(TypeCandidateFailure::Ambiguous),
7007        }
7008    }
7009
7010    fn candidates_for_type_resolution<'b>(
7011        &self,
7012        analyzer: &CppGraphSource<'_>,
7013        file: &ProjectFile,
7014        candidates: &[&'b CodeUnit],
7015        resolution: TypeCandidateResolution<'_>,
7016    ) -> Vec<&'b CodeUnit> {
7017        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7018            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7019            if compile_proven.len() == 1 {
7020                return compile_proven;
7021            }
7022        }
7023        candidates.to_vec()
7024    }
7025
7026    /// Narrow a same-name forward lookup to the declaration selected by the
7027    /// translation unit's compile command. The lexical resolver intentionally
7028    /// does not receive a reference node, so this is the only compile-context
7029    /// evidence available at that stage. A single selected candidate is safe:
7030    /// the caller still checks include activation and the reference's own
7031    /// guards before reporting the result as visible.
7032    fn compile_proven_type_candidates<'b>(
7033        &self,
7034        analyzer: &CppGraphSource<'_>,
7035        file: &ProjectFile,
7036        candidates: &[&'b CodeUnit],
7037    ) -> Vec<&'b CodeUnit> {
7038        let proven = self.compile_proven_guards(file);
7039        if proven.is_empty() {
7040            return Vec::new();
7041        }
7042        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7043            return Vec::new();
7044        };
7045        candidates
7046            .iter()
7047            .copied()
7048            .filter(|candidate| {
7049                let declaration_guards =
7050                    declaration_guard_requirements(analyzer, self.cpp, candidate);
7051                if declaration_guards.is_empty() {
7052                    return false;
7053                }
7054                if candidate.source() == file {
7055                    return declaration_guards.iter().any(|(_, required)| {
7056                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7057                    });
7058                }
7059                declaration_guards.iter().any(|(_, required)| {
7060                    self.foreign_declaration_reachable_from_compile_proven_guards(
7061                        file,
7062                        prepared.as_ref(),
7063                        candidate.source(),
7064                        required,
7065                        usize::MAX,
7066                    )
7067                })
7068            })
7069            .collect()
7070    }
7071
7072    pub fn resolve_callable_value_components_lexically(
7073        &self,
7074        analyzer: &CppGraphSource<'_>,
7075        file: &ProjectFile,
7076        owner_components: &[String],
7077        member_name: &str,
7078        global: bool,
7079        lexical_scope: &[String],
7080    ) -> LexicalCallableValueResolution {
7081        if owner_components.is_empty() || member_name.is_empty() {
7082            return LexicalCallableValueResolution::Missing;
7083        }
7084        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7085            let owner_name = qualified_owner.join("::");
7086            let type_candidates = self
7087                .type_candidates(file, &owner_name)
7088                .into_iter()
7089                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7090                .collect::<Vec<_>>();
7091            let resolved_type = if type_candidates.is_empty() {
7092                None
7093            } else {
7094                let Some(unit) =
7095                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7096                else {
7097                    return LexicalCallableValueResolution::Ambiguous;
7098                };
7099                Some(unit)
7100            };
7101
7102            let mut qualified_callable = qualified_owner;
7103            qualified_callable.push(member_name.to_string());
7104            let callable_name = qualified_callable.join("::");
7105            let free_function = self
7106                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7107                .into_iter()
7108                .find(|candidate| {
7109                    canonical_cpp_name_matches(candidate, &callable_name)
7110                        && type_owner_of(analyzer, candidate).is_none()
7111                })
7112                .cloned();
7113
7114            match (resolved_type, free_function) {
7115                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7116                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7117                (None, Some(function)) => {
7118                    return LexicalCallableValueResolution::FreeFunction(function);
7119                }
7120                (None, None) => {}
7121            }
7122        }
7123        LexicalCallableValueResolution::Missing
7124    }
7125
7126    fn resolve_type_for_declaration(
7127        &self,
7128        visible_from: &ProjectFile,
7129        declaration: &CodeUnit,
7130        raw_name: &str,
7131    ) -> Option<CodeUnit> {
7132        let normalized = normalize_reference_name(raw_name)?;
7133        if !normalized.contains("::")
7134            && let Some(namespace) = cpp_namespace_for(declaration)
7135        {
7136            for prefix in namespace_prefixes(&namespace) {
7137                let qualified = format!("{prefix}::{normalized}");
7138                if let Some(unit) = self
7139                    .type_candidates(visible_from, &qualified)
7140                    .into_iter()
7141                    .next()
7142                {
7143                    return Some(unit.clone());
7144                }
7145            }
7146        }
7147        self.resolve_type(visible_from, raw_name)
7148    }
7149
7150    fn resolve_unique_canonical_type_for_declaration(
7151        &self,
7152        analyzer: &CppGraphSource<'_>,
7153        visible_from: &ProjectFile,
7154        declaration: &CodeUnit,
7155        raw_name: &str,
7156    ) -> Option<CodeUnit> {
7157        let current = self.resolve_defining_type_for_declaration(
7158            analyzer,
7159            visible_from,
7160            declaration,
7161            raw_name,
7162        )?;
7163        self.canonical_type_resolution(analyzer, visible_from, &current)
7164            .ok()
7165    }
7166
7167    pub fn canonical_type_unit(
7168        &self,
7169        analyzer: &CppGraphSource<'_>,
7170        visible_from: &ProjectFile,
7171        unit: &CodeUnit,
7172    ) -> Option<CodeUnit> {
7173        self.canonical_type_resolution(analyzer, visible_from, unit)
7174            .ok()
7175    }
7176
7177    /// Follow an alias only when it is visible at `reference`.
7178    ///
7179    /// The consumer need not spell the alias target. In particular, a
7180    /// conditional include can make `PublicPtr` visible at the reference while
7181    /// ordinary physical-include visibility is false. Prove the public alias
7182    /// with the reference's guard environment, then use the existing structured
7183    /// alias-chain resolver over the consumer's bounded declaration index.
7184    pub fn canonical_type_unit_in_context(
7185        &self,
7186        analyzer: &CppGraphSource<'_>,
7187        visible_from: &ProjectFile,
7188        reference: Node<'_>,
7189        unit: &CodeUnit,
7190    ) -> Option<CodeUnit> {
7191        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7192        {
7193            return None;
7194        }
7195        self.canonical_type_resolution(analyzer, visible_from, unit)
7196            .ok()
7197    }
7198
7199    /// Follow `unit`'s alias chain to the class it names, or report why the
7200    /// chain does not end at one indexed class.
7201    ///
7202    /// A chain that leaves the index - an alias to a template parameter, to a
7203    /// standard-library type, or to any other declaration the workspace does
7204    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7205    /// there is still nothing to choose between.
7206    fn canonical_type_resolution(
7207        &self,
7208        analyzer: &CppGraphSource<'_>,
7209        visible_from: &ProjectFile,
7210        unit: &CodeUnit,
7211    ) -> Result<CodeUnit, TypeCandidateFailure> {
7212        let mut current = unit.clone();
7213        let mut seen_aliases = HashSet::default();
7214        loop {
7215            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7216                return current
7217                    .is_class()
7218                    .then_some(current)
7219                    .ok_or(TypeCandidateFailure::Unresolvable);
7220            };
7221            if matches!(target, StructuredAliasTarget::Builtin) {
7222                return current
7223                    .is_class()
7224                    .then_some(current)
7225                    .ok_or(TypeCandidateFailure::Unresolvable);
7226            }
7227            if !seen_aliases.insert(current.clone()) {
7228                return Err(TypeCandidateFailure::Unresolvable);
7229            }
7230            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7231        }
7232    }
7233
7234    pub fn canonical_visible_full_type_unit(
7235        &self,
7236        analyzer: &CppGraphSource<'_>,
7237        visible_from: &ProjectFile,
7238        unit: &CodeUnit,
7239    ) -> Option<CodeUnit> {
7240        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7241        if cpp_class_declaration_strength(analyzer, &canonical)
7242            != CppClassDeclarationStrength::Forward
7243        {
7244            return Some(canonical);
7245        }
7246        let mut full = Vec::new();
7247        for candidate in self
7248            .visible_identifier_candidates(visible_from, canonical.identifier())
7249            .filter(|candidate| {
7250                candidate.is_class()
7251                    && candidate.fq_name() == canonical.fq_name()
7252                    && cpp_class_declaration_strength(analyzer, candidate)
7253                        == CppClassDeclarationStrength::Full
7254            })
7255        {
7256            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7257                full.push(candidate.clone());
7258            }
7259        }
7260        match full.len() {
7261            0 => Some(canonical),
7262            1 => full.pop(),
7263            _ => None,
7264        }
7265    }
7266
7267    fn resolve_structured_alias_target(
7268        &self,
7269        visible_from: &ProjectFile,
7270        declaration: &CodeUnit,
7271        target: &StructuredAliasTarget,
7272    ) -> Option<CodeUnit> {
7273        self.structured_alias_target_resolution(visible_from, declaration, target)
7274            .ok()
7275    }
7276
7277    fn structured_alias_target_resolution(
7278        &self,
7279        visible_from: &ProjectFile,
7280        declaration: &CodeUnit,
7281        target: &StructuredAliasTarget,
7282    ) -> Result<CodeUnit, TypeCandidateFailure> {
7283        let primary =
7284            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7285        let StructuredAliasTarget::Named { arguments, .. } = target else {
7286            return Err(TypeCandidateFailure::Unresolvable);
7287        };
7288        match arguments {
7289            Some(arguments) => self
7290                .resolve_template_arguments(visible_from, primary, arguments)
7291                .map_err(|error| match error {
7292                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7293                        TypeCandidateFailure::Ambiguous
7294                    }
7295                    _ => TypeCandidateFailure::Unresolvable,
7296                }),
7297            None => Ok(primary),
7298        }
7299    }
7300
7301    fn resolve_structured_alias_primary(
7302        &self,
7303        visible_from: &ProjectFile,
7304        declaration: &CodeUnit,
7305        target: &StructuredAliasTarget,
7306    ) -> Option<CodeUnit> {
7307        self.structured_alias_primary_resolution(visible_from, declaration, target)
7308            .ok()
7309    }
7310
7311    fn structured_alias_primary_resolution(
7312        &self,
7313        visible_from: &ProjectFile,
7314        declaration: &CodeUnit,
7315        target: &StructuredAliasTarget,
7316    ) -> Result<CodeUnit, TypeCandidateFailure> {
7317        let StructuredAliasTarget::Named {
7318            components, global, ..
7319        } = target
7320        else {
7321            return Err(TypeCandidateFailure::Unresolvable);
7322        };
7323        let qualified = components.join("::");
7324        let candidates = if *global {
7325            // `::A::B` anchors at the root scope, so a candidate whose
7326            // canonical path merely ends with the spelled components does not
7327            // qualify. Without this filter a global `::Canonical` target also
7328            // collects `alpha::Canonical`, the lookup reports a false
7329            // ambiguity, and the alias arm silently drops out of its
7330            // conflicting family instead of proving the conflict (#2398).
7331            let mut candidates = self.type_candidates(visible_from, &qualified);
7332            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7333            candidates
7334        } else {
7335            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7336        };
7337        logical_type_candidate(candidates)
7338    }
7339
7340    pub fn structured_alias_primary_preserves_target(
7341        &self,
7342        analyzer: &CppGraphSource<'_>,
7343        visible_from: &ProjectFile,
7344        candidate: &CodeUnit,
7345        target: &CodeUnit,
7346    ) -> bool {
7347        let mut current = candidate.clone();
7348        let mut seen = HashSet::default();
7349        let mut matched_target = false;
7350        loop {
7351            if same_visible_symbol(&current, target)
7352                || self.compatible_primary_template_redeclarations(&current, target)
7353            {
7354                matched_target = true;
7355            }
7356            if !seen.insert(current.clone()) {
7357                return false;
7358            }
7359            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7360                return matched_target;
7361            };
7362            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7363                return matched_target;
7364            };
7365            let Some(primary) =
7366                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7367            else {
7368                // A dependent member target such as `Detector<T>::type`
7369                // cannot be reduced to an indexed primary, but a preceding
7370                // structured alias hop may already have proven the requested
7371                // alias identity. Cycles still resolve a primary and are
7372                // rejected by `seen` above.
7373                return matched_target;
7374            };
7375            current = primary;
7376        }
7377    }
7378
7379    fn lexical_type_candidates_match_target(
7380        &self,
7381        analyzer: &CppGraphSource<'_>,
7382        unit: &CodeUnit,
7383        candidates: &[CodeUnit],
7384        target: &CodeUnit,
7385    ) -> bool {
7386        same_visible_symbol(unit, target)
7387            || self.same_template_member_identity(analyzer, unit, target)
7388            || candidates.iter().any(|candidate| {
7389                same_visible_symbol(candidate, target)
7390                    || self.same_template_member_identity(analyzer, candidate, target)
7391            })
7392    }
7393
7394    pub fn structured_class_alias_resolves_to_target(
7395        &self,
7396        analyzer: &CppGraphSource<'_>,
7397        visible_from: &ProjectFile,
7398        alias: &CodeUnit,
7399        target: &CodeUnit,
7400    ) -> bool {
7401        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7402            return false;
7403        };
7404        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7405            return false;
7406        };
7407        let StructuredAliasTarget::Named {
7408            components, global, ..
7409        } = &alias_target
7410        else {
7411            return false;
7412        };
7413        let lexical_scope = canonical_cpp_scope_components(&owner);
7414        match self.resolve_type_components_lexically_for_target(
7415            analyzer,
7416            visible_from,
7417            components,
7418            *global,
7419            &lexical_scope,
7420            target,
7421        ) {
7422            LexicalTypeResolution::Resolved {
7423                unit, candidates, ..
7424            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7425            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7426                self.structured_alias_primary_preserves_target(
7427                    analyzer,
7428                    visible_from,
7429                    alias,
7430                    target,
7431                ) || self.flattened_macro_namespace_alias_target_matches(
7432                    analyzer,
7433                    visible_from,
7434                    alias,
7435                    &alias_target,
7436                    target,
7437                )
7438            }
7439        }
7440    }
7441
7442    /// Return true when a class-owned alias names the requested type as one
7443    /// structured qualifier in its target path.
7444    ///
7445    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7446    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7447    /// canonical identity. Inverse lookup needs the same evidence when later
7448    /// references use only the alias spelling.
7449    pub fn structured_class_alias_path_preserves_target(
7450        &self,
7451        analyzer: &CppGraphSource<'_>,
7452        visible_from: &ProjectFile,
7453        alias: &CodeUnit,
7454        target: &CodeUnit,
7455    ) -> bool {
7456        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7457            return false;
7458        };
7459        let Some(StructuredAliasTarget::Named {
7460            components, global, ..
7461        }) = self.structured_alias_target(analyzer, alias)
7462        else {
7463            return false;
7464        };
7465        let lexical_scope = canonical_cpp_scope_components(&owner);
7466        (1..components.len()).rev().any(|component_count| {
7467            matches!(
7468                self.resolve_type_components_lexically_for_target(
7469                    analyzer,
7470                    visible_from,
7471                    &components[..component_count],
7472                    global,
7473                    &lexical_scope,
7474                    target,
7475                ),
7476                LexicalTypeResolution::Resolved {
7477                    ref unit,
7478                    ref candidates,
7479                    ..
7480                } if self.lexical_type_candidates_match_target(
7481                    analyzer,
7482                    unit,
7483                    candidates,
7484                    target,
7485                )
7486            )
7487        })
7488    }
7489
7490    fn flattened_macro_namespace_alias_target_matches(
7491        &self,
7492        analyzer: &CppGraphSource<'_>,
7493        visible_from: &ProjectFile,
7494        alias: &CodeUnit,
7495        alias_target: &StructuredAliasTarget,
7496        target: &CodeUnit,
7497    ) -> bool {
7498        let StructuredAliasTarget::Named {
7499            components,
7500            global: false,
7501            arguments: None,
7502        } = alias_target
7503        else {
7504            return false;
7505        };
7506        let Some((target_name, namespace_components)) = components.split_last() else {
7507            return false;
7508        };
7509        if namespace_components.is_empty()
7510            || target_name != target.identifier()
7511            || alias.source() != target.source()
7512            || alias.source() != visible_from
7513            || !target.is_class()
7514            || declared_type_alias(analyzer, target)
7515        {
7516            return false;
7517        }
7518        if self
7519            .resolve_structured_alias_target(visible_from, alias, alias_target)
7520            .is_some()
7521        {
7522            return false;
7523        }
7524
7525        let Some(alias_start) = analyzer
7526            .declaration_source_occurrences(alias)
7527            .into_iter()
7528            .flatten()
7529            .map(|occurrence| occurrence.range.start_byte)
7530            .min()
7531        else {
7532            return false;
7533        };
7534        let has_matching_declaration = analyzer
7535            .declaration_source_occurrences(target)
7536            .into_iter()
7537            .flatten()
7538            .any(|occurrence| {
7539                occurrence.range.end_byte <= alias_start
7540                    && occurrence.fact.flattened_macro_namespace.as_deref()
7541                        == Some(namespace_components)
7542            });
7543        if !has_matching_declaration {
7544            return false;
7545        }
7546
7547        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7548        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7549        guard_requirement_sets_match(&alias_guards, &target_guards)
7550    }
7551
7552    pub fn template_alias_arguments_preserve_target(
7553        &self,
7554        analyzer: &CppGraphSource<'_>,
7555        visible_from: &ProjectFile,
7556        alias: &CodeUnit,
7557        arguments: &[CppTemplateExpression],
7558        target: &CodeUnit,
7559    ) -> bool {
7560        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7561            return false;
7562        };
7563        if metadata.alias_target.is_none()
7564            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7565        {
7566            return false;
7567        }
7568        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7569    }
7570
7571    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7572        self.cpp_template_metadata
7573            .get(unit)
7574            .is_some_and(CppTemplateMetadata::is_primary)
7575    }
7576
7577    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7578        self.cpp_template_metadata
7579            .get(unit)
7580            .is_some_and(CppTemplateMetadata::is_specialization)
7581    }
7582
7583    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7584        same_visible_symbol(left, right)
7585            || self.compatible_primary_template_redeclarations(left, right)
7586    }
7587
7588    pub fn same_template_member_identity(
7589        &self,
7590        analyzer: &CppGraphSource<'_>,
7591        left: &CodeUnit,
7592        right: &CodeUnit,
7593    ) -> bool {
7594        if same_visible_symbol(left, right) {
7595            return true;
7596        }
7597        if left.kind() != right.kind()
7598            || left.identifier() != right.identifier()
7599            || left.signature() != right.signature()
7600        {
7601            return false;
7602        }
7603        let (Some(left_owner), Some(right_owner)) =
7604            (analyzer.parent_of(left), analyzer.parent_of(right))
7605        else {
7606            return false;
7607        };
7608        left_owner.is_class()
7609            && right_owner.is_class()
7610            && self.same_template_owner_identity(&left_owner, &right_owner)
7611    }
7612
7613    fn unique_canonical_type_candidate(
7614        &self,
7615        analyzer: &CppGraphSource<'_>,
7616        visible_from: &ProjectFile,
7617        candidates: &[&CodeUnit],
7618    ) -> Option<CodeUnit> {
7619        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7620            .ok()
7621    }
7622
7623    fn canonical_type_candidate_resolution(
7624        &self,
7625        analyzer: &CppGraphSource<'_>,
7626        visible_from: &ProjectFile,
7627        candidates: &[&CodeUnit],
7628    ) -> Result<CodeUnit, TypeCandidateFailure> {
7629        let mut canonical = Vec::new();
7630        for candidate in candidates {
7631            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7632            if canonical
7633                .iter()
7634                .any(|existing| same_visible_symbol(existing, &resolved))
7635            {
7636                continue;
7637            }
7638            if let Some(existing) = canonical.iter_mut().find(|existing| {
7639                self.compatible_primary_template_redeclarations(existing, &resolved)
7640            }) {
7641                // A forward declaration and its full primary-template
7642                // definition are one C++ type even when they live in
7643                // different headers and alpha-rename their parameters. The
7644                // target-preserving path already reconciles this family; do
7645                // the same for ordinary canonical lookup so an out-of-line
7646                // member's lexical owner is not made ambiguous by its own
7647                // forward declaration. Retain the strongest physical
7648                // declaration for later owner/range queries.
7649                if matches!(
7650                    (
7651                        cpp_class_declaration_strength(analyzer, existing),
7652                        cpp_class_declaration_strength(analyzer, &resolved),
7653                    ),
7654                    (
7655                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7656                        CppClassDeclarationStrength::Full,
7657                    ) | (
7658                        CppClassDeclarationStrength::Unknown,
7659                        CppClassDeclarationStrength::Forward,
7660                    )
7661                ) {
7662                    *existing = resolved;
7663                }
7664                continue;
7665            }
7666            canonical.push(resolved);
7667            if canonical.len() > 1 {
7668                return Err(TypeCandidateFailure::Ambiguous);
7669            }
7670        }
7671        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7672    }
7673
7674    pub fn unique_type_candidate_preserving_target(
7675        &self,
7676        analyzer: &CppGraphSource<'_>,
7677        visible_from: &ProjectFile,
7678        candidates: &[&CodeUnit],
7679        target: &CodeUnit,
7680    ) -> Option<CodeUnit> {
7681        // C++ headers often expose one logical type through mutually exclusive
7682        // physical declarations, for example a class in the fallback branch
7683        // and a `using` alias to the standard-library type in the configured
7684        // branch. The index intentionally retains both declarations so forward
7685        // lookup can report each target. Preserve the requested target when
7686        // that is the only ambiguity: every candidate has the same type kind,
7687        // exact canonical FQN, and source file, and the requested declaration
7688        // itself is one of the physical candidates. Do not merge same-named
7689        // declarations from different files or namespaces; those remain
7690        // ambiguous and fail closed below.
7691        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7692            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7693        {
7694            return Some(target.clone());
7695        }
7696        let mut resolved_candidates = Vec::new();
7697        for candidate in candidates {
7698            // An ifdef branch that aliases an unindexed system type (for
7699            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7700            // canonicalized. That branch does not name `target`. Dropping it
7701            // keeps the branch that does. Failing the whole family here would
7702            // deny every usage of the reachable spelling (#2368).
7703            let Some(resolved) =
7704                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7705            else {
7706                continue;
7707            };
7708            if resolved_candidates
7709                .iter()
7710                .any(|existing| same_visible_symbol(existing, &resolved))
7711            {
7712                continue;
7713            }
7714            resolved_candidates.push(resolved);
7715        }
7716        match resolved_candidates.as_slice() {
7717            [] => None,
7718            [single] => Some(single.clone()),
7719            // The branches disagree about what the name aliases. When they are
7720            // spellings of one entity (#1845) that disagreement is a build
7721            // configuration, not a choice between types, so it must not deny
7722            // the requested target its reference.
7723            _ => self
7724                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7725                .map(|_| target.clone()),
7726        }
7727    }
7728
7729    /// The declaration a same-file same-FQN family stands for when a reference
7730    /// names `target`, or `None` when the candidates are not one family or the
7731    /// family does not name `target`.
7732    ///
7733    /// A translation unit cannot hold two different types under one qualified
7734    /// name, so several same-kind declarations of one FQN in one file are
7735    /// alternate spellings of one entity - the configuration branches of an
7736    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7737    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7738    /// targets differ; canonicalizing each branch on its own and then demanding
7739    /// agreement reports an ambiguity that denies every declaration in the
7740    /// family its usages (#1845). The family names `target` when it declares
7741    /// it, or when one branch's alias chain reaches it.
7742    ///
7743    /// Declarations in different files or namespaces are distinct entities and
7744    /// are deliberately excluded: their disagreement is a real ambiguity.
7745    pub fn same_fqn_type_spelling_for_target<'b>(
7746        &self,
7747        analyzer: &CppGraphSource<'_>,
7748        visible_from: &ProjectFile,
7749        candidates: &[&'b CodeUnit],
7750        target: &CodeUnit,
7751    ) -> Option<&'b CodeUnit> {
7752        let [first, rest @ ..] = candidates else {
7753            return None;
7754        };
7755        if rest.is_empty()
7756            || !rest.iter().all(|candidate| {
7757                candidate.kind() == first.kind()
7758                    && candidate.fq_name() == first.fq_name()
7759                    && candidate.source() == first.source()
7760            })
7761        {
7762            return None;
7763        }
7764        candidates
7765            .iter()
7766            .copied()
7767            .find(|candidate| same_symbol(candidate, target))
7768            .or_else(|| {
7769                candidates.iter().copied().find(|candidate| {
7770                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7771                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7772                })
7773            })
7774    }
7775
7776    pub fn alternate_same_fqn_type_declarations(
7777        &self,
7778        analyzer: &CppGraphSource<'_>,
7779        candidates: &[&CodeUnit],
7780        target: &CodeUnit,
7781    ) -> bool {
7782        let Some(first) = candidates.first() else {
7783            return false;
7784        };
7785        let same_api = first.kind() == target.kind()
7786            && first.fq_name() == target.fq_name()
7787            && first.source() == target.source()
7788            && candidates.iter().all(|candidate| {
7789                candidate.kind() == target.kind()
7790                    && candidate.fq_name() == target.fq_name()
7791                    && candidate.source() == target.source()
7792            })
7793            && candidates
7794                .iter()
7795                .any(|candidate| same_symbol(candidate, target))
7796            && candidates
7797                .iter()
7798                .any(|candidate| !same_logical_symbol(candidate, target));
7799        if !same_api {
7800            return false;
7801        }
7802
7803        let requirements = candidates
7804            .iter()
7805            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7806            .collect::<Vec<_>>();
7807        requirements.len() > 1
7808            && requirements
7809                .iter()
7810                .all(|requirement| !requirement.is_empty())
7811            && requirements.iter().enumerate().all(|(index, left)| {
7812                requirements[index + 1..].iter().all(|right| {
7813                    left.iter().all(|(_, left_guards)| {
7814                        right.iter().all(|(_, right_guards)| {
7815                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7816                        })
7817                    })
7818                })
7819            })
7820    }
7821
7822    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7823        let mut pending = vec![terms.to_vec()];
7824        while let Some(branch_terms) = pending.pop() {
7825            let mut normalized = Vec::new();
7826            let mut covers_branch = false;
7827            for term in branch_terms {
7828                if term.iter().any(|guard| term.contains(&guard.negated())) {
7829                    continue;
7830                }
7831                if term.is_empty() {
7832                    covers_branch = true;
7833                    break;
7834                }
7835                if !normalized.iter().any(|existing| existing == &term) {
7836                    normalized.push(term);
7837                }
7838            }
7839            if covers_branch {
7840                continue;
7841            }
7842            let Some(split_guard) = normalized
7843                .iter()
7844                .flat_map(|term| term.iter())
7845                .next()
7846                .cloned()
7847            else {
7848                return false;
7849            };
7850            let negated_guard = split_guard.negated();
7851            let mut when_defined = Vec::new();
7852            let mut when_undefined = Vec::new();
7853            for term in normalized {
7854                if term.contains(&negated_guard) {
7855                    // This term cannot hold when `split_guard` is true.
7856                } else if term.contains(&split_guard) {
7857                    let mut reduced = term.clone();
7858                    reduced.remove(&split_guard);
7859                    when_defined.push(reduced);
7860                } else {
7861                    when_defined.push(term.clone());
7862                }
7863                if term.contains(&split_guard) {
7864                    // This term cannot hold when `split_guard` is false.
7865                } else if term.contains(&negated_guard) {
7866                    let mut reduced = term;
7867                    reduced.remove(&negated_guard);
7868                    when_undefined.push(reduced);
7869                } else {
7870                    when_undefined.push(term);
7871                }
7872            }
7873            pending.push(when_defined);
7874            pending.push(when_undefined);
7875        }
7876        true
7877    }
7878
7879    /// The byte range of the one `#if` family with a terminal `#else` that holds
7880    /// every physical declaration of every candidate, or `None` when they do not
7881    /// share one such family.
7882    ///
7883    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7884    /// whose macros changed between declarations. Require every physical range to
7885    /// belong to one syntax-tree family with a terminal `#else` before the terms
7886    /// can prove branch coverage.
7887    fn declarations_share_exhaustive_conditional_family(
7888        &self,
7889        analyzer: &CppGraphSource<'_>,
7890        candidates: &[&CodeUnit],
7891    ) -> Option<(usize, usize)> {
7892        let mut family_range = None;
7893        for candidate in candidates {
7894            let mut candidate_family = None;
7895            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7896                let key = occurrence.exhaustive_family?;
7897                if candidate_family.is_some_and(|existing| existing != key) {
7898                    return None;
7899                }
7900                candidate_family = Some(key);
7901            }
7902            let candidate_family = candidate_family?;
7903            if family_range.is_some_and(|existing| existing != candidate_family) {
7904                return None;
7905            }
7906            family_range = Some(candidate_family);
7907        }
7908        family_range
7909    }
7910
7911    pub fn complementary_same_fqn_type_declarations(
7912        &self,
7913        analyzer: &CppGraphSource<'_>,
7914        candidates: &[&CodeUnit],
7915        target: &CodeUnit,
7916    ) -> bool {
7917        if candidates.len() < 2
7918            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7919            || self
7920                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7921                .is_none()
7922        {
7923            return false;
7924        }
7925        Self::preprocessor_guard_terms_cover_all_paths(
7926            &self.declaration_family_guard_terms(analyzer, candidates),
7927        )
7928    }
7929
7930    fn declaration_family_guard_terms(
7931        &self,
7932        analyzer: &CppGraphSource<'_>,
7933        candidates: &[&CodeUnit],
7934    ) -> Vec<HashSet<PreprocessorGuard>> {
7935        candidates
7936            .iter()
7937            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7938            .map(|(_, guards)| guards)
7939            .collect()
7940    }
7941
7942    /// A callable name declared on every branch of one completed `#if`/`#else`
7943    /// family is declared on every configuration path, so a reference below the
7944    /// whole family sees one of the branches whatever the preprocessor decides.
7945    /// Answer the family's end byte: only past `#endif` is every branch's
7946    /// declaration behind the reference.
7947    ///
7948    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7949    /// and shares both of its primitives. It does not require two distinct
7950    /// `CodeUnit`s: branches that declare the same signature can collapse into
7951    /// one unit carrying one physical range per branch.
7952    ///
7953    /// The branches are alternate spellings of one declaration, never competing
7954    /// declarations, so only the first branch stands for the family. Reporting
7955    /// every branch as visible would turn a name the source declares exactly
7956    /// once into an ambiguity between build configurations.
7957    fn exhaustive_guard_family_activation(
7958        &self,
7959        analyzer: &CppGraphSource<'_>,
7960        candidate: &CodeUnit,
7961        reference: &CallableReferenceContext<'_>,
7962    ) -> Option<usize> {
7963        // Branch coverage says nothing about scope: a block-local declaration
7964        // stays invisible however many branches declare it.
7965        if !analyzer
7966            .declaration_source_occurrences(candidate)?
7967            .iter()
7968            .any(|occurrence| occurrence.fact.callable_activation.is_some())
7969        {
7970            return None;
7971        }
7972        let family = self
7973            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7974            .filter(|peer| {
7975                peer.kind() == candidate.kind()
7976                    && peer.fq_name() == candidate.fq_name()
7977                    && peer.source() == candidate.source()
7978            })
7979            .collect::<Vec<_>>();
7980        let (_, family_end) =
7981            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7982        if !Self::preprocessor_guard_terms_cover_all_paths(
7983            &self.declaration_family_guard_terms(analyzer, &family),
7984        ) {
7985            return None;
7986        }
7987        // A reference whose own guards pick one branch already reaches that
7988        // branch through the ordinary same-guard path; the family must not
7989        // resurrect the branch the reference contradicts.
7990        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7991            .iter()
7992            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7993        {
7994            return None;
7995        }
7996        (first_declaration_byte(analyzer, candidate)?
7997            == family
7998                .iter()
7999                .filter_map(|peer| first_declaration_byte(analyzer, peer))
8000                .min()?)
8001        .then_some(family_end)
8002    }
8003
8004    fn type_candidate_preserving_target(
8005        &self,
8006        analyzer: &CppGraphSource<'_>,
8007        visible_from: &ProjectFile,
8008        candidate: &CodeUnit,
8009        target: &CodeUnit,
8010    ) -> Option<CodeUnit> {
8011        let mut current = candidate.clone();
8012        let mut matched_target = same_visible_symbol(&current, target)
8013            || self.compatible_primary_template_redeclarations(&current, target);
8014        let mut seen = HashSet::default();
8015        loop {
8016            if !seen.insert(current.clone()) {
8017                return None;
8018            }
8019            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8020                return matched_target
8021                    .then(|| target.clone())
8022                    .or_else(|| current.is_class().then_some(current));
8023            };
8024            if self.flattened_macro_namespace_alias_target_matches(
8025                analyzer,
8026                visible_from,
8027                &current,
8028                &alias_target,
8029                target,
8030            ) {
8031                return Some(target.clone());
8032            }
8033            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8034                return matched_target
8035                    .then(|| target.clone())
8036                    .or_else(|| current.is_class().then_some(current));
8037            }
8038            // A non-template alias can name a template alias with explicit
8039            // arguments (for example, `using Result = Expected<int>`).  When
8040            // the requested target is that alias's primary declaration, keep
8041            // the primary identity before expanding the RHS arguments.  The
8042            // expansion would otherwise canonicalize through the underlying
8043            // implementation type and lose the target spelling used by the
8044            // forward resolver.
8045            if !self.cpp_template_metadata.contains_key(&current)
8046                && let Some(primary) =
8047                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8048                && (same_visible_symbol(&primary, target)
8049                    || self.compatible_primary_template_redeclarations(&primary, target))
8050            {
8051                return Some(target.clone());
8052            }
8053            if same_visible_symbol(&current, target) {
8054                return Some(target.clone());
8055            }
8056            if self.cpp_template_metadata.contains_key(&current) {
8057                return None;
8058            }
8059            let Some(next) =
8060                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
8061            else {
8062                return matched_target.then(|| target.clone());
8063            };
8064            current = next;
8065            matched_target |= same_visible_symbol(&current, target)
8066                || self.compatible_primary_template_redeclarations(&current, target);
8067        }
8068    }
8069
8070    fn compatible_primary_template_redeclarations(
8071        &self,
8072        left: &CodeUnit,
8073        right: &CodeUnit,
8074    ) -> bool {
8075        let (Some(left_metadata), Some(right_metadata)) = (
8076            self.cpp_template_metadata.get(left),
8077            self.cpp_template_metadata.get(right),
8078        ) else {
8079            return false;
8080        };
8081        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8082            && left_metadata.is_primary()
8083            && right_metadata.is_primary()
8084            && cpp_reconcile_primary_template_parameters(
8085                &[(left, left_metadata), (right, right_metadata)],
8086                right,
8087            )
8088            .is_some()
8089    }
8090
8091    fn alias_candidate_may_preserve_target(
8092        &self,
8093        analyzer: &CppGraphSource<'_>,
8094        visible_from: &ProjectFile,
8095        candidate: &CodeUnit,
8096        target: &CodeUnit,
8097    ) -> bool {
8098        let mut current = candidate.clone();
8099        let mut seen = HashSet::default();
8100        loop {
8101            if same_visible_symbol(&current, target)
8102                || self.compatible_primary_template_redeclarations(&current, target)
8103            {
8104                return true;
8105            }
8106            if self.cpp_template_metadata.contains_key(&current) {
8107                return true;
8108            }
8109            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8110                return false;
8111            };
8112            let StructuredAliasTarget::Named {
8113                components,
8114                global,
8115                arguments,
8116            } = alias_target
8117            else {
8118                return false;
8119            };
8120            if arguments.is_some() || !seen.insert(current.clone()) {
8121                return true;
8122            }
8123            let qualified = components.join("::");
8124            let next = if global {
8125                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8126            } else {
8127                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8128            };
8129            let Some(next) = next else {
8130                return true;
8131            };
8132            current = next;
8133        }
8134    }
8135
8136    fn alias_candidate_structurally_reaches_target(
8137        &self,
8138        analyzer: &CppGraphSource<'_>,
8139        visible_from: &ProjectFile,
8140        candidate: &CodeUnit,
8141        target: &CodeUnit,
8142    ) -> bool {
8143        let mut current = candidate.clone();
8144        let mut seen = HashSet::default();
8145        loop {
8146            if same_visible_symbol(&current, target)
8147                || self.compatible_primary_template_redeclarations(&current, target)
8148                || self
8149                    .cpp_template_metadata
8150                    .get(&current)
8151                    .zip(self.cpp_template_metadata.get(target))
8152                    .is_some_and(|(current, target)| {
8153                        current.primary_fq_name == target.primary_fq_name
8154                    })
8155            {
8156                return true;
8157            }
8158            if !seen.insert(current.clone()) {
8159                return false;
8160            }
8161            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8162                return false;
8163            };
8164            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8165                && components[..components.len().saturating_sub(1)]
8166                    .iter()
8167                    .any(|component| {
8168                        self.type_reference_component_directly_names_target(component, target)
8169                    })
8170                && self.structured_class_alias_path_preserves_target(
8171                    analyzer,
8172                    visible_from,
8173                    &current,
8174                    target,
8175                )
8176            {
8177                return true;
8178            }
8179            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8180                return false;
8181            }
8182            let Some(primary) =
8183                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8184            else {
8185                return false;
8186            };
8187            current = primary;
8188        }
8189    }
8190
8191    /// Whether one indexed alias component in a qualified type reference has
8192    /// a structured path to `target`.
8193    ///
8194    /// Alias terminal names such as `type` are deliberately common. A
8195    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8196    /// names the target, from thousands of unrelated `Traits<T>::type`
8197    /// references. Match every qualified prefix against the alias's canonical
8198    /// scope before following its structured RHS. An unresolved dependent RHS
8199    /// fails this coarse gate only when no other component names the target;
8200    /// references such as `Identity<Target>::type` are admitted by that target
8201    /// component before this helper is called.
8202    pub fn qualified_alias_reference_may_reach_target(
8203        &self,
8204        analyzer: &CppGraphSource<'_>,
8205        file: &ProjectFile,
8206        components: &[String],
8207        global: bool,
8208        target: &CodeUnit,
8209    ) -> bool {
8210        for end in 0..components.len() {
8211            let spelled = &components[..=end];
8212            for candidate in self
8213                .visible_identifier_candidates(file, &components[end])
8214                .filter(|candidate| declared_type_alias(analyzer, candidate))
8215            {
8216                let candidate_components = canonical_cpp_scope_components(candidate);
8217                let shape_matches = if global {
8218                    candidate_components == spelled
8219                } else {
8220                    candidate_components.ends_with(spelled)
8221                };
8222                if shape_matches
8223                    && self.alias_candidate_structurally_reaches_target(
8224                        analyzer, file, candidate, target,
8225                    )
8226                {
8227                    return true;
8228                }
8229            }
8230        }
8231        false
8232    }
8233
8234    /// Every indexed type declaration `raw_name` names when it is written in
8235    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8236    /// the name wins, otherwise the name is looked up unqualified.
8237    fn type_candidates_for_declaration<'b>(
8238        &'b self,
8239        visible_from: &ProjectFile,
8240        declaration: &CodeUnit,
8241        raw_name: &str,
8242    ) -> Vec<&'b CodeUnit> {
8243        let Some(normalized) = normalize_reference_name(raw_name) else {
8244            return Vec::new();
8245        };
8246        if let Some(namespace) = cpp_namespace_for(declaration) {
8247            for prefix in namespace_prefixes(&namespace) {
8248                let qualified = format!("{prefix}::{normalized}");
8249                let candidates = self.type_candidates(visible_from, &qualified);
8250                if !candidates.is_empty() {
8251                    return candidates;
8252                }
8253            }
8254        }
8255        self.type_candidates(visible_from, &normalized)
8256    }
8257
8258    fn resolve_unique_type_for_declaration(
8259        &self,
8260        visible_from: &ProjectFile,
8261        declaration: &CodeUnit,
8262        raw_name: &str,
8263    ) -> Option<CodeUnit> {
8264        unique_logical_type_candidate(self.type_candidates_for_declaration(
8265            visible_from,
8266            declaration,
8267            raw_name,
8268        ))
8269    }
8270
8271    /// The type `raw_name` names at `declaration`, answered with the
8272    /// declaration that defines it.
8273    ///
8274    /// C spells one type twice. `typedef struct T T;` in the header that
8275    /// publishes the handle indexes an incomplete `struct T` beside the
8276    /// complete `struct T { ... }` in the header that declares the members, and
8277    /// both carry the same kind and fully qualified name, so
8278    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8279    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8280    /// `mbedtls_ssl_handshake_params` for the pointer member
8281    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8282    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8283    /// declaration is asked for its members on the next link of the chain, and
8284    /// only the definition owns them (#2982).
8285    ///
8286    /// Competing definitions leave nothing to choose between, so keep the
8287    /// logical answer rather than picking one of them.
8288    fn resolve_defining_type_for_declaration(
8289        &self,
8290        analyzer: &CppGraphSource<'_>,
8291        visible_from: &ProjectFile,
8292        declaration: &CodeUnit,
8293        raw_name: &str,
8294    ) -> Option<CodeUnit> {
8295        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8296        let logical = unique_logical_type_candidate(candidates.clone())?;
8297        let mut defining = candidates.into_iter().filter(|candidate| {
8298            candidate.is_class()
8299                && cpp_class_declaration_strength(analyzer, candidate)
8300                    == CppClassDeclarationStrength::Full
8301        });
8302        match (defining.next(), defining.next()) {
8303            (Some(unique_definition), None) => Some(unique_definition.clone()),
8304            _ => Some(logical),
8305        }
8306    }
8307
8308    pub fn resolves_to_type(
8309        &self,
8310        analyzer: &CppGraphSource<'_>,
8311        file: &ProjectFile,
8312        raw_name: &str,
8313        target: &CodeUnit,
8314    ) -> bool {
8315        let Some(normalized) = normalize_reference_name(raw_name) else {
8316            return false;
8317        };
8318        let candidates = self.type_candidates(file, &normalized);
8319        if candidates.is_empty() {
8320            return self.parser_alias_resolves_to_type(file, raw_name, target);
8321        }
8322        let Some(resolved) =
8323            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8324        else {
8325            return false;
8326        };
8327        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8328    }
8329
8330    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8331        let mut current = alias.clone();
8332        let mut visited = HashSet::default();
8333        while visited.insert(current.clone()) {
8334            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8335            let resolved =
8336                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8337            if resolved.is_class() {
8338                return Some(resolved);
8339            }
8340            if !declared_type_alias(&self.cpp_source(), &resolved) {
8341                return None;
8342            }
8343            current = resolved;
8344        }
8345        None
8346    }
8347
8348    /// Whether two callable declarations declare one function.
8349    ///
8350    /// [`same_logical_symbol`] compares the persisted signature strings, which
8351    /// embed each parameter type exactly as it was spelled. A header
8352    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8353    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8354    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8355    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8356    /// This resolves the written parameter names before comparing them and
8357    /// reports the same answer the language does for the cases it can prove.
8358    ///
8359    /// Everything it cannot prove stays two symbols: a template declaration, a
8360    /// parameter with no comparable shape, a name that resolves on one side
8361    /// only, and an alias chain it cannot follow safely (#2010).
8362    pub fn same_logical_callable(
8363        &self,
8364        analyzer: &CppGraphSource<'_>,
8365        left: &CodeUnit,
8366        right: &CodeUnit,
8367    ) -> bool {
8368        if same_logical_symbol(left, right) {
8369            return true;
8370        }
8371        if left.kind() != right.kind()
8372            || !left.is_callable()
8373            || !right.is_callable()
8374            || left.fq_name() != right.fq_name()
8375        {
8376            return false;
8377        }
8378        // A template declaration and its out-of-line body can also diverge
8379        // outside the parameter list - `template <class T>` against
8380        // `template <typename T>` - and the template head is part of the
8381        // persisted signature. Deciding template-head equivalence is a
8382        // separate question, so templates keep string identity.
8383        if self.callable_is_template_declaration(analyzer, left)
8384            || self.callable_is_template_declaration(analyzer, right)
8385        {
8386            return false;
8387        }
8388        let (Some(left_comparable), Some(right_comparable)) = (
8389            self.callable_comparable(analyzer, left),
8390            self.callable_comparable(analyzer, right),
8391        ) else {
8392            return false;
8393        };
8394        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8395        // return type and requires-clause are part of C++ callable identity and
8396        // an out-of-line definition repeats them verbatim, so they must agree
8397        // as written.
8398        if left_comparable.suffix != right_comparable.suffix
8399            || left_comparable.shapes.len() != right_comparable.shapes.len()
8400        {
8401            return false;
8402        }
8403        left_comparable
8404            .shapes
8405            .iter()
8406            .zip(right_comparable.shapes.iter())
8407            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8408                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8409                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8410                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8411                }
8412                // An unstructured parameter records that the reduction failed,
8413                // not that the two spellings mean the same type, so it agrees
8414                // with nothing - including another unstructured parameter.
8415                _ => false,
8416            })
8417    }
8418
8419    /// Compare two parameter shapes node by node with an explicit paired stack.
8420    ///
8421    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8422    /// the named leaves may be spelled differently, and they agree when they
8423    /// resolve to one type declaration.
8424    fn comparable_shapes_agree(
8425        &self,
8426        analyzer: &CppGraphSource<'_>,
8427        left: &CppComparableParameter,
8428        right: &CppComparableParameter,
8429    ) -> bool {
8430        let mut stack = vec![(left.root(), right.root())];
8431        while let Some((left_index, right_index)) = stack.pop() {
8432            match (left.node(left_index), right.node(right_index)) {
8433                (
8434                    CppComparableNode::Named {
8435                        name: left_name,
8436                        primitive: left_primitive,
8437                        konst: left_konst,
8438                        volatil: left_volatil,
8439                    },
8440                    CppComparableNode::Named {
8441                        name: right_name,
8442                        primitive: right_primitive,
8443                        konst: right_konst,
8444                        volatil: right_volatil,
8445                    },
8446                ) => {
8447                    if left_konst != right_konst
8448                        || left_volatil != right_volatil
8449                        || left_primitive != right_primitive
8450                        || !self.comparable_names_agree(
8451                            analyzer,
8452                            left_name,
8453                            right_name,
8454                            *left_primitive,
8455                        )
8456                    {
8457                        return false;
8458                    }
8459                }
8460                (
8461                    CppComparableNode::Pointer {
8462                        inner: left_inner,
8463                        konst: left_konst,
8464                        volatil: left_volatil,
8465                    },
8466                    CppComparableNode::Pointer {
8467                        inner: right_inner,
8468                        konst: right_konst,
8469                        volatil: right_volatil,
8470                    },
8471                ) => {
8472                    if left_konst != right_konst || left_volatil != right_volatil {
8473                        return false;
8474                    }
8475                    stack.push((*left_inner, *right_inner));
8476                }
8477                (
8478                    CppComparableNode::Reference { inner: left_inner },
8479                    CppComparableNode::Reference { inner: right_inner },
8480                )
8481                | (
8482                    CppComparableNode::Array { inner: left_inner },
8483                    CppComparableNode::Array { inner: right_inner },
8484                ) => stack.push((*left_inner, *right_inner)),
8485                (
8486                    CppComparableNode::Generic {
8487                        base: left_base,
8488                        arguments: left_arguments,
8489                    },
8490                    CppComparableNode::Generic {
8491                        base: right_base,
8492                        arguments: right_arguments,
8493                    },
8494                ) => {
8495                    if left_arguments.len() != right_arguments.len() {
8496                        return false;
8497                    }
8498                    stack.push((*left_base, *right_base));
8499                    stack.extend(
8500                        left_arguments.iter().zip(right_arguments.iter()).map(
8501                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8502                        ),
8503                    );
8504                }
8505                _ => return false,
8506            }
8507        }
8508        true
8509    }
8510
8511    /// Whether two written type names denote one type.
8512    ///
8513    /// A primitive denotes the same type in every scope, so its recorded
8514    /// lexical scope is noise and its spelling decides. A nominal name is
8515    /// resolved on each side independently: two resolved names agree when they
8516    /// reach one type declaration, and two unresolved names agree only on
8517    /// exact agreement of what was written, which is no weaker than the
8518    /// whole-signature string equality this comparison replaces. Resolution on
8519    /// one side only is evidence of difference, never of agreement.
8520    fn comparable_names_agree(
8521        &self,
8522        analyzer: &CppGraphSource<'_>,
8523        left: &StructuredTypeName,
8524        right: &StructuredTypeName,
8525        primitive: bool,
8526    ) -> bool {
8527        if primitive {
8528            return left.path() == right.path();
8529        }
8530        match (
8531            self.comparable_name_terminal(analyzer, left),
8532            self.comparable_name_terminal(analyzer, right),
8533        ) {
8534            (Some(left_terminal), Some(right_terminal)) => {
8535                same_logical_symbol(&left_terminal, &right_terminal)
8536            }
8537            (None, None) => {
8538                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8539            }
8540            _ => false,
8541        }
8542    }
8543
8544    /// The class declaration a written type name denotes, or `None` when the
8545    /// workspace cannot prove one.
8546    ///
8547    /// The lookup is a closure-independent lexical-scope prefix walk over the
8548    /// stored C++ identifier index rather than a visibility lookup: a body's
8549    /// `.cpp` is almost never in the reference file's include closure. Any name
8550    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8551    /// cannot invent a type the compiler could not see; `using`-directives are
8552    /// not modelled, and a name that needs one stays unresolved.
8553    fn comparable_name_terminal(
8554        &self,
8555        analyzer: &CppGraphSource<'_>,
8556        name: &StructuredTypeName,
8557    ) -> Option<CodeUnit> {
8558        let mut current = self.comparable_name_declaration(analyzer, name)?;
8559        let mut visited = HashSet::default();
8560        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8561            // The alias question is asked before the class question, and
8562            // through `declared_type_alias` rather than `is_type_alias`,
8563            // because extraction records `using A8 = A7;` as a *Class* unit
8564            // whose signature is the alias declaration. Reading the kind first
8565            // would end the chase on the alias itself and report an alias
8566            // spelling and its underlying class as two types (#2010).
8567            if !declared_type_alias(analyzer, &current) {
8568                return current.is_class().then_some(current);
8569            }
8570            if !visited.insert(current.clone()) {
8571                return None;
8572            }
8573            let facts = analyzer.declaration_source_properties(&current)?;
8574            if facts.iter().any(|fact| fact.adds_indirection) {
8575                return None;
8576            }
8577            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
8578            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8579        }
8580        None
8581    }
8582
8583    /// The declaration one alias hop lands on: the type `raw_target` names,
8584    /// looked up from the alias declaration's own enclosing namespace.
8585    ///
8586    /// The hop takes the same closure-independent prefix walk the first lookup
8587    /// took, and deliberately not `resolve_type_for_declaration`: that one
8588    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8589    /// file, while the alias declaration this hop starts from is reached
8590    /// through the workspace definition index and its file need not be in that
8591    /// root's include closure - where the visibility lookup answers nothing and
8592    /// the chase would stop on the alias itself (#2010).
8593    fn comparable_alias_target(
8594        &self,
8595        analyzer: &CppGraphSource<'_>,
8596        alias: &CodeUnit,
8597        raw_target: &str,
8598    ) -> Option<CodeUnit> {
8599        // `raw_target` is the alias declaration's written type text, so it is a
8600        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8601        // parser reads, and the same leading `::` that marks an absolute name
8602        // everywhere else this crate normalizes a reference.
8603        let absolute = raw_target.trim_start().starts_with("::");
8604        let normalized = normalize_reference_name(raw_target)?;
8605        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8606            brokk_bifrost_core::analyzer::Language::Cpp,
8607            &normalized,
8608        );
8609        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8610            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8611                brokk_bifrost_core::analyzer::Language::Cpp,
8612                &namespace,
8613            )
8614        });
8615        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8616        self.comparable_name_declaration(analyzer, &name)
8617    }
8618
8619    /// The one type declaration `name` names, by enclosing scope, innermost
8620    /// first.
8621    ///
8622    /// The first prefix depth that names anything decides: an inner scope hides
8623    /// an outer one, so a match there is the answer even when an outer scope
8624    /// also declares the name. Several logically distinct declarations at that
8625    /// depth are an ambiguity this comparison must not guess at.
8626    fn comparable_name_declaration(
8627        &self,
8628        analyzer: &CppGraphSource<'_>,
8629        name: &StructuredTypeName,
8630    ) -> Option<CodeUnit> {
8631        // The same parameter-name pair is commonly compared once for every
8632        // physical declaration in a candidate set. Keep the index lookup under
8633        // the cache lock so concurrent workers cannot all miss the same key
8634        // and repeat the workspace candidate filter. `None` is a meaningful
8635        // result too: unresolved and ambiguous names must remain unresolved on
8636        // every later comparison.
8637        let mut cache = self
8638            .comparable_name_declarations
8639            .lock()
8640            .expect("C++ comparable name declaration cache poisoned");
8641        if let Some(cached) = cache.get(name) {
8642            return cached.clone();
8643        }
8644        let interner = segment_interner();
8645        let identifier = name.path().last()?;
8646        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8647        let first_depth = if name.is_absolute() {
8648            0
8649        } else {
8650            name.lexical_scope().len()
8651        };
8652        let mut resolved = None;
8653        for depth in (0..=first_depth).rev() {
8654            let mut structured = FqName::new();
8655            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8656                structured.push(interner.intern(component, SegmentKind::Unknown));
8657            }
8658            let mut candidates = candidates_by_identifier
8659                .iter()
8660                .filter(|unit| unit.fq().same_segment_texts(&structured))
8661                .filter(|unit| {
8662                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8663                })
8664                .cloned();
8665            let Some(first) = candidates.next() else {
8666                continue;
8667            };
8668            resolved = candidates
8669                .all(|unit| same_logical_symbol(&unit, &first))
8670                .then_some(first);
8671            break;
8672        }
8673        cache.insert(name.clone(), resolved.clone());
8674        resolved
8675    }
8676
8677    /// The comparison inputs of one callable declaration, extracted once.
8678    ///
8679    /// The comparison itself runs only when two candidates share kind and fully
8680    /// qualified name but not signature, which is rare; re-reading the same
8681    /// declaration for every pair in a candidate set is not.
8682    fn callable_comparable(
8683        &self,
8684        analyzer: &CppGraphSource<'_>,
8685        unit: &CodeUnit,
8686    ) -> Option<Arc<ExtractedComparable>> {
8687        if let Some(cached) = self
8688            .callable_comparables
8689            .lock()
8690            .expect("C++ callable comparable cache poisoned")
8691            .get(unit)
8692            .cloned()
8693        {
8694            return cached;
8695        }
8696        let extracted = self
8697            .extract_callable_comparable(analyzer, unit)
8698            .map(Arc::new);
8699        self.callable_comparables
8700            .lock()
8701            .expect("C++ callable comparable cache poisoned")
8702            .insert(unit.clone(), extracted.clone());
8703        extracted
8704    }
8705
8706    fn extract_callable_comparable(
8707        &self,
8708        analyzer: &CppGraphSource<'_>,
8709        unit: &CodeUnit,
8710    ) -> Option<ExtractedComparable> {
8711        let fact = analyzer
8712            .declaration_source_properties(unit)?
8713            .into_iter()
8714            .find(|fact| fact.callable_comparable_shapes.is_some())?;
8715        Some(ExtractedComparable {
8716            shapes: fact.callable_comparable_shapes?,
8717            suffix: fact.callable_identity_suffix?,
8718        })
8719    }
8720
8721    pub fn canonical_type_for_reference(
8722        &self,
8723        file: &ProjectFile,
8724        raw_name: &str,
8725    ) -> Option<CodeUnit> {
8726        let resolved = self.resolve_type(file, raw_name)?;
8727        self.alias_target(&resolved).or(Some(resolved))
8728    }
8729
8730    pub fn parser_alias_resolves_to_type(
8731        &self,
8732        file: &ProjectFile,
8733        raw_name: &str,
8734        target: &CodeUnit,
8735    ) -> bool {
8736        let Some(alias_name) = normalize_reference_name(raw_name) else {
8737            return false;
8738        };
8739        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8740            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8741        })
8742    }
8743
8744    fn file_alias_matches(
8745        &self,
8746        cpp: &dyn CppSource,
8747        file: &ProjectFile,
8748        alias_name: &str,
8749        target: &CodeUnit,
8750    ) -> bool {
8751        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8752            cell.get()
8753                .expect("published canonical aliases")
8754                .iter()
8755                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8756        })
8757    }
8758
8759    fn canonical_alias_cell(
8760        &self,
8761        cpp: &dyn CppSource,
8762        file: &ProjectFile,
8763    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8764        let cell = {
8765            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8766            Arc::clone(
8767                cells
8768                    .entry(file.clone())
8769                    .or_insert_with(|| Arc::new(OnceLock::new())),
8770            )
8771        };
8772        if cell.get().is_none() {
8773            let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8774            cell.get_or_init(|| {
8775                self.parser_alias_source_parses
8776                    .fetch_add(1, Ordering::Relaxed);
8777                #[cfg(any(test, feature = "test-support"))]
8778                {
8779                    *self
8780                        .alias_source_parse_counts
8781                        .lock()
8782                        .expect("alias source parse count lock")
8783                        .entry(file.clone())
8784                        .or_default() += 1;
8785                }
8786                aliases.into_boxed_slice()
8787            });
8788        }
8789        Some(cell)
8790    }
8791
8792    #[cfg(any(test, feature = "test-support"))]
8793    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8794        self.visible_source_files_by_root
8795            .get(file)
8796            .cloned()
8797            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8798    }
8799
8800    #[cfg(any(test, feature = "test-support"))]
8801    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8802        self.alias_source_parse_counts
8803            .lock()
8804            .expect("alias source parse count lock")
8805            .get(file)
8806            .copied()
8807            .unwrap_or(0)
8808    }
8809
8810    #[cfg(any(test, feature = "test-support"))]
8811    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8812        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8813    }
8814
8815    pub fn resolve_named(
8816        &self,
8817        file: &ProjectFile,
8818        raw_name: &str,
8819        kind: TargetKind,
8820    ) -> Option<CodeUnit> {
8821        let normalized = normalize_reference_name(raw_name)?;
8822        self.named_candidates_for_normalized(file, &normalized, kind)
8823            .into_iter()
8824            .next()
8825            .cloned()
8826    }
8827
8828    pub fn contains_named_symbol(
8829        &self,
8830        file: &ProjectFile,
8831        raw_name: &str,
8832        kind: TargetKind,
8833        target: &CodeUnit,
8834    ) -> bool {
8835        let Some(normalized) = normalize_reference_name(raw_name) else {
8836            return false;
8837        };
8838        self.named_candidates_for_normalized(file, &normalized, kind)
8839            .into_iter()
8840            .any(|unit| {
8841                matches_kind_for_lookup(unit, kind)
8842                    && reference_matches_unit(&normalized, unit)
8843                    && same_visible_symbol(unit, target)
8844            })
8845    }
8846
8847    pub fn named_candidates(
8848        &self,
8849        file: &ProjectFile,
8850        raw_name: &str,
8851        kind: TargetKind,
8852    ) -> Vec<CodeUnit> {
8853        let Some(normalized) = normalize_reference_name(raw_name) else {
8854            return Vec::new();
8855        };
8856        self.named_candidates_for_normalized(file, &normalized, kind)
8857            .into_iter()
8858            .cloned()
8859            .collect()
8860    }
8861
8862    pub fn resolve_known_non_target(
8863        &self,
8864        file: &ProjectFile,
8865        raw_name: &str,
8866        kind: TargetKind,
8867        target: &CodeUnit,
8868    ) -> bool {
8869        let Some(normalized) = normalize_reference_name(raw_name) else {
8870            return false;
8871        };
8872        normalized.contains("::")
8873            && self
8874                .named_candidates_for_normalized(file, &normalized, kind)
8875                .into_iter()
8876                .any(|unit| {
8877                    matches_kind_for_lookup(unit, kind)
8878                        && reference_matches_unit(&normalized, unit)
8879                        && !same_visible_symbol(unit, target)
8880                })
8881    }
8882
8883    pub fn resolve_call_return_binding(
8884        &self,
8885        analyzer: &CppGraphSource<'_>,
8886        file: &ProjectFile,
8887        raw_name: &str,
8888        arity: usize,
8889        lexical_namespace: Option<&str>,
8890        direct_type: Option<&CodeUnit>,
8891    ) -> Option<CppScanBinding> {
8892        let normalized = normalize_reference_name(raw_name)?;
8893        let mut candidates = Vec::new();
8894        for function in
8895            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8896        {
8897            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8898                && !direct_type.is_some_and(|direct_type| {
8899                    self.callable_is_constructor_declaration(analyzer, function)
8900                        && type_owner_of(analyzer, function)
8901                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8902                })
8903            {
8904                candidates.push(function.clone());
8905            }
8906        }
8907        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8908        unanimous_return_binding(analyzer, self, file, &candidates)
8909    }
8910
8911    pub fn resolve_call_return_binding_without_arity(
8912        &self,
8913        analyzer: &CppGraphSource<'_>,
8914        file: &ProjectFile,
8915        raw_name: &str,
8916        lexical_namespace: Option<&str>,
8917        direct_type: Option<&CodeUnit>,
8918    ) -> (bool, Option<CppScanBinding>) {
8919        let Some(normalized) = normalize_reference_name(raw_name) else {
8920            return (false, None);
8921        };
8922        let mut candidates = self
8923            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8924            .into_iter()
8925            .filter(|function| {
8926                function.is_function()
8927                    && !direct_type.is_some_and(|direct_type| {
8928                        self.callable_is_constructor_declaration(analyzer, function)
8929                            && type_owner_of(analyzer, function)
8930                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8931                    })
8932            })
8933            .cloned()
8934            .collect::<Vec<_>>();
8935        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8936        let has_candidates = !candidates.is_empty();
8937        (
8938            has_candidates,
8939            unanimous_return_binding(analyzer, self, file, &candidates),
8940        )
8941    }
8942
8943    pub fn visible_identifier_candidates<'b>(
8944        &'b self,
8945        file: &ProjectFile,
8946        identifier: &str,
8947    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8948        self.visible_by_identifier
8949            .get(file)
8950            .and_then(|by_name| by_name.get(identifier))
8951            .into_iter()
8952            .flatten()
8953    }
8954
8955    /// Whether a grammar component is the direct spelling of `target`.
8956    ///
8957    /// Concrete template specializations are indexed with their arguments in
8958    /// `identifier`, while a reference's name component contains only the
8959    /// primary name. Argument matching remains the later resolver's job.
8960    pub fn type_reference_component_directly_names_target(
8961        &self,
8962        component: &str,
8963        target: &CodeUnit,
8964    ) -> bool {
8965        component == target.identifier()
8966            || self
8967                .cpp_template_metadata
8968                .get(target)
8969                .is_some_and(|metadata| component == metadata.primary_name)
8970    }
8971
8972    /// Return terminal reference names that can denote `target` from `file`.
8973    ///
8974    /// The indexed candidate table covers ordinary declarations and aliases;
8975    /// Parser-only aliases are tested lazily when their spelling is actually
8976    /// encountered in a scanned type node. Enumerating them here would parse
8977    /// every source in the include closure even when the target's direct name
8978    /// is the only spelling present in the file.
8979    pub fn visible_type_reference_component_names_for_target(
8980        &self,
8981        analyzer: &CppGraphSource<'_>,
8982        file: &ProjectFile,
8983        target: &CodeUnit,
8984    ) -> HashSet<String> {
8985        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8986        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8987            names.insert(metadata.primary_name.clone());
8988        }
8989
8990        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8991            for (identifier, candidates) in by_identifier {
8992                if candidates.iter().any(|candidate| {
8993                    (candidate.is_class()
8994                        && (same_visible_symbol(candidate, target)
8995                            || self.compatible_primary_template_redeclarations(candidate, target)))
8996                        || (declared_type_alias(analyzer, candidate)
8997                            && self.alias_candidate_structurally_reaches_target(
8998                                analyzer, file, candidate, target,
8999                            ))
9000                }) {
9001                    names.insert(identifier.clone());
9002                }
9003            }
9004        }
9005
9006        names
9007    }
9008
9009    pub fn indexed_structural_class_scope(
9010        &self,
9011        file: &ProjectFile,
9012        class: Node<'_>,
9013        source: &str,
9014    ) -> Option<Vec<String>> {
9015        let key = (file.clone(), class.start_byte(), class.end_byte());
9016        if let Some(cached) = self
9017            .indexed_structural_class_scopes
9018            .lock()
9019            .expect("C++ indexed structural-class scope cache poisoned")
9020            .get(&key)
9021            .cloned()
9022        {
9023            return cached;
9024        }
9025        let resolved = (|| {
9026            let name = class.child_by_field_name("name")?;
9027            let identifier = if name.kind() == "template_type" {
9028                node_text(name.child_by_field_name("name")?, source).to_string()
9029            } else {
9030                let mut components = Vec::new();
9031                append_cpp_name_components(name, source, &mut components)?;
9032                components.last()?.clone()
9033            };
9034            let visible = self
9035                .visible_identifier_candidates(file, &identifier)
9036                .cloned()
9037                .collect::<Vec<_>>();
9038            let mut visible = visible;
9039            for candidate in
9040                self.visible_by_file
9041                    .get(file)
9042                    .into_iter()
9043                    .flatten()
9044                    .filter(|candidate| {
9045                        self.cpp_template_metadata
9046                            .get(candidate)
9047                            .is_some_and(|metadata| metadata.primary_name == identifier)
9048                    })
9049            {
9050                if !visible
9051                    .iter()
9052                    .any(|existing| same_logical_symbol(existing, candidate))
9053                {
9054                    visible.push(candidate.clone());
9055                }
9056            }
9057            // Built once per call rather than per candidate; `cpp_source` rebuilds
9058            // the five-field source from the same `self.cpp` on every call.
9059            let cpp_source = self.cpp_source();
9060            let candidates = visible
9061                .iter()
9062                .filter(|candidate| {
9063                    candidate.source() == file
9064                        && candidate.is_class()
9065                        && !declared_type_alias(&cpp_source, candidate)
9066                        && self.cpp.ranges(candidate).iter().any(|range| {
9067                            range.start_byte <= class.start_byte()
9068                                && class.end_byte() <= range.end_byte
9069                        })
9070                })
9071                .collect::<Vec<_>>();
9072            let owner = if name.kind() == "template_type" {
9073                let expected = normalize_cpp_whitespace(node_text(name, source));
9074                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9075                let exact = candidates
9076                    .iter()
9077                    .copied()
9078                    .filter(|candidate| {
9079                        candidate
9080                            .fq()
9081                            .segments()
9082                            .iter()
9083                            .rev()
9084                            .find_map(|&segment| {
9085                                let (text, kind) = interner.resolve(segment);
9086                                matches!(
9087                                    kind,
9088                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9089                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9090                                )
9091                                .then_some(text)
9092                            })
9093                            .is_some_and(|text| text == expected)
9094                    })
9095                    .collect::<Vec<_>>();
9096                unique_logical_type_candidate(exact)
9097                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9098            } else {
9099                unique_logical_type_candidate(candidates)?
9100            };
9101            Some(canonical_cpp_scope_components(&owner))
9102        })();
9103        self.indexed_structural_class_scopes
9104            .lock()
9105            .expect("C++ indexed structural-class scope cache poisoned")
9106            .insert(key, resolved.clone());
9107        resolved
9108    }
9109
9110    pub fn indexed_enclosing_owner_scope(
9111        &self,
9112        analyzer: &CppGraphSource<'_>,
9113        file: &ProjectFile,
9114        node: Node<'_>,
9115    ) -> Option<Vec<String>> {
9116        let anchor = std::iter::successors(Some(node), |current| current.parent())
9117            .find(|current| {
9118                matches!(
9119                    current.kind(),
9120                    "function_definition"
9121                        | "class_specifier"
9122                        | "struct_specifier"
9123                        | "union_specifier"
9124                )
9125            })
9126            .unwrap_or(node);
9127        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9128        if let Some(cached) = self
9129            .indexed_enclosing_owner_scopes
9130            .lock()
9131            .expect("C++ indexed enclosing-owner scope cache poisoned")
9132            .get(&key)
9133            .cloned()
9134        {
9135            return cached;
9136        }
9137        let resolved = (|| {
9138            let range = Range {
9139                start_byte: node.start_byte(),
9140                end_byte: node.end_byte(),
9141                start_line: node.start_position().row,
9142                end_line: node.end_position().row,
9143            };
9144            let start = analyzer.enclosing_code_unit(file, &range)?;
9145            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9146                start,
9147                |unit| self.cached_precise_parent_of(analyzer, unit),
9148            )
9149            .find(|unit| {
9150                unit.is_class()
9151                    && !analyzer
9152                        .type_alias_provider()
9153                        .is_some_and(|provider| provider.is_type_alias(unit))
9154            })?;
9155            Some(canonical_cpp_scope_components(&owner))
9156        })();
9157        self.indexed_enclosing_owner_scopes
9158            .lock()
9159            .expect("C++ indexed enclosing-owner scope cache poisoned")
9160            .insert(key, resolved.clone());
9161        resolved
9162    }
9163
9164    fn cached_precise_parent_of(
9165        &self,
9166        analyzer: &CppGraphSource<'_>,
9167        code_unit: &CodeUnit,
9168    ) -> Option<CodeUnit> {
9169        if let Some(cached) = self
9170            .precise_parent_cache
9171            .lock()
9172            .expect("C++ precise-parent cache poisoned")
9173            .get(code_unit)
9174            .cloned()
9175        {
9176            return cached;
9177        }
9178        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9179        self.precise_parent_cache
9180            .lock()
9181            .expect("C++ precise-parent cache poisoned")
9182            .insert(code_unit.clone(), resolved.clone());
9183        resolved
9184    }
9185
9186    pub fn callable_is_constructor_declaration(
9187        &self,
9188        analyzer: &CppGraphSource<'_>,
9189        candidate: &CodeUnit,
9190    ) -> bool {
9191        if !candidate.is_function() {
9192            return false;
9193        }
9194        if analyzer
9195            .declaration_source_properties(candidate)
9196            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9197        {
9198            return true;
9199        }
9200        let metadata = analyzer.signature_metadata(candidate);
9201        !metadata.is_empty()
9202            && metadata
9203                .iter()
9204                .all(|signature| signature.return_type_text().is_none())
9205    }
9206
9207    /// Whether a callable declaration is a class-template deduction guide.
9208    ///
9209    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9210    /// type field whose function declarator owns a trailing return type. This
9211    /// structured shape distinguishes a guide from both a constructor (no
9212    /// trailing return) and an ordinary trailing-return function (an `auto`
9213    /// type field).
9214    pub fn callable_is_deduction_guide_declaration(
9215        &self,
9216        analyzer: &CppGraphSource<'_>,
9217        candidate: &CodeUnit,
9218    ) -> bool {
9219        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9220    }
9221
9222    /// Whether a callable occurrence is directly wrapped by a C++ template
9223    /// declaration. This deliberately inspects declaration syntax instead of
9224    /// inferring template status from the rendered signature.
9225    pub fn callable_is_template_declaration(
9226        &self,
9227        analyzer: &CppGraphSource<'_>,
9228        candidate: &CodeUnit,
9229    ) -> bool {
9230        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9231    }
9232
9233    pub fn type_name_candidates<'b>(
9234        &'b self,
9235        file: &ProjectFile,
9236        normalized: &str,
9237    ) -> Vec<&'b CodeUnit> {
9238        self.candidate_units(file, normalized, TargetKind::Type)
9239    }
9240
9241    pub fn visible_members_for_owner_name<'b>(
9242        &'b self,
9243        file: &ProjectFile,
9244        owner: &CodeUnit,
9245        name: &str,
9246    ) -> Vec<&'b CodeUnit> {
9247        self.visible_identifier_candidates(file, name)
9248            .filter(|unit| {
9249                // Structured owner pop on the unit's own `fq()` (shared with
9250                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9251                // string.
9252                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9253                    .is_some_and(|parent| parent == owner.fq_name())
9254            })
9255            .collect()
9256    }
9257
9258    pub fn visible_member_for_owner_name(
9259        &self,
9260        file: &ProjectFile,
9261        owner: &CodeUnit,
9262        name: &str,
9263    ) -> VisibleMemberResolution {
9264        let candidates = self.visible_members_for_owner_name(file, owner, name);
9265        let mut callables = Vec::new();
9266        let mut non_callable = None;
9267        for candidate in candidates {
9268            if candidate.is_function() {
9269                callables.push(candidate.clone());
9270            } else if non_callable.is_none() {
9271                non_callable = Some(candidate.clone());
9272            }
9273        }
9274        match (callables.is_empty(), non_callable) {
9275            (false, None) => VisibleMemberResolution::Callable(callables),
9276            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9277            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9278            (true, None) => VisibleMemberResolution::Missing,
9279        }
9280    }
9281
9282    fn field_declared_type_fact(
9283        &self,
9284        analyzer: &CppGraphSource<'_>,
9285        field: &CodeUnit,
9286    ) -> Option<DeclaredFieldTypeFact> {
9287        if let Some(cached) = self
9288            .field_type_facts
9289            .lock()
9290            .expect("C++ field type fact cache poisoned")
9291            .get(field)
9292            .cloned()
9293        {
9294            return cached;
9295        }
9296        let decoded = decode_field_declared_type_fact(analyzer, field);
9297        self.field_type_facts
9298            .lock()
9299            .expect("C++ field type fact cache poisoned")
9300            .insert(field.clone(), decoded.clone());
9301        decoded
9302    }
9303
9304    fn structured_alias_target(
9305        &self,
9306        analyzer: &CppGraphSource<'_>,
9307        unit: &CodeUnit,
9308    ) -> Option<StructuredAliasTarget> {
9309        if let Some(cached) = self
9310            .structured_alias_targets
9311            .lock()
9312            .expect("C++ structured alias target cache poisoned")
9313            .get(unit)
9314            .cloned()
9315        {
9316            return cached;
9317        }
9318        let decoded = decode_structured_alias_target(analyzer, unit);
9319        self.structured_alias_targets
9320            .lock()
9321            .expect("C++ structured alias target cache poisoned")
9322            .insert(unit.clone(), decoded.clone());
9323        decoded
9324    }
9325
9326    pub fn type_candidates<'b>(
9327        &'b self,
9328        file: &ProjectFile,
9329        normalized: &str,
9330    ) -> Vec<&'b CodeUnit> {
9331        let mut candidates = self
9332            .candidate_units(file, normalized, TargetKind::Type)
9333            .into_iter()
9334            .filter(|unit| {
9335                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9336            })
9337            .collect::<Vec<_>>();
9338        dedup_unit_refs(&mut candidates);
9339        candidates
9340    }
9341
9342    pub fn named_candidates_for_normalized<'b>(
9343        &'b self,
9344        file: &ProjectFile,
9345        normalized: &str,
9346        kind: TargetKind,
9347    ) -> Vec<&'b CodeUnit> {
9348        let mut candidates = self
9349            .candidate_units(file, normalized, kind)
9350            .into_iter()
9351            .filter(|unit| {
9352                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9353            })
9354            .collect::<Vec<_>>();
9355        dedup_unit_refs(&mut candidates);
9356        candidates
9357    }
9358
9359    pub fn candidate_units<'b>(
9360        &'b self,
9361        file: &ProjectFile,
9362        normalized: &str,
9363        kind: TargetKind,
9364    ) -> Vec<&'b CodeUnit> {
9365        if normalized.contains("::") {
9366            // `normalized` comes from `normalize_cpp_reference_text`, which
9367            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9368            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9369            // kept intact by the shared splitter's operator merge — the same
9370            // domain `cpp_reference_fqn_candidates` below already parses with
9371            // the shared splitter. Re-tokenizing and taking the last segment
9372            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9373            // scan exactly.
9374            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9375                brokk_bifrost_core::analyzer::Language::Cpp,
9376                normalized,
9377            );
9378            let Some(identifier) = parts.last() else {
9379                return Vec::new();
9380            };
9381            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9382            return self
9383                .visible_identifier_candidates(file, identifier)
9384                .filter(|unit| {
9385                    #[cfg(any(test, feature = "test-support"))]
9386                    self.qualified_candidate_inspections
9387                        .fetch_add(1, Ordering::Relaxed);
9388                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9389                        || canonical_cpp_name_matches(unit, normalized)
9390                })
9391                .collect();
9392        }
9393        self.visible_identifier_candidates(file, normalized)
9394            .collect()
9395    }
9396
9397    #[cfg(any(test, feature = "test-support"))]
9398    pub fn reset_qualified_candidate_inspections(&self) {
9399        self.qualified_candidate_inspections
9400            .store(0, Ordering::Relaxed);
9401    }
9402
9403    #[cfg(any(test, feature = "test-support"))]
9404    pub fn qualified_candidate_inspections(&self) -> usize {
9405        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9406    }
9407
9408    #[cfg(any(test, feature = "test-support"))]
9409    pub fn visibility_identifier_lookup_count(&self) -> usize {
9410        self.visibility_identifier_lookup_count
9411    }
9412
9413    #[cfg(any(test, feature = "test-support"))]
9414    pub fn visibility_identifier_batch_count(&self) -> usize {
9415        self.visibility_identifier_batch_count
9416    }
9417
9418    #[cfg(any(test, feature = "test-support"))]
9419    pub fn reset_target_preserving_type_resolution_count(&self) {
9420        self.target_preserving_type_resolution_count
9421            .store(0, Ordering::Relaxed);
9422    }
9423
9424    #[cfg(any(test, feature = "test-support"))]
9425    pub fn target_preserving_type_resolution_count(&self) -> usize {
9426        self.target_preserving_type_resolution_count
9427            .load(Ordering::Relaxed)
9428    }
9429
9430    #[cfg(any(test, feature = "test-support"))]
9431    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9432        self.visible_parser_alias_name_set_build_count
9433            .load(Ordering::Relaxed)
9434    }
9435}
9436
9437fn callable_declaration_has_fact(
9438    analyzer: &CppGraphSource<'_>,
9439    candidate: &CodeUnit,
9440    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9441) -> bool {
9442    candidate.is_function()
9443        && analyzer
9444            .declaration_source_properties(candidate)
9445            .is_some_and(|facts| facts.iter().any(matches))
9446}
9447
9448#[derive(Default)]
9449struct IncludeGraph {
9450    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9451}
9452
9453impl IncludeGraph {
9454    fn extend_with<F>(
9455        &mut self,
9456        root: &ProjectFile,
9457        cancellation: Option<&CancellationToken>,
9458        targets_for: &mut F,
9459    ) where
9460        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9461    {
9462        let mut stack = vec![root.clone()];
9463        while let Some(file) = stack.pop() {
9464            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9465                break;
9466            }
9467            if self.targets_by_file.contains_key(&file) {
9468                continue;
9469            }
9470            let targets = targets_for(&file);
9471            stack.extend(targets.iter().cloned());
9472            self.targets_by_file.insert(file, targets);
9473        }
9474    }
9475
9476    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9477        self.targets_by_file.keys()
9478    }
9479
9480    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9481        self.targets_by_file
9482            .get(file)
9483            .map(Vec::as_slice)
9484            .unwrap_or_default()
9485    }
9486
9487    fn reachable_files(
9488        &self,
9489        root: &ProjectFile,
9490        cancellation: Option<&CancellationToken>,
9491    ) -> HashSet<ProjectFile> {
9492        let mut pending = vec![root.clone()];
9493        let mut visited = HashSet::default();
9494        while let Some(file) = pending.pop() {
9495            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9496                break;
9497            }
9498            if visited.insert(file.clone()) {
9499                pending.extend(self.targets(&file).iter().cloned());
9500            }
9501        }
9502        visited
9503    }
9504}
9505
9506fn build_bounded_visible_declarations(
9507    cpp: &dyn CppSource,
9508    token: QueryToken<'_>,
9509    analyzer: &CppGraphSource<'_>,
9510    roots: &HashSet<ProjectFile>,
9511    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9512    cancellation: Option<&CancellationToken>,
9513    stats: &mut BoundedVisibilityStats,
9514) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9515    let mut candidates_by_identifier = HashMap::default();
9516    let mut declarations_by_source_and_reading: HashMap<
9517        (ProjectFile, bool),
9518        BoundedVisibilityDeclarations,
9519    > = HashMap::default();
9520    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9521    roots
9522        .iter()
9523        .map(|root| {
9524            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9525            let root_declarations = declarations_by_source_and_reading
9526                .entry((root.clone(), reading_is_c))
9527                .or_insert_with(|| {
9528                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9529                });
9530            let mut visible = root_declarations
9531                .all
9532                .iter()
9533                .cloned()
9534                .collect::<HashSet<_>>();
9535            let mut pending_names = HashSet::default();
9536            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9537                // One cursor for the whole file walk: `named_child(index)`
9538                // re-steps the sibling list from the first child on every
9539                // access, which made this whole-file scan quadratic in the
9540                // fan-out of each node (#3097).
9541                let mut cursor = prepared.tree().walk();
9542                let mut pending_nodes = vec![prepared.tree().root_node()];
9543                while let Some(node) = pending_nodes.pop() {
9544                    if matches!(
9545                        node.kind(),
9546                        "identifier"
9547                            | "type_identifier"
9548                            | "field_identifier"
9549                            | "namespace_identifier"
9550                    ) {
9551                        pending_names.insert(node_text(node, prepared.source()).to_string());
9552                    }
9553                    if node.kind() == "preproc_arg" {
9554                        for reference in
9555                            object_macro_replacement_type_references(node, prepared.source())
9556                        {
9557                            pending_names.extend(reference.components);
9558                        }
9559                    }
9560                    pending_nodes.extend(node.named_children(&mut cursor));
9561                }
9562            }
9563            stats.root_names += pending_names.len();
9564            let mut completed_names = HashSet::default();
9565            while !pending_names.is_empty() {
9566                stats.rounds += 1;
9567                let round_names = std::mem::take(&mut pending_names);
9568                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9569                    HashMap::default();
9570                let mut identifiers = Vec::new();
9571                for identifier in round_names {
9572                    if !completed_names.insert(identifier.clone())
9573                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9574                    {
9575                        continue;
9576                    }
9577                    identifiers.push(identifier);
9578                }
9579                let missing_identifiers = identifiers
9580                    .iter()
9581                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9582                    .cloned()
9583                    .collect::<HashSet<_>>();
9584                if !missing_identifiers.is_empty() {
9585                    let lookup_started = Instant::now();
9586                    let mut candidates = cpp
9587                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9588                    stats.lookup_elapsed += lookup_started.elapsed();
9589                    stats.identifier_lookups += missing_identifiers.len();
9590                    stats.identifier_batches += 1;
9591                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9592                        break;
9593                    }
9594                    for identifier in missing_identifiers {
9595                        let units = candidates.remove(&identifier).unwrap_or_default();
9596                        let candidate_count = units.len();
9597                        let candidate_sources = units
9598                            .into_iter()
9599                            .map(|unit| unit.source().clone())
9600                            .collect::<HashSet<_>>();
9601                        candidates_by_identifier
9602                            .insert(identifier, (candidate_sources, candidate_count));
9603                    }
9604                }
9605                for identifier in identifiers {
9606                    let (candidate_sources, candidate_count) = candidates_by_identifier
9607                        .get(&identifier)
9608                        .expect("every missing identifier was inserted after the batch lookup");
9609                    stats.candidate_units += *candidate_count;
9610                    for source in candidate_sources.iter().cloned() {
9611                        if source != *root
9612                            && visible_sources
9613                                .get(root)
9614                                .is_some_and(|files| files.contains(&source))
9615                        {
9616                            requested_names_by_source
9617                                .entry(source)
9618                                .or_default()
9619                                .insert(identifier.clone());
9620                        }
9621                    }
9622                }
9623                stats.candidate_sources += requested_names_by_source.len();
9624                for (source, requested_names) in requested_names_by_source {
9625                    let declarations = declarations_by_source_and_reading
9626                        .entry((source.clone(), reading_is_c))
9627                        .or_insert_with(|| {
9628                            bounded_visibility_declarations(
9629                                cpp,
9630                                analyzer,
9631                                &source,
9632                                reading_is_c,
9633                                stats,
9634                            )
9635                        });
9636                    let mut selected = HashSet::default();
9637                    for name in requested_names {
9638                        if let Some(units) = declarations.by_identifier.get(&name) {
9639                            selected.extend(units.iter().cloned());
9640                        }
9641                    }
9642                    for unit in selected {
9643                        stats.selected_units += 1;
9644                        let dependency_names = dependency_names_by_unit
9645                            .entry(unit.clone())
9646                            .or_insert_with(|| {
9647                                let facts_started = Instant::now();
9648                                let mut dependency_names = HashSet::default();
9649                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9650                                    for fact in facts {
9651                                        for name in fact.dependency_type_names {
9652                                            stats.dependency_source_names += 1;
9653                                            #[cfg(any(test, feature = "test-support"))]
9654                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9655                                                .with(|count| count.set(count.get() + 1));
9656                                            dependency_names.insert(name);
9657                                        }
9658                                    }
9659                                    stats.dependency_source_elapsed += facts_started.elapsed();
9660                                }
9661                                dependency_names
9662                            });
9663                        for name in dependency_names.iter() {
9664                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9665                            {
9666                                stats.dependency_names += 1;
9667                            }
9668                        }
9669                        visible.insert(unit);
9670                    }
9671                }
9672            }
9673            (root.clone(), visible)
9674        })
9675        .collect()
9676}
9677
9678struct BoundedVisibilityDeclarations {
9679    all: Vec<CodeUnit>,
9680    by_identifier: HashMap<String, Vec<CodeUnit>>,
9681}
9682
9683fn bounded_visibility_declarations(
9684    cpp: &dyn CppSource,
9685    analyzer: &CppGraphSource<'_>,
9686    file: &ProjectFile,
9687    c_semantics: bool,
9688    stats: &mut BoundedVisibilityStats,
9689) -> BoundedVisibilityDeclarations {
9690    let declarations_started = Instant::now();
9691    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9692        .into_iter()
9693        .collect::<Vec<_>>();
9694    stats.declaration_elapsed += declarations_started.elapsed();
9695    stats.declaration_reads += 1;
9696    stats.declaration_units += all.len();
9697
9698    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9699    for unit in &all {
9700        by_identifier
9701            .entry(unit.identifier().to_string())
9702            .or_default()
9703            .push(unit.clone());
9704        if unit.is_class()
9705            && let Some(metadata) = cpp.template_metadata(unit)
9706            && metadata.primary_name != unit.identifier()
9707        {
9708            by_identifier
9709                .entry(metadata.primary_name)
9710                .or_default()
9711                .push(unit.clone());
9712        }
9713    }
9714    BoundedVisibilityDeclarations { all, by_identifier }
9715}
9716
9717#[derive(Default)]
9718struct BoundedVisibilityStats {
9719    rounds: usize,
9720    root_names: usize,
9721    identifier_lookups: usize,
9722    identifier_batches: usize,
9723    candidate_units: usize,
9724    candidate_sources: usize,
9725    declaration_reads: usize,
9726    declaration_units: usize,
9727    selected_units: usize,
9728    dependency_source_names: usize,
9729    dependency_names: usize,
9730    lookup_elapsed: Duration,
9731    declaration_elapsed: Duration,
9732    dependency_source_elapsed: Duration,
9733}
9734
9735fn bounded_visibility_declarations_in_reading(
9736    analyzer: &CppGraphSource<'_>,
9737    file: &ProjectFile,
9738    c_semantics: bool,
9739) -> BTreeSet<CodeUnit> {
9740    #[cfg(any(test, feature = "test-support"))]
9741    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9742    analyzer.declarations_in_reading(file, c_semantics)
9743}
9744
9745#[cfg(any(test, feature = "test-support"))]
9746pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9747    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9748}
9749
9750#[cfg(any(test, feature = "test-support"))]
9751pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9752    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9753}
9754
9755#[cfg(any(test, feature = "test-support"))]
9756pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9757    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9758}
9759
9760#[cfg(any(test, feature = "test-support"))]
9761pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9762    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9763}
9764
9765pub struct VisibilityData {
9766    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9767    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9768}
9769
9770/// Build the per-root include closure and the declarations each root can see
9771/// through it.
9772///
9773/// `declarations_for` takes the reading to answer in (issue #1970): a root
9774/// compiled as C sees the C reading of every file in its closure, a root
9775/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9776/// per root. The two readings agree for all but a handful of headers, so the
9777/// C map is built only when some root actually asks for it, and only over the
9778/// files that root reaches.
9779pub fn build_visibility_data<F, R, D>(
9780    roots: &HashSet<ProjectFile>,
9781    cancellation: Option<&CancellationToken>,
9782    mut targets_for: F,
9783    mut reading_is_c_for: R,
9784    mut declarations_for: D,
9785) -> VisibilityData
9786where
9787    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9788    R: FnMut(&ProjectFile) -> bool,
9789    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9790{
9791    let mut include_graph = IncludeGraph::default();
9792    for file in roots {
9793        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9794            break;
9795        }
9796        include_graph.extend_with(file, cancellation, &mut targets_for);
9797    }
9798    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9799        .files()
9800        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9801        .map(|file| (file.clone(), declarations_for(file, false)))
9802        .collect();
9803    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9804    let mut visible_by_file = HashMap::default();
9805    let mut visible_source_files_by_root = HashMap::default();
9806    for file in roots {
9807        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9808            break;
9809        }
9810        let mut visited = HashSet::default();
9811        let mut visible = HashSet::default();
9812        let declarations_by_file = if reading_is_c_for(file) {
9813            for reached in cpp_declarations_by_file.keys() {
9814                if !c_declarations_by_file.contains_key(reached) {
9815                    let declarations = declarations_for(reached, true);
9816                    c_declarations_by_file.insert(reached.clone(), declarations);
9817                }
9818            }
9819            &c_declarations_by_file
9820        } else {
9821            &cpp_declarations_by_file
9822        };
9823        collect_visible_declarations(
9824            &include_graph,
9825            declarations_by_file,
9826            file,
9827            &mut visited,
9828            &mut visible,
9829            cancellation,
9830        );
9831        visible_by_file.insert(file.clone(), visible);
9832        visible_source_files_by_root.insert(file.clone(), visited);
9833    }
9834    VisibilityData {
9835        visible_by_file,
9836        visible_source_files_by_root,
9837    }
9838}
9839
9840/// Admit the class that an out-of-line definition proves is in scope.
9841///
9842/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9843/// names a class-like entity in that file's scope: a member declaration can
9844/// live in a file other than its class's only when it is written out of line.
9845/// A file a build concatenates rather than compiles carries no `#include` edge
9846/// to the header declaring `Owner` -- google/wuffs
9847/// `internal/cgen/auxiliary/image.cc` defines
9848/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9849/// every unqualified member and constructor reference in it had no candidate at
9850/// all (#1832).
9851///
9852/// The evidence is the indexed declaration's own owner name, taken from its
9853/// `FqName`, so this stays a structured answer rather than a text fallback.
9854/// Only an owner the file cannot already see is admitted: that is what keeps a
9855/// header declaring its own class from additionally seeing every same-named
9856/// class in the workspace, and it makes the pass free for the ordinary file
9857/// whose owners are all visible.
9858#[derive(Default)]
9859struct OutOfLineOwnerBindingStats {
9860    unseen_owners: usize,
9861    definition_lookups: usize,
9862    admitted: usize,
9863}
9864
9865fn extend_with_out_of_line_owner_bindings(
9866    cpp: &dyn CppSource,
9867    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9868) -> OutOfLineOwnerBindingStats {
9869    let mut stats = OutOfLineOwnerBindingStats::default();
9870    for (file, visible) in visible_by_file.iter_mut() {
9871        // The include-closure walk seeds every root with its own declarations,
9872        // so the file's members are already here; re-reading them from the
9873        // analyzer would pay for the same declaration set twice.
9874        let mut unseen_owners: HashSet<String> = visible
9875            .iter()
9876            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9877            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9878            .collect();
9879        if unseen_owners.is_empty() {
9880            continue;
9881        }
9882        for unit in visible.iter().filter(|unit| unit.is_class()) {
9883            unseen_owners.remove(&unit.fq_name());
9884        }
9885        stats.unseen_owners += unseen_owners.len();
9886        stats.definition_lookups += unseen_owners.len();
9887        let admitted = unseen_owners
9888            .iter()
9889            .flat_map(|owner| cpp.definitions(owner))
9890            .filter(CodeUnit::is_class)
9891            .collect::<Vec<_>>();
9892        stats.admitted += admitted.len();
9893        visible.extend(admitted);
9894    }
9895    stats
9896}
9897
9898pub enum VisibleMemberResolution {
9899    Callable(Vec<CodeUnit>),
9900    NonCallable,
9901    AmbiguousKind,
9902    Missing,
9903}
9904
9905#[derive(Clone)]
9906pub enum EnclosingMemberOwnerResolution {
9907    Owner(CodeUnit),
9908    Ambiguous,
9909    Missing,
9910}
9911
9912pub fn resolve_declaring_member_owner(
9913    analyzer: &CppGraphSource<'_>,
9914    visibility: &VisibilityIndex<'_>,
9915    file: &ProjectFile,
9916    receiver_owner: &CodeUnit,
9917    member_name: &str,
9918) -> EnclosingMemberOwnerResolution {
9919    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9920        return EnclosingMemberOwnerResolution::Missing;
9921    };
9922    let Some(receiver_owner) =
9923        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9924    else {
9925        return EnclosingMemberOwnerResolution::Ambiguous;
9926    };
9927    let resolve_level = |frontier: &[CodeUnit]| {
9928        let mut member_owners = Vec::new();
9929        for raw_owner in frontier {
9930            let Some(owner) =
9931                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9932            else {
9933                return EnclosingMemberOwnerResolution::Ambiguous;
9934            };
9935            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9936                // A type nested in an owner shares the owner's member-name
9937                // index, but it cannot be the value receiver of `owner.name`.
9938                // Keep value members here so an anonymous aggregate field
9939                // does not become ambiguous with its promoted receiver type.
9940                if !member.is_field() && !member.is_function() {
9941                    continue;
9942                }
9943                let Some(member_owner) = type_owner_of(analyzer, member) else {
9944                    return EnclosingMemberOwnerResolution::Ambiguous;
9945                };
9946                if !member_owners
9947                    .iter()
9948                    .any(|existing| same_visible_symbol(existing, &member_owner))
9949                {
9950                    member_owners.push(member_owner);
9951                }
9952            }
9953        }
9954        match member_owners.len() {
9955            0 => EnclosingMemberOwnerResolution::Missing,
9956            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9957            _ => EnclosingMemberOwnerResolution::Ambiguous,
9958        }
9959    };
9960    // The first declaration on each structured base path hides deeper names,
9961    // regardless of whether its callable overload is applicable at a particular
9962    // call site. Applicability is checked only after this owner is established.
9963    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9964    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9965        return direct;
9966    }
9967    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9968    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9969    let mut path_matches = Vec::new();
9970    while let Some(raw_owner) = stack.pop() {
9971        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9972        else {
9973            return EnclosingMemberOwnerResolution::Ambiguous;
9974        };
9975        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9976        // Propagate at most two occurrences of each owner: that preserves the distinction
9977        // between one and multiple resolving base paths without exponential diamond walks.
9978        let propagated = propagated_counts.entry(owner.clone()).or_default();
9979        if *propagated == 2 {
9980            continue;
9981        }
9982        *propagated += 1;
9983        match resolve_level(std::slice::from_ref(&owner)) {
9984            EnclosingMemberOwnerResolution::Owner(owner) => {
9985                path_matches.push(owner);
9986                if path_matches.len() == 2 {
9987                    return EnclosingMemberOwnerResolution::Ambiguous;
9988                }
9989            }
9990            EnclosingMemberOwnerResolution::Ambiguous => {
9991                return EnclosingMemberOwnerResolution::Ambiguous;
9992            }
9993            EnclosingMemberOwnerResolution::Missing => {
9994                stack.extend(hierarchy.get_direct_ancestors(&owner));
9995            }
9996        }
9997    }
9998    match path_matches.len() {
9999        0 => EnclosingMemberOwnerResolution::Missing,
10000        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10001        _ => unreachable!("base-path matches are capped at one before returning"),
10002    }
10003}
10004
10005/// Resolve the declaring owner of a callable after applying a member
10006/// `using <Base>::<member>;` declaration to one exact call arity.
10007///
10008/// Ordinary member lookup is intentionally name-based: the first class that
10009/// declares a name hides the same name on deeper bases. A member
10010/// using-declaration is the one exception. When none of the declarations on
10011/// that first owner accepts the call arity, it can reintroduce an applicable
10012/// overload from the named base. If a declaration on the first owner does
10013/// accept the arity, argument types would be needed to choose between it and
10014/// a same-arity introduced overload, so this resolver conservatively keeps the
10015/// ordinary owner (#1835/#1843).
10016///
10017/// The caller supplies ordinary name-based owner resolution so a file scan can
10018/// reuse its existing owner cache before applying this callable-only exception.
10019pub fn resolve_declaring_callable_owner(
10020    analyzer: &CppGraphSource<'_>,
10021    visibility: &VisibilityIndex<'_>,
10022    file: &ProjectFile,
10023    ordinary: EnclosingMemberOwnerResolution,
10024    member_name: &str,
10025    call_arity: usize,
10026) -> EnclosingMemberOwnerResolution {
10027    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10028        return ordinary;
10029    };
10030    if visibility
10031        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10032        .into_iter()
10033        .any(|unit| {
10034            unit.is_function()
10035                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10036        })
10037    {
10038        return ordinary;
10039    }
10040
10041    let mut pending = match member_using_declaration_bases(
10042        analyzer,
10043        visibility,
10044        file,
10045        ordinary_owner,
10046        member_name,
10047    ) {
10048        Ok(bases) => bases,
10049        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10050    };
10051    let mut visited = HashSet::default();
10052    let mut introduced_owners = Vec::new();
10053    while let Some(owner) = pending.pop() {
10054        if !visited.insert(owner.clone()) {
10055            continue;
10056        }
10057        let accepts_arity = visibility
10058            .visible_members_for_owner_name(file, &owner, member_name)
10059            .into_iter()
10060            .any(|unit| {
10061                unit.is_function()
10062                    && cpp_callable_arity(analyzer, unit)
10063                        .is_none_or(|known| known.accepts(call_arity))
10064            });
10065        if accepts_arity {
10066            if !introduced_owners
10067                .iter()
10068                .any(|existing| same_visible_symbol(existing, &owner))
10069            {
10070                introduced_owners.push(owner);
10071            }
10072            continue;
10073        }
10074        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10075            Ok(bases) => pending.extend(bases),
10076            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10077        }
10078    }
10079    match introduced_owners.as_slice() {
10080        [] => ordinary,
10081        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10082        _ => EnclosingMemberOwnerResolution::Ambiguous,
10083    }
10084}
10085
10086fn member_using_declaration_bases(
10087    analyzer: &CppGraphSource<'_>,
10088    visibility: &VisibilityIndex<'_>,
10089    file: &ProjectFile,
10090    owner: &CodeUnit,
10091    member_name: &str,
10092) -> Result<Vec<CodeUnit>, ()> {
10093    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10094        return Err(());
10095    };
10096    let scopes: Vec<_> = facts
10097        .iter()
10098        .flat_map(|fact| &fact.member_usings)
10099        .filter(|using| using.member == member_name)
10100        .map(|using| using.scope.join("::"))
10101        .collect();
10102    if scopes.is_empty() {
10103        return Ok(Vec::new());
10104    }
10105    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10106        return Ok(Vec::new());
10107    };
10108    let mut bases = Vec::new();
10109    for raw_ancestor in hierarchy.get_ancestors(owner) {
10110        let Some(ancestor) =
10111            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10112        else {
10113            return Err(());
10114        };
10115        let qualified = cpp_name_for(&ancestor);
10116        if scopes
10117            .iter()
10118            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10119            && !bases
10120                .iter()
10121                .any(|existing| same_visible_symbol(existing, &ancestor))
10122        {
10123            bases.push(ancestor);
10124        }
10125    }
10126    Ok(bases)
10127}
10128
10129pub fn lexical_component_tiers<'a>(
10130    components: &'a [String],
10131    global: bool,
10132    lexical_scope: &'a [String],
10133) -> impl Iterator<Item = Vec<String>> + 'a {
10134    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10135    (0..=first_prefix_len).rev().map(move |prefix_len| {
10136        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10137        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10138        qualified.extend_from_slice(components);
10139        qualified
10140    })
10141}
10142
10143pub fn build_visible_identifier_index(
10144    analyzer: &CppGraphSource<'_>,
10145    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10146    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10147    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10148) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10149    let mut out = HashMap::default();
10150    for (file, visible) in visible_by_file {
10151        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10152        for unit in visible {
10153            if unit.is_field()
10154                && !visible_source_files_by_root
10155                    .get(file)
10156                    .is_some_and(|sources| sources.contains(unit.source()))
10157                && cpp_global_field_has_internal_linkage_cached(
10158                    analyzer,
10159                    global_field_internal_linkage,
10160                    unit,
10161                )
10162            {
10163                continue;
10164            }
10165            by_identifier
10166                .entry(unit.identifier().to_string())
10167                .or_default()
10168                .push(unit.clone());
10169        }
10170        for units in by_identifier.values_mut() {
10171            sort_lookup_units(units);
10172            units.dedup();
10173        }
10174        out.insert(file.clone(), by_identifier);
10175    }
10176    out
10177}
10178
10179fn sort_lookup_units(units: &mut [CodeUnit]) {
10180    units.sort_by(|left, right| {
10181        left.fq_name()
10182            .cmp(&right.fq_name())
10183            .then_with(|| left.signature().cmp(&right.signature()))
10184            .then_with(|| left.source().cmp(right.source()))
10185            .then_with(|| left.kind().cmp(&right.kind()))
10186            .then_with(|| {
10187                left.package_segment_count()
10188                    .cmp(&right.package_segment_count())
10189            })
10190            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10191            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10192    });
10193}
10194
10195fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10196    let interner = segment_interner();
10197    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10198        let (left_text, left_kind) = interner.resolve(left_id);
10199        let (right_text, right_kind) = interner.resolve(right_id);
10200        let order = left_text
10201            .cmp(right_text)
10202            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10203        if order != CmpOrdering::Equal {
10204            return order;
10205        }
10206    }
10207    left.len().cmp(&right.len())
10208}
10209
10210const fn segment_kind_order(kind: SegmentKind) -> u8 {
10211    match kind {
10212        SegmentKind::Path => 0,
10213        SegmentKind::Package => 1,
10214        SegmentKind::Type => 2,
10215        SegmentKind::Companion => 3,
10216        SegmentKind::Nested => 4,
10217        SegmentKind::Member => 5,
10218        SegmentKind::Unknown => 6,
10219    }
10220}
10221
10222fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10223    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10224        std::mem::take(units),
10225    );
10226}
10227
10228pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10229    // Same domain as `candidate_units` above: `reference` is a plain
10230    // `::`-joined qualified-id with operator tokens kept intact by the shared
10231    // splitter's operator merge.
10232    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10233        brokk_bifrost_core::analyzer::Language::Cpp,
10234        reference,
10235    );
10236    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10237}
10238
10239fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10240    if parts.is_empty() {
10241        return Vec::new();
10242    }
10243
10244    let mut candidates = Vec::new();
10245    for package_len in 0..parts.len() {
10246        let package = parts[..package_len].join("::");
10247        let rest = &parts[package_len..];
10248        if rest.is_empty() {
10249            continue;
10250        }
10251        match kind {
10252            TargetKind::Type | TargetKind::Constructor => {
10253                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10254                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10255            }
10256            TargetKind::FreeFunction
10257            | TargetKind::Method
10258            | TargetKind::GlobalField
10259            | TargetKind::MemberField
10260            | TargetKind::Macro => {
10261                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10262                if rest.len() > 1 {
10263                    let owner = rest[..rest.len() - 1].join("$");
10264                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10265                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10266                }
10267            }
10268        }
10269    }
10270    candidates
10271}
10272
10273fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10274    let fqn = if package.is_empty() {
10275        short.to_string()
10276    } else {
10277        format!("{package}.{short}")
10278    };
10279    if !out.contains(&fqn) {
10280        out.push(fqn);
10281    }
10282}
10283
10284pub fn infer_cpp_initializer_type(
10285    analyzer: &CppGraphSource<'_>,
10286    visibility: &VisibilityIndex<'_>,
10287    file: &ProjectFile,
10288    source: &str,
10289    node: Node<'_>,
10290) -> Option<CodeUnit> {
10291    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10292        .and_then(|binding| binding.unit)
10293}
10294
10295pub fn infer_cpp_initializer_binding(
10296    analyzer: &CppGraphSource<'_>,
10297    visibility: &VisibilityIndex<'_>,
10298    file: &ProjectFile,
10299    source: &str,
10300    node: Node<'_>,
10301    receiver_resolver: Option<&ReceiverResolver<'_>>,
10302) -> Option<CppScanBinding> {
10303    match node.kind() {
10304        "new_expression" => {
10305            let type_node = cpp_new_expression_type_node(node)?;
10306            let type_text = node_text(type_node, source);
10307            let name = normalize_cpp_type_name(type_text);
10308            Some(CppScanBinding::from_type_name(
10309                name.clone(),
10310                visibility.resolve_type(file, &name),
10311                1,
10312            ))
10313        }
10314        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10315            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10316            // callee's source text is an expression, not a name, and every name
10317            // lookup below normalizes a reference by truncating at the first
10318            // `(`: `first().second` would read as `first`, so the chained call
10319            // would take the type of `first()` instead of the type of
10320            // `first().second()` (#2178). Only the member path can answer for
10321            // this shape, so route to it from the callee's node kind.
10322            if function.kind() == "field_expression" {
10323                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10324                return resolve_field_method_call_return_binding(
10325                    analyzer,
10326                    visibility,
10327                    file,
10328                    source,
10329                    function,
10330                    arity,
10331                    receiver_resolver,
10332                );
10333            }
10334            let function_text = node_text(function, source);
10335            let direct_type_binding = visibility
10336                .resolve_type(file, function_text)
10337                .map(|unit| CppScanBinding::from_unit(unit, 0));
10338            if function.kind() == "template_function" && direct_type_binding.is_some() {
10339                let lexical_namespace = enclosing_namespace_context(node, source);
10340                let arity = visibility.call_arity_evidence(file, node, source).exact();
10341                if let Some(arity) = arity
10342                    && let Some(binding) = visibility.resolve_call_return_binding(
10343                        analyzer,
10344                        file,
10345                        function_text,
10346                        arity,
10347                        lexical_namespace.as_deref(),
10348                        direct_type_binding
10349                            .as_ref()
10350                            .and_then(|binding| binding.unit.as_ref()),
10351                    )
10352                {
10353                    return Some(binding);
10354                }
10355                let (has_callable, callable_binding) = visibility
10356                    .resolve_call_return_binding_without_arity(
10357                        analyzer,
10358                        file,
10359                        function_text,
10360                        lexical_namespace.as_deref(),
10361                        direct_type_binding
10362                            .as_ref()
10363                            .and_then(|binding| binding.unit.as_ref()),
10364                    );
10365                if let Some(binding) = callable_binding {
10366                    return Some(binding);
10367                }
10368                if has_callable {
10369                    return None;
10370                }
10371                return direct_type_binding;
10372            }
10373            // Only the return-typed branches need the argument count. An
10374            // unknown arity leaves them out, exactly as in the template arm
10375            // above, and still constructs the direct type: `File(getPath())`
10376            // names `File` whether or not `getPath()`'s expansion is provable.
10377            let arity = visibility.call_arity_evidence(file, node, source).exact();
10378            if let Some(arity) = arity {
10379                let direct_type_binding_for_call = direct_type_binding.clone();
10380                if let Some(binding) = resolve_static_method_call_return_binding(
10381                    analyzer, visibility, file, source, function, arity,
10382                )
10383                .or_else(|| {
10384                    // An applicable free function supplies the receiver value
10385                    // before an unrelated visible type with the same terminal
10386                    // name. The direct type still excludes its own constructor
10387                    // declaration below and remains the construction fallback.
10388                    visibility.resolve_call_return_binding(
10389                        analyzer,
10390                        file,
10391                        function_text,
10392                        arity,
10393                        enclosing_namespace_context(node, source).as_deref(),
10394                        direct_type_binding_for_call
10395                            .as_ref()
10396                            .and_then(|binding| binding.unit.as_ref()),
10397                    )
10398                }) {
10399                    return Some(binding);
10400                }
10401            }
10402            direct_type_binding
10403        }),
10404        _ => None,
10405    }
10406}
10407
10408/// Return the type specifier carried by a C++ `new_expression`.
10409///
10410/// The grammar names this child with the `type` field. If recovery omits that
10411/// field, recover only from type-specifier nodes retained in the parse tree;
10412/// skip placement arguments, the array declarator, and initializer arguments.
10413pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10414    debug_assert_eq!(expression.kind(), "new_expression");
10415    if let Some(type_node) = expression.child_by_field_name("type") {
10416        return Some(type_node);
10417    }
10418
10419    let mut pending = Vec::new();
10420    let mut cursor = expression.walk();
10421    pending.extend(
10422        expression
10423            .named_children(&mut cursor)
10424            .filter(|child| {
10425                !matches!(
10426                    child.kind(),
10427                    "argument_list" | "initializer_list" | "new_declarator"
10428                )
10429            })
10430            .collect::<Vec<_>>()
10431            .into_iter()
10432            .rev(),
10433    );
10434    while let Some(node) = pending.pop() {
10435        if is_cpp_type_specifier_node(node) {
10436            return Some(node);
10437        }
10438        if node.kind() == "ERROR" {
10439            let mut cursor = node.walk();
10440            pending.extend(
10441                node.named_children(&mut cursor)
10442                    .collect::<Vec<_>>()
10443                    .into_iter()
10444                    .rev(),
10445            );
10446        }
10447    }
10448    None
10449}
10450
10451fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10452    matches!(
10453        node.kind(),
10454        "class_specifier"
10455            | "decltype"
10456            | "dependent_type"
10457            | "enum_specifier"
10458            | "placeholder_type_specifier"
10459            | "primitive_type"
10460            | "qualified_identifier"
10461            | "sized_type_specifier"
10462            | "struct_specifier"
10463            | "template_type"
10464            | "type_identifier"
10465            | "union_specifier"
10466    )
10467}
10468
10469fn resolve_static_method_call_return_binding(
10470    analyzer: &CppGraphSource<'_>,
10471    visibility: &VisibilityIndex<'_>,
10472    file: &ProjectFile,
10473    source: &str,
10474    function: Node<'_>,
10475    arity: usize,
10476) -> Option<CppScanBinding> {
10477    if function.kind() != "qualified_identifier" {
10478        return None;
10479    }
10480    let qualified = normalize_cpp_reference_text(node_text(function, source));
10481    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10482    // single component (the shared splitter's operator-token merge keeps
10483    // `operator+`-style names intact), so re-tokenizing with the shared
10484    // structured splitter and peeling the terminal segment reproduces
10485    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10486    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10487    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10488        brokk_bifrost_core::analyzer::Language::Cpp,
10489        &qualified,
10490    );
10491    let (owner_text, member_name) = match parts.split_last() {
10492        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10493            (owner_parts.join("::"), member.clone())
10494        }
10495        _ => {
10496            let scope = function.child_by_field_name("scope")?;
10497            let name = function.child_by_field_name("name")?;
10498            (
10499                node_text(scope, source).to_string(),
10500                node_text(name, source).to_string(),
10501            )
10502        }
10503    };
10504    let owner = visibility.resolve_type(file, &owner_text)?;
10505    let candidates =
10506        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10507    unanimous_return_binding(analyzer, visibility, file, &candidates)
10508}
10509
10510fn resolve_field_method_call_return_binding(
10511    analyzer: &CppGraphSource<'_>,
10512    visibility: &VisibilityIndex<'_>,
10513    file: &ProjectFile,
10514    source: &str,
10515    function: Node<'_>,
10516    arity: usize,
10517    receiver_resolver: Option<&ReceiverResolver<'_>>,
10518) -> Option<CppScanBinding> {
10519    debug_assert_eq!(
10520        function.kind(),
10521        "field_expression",
10522        "the member-call return binding answers only for a field-expression callee"
10523    );
10524    let receiver_resolver = receiver_resolver?;
10525    let field = function.child_by_field_name("field")?;
10526    let member_name = node_text(function_terminal_node(field), source);
10527    let receiver = function
10528        .child_by_field_name("argument")
10529        .or_else(|| function.named_child(0))?;
10530    let owners = receiver_resolver(receiver, source);
10531    let mut candidates = Vec::new();
10532    for owner in owners {
10533        let declaring_owner =
10534            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10535                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10536                EnclosingMemberOwnerResolution::Missing => continue,
10537                EnclosingMemberOwnerResolution::Ambiguous => return None,
10538            };
10539        candidates.extend(callable_members_accepting_arity(
10540            analyzer,
10541            visibility,
10542            file,
10543            &declaring_owner,
10544            member_name,
10545            arity,
10546        ));
10547    }
10548    unanimous_return_binding(analyzer, visibility, file, &candidates)
10549}
10550
10551fn callable_members_accepting_arity(
10552    analyzer: &CppGraphSource<'_>,
10553    visibility: &VisibilityIndex<'_>,
10554    file: &ProjectFile,
10555    owner: &CodeUnit,
10556    member_name: &str,
10557    arity: usize,
10558) -> Vec<CodeUnit> {
10559    visibility
10560        .visible_members_for_owner_name(file, owner, member_name)
10561        .into_iter()
10562        .filter(|unit| {
10563            unit.is_function()
10564                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10565        })
10566        .cloned()
10567        .collect()
10568}
10569
10570fn unanimous_return_binding(
10571    analyzer: &CppGraphSource<'_>,
10572    visibility: &VisibilityIndex<'_>,
10573    file: &ProjectFile,
10574    candidates: &[CodeUnit],
10575) -> Option<CppScanBinding> {
10576    let mut resolved_return: Option<CppScanBinding> = None;
10577    for function in candidates {
10578        let metadata = analyzer.signature_metadata(function);
10579        let return_types = if metadata.is_empty() {
10580            vec![cpp_function_return_type_text(analyzer, function)?]
10581        } else {
10582            metadata
10583                .iter()
10584                .map(|metadata| metadata.return_type_text().map(str::to_string))
10585                .collect::<Option<Vec<_>>>()?
10586        };
10587        for return_text in return_types {
10588            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10589            let name = normalize_cpp_type_name(&return_text);
10590            let binding = CppScanBinding::from_type_name(
10591                name.clone(),
10592                visibility
10593                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10594                indirection,
10595            );
10596            if let Some(existing) = resolved_return.as_ref()
10597                && (existing.indirection != binding.indirection
10598                    || match (&existing.unit, &binding.unit) {
10599                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10600                        (None, None) => existing.type_name != binding.type_name,
10601                        (Some(_), None) | (None, Some(_)) => true,
10602                    })
10603            {
10604                return None;
10605            }
10606            resolved_return = Some(binding);
10607        }
10608    }
10609    resolved_return
10610}
10611
10612fn aliases_from_canonical_source(
10613    cpp: &dyn CppSource,
10614    token: QueryToken<'_>,
10615    file: &ProjectFile,
10616) -> Option<Vec<CppAlias>> {
10617    let source = cpp.declaration_source_facts(token, file)?;
10618    Some(
10619        source
10620            .facts
10621            .declarations
10622            .iter()
10623            .filter_map(|fact| {
10624                let alias = fact.file_scope_alias.as_ref()?;
10625                Some(CppAlias {
10626                    name: alias.name.clone(),
10627                    target: alias.target.clone(),
10628                    namespace: alias.namespace.clone(),
10629                })
10630            })
10631            .collect(),
10632    )
10633}
10634
10635pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10636    let mut current = node.parent();
10637    while let Some(parent) = current {
10638        match parent.kind() {
10639            "translation_unit"
10640            | "namespace_definition"
10641            | "declaration_list"
10642            | "linkage_specification" => current = parent.parent(),
10643            "template_declaration" => current = parent.parent(),
10644            _ => return false,
10645        }
10646    }
10647    true
10648}
10649
10650pub fn collect_include_closure(
10651    analyzer: &CppGraphSource<'_>,
10652    include_targets: &IncludeTargetIndex,
10653    file: &ProjectFile,
10654    out: &mut HashSet<ProjectFile>,
10655    cancellation: Option<&CancellationToken>,
10656) {
10657    let mut stack = vec![file.clone()];
10658    while let Some(file) = stack.pop() {
10659        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10660            break;
10661        }
10662        if !out.insert(file.clone()) {
10663            continue;
10664        }
10665        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10666            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10667                stack.push(target);
10668            }
10669        }
10670    }
10671}
10672
10673fn collect_visible_declarations(
10674    include_graph: &IncludeGraph,
10675    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10676    file: &ProjectFile,
10677    visited: &mut HashSet<ProjectFile>,
10678    out: &mut HashSet<CodeUnit>,
10679    cancellation: Option<&CancellationToken>,
10680) {
10681    let mut stack = vec![file.clone()];
10682    while let Some(file) = stack.pop() {
10683        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10684            break;
10685        }
10686        if !visited.insert(file.clone()) {
10687            continue;
10688        }
10689        if let Some(declarations) = declarations_by_file.get(&file) {
10690            out.extend(declarations.iter().cloned());
10691        }
10692        stack.extend(include_graph.targets(&file).iter().cloned());
10693    }
10694}
10695
10696fn parse_macro_parameter_list<T>(
10697    replacement: &str,
10698    read: impl FnOnce(Node<'_>, &str) -> T,
10699) -> Option<T> {
10700    let source = format!("void __bifrost_macro_parameters({replacement});");
10701    let mut parser = Parser::new();
10702    parser
10703        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10704        .ok()?;
10705    let tree = parser.parse(&source, None)?;
10706    let root = tree.root_node();
10707    if root.has_error() {
10708        return None;
10709    }
10710    let declaration = root.named_child(0)?;
10711    let declarator = declaration.child_by_field_name("declarator")?;
10712    let parameters = declarator.child_by_field_name("parameters")?;
10713    Some(read(parameters, &source))
10714}
10715
10716fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10717    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10718}
10719
10720/// Interpret opaque macro replacement parameters with the primary producer's
10721/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
10722pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10723    parse_macro_parameter_list(
10724        replacement,
10725        crate::declarations::cpp_callable_parameter_types,
10726    )
10727}
10728
10729pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10730    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10731}
10732
10733pub fn cpp_callable_parameter_types(
10734    analyzer: &CppGraphSource<'_>,
10735    unit: &CodeUnit,
10736) -> Option<Vec<String>> {
10737    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10738        analyzer.signature_metadata(unit),
10739    )
10740}
10741
10742fn merge_compatible_callable_arities(
10743    left: CallableArity,
10744    right: CallableArity,
10745) -> Option<CallableArity> {
10746    let total = left.total();
10747    let left_repeated = left.accepts(total.saturating_add(1));
10748    let right_repeated = right.accepts(right.total().saturating_add(1));
10749    if total != right.total() || left_repeated != right_repeated {
10750        return None;
10751    }
10752    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10753    Some(CallableArity::new(required, total, left_repeated))
10754}
10755
10756fn collect_macro_events(
10757    cpp: &dyn CppSource,
10758    token: QueryToken<'_>,
10759    file: &ProjectFile,
10760) -> Vec<MacroEvent> {
10761    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10762        return Vec::new();
10763    };
10764    let source = prepared.source();
10765    let mut events = Vec::new();
10766    let root = prepared.tree().root_node();
10767    let mut stack = vec![root];
10768    while let Some(node) = stack.pop() {
10769        match node.kind() {
10770            "preproc_def" | "preproc_function_def" => {
10771                let Some(name) = node.child_by_field_name("name") else {
10772                    continue;
10773                };
10774                let name = node_text(name, source).to_string();
10775                events.push(MacroEvent::Define {
10776                    name,
10777                    binding: MacroBinding {
10778                        source: file.clone(),
10779                        declaration_byte: node.start_byte(),
10780                        definition: VisibilityIndex::decode_macro_definition(node, source),
10781                        exact: true,
10782                    },
10783                    byte: node.start_byte(),
10784                    conditionals: owning_preprocessor_conditionals(root, node, source),
10785                });
10786                continue;
10787            }
10788            "preproc_include" => {
10789                let Some(path) = node.child_by_field_name("path") else {
10790                    events.push(MacroEvent::Include {
10791                        targets: Vec::new(),
10792                        byte: node.start_byte(),
10793                        conditionals: owning_preprocessor_conditionals(root, node, source),
10794                    });
10795                    continue;
10796                };
10797                let include = crate::imports::include_path_literal_value(path, source);
10798                let targets = include.map_or_else(Vec::new, |include| {
10799                    resolve_include_targets_with_index(file, include, cpp.include_target_index())
10800                });
10801                // An include no indexed file can answer -- its final path component names
10802                // no analyzable workspace file -- crosses into sources this index does not
10803                // hold, so it cannot hide indexed macro state and must not poison every
10804                // later local macro binding. The include's spelling does not decide that:
10805                // a quoted include falls back to the same search path an angle include
10806                // uses, and esphome's `core/log.h` reaches every component with
10807                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
10808                // A computed include, and a spelling some indexed file name does match
10809                // without resolving to one target, may still hide indexed macro state and
10810                // therefore fail closed.
10811                if targets.is_empty()
10812                    && include.is_some_and(|include| {
10813                        !cpp.include_target_index().names_indexed_file(include)
10814                    })
10815                {
10816                    continue;
10817                }
10818                events.push(MacroEvent::Include {
10819                    targets,
10820                    byte: node.start_byte(),
10821                    conditionals: owning_preprocessor_conditionals(root, node, source),
10822                });
10823                continue;
10824            }
10825            "preproc_call" => {
10826                let Some(directive) = node.child_by_field_name("directive") else {
10827                    continue;
10828                };
10829                if node_text(directive, source) != "#undef" {
10830                    continue;
10831                }
10832                let name = node
10833                    .child_by_field_name("argument")
10834                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10835                if let Some(name) = name {
10836                    events.push(MacroEvent::Undef {
10837                        name,
10838                        byte: node.start_byte(),
10839                        conditionals: owning_preprocessor_conditionals(root, node, source),
10840                    });
10841                } else {
10842                    events.push(MacroEvent::Invalidate {
10843                        byte: node.start_byte(),
10844                    });
10845                }
10846                continue;
10847            }
10848            _ => {}
10849        }
10850        push_named_children_reversed(node, &mut stack);
10851    }
10852    events.sort_by_key(MacroEvent::byte);
10853    events
10854}
10855
10856fn find_include_activation(
10857    cpp: &dyn CppSource,
10858    token: QueryToken<'_>,
10859    file: &ProjectFile,
10860    prepared: &PreparedSyntaxTree,
10861    donor_source: &ProjectFile,
10862    language_guard_is_stable: &dyn Fn() -> bool,
10863) -> Option<usize> {
10864    let include_targets = cpp.include_target_index();
10865    let reference_is_c = file
10866        .rel_path()
10867        .extension()
10868        .and_then(|extension| extension.to_str())
10869        == Some("c");
10870    let mut direct_includes = Vec::new();
10871    let mut nodes = vec![prepared.tree().root_node()];
10872    // An include activates for the whole file, so only an unconditional
10873    // directive counts here.
10874    while let Some(node) = nodes.pop() {
10875        if node.kind() == "preproc_include" {
10876            let Some(required_guards) = include_directive_guard_requirements(
10877                node,
10878                prepared.source(),
10879                reference_is_c,
10880                language_guard_is_stable,
10881            ) else {
10882                continue;
10883            };
10884            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10885                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10886                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10887                        file,
10888                        include,
10889                        include_targets,
10890                    )) {
10891                        direct_includes.push((node.end_byte(), target));
10892                    }
10893                }
10894            }
10895            continue;
10896        }
10897        push_named_children_reversed(node, &mut nodes);
10898    }
10899    direct_includes.sort_by_key(|(activation, _)| *activation);
10900    let mut known_missing = HashSet::default();
10901    direct_includes
10902        .into_iter()
10903        .find(|(_, direct)| {
10904            unconditional_include_reaches(
10905                cpp,
10906                token,
10907                direct,
10908                donor_source,
10909                file,
10910                &mut known_missing,
10911                language_guard_is_stable,
10912            )
10913        })
10914        .map(|(activation, _)| activation)
10915}
10916
10917fn find_conditional_include_projection_index(
10918    cpp: &dyn CppSource,
10919    token: QueryToken<'_>,
10920    file: &ProjectFile,
10921    prepared: &PreparedSyntaxTree,
10922    language_guard_is_stable: &dyn Fn() -> bool,
10923    on_state: &dyn Fn(),
10924) -> ConditionalIncludeProjectionIndex {
10925    let reference_is_c = reference_uses_c_semantics(cpp, file);
10926    let include_targets = cpp.include_target_index();
10927    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10928        HashMap::default();
10929    let mut pending = Vec::new();
10930    let mut nodes = vec![prepared.tree().root_node()];
10931    while let Some(node) = nodes.pop() {
10932        if node.kind() == "preproc_include" {
10933            let Some(required_guards) = include_directive_guard_requirements(
10934                node,
10935                prepared.source(),
10936                reference_is_c,
10937                language_guard_is_stable,
10938            ) else {
10939                continue;
10940            };
10941            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10942                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10943                    file,
10944                    include,
10945                    include_targets,
10946                )) else {
10947                    continue;
10948                };
10949                pending.push((target, node.end_byte(), required_guards.clone()));
10950            }
10951            continue;
10952        }
10953        push_named_children_reversed(node, &mut nodes);
10954    }
10955
10956    // One reached file can have several distinct compatible guard paths. Each
10957    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10958    // the consumers ask existence questions whose answers are monotone in the
10959    // guard set -- a path whose requirements hold, stay stable, and stay
10960    // compatible under one environment does so under every subset as well --
10961    // so a state subsumed by an existing subset cannot witness anything its
10962    // subset does not, and inserting a smaller set evicts the supersets it
10963    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10964    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10965    // path-union guard sets through it: the state space, the per-key linear
10966    // scans, and resident memory all grew without bound (#2365).
10967    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10968        HashMap::default();
10969    while let Some((current_file, activation_byte, path)) = pending.pop() {
10970        let guard_sets = expanded
10971            .entry((current_file.clone(), activation_byte))
10972            .or_default();
10973        // Minimality is keyed on the full guard set alone: a path's undecided
10974        // guards are the per-conditional subset of that set, so a subset path
10975        // carries a subset of them too.
10976        if guard_sets
10977            .iter()
10978            .any(|existing| existing.is_subset(&path.all))
10979        {
10980            continue;
10981        }
10982        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10983            .drain(..)
10984            .partition(|existing| path.all.is_subset(existing));
10985        *guard_sets = kept;
10986        guard_sets.push(path.all.clone());
10987        if !evicted.is_empty()
10988            && let Some(projections) = projections_by_source.get_mut(&current_file)
10989        {
10990            projections.retain(|projection| {
10991                projection.activation_byte != activation_byte
10992                    || !evicted.contains(&projection.required_guards)
10993            });
10994        }
10995        on_state();
10996
10997        // A fresh minimal set has no equal in the store: equality would have
10998        // been caught by the subset check above.
10999        projections_by_source
11000            .entry(current_file.clone())
11001            .or_default()
11002            .push(ConditionalIncludeProjection {
11003                activation_byte,
11004                required_guards: path.all.clone(),
11005                partial_guards: path.partial.clone(),
11006            });
11007
11008        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11009            continue;
11010        };
11011        let mut nodes = vec![current_prepared.tree().root_node()];
11012        while let Some(node) = nodes.pop() {
11013            if node.kind() == "preproc_include" {
11014                let Some(include_guards) = include_directive_guard_requirements(
11015                    node,
11016                    current_prepared.source(),
11017                    reference_is_c,
11018                    language_guard_is_stable,
11019                ) else {
11020                    continue;
11021                };
11022                let Some(reached) = path.merged(&include_guards) else {
11023                    continue;
11024                };
11025                for (include, _) in
11026                    include_path_from_node(node, current_prepared.source()).into_iter()
11027                {
11028                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11029                        &current_file,
11030                        include,
11031                        include_targets,
11032                    )) else {
11033                        continue;
11034                    };
11035                    pending.push((target, activation_byte, reached.clone()));
11036                }
11037                continue;
11038            }
11039            push_named_children_reversed(node, &mut nodes);
11040        }
11041    }
11042
11043    projections_by_source
11044        .into_iter()
11045        .map(|(source, mut projections)| {
11046            projections.sort_by_key(|projection| projection.activation_byte);
11047            (source, Arc::from(projections))
11048        })
11049        .collect()
11050}
11051
11052/// Decide one conditional include target without materializing every source
11053/// reached by every guard combination. A path `admission` already rejects
11054/// cannot become admissible after adding nested include guards -- both rules
11055/// only ever add requirements -- so discard it before expanding the next
11056/// header.
11057#[allow(clippy::too_many_arguments)]
11058fn find_conditional_include_projection_for_source(
11059    cpp: &dyn CppSource,
11060    token: QueryToken<'_>,
11061    file: &ProjectFile,
11062    prepared: &PreparedSyntaxTree,
11063    donor_source: &ProjectFile,
11064    admission: IncludePathAdmission,
11065    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11066    reference_byte: usize,
11067    language_guard_is_stable: &dyn Fn() -> bool,
11068    on_state: &dyn Fn(),
11069) -> bool {
11070    let Some(reference_guards) = reference_guards else {
11071        return false;
11072    };
11073    let reference_is_c = reference_uses_c_semantics(cpp, file);
11074    let include_targets = cpp.include_target_index();
11075    let mut pending = Vec::new();
11076    let mut nodes = vec![prepared.tree().root_node()];
11077    while let Some(node) = nodes.pop() {
11078        if node.kind() == "preproc_include" {
11079            let Some(required_guards) = include_directive_guard_requirements(
11080                node,
11081                prepared.source(),
11082                reference_is_c,
11083                language_guard_is_stable,
11084            ) else {
11085                continue;
11086            };
11087            if node.end_byte() > reference_byte
11088                || !admission.admits(
11089                    &required_guards.all,
11090                    &required_guards.partial,
11091                    Some(reference_guards),
11092                )
11093            {
11094                continue;
11095            }
11096            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11097                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11098                    file,
11099                    include,
11100                    include_targets,
11101                )) else {
11102                    continue;
11103                };
11104                if &target == donor_source {
11105                    return true;
11106                }
11107                pending.push((target, required_guards.clone()));
11108            }
11109            continue;
11110        }
11111        push_named_children_reversed(node, &mut nodes);
11112    }
11113
11114    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11115    while let Some((current_file, path)) = pending.pop() {
11116        let guard_sets = expanded.entry(current_file.clone()).or_default();
11117        if guard_sets.contains(&path.all) {
11118            continue;
11119        }
11120        guard_sets.push(path.all.clone());
11121        on_state();
11122
11123        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11124            continue;
11125        };
11126        let mut nodes = vec![current_prepared.tree().root_node()];
11127        while let Some(node) = nodes.pop() {
11128            if node.kind() == "preproc_include" {
11129                let Some(include_guards) = include_directive_guard_requirements(
11130                    node,
11131                    current_prepared.source(),
11132                    reference_is_c,
11133                    language_guard_is_stable,
11134                ) else {
11135                    continue;
11136                };
11137                let Some(reached) = path.merged(&include_guards) else {
11138                    continue;
11139                };
11140                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11141                    continue;
11142                }
11143                for (include, _) in
11144                    include_path_from_node(node, current_prepared.source()).into_iter()
11145                {
11146                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11147                        &current_file,
11148                        include,
11149                        include_targets,
11150                    )) else {
11151                        continue;
11152                    };
11153                    if &target == donor_source {
11154                        return true;
11155                    }
11156                    pending.push((target, reached.clone()));
11157                }
11158                continue;
11159            }
11160            push_named_children_reversed(node, &mut nodes);
11161        }
11162    }
11163    false
11164}
11165
11166/// Whether `translation_unit`'s unconditional `#include` closure reaches
11167/// `header`, directly or through any chain of headers.
11168///
11169/// The include-closure question asked on its own, for
11170/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11171/// each include the way visibility does -- to a unique target or to nothing --
11172/// so a duplicated basename relates nothing, and it is memoized per file pair
11173/// and language-proof state on the analyzer.
11174///
11175/// The reference position is `translation_unit` itself: the question is
11176/// whether that unit compiles the header, so that unit's own dialect and
11177/// preprocessor context govern the walk.
11178pub fn cpp_include_closure_reaches(
11179    cpp: &dyn CppSource,
11180    token: QueryToken<'_>,
11181    translation_unit: &ProjectFile,
11182    header: &ProjectFile,
11183) -> bool {
11184    let language_guard_stable = OnceCell::new();
11185    unconditional_include_reaches(
11186        cpp,
11187        token,
11188        translation_unit,
11189        header,
11190        translation_unit,
11191        &mut HashSet::default(),
11192        &|| {
11193            *language_guard_stable.get_or_init(|| {
11194                let mut macro_events =
11195                    |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11196                include_language_guard_is_stable_with(
11197                    cpp,
11198                    token,
11199                    translation_unit,
11200                    &mut macro_events,
11201                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11202                        collect_structured_include_facts(prepared)
11203                    },
11204                )
11205            })
11206        },
11207    )
11208}
11209
11210fn unconditional_include_reaches(
11211    cpp: &dyn CppSource,
11212    token: QueryToken<'_>,
11213    first: &ProjectFile,
11214    donor_source: &ProjectFile,
11215    reference_file: &ProjectFile,
11216    known_missing: &mut HashSet<ProjectFile>,
11217    language_guard_is_stable: &dyn Fn() -> bool,
11218) -> bool {
11219    if first == donor_source {
11220        return true;
11221    }
11222    if known_missing.contains(first) {
11223        return false;
11224    }
11225    let reference_is_c = reference_file
11226        .rel_path()
11227        .extension()
11228        .and_then(|extension| extension.to_str())
11229        == Some("c");
11230    if let Some(reaches) =
11231        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11232    {
11233        return reaches;
11234    }
11235    let mut visited = HashSet::default();
11236    let mut files = vec![first.clone()];
11237    let include_targets = cpp.include_target_index();
11238    while let Some(file) = files.pop() {
11239        if file == *donor_source {
11240            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11241            return true;
11242        }
11243        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11244            continue;
11245        }
11246        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11247            continue;
11248        };
11249        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11250        let mut cursor = prepared.tree().walk();
11251        let mut nodes = vec![prepared.tree().root_node()];
11252        while let Some(node) = nodes.pop() {
11253            if node.kind() == "preproc_include" {
11254                let Some(required_guards) = include_directive_guard_requirements(
11255                    node,
11256                    prepared.source(),
11257                    reference_is_c,
11258                    language_guard_is_stable,
11259                ) else {
11260                    continue;
11261                };
11262                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11263                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11264                    {
11265                        if let Some(target) = unique_include_target(
11266                            resolve_include_targets_with_index(&file, include, include_targets),
11267                        ) {
11268                            files.push(target);
11269                        }
11270                    }
11271                }
11272                continue;
11273            }
11274            let first_pushed = nodes.len();
11275            nodes.extend(node.named_children(&mut cursor));
11276            nodes[first_pushed..].reverse();
11277        }
11278    }
11279    known_missing.extend(visited);
11280    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11281    false
11282}
11283
11284fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11285    cpp: &dyn CppSource,
11286    token: QueryToken<'_>,
11287    file: &ProjectFile,
11288    mut macro_events: MacroEvents,
11289    mut include_facts: IncludeFacts,
11290) -> bool
11291where
11292    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11293    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11294{
11295    // A header has no intrinsic dialect. An opaque incoming include can hide
11296    // its compiler context from reverse include attribution. Only a
11297    // translation unit can supply the source-only language fact.
11298    if !crate::imports::is_cpp_translation_unit(file) {
11299        return false;
11300    }
11301    // When compiler data exists, keep its existing authoritative guard proof.
11302    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11303    if !cpp.compile_contexts_for(file).is_empty()
11304        || cpp
11305            .reaching_translation_units(file)
11306            .iter()
11307            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11308    {
11309        return false;
11310    }
11311    let mut pending = vec![file.clone()];
11312    let mut visited = HashSet::default();
11313    while let Some(file) = pending.pop() {
11314        if !visited.insert(file.clone()) {
11315            continue;
11316        }
11317        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11318            return false;
11319        };
11320        if macro_events(&file).iter().any(|event| match event {
11321            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11322                name == "__cplusplus"
11323            }
11324            MacroEvent::Invalidate { .. } => true,
11325            MacroEvent::Include { targets, .. } => targets.is_empty(),
11326        }) {
11327            return false;
11328        }
11329        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11330        // needs every include source: an opaque header may change it.
11331        for fact in include_facts(&file, prepared.as_ref()).iter() {
11332            let targets =
11333                resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11334            if targets.is_empty() {
11335                return false;
11336            }
11337            pending.extend(targets);
11338        }
11339    }
11340    true
11341}
11342
11343fn declaration_guard_requirements(
11344    analyzer: &CppGraphSource<'_>,
11345    cpp: &dyn CppSource,
11346    candidate: &CodeUnit,
11347) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11348    declaration_guard_sites(analyzer, cpp, candidate, None)
11349        .into_iter()
11350        .map(|site| (site.byte, site.guards))
11351        .collect()
11352}
11353
11354/// One declaration range's preprocessor facts, as a visible-at-a-reference
11355/// question reads them.
11356struct DeclarationGuardSite {
11357    /// The byte at which the range introduces the name.
11358    byte: usize,
11359    /// The preprocessor requirements the range's declaration stands under.
11360    guards: HashSet<PreprocessorGuard>,
11361    /// Whether the range is one branch of completed `#if`/`#else` families
11362    /// that all close before the reference. Always false without a reference
11363    /// position.
11364    completed_family_before_reference: bool,
11365}
11366
11367/// The per-range facts behind [`declaration_guard_requirements`], plus the
11368/// reference-relative fact the completed-family rule asks for.
11369fn declaration_guard_sites(
11370    analyzer: &CppGraphSource<'_>,
11371    cpp: &dyn CppSource,
11372    candidate: &CodeUnit,
11373    reference_byte: Option<usize>,
11374) -> Vec<DeclarationGuardSite> {
11375    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11376        return Vec::new();
11377    };
11378    let root = prepared.tree().root_node();
11379    analyzer
11380        .declaration_source_occurrences(candidate)
11381        .into_iter()
11382        .flatten()
11383        .filter_map(|occurrence| {
11384            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11385            Some(DeclarationGuardSite {
11386                byte: occurrence.range.start_byte,
11387                guards,
11388                completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11389                    root.descendant_for_byte_range(
11390                        occurrence.range.start_byte,
11391                        occurrence.range.end_byte,
11392                    )
11393                    .is_some_and(|node| {
11394                        declaration_follows_completed_family_before_reference(
11395                            node,
11396                            prepared.source(),
11397                            reference_byte,
11398                        )
11399                    })
11400                }),
11401            })
11402        })
11403        .collect()
11404}
11405
11406/// Whether every conditional enclosing `node` is a completed
11407/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11408///
11409/// A completed family is an exhaustive case analysis: every configuration
11410/// takes exactly one of its branches. A reference sited below the whole family
11411/// is compiled in a configuration that either takes the declaration's branch,
11412/// in which case the declaration is what the name resolves to there, or takes
11413/// another branch, in which case the declaration is simply not part of that
11414/// configuration. Nothing in the reference's own position settles the family,
11415/// so the declaration is one resolution the source presents at the reference
11416/// rather than a contradiction of it (#3297).
11417///
11418/// A lone `#if` is not a case analysis: nothing in the source says any
11419/// configuration defines its macro, so a declaration under one stays
11420/// unprovable and the implication rule keeps it out. Neither does a family
11421/// whose end is not behind the reference: a reference inside the other branch
11422/// contradicts the declaration, and a reference inside the declaration's own
11423/// branch is already covered by implication.
11424fn declaration_follows_completed_family_before_reference(
11425    node: Node<'_>,
11426    source: &str,
11427    reference_byte: usize,
11428) -> bool {
11429    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11430}
11431
11432/// Producer-owned completion boundary for all enclosing conditional families.
11433/// Zero is the neutral boundary for a declaration outside those families.
11434pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11435    let mut cache = PreprocessorContextCache::default();
11436    callable_guard_completion_byte_with_cache(node, source, &mut cache)
11437}
11438
11439pub(crate) fn callable_guard_completion_byte_with_cache(
11440    node: Node<'_>,
11441    source: &str,
11442    cache: &mut PreprocessorContextCache,
11443) -> Option<usize> {
11444    let mut completion_byte = 0;
11445    let mut ancestor = node.parent();
11446    while let Some(conditional) = ancestor {
11447        if matches!(
11448            conditional.kind(),
11449            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11450        ) && !is_file_covering_include_guard(conditional, source)
11451            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11452            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11453        {
11454            let family = preprocessor_conditional_family_root(conditional);
11455            if !preprocessor_conditional_family_has_terminal_else(family, cache) {
11456                return None;
11457            }
11458            completion_byte = completion_byte.max(family.end_byte());
11459        }
11460        ancestor = conditional.parent();
11461    }
11462    Some(completion_byte)
11463}
11464
11465fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11466    analyzer
11467        .ranges(candidate)
11468        .into_iter()
11469        .map(|range| range.start_byte)
11470        .min()
11471}
11472
11473/// The macro names every configuration in `contexts` defines -- the fact set
11474/// one file's compile-database coverage proves (#2011). `None` when the
11475/// database has no entry for the file, which is different from an empty
11476/// intersection: no entry means no coverage, while an empty intersection is
11477/// covered-and-proves-nothing.
11478fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11479    let (first, rest) = contexts.split_first()?;
11480    Some(
11481        first
11482            .defined_macros
11483            .iter()
11484            .filter(|name| {
11485                rest.iter()
11486                    .all(|context| context.defined_macros.contains(*name))
11487            })
11488            .cloned()
11489            .collect(),
11490    )
11491}
11492
11493pub fn guard_requirements_hold_at_reference(
11494    required: &HashSet<PreprocessorGuard>,
11495    reference: Option<&HashSet<PreprocessorGuard>>,
11496) -> bool {
11497    reference.is_some_and(|active| {
11498        required
11499            .iter()
11500            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11501    })
11502}
11503
11504fn preprocessor_guard_holds_at_reference(
11505    required: &PreprocessorGuard,
11506    active: &HashSet<PreprocessorGuard>,
11507) -> bool {
11508    if active.contains(required) {
11509        return true;
11510    }
11511    let active_expression = BooleanGuardExpression::all(
11512        active
11513            .iter()
11514            .filter_map(PreprocessorGuard::as_boolean_expression),
11515    );
11516    required
11517        .as_boolean_expression()
11518        .is_some_and(|required| active_expression.implies(&required))
11519}
11520
11521/// Cross-file guard rule: two guard sets are compatible when neither one
11522/// contradicts the other. Use this instead of the subset test whenever the
11523/// guards come from a foreign file, which resolves its own conditionals
11524/// independently of the reference.
11525fn guards_compatible_at_reference(
11526    declaration: &HashSet<PreprocessorGuard>,
11527    reference: Option<&HashSet<PreprocessorGuard>>,
11528) -> bool {
11529    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11530}
11531
11532/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11533/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11534/// conditional.
11535///
11536/// Two declarations of one name that report the same chain stand in different
11537/// branches of it, so at most one of them is compiled in any configuration.
11538/// They are alternate spellings of a single declaration, not competing
11539/// declarations, and navigation must not present them as an ambiguity.
11540pub fn preprocessor_conditional_family_range(
11541    root: Node<'_>,
11542    start_byte: usize,
11543    end_byte: usize,
11544) -> Option<(usize, usize)> {
11545    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11546    preprocessor_conditional_family_node(node)
11547        .map(|family| (family.start_byte(), family.end_byte()))
11548}
11549
11550pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11551    let mut ancestor = Some(node);
11552    while let Some(current) = ancestor {
11553        if is_cpp_preprocessor_conditional(current)
11554            && preprocessor_conditional_contains_descendant(current, node)
11555        {
11556            return Some(preprocessor_conditional_family_root(current));
11557        }
11558        ancestor = current.parent();
11559    }
11560    None
11561}
11562
11563pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11564    node: Node<'tree>,
11565    cache: &mut PreprocessorContextCache,
11566) -> Option<Node<'tree>> {
11567    let mut ancestor = node.parent();
11568    while let Some(current) = ancestor {
11569        if is_cpp_preprocessor_conditional(current)
11570            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11571        {
11572            let family = preprocessor_conditional_family_root(current);
11573            if preprocessor_conditional_family_has_terminal_else(family, cache) {
11574                return Some(family);
11575            }
11576        }
11577        ancestor = current.parent();
11578    }
11579    None
11580}
11581
11582fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11583    while let Some(parent) = conditional.parent() {
11584        let is_alternative = parent
11585            .child_by_field_name("alternative")
11586            .is_some_and(|alternative| {
11587                alternative.start_byte() == conditional.start_byte()
11588                    && alternative.end_byte() == conditional.end_byte()
11589            });
11590        if !is_alternative {
11591            break;
11592        }
11593        conditional = parent;
11594    }
11595    conditional
11596}
11597
11598fn preprocessor_conditional_family_has_terminal_else(
11599    mut conditional: Node<'_>,
11600    cache: &mut PreprocessorContextCache,
11601) -> bool {
11602    loop {
11603        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11604            return cache
11605                .displaced_branch(conditional)
11606                .is_some_and(|directive| directive.is_else);
11607        };
11608        match alternative.kind() {
11609            "preproc_else" => return true,
11610            "preproc_elif" => conditional = alternative,
11611            _ => return false,
11612        }
11613    }
11614}
11615
11616/// The undecidable preprocessor guards an `#include` directive stands under,
11617/// or `None` when its conditional context keeps the directive out of a
11618/// translation unit of the reference's language: a false constant, a
11619/// condition this analyzer cannot read, or a `__cplusplus` branch that
11620/// language never takes.
11621///
11622/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11623/// edge, and the language test is what keeps
11624/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11625/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11626/// so without this a `.c` file would reach the C++ arm of every
11627/// `#ifdef __cplusplus` / `#else` include pair.
11628fn include_directive_guard_requirements(
11629    node: Node<'_>,
11630    source: &str,
11631    reference_is_c: bool,
11632    language_guard_is_stable: &dyn Fn() -> bool,
11633) -> Option<PreprocessorGuardEnvironment> {
11634    let mut required = preprocessor_guard_environment_by_family(node, source)?;
11635    let excluded_by_language = required
11636        .all
11637        .iter()
11638        .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11639    if excluded_by_language {
11640        return None;
11641    }
11642    let is_language_guard = |guard: &PreprocessorGuard| {
11643        matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11644            if name == "__cplusplus")
11645    };
11646    // The dialect decides this guard only when the admitted source closure
11647    // cannot mutate the builtin. Unknown include sources keep it undecided.
11648    if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11649        for guards in [&mut required.all, &mut required.partial] {
11650            guards.retain(|guard| !is_language_guard(guard));
11651        }
11652    }
11653    Some(required)
11654}
11655
11656fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11657    match guard {
11658        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11659        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11660        _ => false,
11661    }
11662}
11663
11664pub fn preprocessor_guard_environment(
11665    node: Node<'_>,
11666    source: &str,
11667) -> Option<HashSet<PreprocessorGuard>> {
11668    let mut cache = PreprocessorContextCache::default();
11669    preprocessor_guard_environment_with_cache(node, source, &mut cache)
11670}
11671
11672pub(crate) fn preprocessor_guard_environment_with_cache(
11673    node: Node<'_>,
11674    source: &str,
11675    cache: &mut PreprocessorContextCache,
11676) -> Option<HashSet<PreprocessorGuard>> {
11677    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11678        .map(|environment| environment.all)
11679}
11680
11681/// The preprocessor guards `node` stands under, split by whether the
11682/// conditional that contributes each one belongs to a family that covers every
11683/// configuration.
11684///
11685/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11686/// every configuration takes exactly one branch of, which is why the resolver
11687/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11688/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11689/// the source says any configuration defines that macro, so only the build can
11690/// decide it, and it stays in `partial`.
11691#[derive(Clone)]
11692struct PreprocessorGuardEnvironment {
11693    all: HashSet<PreprocessorGuard>,
11694    partial: HashSet<PreprocessorGuard>,
11695}
11696
11697impl PreprocessorGuardEnvironment {
11698    /// The environment of a path standing under both `self` and `other`, or
11699    /// `None` when the two contradict. A guard a lone `#if` contributes on
11700    /// either side stays undecided for the whole path.
11701    fn merged(&self, other: &Self) -> Option<Self> {
11702        Some(Self {
11703            all: merge_preprocessor_guards(&self.all, &other.all)?,
11704            partial: self.partial.union(&other.partial).cloned().collect(),
11705        })
11706    }
11707}
11708
11709fn preprocessor_guard_environment_by_family(
11710    node: Node<'_>,
11711    source: &str,
11712) -> Option<PreprocessorGuardEnvironment> {
11713    let mut cache = PreprocessorContextCache::default();
11714    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11715}
11716
11717fn preprocessor_guard_environment_by_family_with_cache(
11718    node: Node<'_>,
11719    source: &str,
11720    cache: &mut PreprocessorContextCache,
11721) -> Option<PreprocessorGuardEnvironment> {
11722    let mut all = HashSet::default();
11723    let mut partial = HashSet::default();
11724    let mut ancestor = node.parent();
11725    while let Some(conditional) = ancestor {
11726        if matches!(
11727            conditional.kind(),
11728            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11729        ) && !is_file_covering_include_guard(conditional, source)
11730            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11731            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11732        {
11733            let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
11734            match guard {
11735                PreprocessorGuard::Constant(true) => {
11736                    ancestor = conditional.parent();
11737                    continue;
11738                }
11739                PreprocessorGuard::Constant(false) => return None,
11740                _ => {}
11741            }
11742            if all.contains(&guard.negated()) {
11743                return None;
11744            }
11745            if !preprocessor_conditional_family_has_terminal_else(
11746                preprocessor_conditional_family_root(conditional),
11747                cache,
11748            ) {
11749                partial.insert(guard.clone());
11750            }
11751            all.insert(guard);
11752        }
11753        ancestor = conditional.parent();
11754    }
11755    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11756        match guard {
11757            PreprocessorGuard::Constant(true) => {}
11758            PreprocessorGuard::Constant(false) => return None,
11759            _ => {
11760                if all.contains(&guard.negated()) {
11761                    return None;
11762                }
11763                // The family of a conditional the parser split across a
11764                // statement boundary is not readable from the tree, so it
11765                // counts as undecided.
11766                partial.insert(guard.clone());
11767                all.insert(guard);
11768            }
11769        }
11770    }
11771    Some(PreprocessorGuardEnvironment { all, partial })
11772}
11773
11774pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11775    node: Node<'_>,
11776    source: &str,
11777    cache: &mut PreprocessorContextCache,
11778) -> Option<HashSet<PreprocessorGuard>> {
11779    let mut guards = HashSet::default();
11780    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11781        match guard {
11782            PreprocessorGuard::Constant(true) => {}
11783            PreprocessorGuard::Constant(false) => return None,
11784            _ => {
11785                if guards.contains(&guard.negated()) {
11786                    return None;
11787                }
11788                guards.insert(guard);
11789            }
11790        }
11791    }
11792    Some(guards)
11793}
11794
11795fn callable_preprocessor_ancestor_guards(
11796    node: Node<'_>,
11797    source: &str,
11798) -> Option<Vec<PreprocessorGuard>> {
11799    let mut cache = PreprocessorContextCache::default();
11800    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11801}
11802
11803fn callable_preprocessor_ancestor_guards_with_cache(
11804    node: Node<'_>,
11805    source: &str,
11806    cache: &mut PreprocessorContextCache,
11807) -> Option<Vec<PreprocessorGuard>> {
11808    let mut guards = Vec::new();
11809    let mut ancestor = node.parent();
11810    while let Some(conditional) = ancestor {
11811        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11812            && !is_file_covering_include_guard(conditional, source)
11813            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11814            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11815        {
11816            guards.push(preprocessor_guard_for_descendant(
11817                conditional,
11818                node,
11819                source,
11820                cache,
11821            )?);
11822        }
11823        ancestor = conditional.parent();
11824    }
11825    Some(guards)
11826}
11827
11828fn fragmented_statement_preprocessor_guard(
11829    descendant: Node<'_>,
11830    source: &str,
11831) -> Option<PreprocessorGuard> {
11832    // A conditional that starts before `} else if (...) {` crosses the
11833    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11834    // as a `preproc_if` with a missing terminator in the consequence and
11835    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11836    // those structured nodes before restoring the guard to intervening uses.
11837    let mut ancestor = descendant.parent();
11838    while let Some(statement) = ancestor {
11839        if statement.kind() == "if_statement"
11840            && let (Some(consequence), Some(alternative)) = (
11841                statement.child_by_field_name("consequence"),
11842                statement.child_by_field_name("alternative"),
11843            )
11844            && alternative.start_byte() <= descendant.start_byte()
11845            && descendant.end_byte() <= alternative.end_byte()
11846        {
11847            let mut cursor = consequence.walk();
11848            let openers = consequence
11849                .named_children(&mut cursor)
11850                .filter(|child| {
11851                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11852                        && child
11853                            .child(child.child_count().saturating_sub(1))
11854                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11855                })
11856                .collect::<Vec<_>>();
11857            if openers.len() != 1 {
11858                ancestor = statement.parent();
11859                continue;
11860            }
11861
11862            let mut terminators = Vec::new();
11863            let mut stack = vec![alternative];
11864            while let Some(node) = stack.pop() {
11865                if node.kind() == "preproc_call"
11866                    && node.start_byte() >= descendant.end_byte()
11867                    && node
11868                        .child_by_field_name("directive")
11869                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11870                {
11871                    terminators.push(node);
11872                    continue;
11873                }
11874                push_named_children_reversed(node, &mut stack);
11875            }
11876            if terminators.len() == 1 {
11877                return simple_preprocessor_guard(openers[0], source);
11878            }
11879        }
11880        ancestor = statement.parent();
11881    }
11882    None
11883}
11884
11885pub(crate) fn preprocessor_guard_for_descendant(
11886    conditional: Node<'_>,
11887    descendant: Node<'_>,
11888    source: &str,
11889    cache: &mut PreprocessorContextCache,
11890) -> Option<PreprocessorGuard> {
11891    let guard = simple_preprocessor_guard(conditional, source)?;
11892    match preprocessor_branch_of_descendant(conditional, descendant, cache) {
11893        PreprocessorBranch::Consequence => Some(guard),
11894        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11895        // descendant in any later branch must first exclude the parent branch,
11896        // then collect the nested `preproc_elif` guard from its own ancestor.
11897        PreprocessorBranch::Structured(alternative)
11898            if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
11899        {
11900            Some(guard.negated())
11901        }
11902        PreprocessorBranch::DisplacedElse => Some(guard.negated()),
11903        PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
11904    }
11905}
11906
11907/// The branch of `conditional` that holds `descendant`.
11908enum PreprocessorBranch<'tree> {
11909    Consequence,
11910    /// Inside the structured `alternative` field.
11911    Structured(Node<'tree>),
11912    /// After an `#else` the parser displaced into an `ERROR` of the
11913    /// consequence ([`cpp_displaced_preprocessor_branch`]).
11914    DisplacedElse,
11915    /// After a displaced `#elif`, whose condition has no structured node.
11916    Unreadable,
11917}
11918
11919fn preprocessor_branch_of_descendant<'tree>(
11920    conditional: Node<'tree>,
11921    descendant: Node<'_>,
11922    cache: &mut PreprocessorContextCache,
11923) -> PreprocessorBranch<'tree> {
11924    if let Some(alternative) =
11925        conditional
11926            .child_by_field_name("alternative")
11927            .filter(|alternative| {
11928                alternative.start_byte() <= descendant.start_byte()
11929                    && descendant.end_byte() <= alternative.end_byte()
11930            })
11931    {
11932        return PreprocessorBranch::Structured(alternative);
11933    }
11934    match cache.displaced_branch(conditional) {
11935        Some(directive) if directive.end_byte <= descendant.start_byte() => {
11936            if directive.is_else {
11937                PreprocessorBranch::DisplacedElse
11938            } else {
11939                PreprocessorBranch::Unreadable
11940            }
11941        }
11942        _ => PreprocessorBranch::Consequence,
11943    }
11944}
11945
11946pub(crate) fn preprocessor_conditional_contains_descendant(
11947    conditional: Node<'_>,
11948    descendant: Node<'_>,
11949) -> bool {
11950    cpp_displaced_preprocessor_boundary(conditional)
11951        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11952}
11953
11954fn preprocessor_conditional_contains_descendant_with_cache(
11955    conditional: Node<'_>,
11956    descendant: Node<'_>,
11957    cache: &mut PreprocessorContextCache,
11958) -> bool {
11959    cache
11960        .displaced_boundary_end(conditional)
11961        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11962}
11963
11964pub fn merge_preprocessor_guards(
11965    left: &HashSet<PreprocessorGuard>,
11966    right: &HashSet<PreprocessorGuard>,
11967) -> Option<HashSet<PreprocessorGuard>> {
11968    let mut merged = left.clone();
11969    for guard in right {
11970        let boolean_negation = guard
11971            .as_boolean_expression()
11972            .map(|expression| expression.negated());
11973        if merged.contains(&guard.negated())
11974            || boolean_negation.is_some_and(|negated| {
11975                merged
11976                    .iter()
11977                    .filter_map(PreprocessorGuard::as_boolean_expression)
11978                    .any(|existing| existing == negated)
11979            })
11980        {
11981            return None;
11982        }
11983        merged.insert(guard.clone());
11984    }
11985    Some(merged)
11986}
11987
11988fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11989    if conditional.kind() == "preproc_ifdef" {
11990        let name = conditional.child_by_field_name("name")?;
11991        let name = node_text(name, source).to_string();
11992        return match conditional.child(0)?.kind() {
11993            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11994            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11995            _ => None,
11996        };
11997    }
11998    let condition = conditional.child_by_field_name("condition")?;
11999    simple_preprocessor_expression_guard(condition, source).or_else(|| {
12000        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12001            node_text(condition, source),
12002        )))
12003    })
12004}
12005
12006fn simple_preprocessor_expression_guard(
12007    expression: Node<'_>,
12008    source: &str,
12009) -> Option<PreprocessorGuard> {
12010    match expression.kind() {
12011        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12012            node_text(expression, source).to_string(),
12013        ))),
12014        "number_literal" => match node_text(expression, source).trim() {
12015            "0" => Some(PreprocessorGuard::Constant(false)),
12016            "1" => Some(PreprocessorGuard::Constant(true)),
12017            _ => None,
12018        },
12019        "preproc_defined" => {
12020            let identifier = (0..expression.named_child_count())
12021                .filter_map(|index| expression.named_child(index))
12022                .find(|child| child.kind() == "identifier")?;
12023            Some(PreprocessorGuard::Defined(
12024                node_text(identifier, source).to_string(),
12025            ))
12026        }
12027        "unary_expression"
12028            if expression
12029                .child_by_field_name("operator")
12030                .is_some_and(|operator| operator.kind() == "!") =>
12031        {
12032            simple_preprocessor_expression_guard(
12033                expression.child_by_field_name("argument")?,
12034                source,
12035            )
12036            .map(|guard| guard.negated())
12037        }
12038        "parenthesized_expression" => (0..expression.named_child_count())
12039            .filter_map(|index| expression.named_child(index))
12040            .next()
12041            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12042        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12043            expression, source,
12044        ))),
12045        _ => None,
12046    }
12047}
12048
12049fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12050    match expression.kind() {
12051        "number_literal" => match node_text(expression, source).trim() {
12052            "0" => BooleanGuardExpression::Constant(false),
12053            "1" => BooleanGuardExpression::Constant(true),
12054            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12055                expression, source,
12056            ))),
12057        },
12058        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12059        "preproc_defined" => {
12060            let identifier = (0..expression.named_child_count())
12061                .filter_map(|index| expression.named_child(index))
12062                .find(|child| child.kind() == "identifier");
12063            identifier.map_or_else(
12064                || {
12065                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12066                        expression, source,
12067                    )))
12068                },
12069                |identifier| {
12070                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12071                },
12072            )
12073        }
12074        "unary_expression"
12075            if expression
12076                .child_by_field_name("operator")
12077                .is_some_and(|operator| operator.kind() == "!") =>
12078        {
12079            expression.child_by_field_name("argument").map_or_else(
12080                || {
12081                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12082                        expression, source,
12083                    )))
12084                },
12085                |argument| boolean_preprocessor_expression(argument, source).negated(),
12086            )
12087        }
12088        "parenthesized_expression" => (0..expression.named_child_count())
12089            .filter_map(|index| expression.named_child(index))
12090            .next()
12091            .map_or_else(
12092                || {
12093                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12094                        expression, source,
12095                    )))
12096                },
12097                |child| boolean_preprocessor_expression(child, source),
12098            ),
12099        "binary_expression" => {
12100            let operands = || {
12101                Some((
12102                    boolean_preprocessor_expression(
12103                        expression.child_by_field_name("left")?,
12104                        source,
12105                    ),
12106                    boolean_preprocessor_expression(
12107                        expression.child_by_field_name("right")?,
12108                        source,
12109                    ),
12110                ))
12111            };
12112            match expression
12113                .child_by_field_name("operator")
12114                .map(|operator| operator.kind())
12115            {
12116                Some("&&") => operands().map_or_else(
12117                    || {
12118                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12119                            expression, source,
12120                        )))
12121                    },
12122                    |(left, right)| BooleanGuardExpression::all([left, right]),
12123                ),
12124                Some("||") => operands().map_or_else(
12125                    || {
12126                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12127                            expression, source,
12128                        )))
12129                    },
12130                    |(left, right)| BooleanGuardExpression::any([left, right]),
12131                ),
12132                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12133                    expression, source,
12134                ))),
12135            }
12136        }
12137        _ => {
12138            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12139        }
12140    }
12141}
12142
12143fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12144    if targets.len() == 1 {
12145        targets.pop()
12146    } else {
12147        None
12148    }
12149}
12150
12151fn callable_declaration_nodes<'tree>(
12152    analyzer: &CppGraphSource<'_>,
12153    prepared: &'tree PreparedSyntaxTree,
12154    candidate: &CodeUnit,
12155) -> Vec<Node<'tree>> {
12156    let root = prepared.tree().root_node();
12157    analyzer
12158        .ranges(candidate)
12159        .into_iter()
12160        .filter_map(|range| {
12161            let mut declaration =
12162                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12163            // A declaration an attribute-like macro invocation swallowed lives
12164            // inside the `ERROR` the parser left, not inside a `declaration`
12165            // node, so that envelope is where the climb stops (#2552).
12166            //
12167            // The climb can also run out of ancestors. A collapse that takes a
12168            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12169            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12170            // envelope around most of what a region reparse recovers there, and
12171            // the recovered declaration's own `;` can fall outside the envelope
12172            // it does leave, so the range spans the container rather than one
12173            // node. Either way the declaration stands at the scope the climb
12174            // ended in, which is what the callers ask these nodes about.
12175            while !matches!(
12176                declaration.kind(),
12177                "declaration" | "field_declaration" | "function_definition"
12178            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12179                declaration,
12180                prepared.source(),
12181            ) {
12182                let Some(parent) = declaration.parent() else {
12183                    break;
12184                };
12185                declaration = parent;
12186            }
12187            Some(declaration)
12188        })
12189        .collect()
12190}
12191
12192fn real_function_definition_ancestor<'tree>(
12193    node: Node<'tree>,
12194    source: &str,
12195) -> Option<Node<'tree>> {
12196    let mut ancestor = node.parent();
12197    while let Some(node) = ancestor {
12198        if node.kind() == "function_definition"
12199            && !is_recovered_declaration_scope_container(node, source)
12200        {
12201            return Some(node);
12202        }
12203        ancestor = node.parent();
12204    }
12205    None
12206}
12207
12208fn callable_declaration_activation_in_file(
12209    analyzer: &CppGraphSource<'_>,
12210    candidate: &CodeUnit,
12211    reference: &CallableReferenceContext<'_>,
12212) -> Option<usize> {
12213    analyzer
12214        .declaration_source_occurrences(candidate)?
12215        .into_iter()
12216        .filter_map(|occurrence| {
12217            let activation = occurrence.fact.callable_activation?;
12218            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12219            callable_guard_requirements_are_visible(
12220                &guards,
12221                occurrence.fact.callable_guard_completion_byte,
12222                reference,
12223            )
12224            .then_some(activation)
12225        })
12226        .min()
12227}
12228
12229fn callable_guard_requirements_are_visible(
12230    required: &HashSet<PreprocessorGuard>,
12231    completion_byte: Option<usize>,
12232    reference: &CallableReferenceContext<'_>,
12233) -> bool {
12234    if required.iter().all(|guard| match guard {
12235        PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12236        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12237        guard => reference
12238            .guards()
12239            .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12240    }) {
12241        return true;
12242    }
12243    if required.iter().any(|guard| match guard {
12244        PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12245        PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12246        _ => false,
12247    }) {
12248        return false;
12249    }
12250    let undecided: HashSet<_> = required
12251        .iter()
12252        .filter(|guard| {
12253            !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12254            if name == "__cplusplus")
12255        })
12256        .cloned()
12257        .collect();
12258    reference.position.as_ref().is_some_and(|position| {
12259        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12260            && guards_compatible_at_reference(&undecided, reference.guards())
12261    })
12262}
12263
12264/// The reference side of a callable visibility question.
12265///
12266/// An include-graph walk and a whole-file arity activation ask the question
12267/// without one reference position, so they carry no `position` and therefore no
12268/// guard environment.
12269struct CallableReferenceContext<'a> {
12270    file: &'a ProjectFile,
12271    position: Option<CallableReferencePosition<'a>>,
12272}
12273
12274/// One reference position plus its preprocessor guard environment. The
12275/// environment is computed on demand because most declarations carry no
12276/// non-trivial guard.
12277struct CallableReferencePosition<'a> {
12278    prepared: &'a PreparedSyntaxTree,
12279    byte: usize,
12280    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12281}
12282
12283impl CallableReferenceContext<'_> {
12284    fn is_c(&self) -> bool {
12285        self.file
12286            .rel_path()
12287            .extension()
12288            .and_then(|extension| extension.to_str())
12289            == Some("c")
12290    }
12291
12292    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12293        let position = self.position.as_ref()?;
12294        position
12295            .guards
12296            .get_or_init(|| {
12297                position
12298                    .prepared
12299                    .tree()
12300                    .root_node()
12301                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12302                    .and_then(|node| {
12303                        preprocessor_guard_environment(node, position.prepared.source())
12304                    })
12305            })
12306            .as_ref()
12307    }
12308}
12309
12310/// The undecidable preprocessor guards `node` stands under, or `None` when its
12311/// conditional context excludes it from `reference`'s translation unit outright:
12312/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12313/// branch the reference's language never takes.
12314///
12315/// Collecting one guard per enclosing conditional makes the whole walk a
12316/// conjunction of the declaration's requirements. Which comparison those
12317/// requirements then face depends on where the declaration lives: a same-file
12318/// declaration shares the reference's configuration, so the reference's active
12319/// guards must imply them, while a foreign file resolves its own conditionals
12320/// and only has to stay compatible with the reference.
12321fn callable_declaration_guard_requirements(
12322    node: Node<'_>,
12323    source: &str,
12324    reference: &CallableReferenceContext<'_>,
12325) -> Option<HashSet<PreprocessorGuard>> {
12326    let reference_is_c = reference.is_c();
12327    let mut required = HashSet::default();
12328    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12329        match guard {
12330            PreprocessorGuard::Constant(true) => {}
12331            PreprocessorGuard::Constant(false) => return None,
12332            guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12333                return None;
12334            }
12335            guard => {
12336                required.insert(guard);
12337            }
12338        }
12339    }
12340    Some(required)
12341}
12342
12343/// Whether a declaration in the reference's own file is co-active with the
12344/// reference: one translation unit resolves every conditional the same way, so
12345/// the reference's active guards must imply the declaration's requirements.
12346///
12347/// The exception is a declaration in one branch of a completed `#if`/`#else`
12348/// family the reference follows: the reference's own position settles nothing
12349/// about that family, so a compatible branch is one resolution the source
12350/// presents at the reference (#3297), exactly as
12351/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12352/// declaration there.
12353fn callable_preprocessor_context_is_visible_for_reference(
12354    node: Node<'_>,
12355    source: &str,
12356    reference: &CallableReferenceContext<'_>,
12357) -> bool {
12358    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12359        return false;
12360    };
12361    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12362        return true;
12363    }
12364    reference.position.as_ref().is_some_and(|position| {
12365        guards_compatible_at_reference(&required, reference.guards())
12366            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12367    })
12368}
12369
12370fn flattened_macro_namespace_declaration_matches(
12371    analyzer: &CppGraphSource<'_>,
12372    _cpp: &dyn CppSource,
12373    reference_file: &ProjectFile,
12374    visible_declaration: &CodeUnit,
12375    qualified_candidate: &CodeUnit,
12376    reference_byte: usize,
12377) -> bool {
12378    // Namespace-opening macros can leave tree-sitter unable to retain the
12379    // namespace owner after a later recovery point. In that shape the forward
12380    // declaration is indexed at translation-unit scope, while the definition
12381    // still has its qualified owner. Require all surviving structural evidence
12382    // before treating the declaration as activation for that definition.
12383    if visible_declaration.kind() != qualified_candidate.kind()
12384        || visible_declaration.identifier() != qualified_candidate.identifier()
12385        || visible_declaration.signature() != qualified_candidate.signature()
12386        || !visible_declaration.package_name().is_empty()
12387        || qualified_candidate.package_name().is_empty()
12388    {
12389        return false;
12390    }
12391
12392    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12393        return false;
12394    };
12395    occurrences.into_iter().any(|occurrence| {
12396        occurrence
12397            .displaced_namespace_closing_brace
12398            .is_some_and(|closing_brace| {
12399                visible_declaration.source() != reference_file || closing_brace < reference_byte
12400            })
12401    })
12402}
12403
12404pub(crate) fn flattened_macro_namespace_components(
12405    declaration: Node<'_>,
12406    source: &str,
12407) -> Option<Vec<String>> {
12408    flattened_macro_function_namespace_components(declaration, source)
12409        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12410}
12411
12412fn flattened_macro_function_namespace_components(
12413    declaration: Node<'_>,
12414    source: &str,
12415) -> Option<Vec<String>> {
12416    let body = declaration
12417        .parent()
12418        .filter(|parent| parent.kind() == "compound_statement")?;
12419    let function = body.parent()?;
12420    if function.child_by_field_name("body") != Some(body) {
12421        return None;
12422    }
12423    let namespace_name = recovered_macro_namespace_name(function, source)?;
12424    let mut components = enclosing_namespace_components(declaration, source)?;
12425    components.push(namespace_name);
12426    Some(components)
12427}
12428
12429/// The namespace name a namespace-opening macro token displaced into a
12430/// synthetic `function_definition`, or `None` when `function` is not that
12431/// recovery shape.
12432///
12433/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12434/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12435/// the macro token, whose declarator is the namespace name behind an `ERROR`
12436/// holding the `namespace` keyword, and whose body spans the whole namespace
12437/// region. The macro-declaration grammar keeps the same envelope clean: the
12438/// token is a `macro_modifier` (itself admitted only where the parse would
12439/// otherwise contain an error) and the keyword occupies the type field. The
12440/// matching `*_NAMESPACE_END` sibling is what separates the recovery artifact
12441/// from a real function definition.
12442fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12443    if function.kind() != "function_definition" {
12444        return None;
12445    }
12446    let body = function
12447        .child_by_field_name("body")
12448        .filter(|body| body.kind() == "compound_statement")?;
12449    let mut cursor = function.walk();
12450    let prefix = function
12451        .named_children(&mut cursor)
12452        .take_while(|child| child.start_byte() < body.start_byte())
12453        .filter(|child| child.kind() != "comment")
12454        .collect::<Vec<_>>();
12455    let begin_index = prefix.iter().rposition(|child| {
12456        flattened_macro_sentinel_name(*child, source)
12457            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12458    })?;
12459    let mut identifiers = Vec::new();
12460    let mut stack = prefix[begin_index + 1..]
12461        .iter()
12462        .rev()
12463        .copied()
12464        .collect::<Vec<_>>();
12465    while let Some(current) = stack.pop() {
12466        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12467            identifiers.push(identifier);
12468            continue;
12469        }
12470        let mut cursor = current.walk();
12471        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12472        stack.extend(children.into_iter().rev());
12473    }
12474    let [keyword, namespace_name] = identifiers.as_slice() else {
12475        return None;
12476    };
12477    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12478    {
12479        return None;
12480    }
12481    let mut next = function.next_named_sibling();
12482    let next = loop {
12483        let candidate = next?;
12484        next = candidate.next_named_sibling();
12485        if candidate.kind() != "comment" {
12486            break candidate;
12487        }
12488    };
12489    flattened_macro_sentinel_name(next, source)
12490        .is_some_and(|name| is_namespace_end_sentinel(&name))
12491        .then(|| namespace_name.clone())
12492}
12493
12494/// A `function_definition` that exists only because tree-sitter recovered a
12495/// macro-decorated class head or a namespace-opening macro token. A declaration
12496/// in such a body keeps class or namespace scope, so a scope walk must step over
12497/// the wrapper instead of treating the declaration as block local.
12498pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12499    crate::declarations::is_recovered_exported_class_container(node, source)
12500        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12501            node, source,
12502        )
12503        || recovered_macro_namespace_name(node, source).is_some()
12504}
12505
12506fn flattened_macro_error_namespace_components(
12507    declaration: Node<'_>,
12508    source: &str,
12509) -> Option<Vec<String>> {
12510    let parent = declaration
12511        .parent()
12512        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12513    let mut cursor = parent.walk();
12514    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12515    let declaration_index = siblings
12516        .iter()
12517        .position(|candidate| same_node(*candidate, declaration))?;
12518    let begin_index = (0..declaration_index).rev().find(|index| {
12519        flattened_macro_sentinel_name(siblings[*index], source)
12520            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12521    })?;
12522
12523    let significant = siblings[begin_index + 1..declaration_index]
12524        .iter()
12525        .copied()
12526        .filter(|node| node.kind() != "comment")
12527        .collect::<Vec<_>>();
12528    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12529        return None;
12530    };
12531    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12532        return None;
12533    }
12534    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12535    if significant[2..].iter().any(|node| {
12536        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12537            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12538        })
12539    }) {
12540        return None;
12541    }
12542
12543    let mut saw_namespace_close = false;
12544    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12545        if sibling.kind() == "comment" {
12546            continue;
12547        }
12548        if !saw_namespace_close {
12549            if direct_unmatched_closing_brace(sibling) {
12550                saw_namespace_close = true;
12551                continue;
12552            }
12553            if flattened_macro_sentinel_name(sibling, source).is_some() {
12554                return None;
12555            }
12556            continue;
12557        }
12558        if !flattened_macro_sentinel_name(sibling, source)
12559            .is_some_and(|name| is_namespace_end_sentinel(&name))
12560        {
12561            return None;
12562        }
12563        let mut components = enclosing_namespace_components(declaration, source)?;
12564        components.push(namespace_name);
12565        return Some(components);
12566    }
12567    None
12568}
12569
12570fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12571    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12572    // an `expression_statement` with a missing semicolon; inside a namespace
12573    // body the same token stays a bare `type_identifier`. Under the
12574    // macro-declaration grammar the token is wrapped in a `macro_modifier`
12575    // wherever it decorates a declaration head, and a stacked `END`/`BEGIN`
12576    // pair heads the next region's bogus function with the trailing `END` as
12577    // its first modifier child.
12578    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12579        node.named_child(0)?
12580    } else {
12581        node
12582    };
12583    let candidates = [
12584        direct_cpp_identifier_name(node, source),
12585        node.child_by_field_name("type")
12586            .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
12587        macro_modifier_identifier(node)
12588            .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
12589        {
12590            let mut cursor = node.walk();
12591            node.named_children(&mut cursor)
12592                .find(|child| child.kind() != "comment")
12593                .and_then(macro_modifier_identifier)
12594                .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
12595        },
12596    ];
12597    candidates.into_iter().flatten().find(|candidate| {
12598        cpp_export_macro_token(candidate)
12599            && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
12600    })
12601}
12602
12603/// Namespace-opening macros are spelled both ways in the wild:
12604/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12605#[derive(Clone, Copy, PartialEq, Eq)]
12606enum NamespaceSentinel {
12607    Begin,
12608    End,
12609}
12610
12611fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12612    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12613        Some(NamespaceSentinel::Begin)
12614    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12615        Some(NamespaceSentinel::End)
12616    } else {
12617        None
12618    }
12619}
12620
12621fn is_namespace_begin_sentinel(name: &str) -> bool {
12622    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12623}
12624
12625fn is_namespace_end_sentinel(name: &str) -> bool {
12626    namespace_sentinel(name) == Some(NamespaceSentinel::End)
12627}
12628
12629fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12630    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12631        return None;
12632    }
12633    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12634    (!cpp_export_macro_token(&name)).then_some(name)
12635}
12636
12637fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12638    if !matches!(
12639        node.kind(),
12640        "identifier" | "namespace_identifier" | "type_identifier"
12641    ) {
12642        return None;
12643    }
12644    let name = normalize_cpp_whitespace(node_text(node, source));
12645    (!name.is_empty()).then_some(name)
12646}
12647
12648fn guard_requirement_sets_match(
12649    left: &[(usize, HashSet<PreprocessorGuard>)],
12650    right: &[(usize, HashSet<PreprocessorGuard>)],
12651) -> bool {
12652    left.len() == right.len()
12653        && left.iter().all(|(_, left_guards)| {
12654            right
12655                .iter()
12656                .any(|(_, right_guards)| left_guards == right_guards)
12657        })
12658        && right.iter().all(|(_, right_guards)| {
12659            left.iter()
12660                .any(|(_, left_guards)| right_guards == left_guards)
12661        })
12662}
12663
12664pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12665    node.kind() == "ERROR"
12666        && (0..node.child_count())
12667            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12668}
12669
12670pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12671    let mut ancestor = node.parent();
12672    while let Some(parent) = ancestor {
12673        if is_cpp_preprocessor_conditional(parent)
12674            && !is_file_covering_include_guard(parent, source)
12675            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12676        {
12677            return false;
12678        }
12679        ancestor = parent.parent();
12680    }
12681    true
12682}
12683
12684fn is_split_cpp_language_linkage_wrapper(
12685    conditional: Node<'_>,
12686    descendant: Node<'_>,
12687    source: &str,
12688) -> bool {
12689    if conditional.child_by_field_name("alternative").is_some()
12690        || !matches!(
12691            simple_preprocessor_guard(conditional, source),
12692            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12693        )
12694    {
12695        return false;
12696    }
12697    let mut current = descendant.parent();
12698    let linkage = loop {
12699        let Some(node) = current else {
12700            return false;
12701        };
12702        if node == conditional {
12703            return false;
12704        }
12705        if node.kind() == "linkage_specification" {
12706            break node;
12707        }
12708        current = node.parent();
12709    };
12710    if linkage
12711        .child_by_field_name("value")
12712        .is_none_or(|value| node_text(value, source) != "\"C\"")
12713    {
12714        return false;
12715    }
12716    let Some(body) = linkage.child_by_field_name("body") else {
12717        return false;
12718    };
12719    // Walk the body with cursors: an indexed `named_child(i)` restarts from
12720    // the first child, so indexed loops over a header-sized linkage body made
12721    // each call quadratic, and this runs once per declaration inside it
12722    // (#3397).
12723    let mut cursor = body.walk();
12724    let closes_opening_branch = body
12725        .named_children(&mut cursor)
12726        .take_while(|child| child.end_byte() <= descendant.start_byte())
12727        .any(|child| {
12728            child.kind() == "preproc_call"
12729                && child
12730                    .child_by_field_name("directive")
12731                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12732        });
12733    let mut cursor = body.walk();
12734    let reopens_for_closing_brace = body
12735        .named_children(&mut cursor)
12736        .skip_while(|child| child.start_byte() < descendant.end_byte())
12737        .any(|child| {
12738            matches!(
12739                simple_preprocessor_guard(child, source),
12740                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12741            ) && {
12742                let mut tokens = child.walk();
12743                child
12744                    .children(&mut tokens)
12745                    .any(|token| token.kind() == "#endif" && token.is_missing())
12746            }
12747        });
12748    closes_opening_branch && reopens_for_closing_brace
12749}
12750
12751/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12752/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12753/// argument list no field name.
12754pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12755    node.child_by_field_name("arguments")
12756        .or_else(|| node.child_by_field_name("parameters"))
12757        .or_else(|| node.child_by_field_name("value"))
12758        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12759        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12760}
12761
12762pub fn call_arity(node: Node<'_>) -> usize {
12763    call_arguments_node(node)
12764        .map(|args| argument_children(args).count())
12765        .unwrap_or(0)
12766}
12767
12768pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12769    let recovered_block_arguments = recovered_block_literal_arguments(node);
12770    (0..node.child_count())
12771        .filter_map(move |index| node.child(index))
12772        .filter(|child| child.is_named() && !child.is_extra())
12773        // The `^(` opening a Clang block literal argument survives the
12774        // macro-declaration grammar's bogus function envelope as an ERROR in
12775        // the parameter list. It only opens the block: the parameter
12776        // declaration that follows it already stands for the block argument.
12777        .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
12778        .flat_map(move |child| {
12779            if let Some((raw, left, right)) = recovered_block_arguments
12780                && child == raw
12781            {
12782                [Some(left), Some(right)]
12783            } else {
12784                [Some(child), None]
12785            }
12786        })
12787        .flatten()
12788}
12789
12790/// The `^(` token pair that opens a Clang block literal argument, retained by
12791/// the bogus function envelope's parameter list as an `ERROR`. The block's
12792/// parameter declaration follows it and counts as the one logical argument.
12793fn block_literal_open_error(node: Node<'_>) -> bool {
12794    if node.kind() != "ERROR" || node.is_missing() {
12795        return false;
12796    }
12797    let mut cursor = node.walk();
12798    let tokens = node.children(&mut cursor).collect::<Vec<_>>();
12799    matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
12800}
12801
12802/// Recover the two ordinary C values that the C++ grammar folds into one
12803/// `new_expression` for `callee(new, trailing)`.
12804///
12805/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12806/// token, an extra `ERROR` containing only the comma token, and the trailing
12807/// value in the `type` field.  The caller supplies the compilation-dialect
12808/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12809/// reinterprets a real new-expression through this path.
12810pub fn recovered_c_new_expression_arguments(
12811    node: Node<'_>,
12812    uses_c_semantics: bool,
12813) -> Option<[Node<'_>; 2]> {
12814    if !uses_c_semantics || node.kind() != "new_expression" {
12815        return None;
12816    }
12817    let parent = node.parent()?;
12818    if parent.kind() != "argument_list" {
12819        return None;
12820    }
12821    let keyword = node.child(0)?;
12822    let error = node.child(1)?;
12823    let trailing = node.child(2)?;
12824    if node.child(3).is_some()
12825        || keyword.kind() != "new"
12826        || keyword.is_named()
12827        || keyword.child_count() != 0
12828        || error.kind() != "ERROR"
12829        || !error.is_extra()
12830        || error.child_count() != 1
12831        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12832        || node.child_by_field_name("type") != Some(trailing)
12833        || trailing.kind() != "type_identifier"
12834    {
12835        return None;
12836    }
12837    Some([keyword, trailing])
12838}
12839
12840/// The recovered C value covering one focused source range, starting from any
12841/// node within the malformed new-expression.
12842pub fn recovered_c_new_expression_argument_at(
12843    mut node: Node<'_>,
12844    start_byte: usize,
12845    end_byte: usize,
12846    uses_c_semantics: bool,
12847) -> Option<Node<'_>> {
12848    // The recovery only exists for C sources, and the climb below reaches the
12849    // root before it can answer no. `Node::parent` re-descends from the root on
12850    // every step, so leaving the check inside the loop made every scanned node
12851    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12852    // the file's language (#3097).
12853    if !uses_c_semantics {
12854        return None;
12855    }
12856    loop {
12857        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12858            return arguments.into_iter().find(|argument| {
12859                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12860            });
12861        }
12862        node = node.parent()?;
12863    }
12864}
12865
12866fn recovered_c_keyword_argument_count(
12867    file: &ProjectFile,
12868    call: Node<'_>,
12869    arguments: Node<'_>,
12870    source: &str,
12871) -> usize {
12872    // A C identifier that is a C++ keyword can be displaced twice by the C++
12873    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12874    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12875    // the enclosing C function before restoring the otherwise dropped slot;
12876    // ABI15's collapsed function-head shape is handled by the fallback below.
12877    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12878        return 0;
12879    }
12880    let mut ancestor = Some(call);
12881    let function = loop {
12882        let Some(current) = ancestor else {
12883            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12884                .unwrap_or(0);
12885        };
12886        if current.kind() == "function_definition" {
12887            break current;
12888        }
12889        ancestor = current.parent();
12890    };
12891    let Some(parameters) = function
12892        .child_by_field_name("declarator")
12893        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12894    else {
12895        return 0;
12896    };
12897    let displaced_parameter_keywords = (0..parameters.child_count())
12898        .filter_map(|index| parameters.child(index))
12899        .filter(|error| error.kind() == "ERROR")
12900        .filter_map(|error| {
12901            let parameter = error.prev_named_sibling()?;
12902            if parameter.kind() != "parameter_declaration"
12903                || parameter.end_byte() != error.start_byte()
12904                || extract_variable_name(parameter, source).is_some()
12905            {
12906                return None;
12907            }
12908            let mut children = (0..error.child_count())
12909                .filter_map(|index| error.child(index))
12910                .filter(|child| !child.is_extra() && !child.is_missing());
12911            let keyword = children.next()?;
12912            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12913                .then_some(keyword)
12914        })
12915        .collect::<Vec<_>>();
12916    if displaced_parameter_keywords.is_empty() {
12917        return 0;
12918    }
12919
12920    (0..arguments.child_count())
12921        .filter_map(|index| arguments.child(index))
12922        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12923        .filter(|error| {
12924            let mut children = (0..error.child_count())
12925                .filter_map(|index| error.child(index))
12926                .filter(|child| !child.is_extra() && !child.is_missing());
12927            let Some(comma) = children.next() else {
12928                return false;
12929            };
12930            let Some(keyword) = children.next() else {
12931                return false;
12932            };
12933            children.next().is_none()
12934                && comma.kind() == ","
12935                && !keyword.is_named()
12936                && keyword.child_count() == 0
12937                && displaced_parameter_keywords
12938                    .iter()
12939                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12940        })
12941        .count()
12942}
12943
12944/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
12945/// parameter name is a C++ keyword. The call still has a structured body, and
12946/// the outer error retains the function type, name, and opening parenthesis;
12947/// recover the displaced parameter only from those CST relationships. Text
12948/// matching would also accept an unbound call after the collapsed body.
12949fn recovered_c_keyword_argument_count_from_collapsed_function(
12950    call: Node<'_>,
12951    arguments: Node<'_>,
12952) -> Option<usize> {
12953    let argument_keywords = children_iter(arguments)
12954        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12955        .filter_map(|error| {
12956            let mut children =
12957                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12958            let comma = children.next()?;
12959            let keyword = children.next()?;
12960            (children.next().is_none()
12961                && comma.kind() == ","
12962                && !keyword.is_named()
12963                && keyword.child_count() == 0)
12964                .then_some(keyword)
12965        })
12966        .collect::<Vec<_>>();
12967    if argument_keywords.is_empty() {
12968        return Some(0);
12969    }
12970
12971    // The collapsed function keeps its body under the parameter ERROR, while
12972    // ordinary control-flow blocks add compound_statement layers below it.
12973    // Stop before crossing a real callable body, whose parameters are a
12974    // separate binding scope.
12975    let mut ancestor = call.parent();
12976    let body = loop {
12977        let current = ancestor?;
12978        if matches!(current.kind(), "function_definition" | "lambda_expression") {
12979            return None;
12980        }
12981        if current.kind() == "compound_statement"
12982            && current
12983                .parent()
12984                .is_some_and(|parent| parent.kind() == "ERROR")
12985        {
12986            break current;
12987        }
12988        ancestor = current.parent();
12989    };
12990    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12991    let function_error = parameter_error
12992        .parent()
12993        .filter(|parent| parent.kind() == "ERROR")?;
12994    let function_children = children_iter(function_error).collect::<Vec<_>>();
12995    let parameter_error_index = function_children
12996        .iter()
12997        .position(|child| same_node(*child, parameter_error))?;
12998    // ERROR nodes expose their retained field labels by child index, while
12999    // child_by_field_name does not return their type child in runtime 0.25.
13000    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13001        .iter()
13002        .enumerate()
13003        .any(|(index, child)| {
13004            function_error.field_name_for_child(index as u32) == Some("type")
13005                && !child.is_extra()
13006                && !child.is_missing()
13007                && matches!(
13008                    child.kind(),
13009                    "primitive_type" | "sized_type_specifier" | "type_identifier"
13010                )
13011        });
13012    if parameter_error_index < 2
13013        || !has_return_type
13014        || function_children[parameter_error_index - 1].kind() != "("
13015        || function_children[parameter_error_index - 2].kind() != "identifier"
13016    {
13017        return Some(0);
13018    }
13019
13020    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13021    let body_index = parameter_children
13022        .iter()
13023        .position(|child| same_node(*child, body))?;
13024    let close_parenthesis_index = parameter_children[..body_index]
13025        .iter()
13026        .position(|child| child.kind() == ")")?;
13027    let parameter_keywords = parameter_children[..close_parenthesis_index]
13028        .iter()
13029        .enumerate()
13030        .filter(|(index, child)| {
13031            !child.is_named()
13032                && !child.is_extra()
13033                && !child.is_missing()
13034                && child.child_count() == 0
13035                && argument_keywords
13036                    .iter()
13037                    .any(|keyword| keyword.kind_id() == child.kind_id())
13038                && collapsed_c_parameter_name_position(&parameter_children, *index)
13039        })
13040        .map(|(_, child)| *child)
13041        .collect::<Vec<_>>();
13042    Some(
13043        argument_keywords
13044            .iter()
13045            .filter(|keyword| {
13046                parameter_keywords
13047                    .iter()
13048                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13049            })
13050            .count(),
13051    )
13052}
13053
13054/// The ABI15 collapsed head retains a parameter's type and pointer declarator
13055/// as direct children. Require that exact segment before accepting an
13056/// anonymous keyword as its name; a keyword after an `=` or attribute must not
13057/// borrow the same recovery.
13058fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13059    let segment_start = children[..keyword_index]
13060        .iter()
13061        .rposition(|child| matches!(child.kind(), "(" | ","))
13062        .map_or(0, |index| index + 1);
13063    let mut saw_type = false;
13064    let mut saw_pointer = false;
13065    for child in &children[segment_start..keyword_index] {
13066        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13067            saw_pointer = true;
13068            continue;
13069        }
13070        if saw_pointer {
13071            return false;
13072        }
13073        if !child.is_named() || child.is_extra() || child.is_missing() {
13074            return false;
13075        }
13076        if child.kind() == "type_qualifier" {
13077            continue;
13078        }
13079        if matches!(
13080            child.kind(),
13081            "class_specifier"
13082                | "dependent_type"
13083                | "enum_specifier"
13084                | "identifier"
13085                | "primitive_type"
13086                | "qualified_identifier"
13087                | "scoped_identifier"
13088                | "struct_specifier"
13089                | "template_type"
13090                | "type_identifier"
13091                | "union_specifier"
13092        ) {
13093            saw_type = true;
13094            continue;
13095        }
13096        return false;
13097    }
13098    saw_type && saw_pointer
13099}
13100
13101fn recovered_block_literal_arguments<'tree>(
13102    arguments: Node<'tree>,
13103) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13104    if arguments.kind() != "argument_list" {
13105        return None;
13106    }
13107    let mut raw_arguments = (0..arguments.child_count())
13108        .filter_map(|index| arguments.child(index))
13109        .filter(|child| child.is_named() && !child.is_extra());
13110    let raw = raw_arguments.next()?;
13111    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13112        return None;
13113    }
13114
13115    let left = raw.child_by_field_name("left")?;
13116    if left.is_missing() || left.start_byte() == left.end_byte() {
13117        return None;
13118    }
13119    let right = raw.child_by_field_name("right")?;
13120    if right.kind() != "compound_literal_expression"
13121        || right.is_missing()
13122        || right
13123            .child_by_field_name("type")
13124            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13125        || right
13126            .child_by_field_name("value")
13127            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13128    {
13129        return None;
13130    }
13131    let has_intervening_error = (0..raw.child_count())
13132        .filter_map(|index| raw.child(index))
13133        .any(|child| {
13134            child.kind() == "ERROR"
13135                && !child.is_missing()
13136                && child.start_byte() >= left.end_byte()
13137                && child.end_byte() <= right.start_byte()
13138        });
13139    has_intervening_error.then_some((raw, left, right))
13140}
13141
13142pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13143    match node.kind() {
13144        "new_expression" => node
13145            .child_by_field_name("type")
13146            .or_else(|| node.named_child(0)),
13147        "compound_literal_expression" => node.child_by_field_name("type"),
13148        "call_expression" => node.child_by_field_name("function"),
13149        _ => None,
13150    }
13151}
13152
13153/// The structured type named by a C-style cast expression.
13154///
13155/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
13156/// structured `type` field so resolution retains qualified and nested syntax
13157/// without reparsing source text.
13158pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13159    if node.kind() != "cast_expression" {
13160        return None;
13161    }
13162    let descriptor = node.child_by_field_name("type")?;
13163    if descriptor.kind() == "type_descriptor" {
13164        descriptor.child_by_field_name("type")
13165    } else {
13166        Some(descriptor)
13167    }
13168}
13169
13170/// The expression a field access reads its member from.
13171///
13172/// That is normally the node in the `argument` field. A preprocessor
13173/// conditional inside a call's argument list defeats the grammar: tree-sitter
13174/// ends the argument list at the `#if` line, then reads the text that follows
13175/// `#endif` as a field access on the whole call expression and leaves the real
13176/// receiver in an `ERROR` node between the `argument` child and the operator.
13177/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13178/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13179/// (#2982).
13180///
13181/// `.` and `->` read whatever ends immediately before them in either tree, so
13182/// answer from the operator's position instead of trusting the `argument`
13183/// field, and unwrap the single expression a displaced `ERROR` holds.
13184pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13185    debug_assert_eq!(field.kind(), "field_expression");
13186    let operator = field.child_by_field_name("operator")?;
13187    let mut cursor = field.walk();
13188    let receiver = field
13189        .named_children(&mut cursor)
13190        .filter(|child| child.end_byte() <= operator.start_byte())
13191        .last()?;
13192    if receiver.kind() != "ERROR" {
13193        return Some(receiver);
13194    }
13195    (receiver.named_child_count() == 1)
13196        .then(|| receiver.named_child(0))
13197        .flatten()
13198}
13199
13200pub fn field_initializer_constructs_target(
13201    node: Node<'_>,
13202    ctx: &ScanCtx<'_>,
13203    owner: &CodeUnit,
13204) -> bool {
13205    // A qualified name in a constructor initializer denotes a base
13206    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13207    // field-initializer grammar exposes the qualified name as one structured
13208    // `qualified_identifier`; resolve its owner through the same lexical type
13209    // machinery used for ordinary C++ type references before considering the
13210    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13211    // qualified non-constructor member, and an unresolved owner out of the
13212    // target constructor's inverse usage set.
13213    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13214        return qualified_base_initializer_constructs_target(node, ctx, owner);
13215    }
13216    let Some(name) = node
13217        .child_by_field_name("name")
13218        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13219        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13220    else {
13221        return false;
13222    };
13223    let field_name = node_text(name, ctx.source);
13224    ctx.visibility
13225        .visible_identifier_candidates(ctx.file, field_name)
13226        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13227        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13228}
13229
13230/// Whether a member initializer for `unit` runs a constructor of the type the
13231/// member declares.
13232///
13233/// Only a member that holds the type by value does. `Owner* p` initialized as
13234/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13235/// reference; neither runs an `Owner` constructor, even though both declare
13236/// `Owner` as the receiver type a member access through them resolves (#3286).
13237/// A member whose declaration the index cannot decode keeps the by-value
13238/// reading, which is what the declared-type match already assumed.
13239fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13240    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13241}
13242
13243fn qualified_base_initializer_constructs_target(
13244    node: Node<'_>,
13245    ctx: &ScanCtx<'_>,
13246    owner: &CodeUnit,
13247) -> bool {
13248    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13249        return false;
13250    };
13251    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13252        return false;
13253    };
13254    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13255        return false;
13256    };
13257    let resolves_target = |components: &[String]| {
13258        matches!(
13259            ctx.visibility.resolve_type_components_lexically_for_target(
13260                &ctx.analyzer,
13261                ctx.file,
13262                components,
13263                is_globally_qualified_cpp_name(qualified),
13264                &lexical_scope,
13265                owner,
13266            ),
13267            LexicalTypeResolution::Resolved { unit, .. }
13268                if same_visible_symbol(&unit, owner)
13269        )
13270    };
13271    if resolves_target(&components) {
13272        return true;
13273    }
13274
13275    // Some real-world code spells a base mem-initializer as
13276    // `Base::Base(args)`. In that structured path the final component repeats
13277    // the constructor name; resolve the preceding type path. The terminal
13278    // identity check prevents an arbitrary qualified member from taking this
13279    // route.
13280    components
13281        .last()
13282        .is_some_and(|terminal| terminal == owner.identifier())
13283        && resolves_target(&components[..components.len() - 1])
13284}
13285
13286fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13287    ctx.visibility
13288        .field_declared_type_fact(&ctx.analyzer, unit)
13289        .is_some_and(|fact| {
13290            ctx.visibility
13291                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13292                || ctx.visibility.resolves_to_type(
13293                    &ctx.analyzer,
13294                    ctx.file,
13295                    &normalize_field_type_text(&fact.type_text),
13296                    owner,
13297                )
13298        })
13299}
13300
13301pub fn field_declared_binding(
13302    analyzer: &CppGraphSource<'_>,
13303    visibility: &VisibilityIndex<'_>,
13304    visible_from: &ProjectFile,
13305    field: &CodeUnit,
13306) -> Option<CppScanBinding> {
13307    let (normalized, resolved, indirection) =
13308        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13309    let resolved = resolved
13310        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13311    Some(CppScanBinding::from_type_name(
13312        normalized,
13313        resolved,
13314        indirection,
13315    ))
13316}
13317
13318/// Whether a member-initializer `field(args)` runs the declared type's
13319/// constructor: only a member that holds the type by value does. `Owner* p`
13320/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13321/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13322/// whose declaration the index cannot decode keeps the by-value reading, the
13323/// same assumption the usage scan's declared-type match makes.
13324pub fn field_initializer_holds_subobject(
13325    analyzer: &CppGraphSource<'_>,
13326    visibility: &VisibilityIndex<'_>,
13327    field: &CodeUnit,
13328) -> bool {
13329    visibility
13330        .field_declared_type_fact(analyzer, field)
13331        .is_none_or(|fact| !fact.binds_indirectly)
13332}
13333
13334/// Resolve the receiver type minted for a named declarator on an anonymous C
13335/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13336/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13337/// no spelling that can name that class. Confirm the anonymous aggregate from
13338/// its parsed declaration, then use the owner's structured child relationship
13339/// to recover the one corresponding receiver type.
13340fn anonymous_aggregate_field_owner(
13341    analyzer: &CppGraphSource<'_>,
13342    visibility: &VisibilityIndex<'_>,
13343    visible_from: &ProjectFile,
13344    field: &CodeUnit,
13345) -> Option<CodeUnit> {
13346    let owner = type_owner_of(analyzer, field)?;
13347    if !owner.is_class() {
13348        return None;
13349    }
13350    let declaration = analyzer.get_source(field, false)?;
13351    let mut parser = Parser::new();
13352    parser
13353        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13354        .ok()?;
13355    let tree = parser.parse(&declaration, None)?;
13356    let mut stack = vec![tree.root_node()];
13357    while let Some(node) = stack.pop() {
13358        if matches!(node.kind(), "declaration" | "field_declaration")
13359            && let Some(type_node) = node
13360                .child_by_field_name("type")
13361                .or_else(|| first_type_child(node))
13362            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13363            && type_node.child_by_field_name("name").is_none()
13364            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13365        {
13366            let matches = visibility
13367                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13368                .into_iter()
13369                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13370                .collect::<Vec<_>>();
13371            return match matches.as_slice() {
13372                [child] => Some((*child).clone()),
13373                _ => None,
13374            };
13375        }
13376        let mut cursor = node.walk();
13377        stack.extend(node.named_children(&mut cursor));
13378    }
13379    None
13380}
13381
13382/// Resolve an anonymous C aggregate's generated owner from its declaration
13383/// range. Anonymous local structs and unions have no type name to enter into
13384/// the visibility index; declaration extraction gives them a structured class
13385/// identity keyed by the aggregate node's exact CST range instead. Matching
13386/// that range keeps nested aggregates and unrelated generated owners out of
13387/// the result without inspecting source text.
13388pub fn anonymous_aggregate_owner(
13389    analyzer: &CppGraphSource<'_>,
13390    file: &ProjectFile,
13391    node: Node<'_>,
13392) -> Option<CodeUnit> {
13393    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13394        || node.child_by_field_name("name").is_some()
13395    {
13396        return None;
13397    }
13398    let mut candidates = analyzer
13399        .declarations(file)
13400        .into_iter()
13401        .filter(|candidate| {
13402            candidate.is_class()
13403                && analyzer.ranges(candidate).into_iter().any(|range| {
13404                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13405                })
13406        })
13407        .collect::<Vec<_>>();
13408    candidates.sort_by_key(|candidate| candidate.fq_name());
13409    candidates.dedup();
13410    match candidates.as_slice() {
13411        [candidate] => Some(candidate.clone()),
13412        _ => None,
13413    }
13414}
13415
13416/// The one logical type the candidates name, or why they do not name one.
13417fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13418    let Some(first) = candidates.first() else {
13419        return Err(TypeCandidateFailure::Unresolvable);
13420    };
13421    if candidates
13422        .iter()
13423        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13424    {
13425        Ok((*first).clone())
13426    } else {
13427        Err(TypeCandidateFailure::Ambiguous)
13428    }
13429}
13430
13431fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13432    logical_type_candidate(candidates).ok()
13433}
13434
13435fn unique_type_candidate_preserving_alias(
13436    analyzer: &CppGraphSource<'_>,
13437    file: &ProjectFile,
13438    candidates: &[&CodeUnit],
13439) -> Option<CodeUnit> {
13440    let first = *candidates.first()?;
13441    if declared_type_alias(analyzer, first) {
13442        return candidates
13443            .iter()
13444            .all(|candidate| {
13445                declared_type_alias(analyzer, candidate)
13446                    && candidate.kind() == first.kind()
13447                    && candidate.fq_name() == first.fq_name()
13448                    && candidate.source() == first.source()
13449            })
13450            .then(|| first.clone());
13451    }
13452    if analyzer.reference_uses_c_semantics(file)
13453        && first.is_class()
13454        && indexed_c_tag_kind(analyzer, first).is_some()
13455    {
13456        let mut full_source = None;
13457        let mut tag_kind = None;
13458        for candidate in candidates.iter().copied() {
13459            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13460            if tag_kind
13461                .replace(candidate_tag_kind)
13462                .is_some_and(|existing| existing != candidate_tag_kind)
13463            {
13464                return None;
13465            }
13466            if cpp_class_declaration_strength(analyzer, candidate)
13467                == CppClassDeclarationStrength::Full
13468                && full_source
13469                    .replace(candidate.source())
13470                    .is_some_and(|existing| existing != candidate.source())
13471            {
13472                return None;
13473            }
13474        }
13475    }
13476    candidates
13477        .iter()
13478        .all(|candidate| {
13479            !declared_type_alias(analyzer, candidate)
13480                && candidate.kind() == first.kind()
13481                && candidate.fq_name() == first.fq_name()
13482        })
13483        .then(|| first.clone())
13484}
13485
13486pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13487    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13488        analyzer.type_alias_provider(),
13489        unit,
13490    )
13491}
13492
13493pub fn field_declared_type_binding(
13494    analyzer: &CppGraphSource<'_>,
13495    visibility: &VisibilityIndex<'_>,
13496    visible_from: &ProjectFile,
13497    field: &CodeUnit,
13498) -> Option<(String, Option<CodeUnit>, i32)> {
13499    let fact = visibility.field_declared_type_fact(analyzer, field)?;
13500    let normalized = normalize_field_type_text(&fact.type_text);
13501    let primary = visibility.resolve_unique_canonical_type_for_declaration(
13502        analyzer,
13503        visible_from,
13504        field,
13505        &normalized,
13506    );
13507    let resolved = match (primary, fact.template_arguments.as_deref()) {
13508        (Some(primary), Some(arguments)) => visibility
13509            .resolve_template_arguments(visible_from, primary, arguments)
13510            .ok(),
13511        (resolved, None) => resolved,
13512        (None, Some(_)) => None,
13513    };
13514    Some((normalized, resolved, fact.indirection))
13515}
13516
13517fn decode_field_declared_type_fact(
13518    analyzer: &CppGraphSource<'_>,
13519    field: &CodeUnit,
13520) -> Option<DeclaredFieldTypeFact> {
13521    if let Some(facts) = analyzer.declaration_source_properties(field)
13522        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13523        && facts
13524            .iter()
13525            .all(|fact| fact.field_type.as_ref() == Some(first))
13526    {
13527        return Some(first.clone());
13528    }
13529    let Some(declaration) = analyzer.get_source(field, false) else {
13530        return decode_indexed_field_declared_type_fact(analyzer, field);
13531    };
13532    let mut parser = Parser::new();
13533    parser
13534        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13535        .ok()?;
13536    // A field's indexed source is stored without its surrounding class body.
13537    // Give tree-sitter that grammatical context before checking recovery-only
13538    // field shapes such as `PyObject_HEAD Imaging image;`.
13539    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13540    let contextual_tree = parser.parse(&contextual_declaration, None)?;
13541    let mut stack = vec![contextual_tree.root_node()];
13542    while let Some(node) = stack.pop() {
13543        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13544            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13545        {
13546            return Some(DeclaredFieldTypeFact {
13547                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13548                indirection: recovered.pointer_depth(),
13549                binds_indirectly: recovered.pointer_depth() > 0,
13550                template_arguments: None,
13551            });
13552        }
13553        if let Some(recovered) =
13554            recovered_function_like_field_declarator(node, &contextual_declaration)
13555            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13556        {
13557            let type_node = node
13558                .child_by_field_name("type")
13559                .or_else(|| first_type_child(node))?;
13560            return Some(DeclaredFieldTypeFact {
13561                type_text: node_text(type_node, &contextual_declaration).to_string(),
13562                indirection: recovered.pointer_depth(),
13563                // A function-like declarator names a callable member, never a
13564                // subobject of the declared return type.
13565                binds_indirectly: true,
13566                template_arguments: cpp_template_reference_arguments(
13567                    type_node,
13568                    &contextual_declaration,
13569                ),
13570            });
13571        }
13572        if let Some(fact) =
13573            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13574        {
13575            return Some(fact);
13576        }
13577        let mut cursor = node.walk();
13578        stack.extend(node.named_children(&mut cursor));
13579    }
13580    let tree = parser.parse(&declaration, None)?;
13581    let mut stack = vec![tree.root_node()];
13582    while let Some(node) = stack.pop() {
13583        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13584        {
13585            return Some(fact);
13586        }
13587        let mut cursor = node.walk();
13588        stack.extend(node.named_children(&mut cursor));
13589    }
13590    None
13591}
13592
13593/// Decode a field whose generated owner prevents the ordinary source lookup
13594/// from selecting a standalone declaration. The indexed ranges still point
13595/// into the physical syntax tree, so recover the enclosing declaration from
13596/// that structure and apply the same declarator decoder to it.
13597fn decode_indexed_field_declared_type_fact(
13598    analyzer: &CppGraphSource<'_>,
13599    field: &CodeUnit,
13600) -> Option<DeclaredFieldTypeFact> {
13601    let cpp = analyzer.cpp?;
13602    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13603    let source = prepared.source();
13604    let root = prepared.tree().root_node();
13605    for range in analyzer.ranges(field) {
13606        let end = range.start_byte.saturating_add(1).min(source.len());
13607        let mut current = root.descendant_for_byte_range(range.start_byte, end);
13608        while let Some(node) = current {
13609            if matches!(node.kind(), "declaration" | "field_declaration")
13610                && let Some(fact) =
13611                    decode_declared_field_type_node(node, field.identifier(), source)
13612            {
13613                return Some(fact);
13614            }
13615            current = node.parent();
13616        }
13617    }
13618    None
13619}
13620
13621fn decode_declared_field_type_node(
13622    node: Node<'_>,
13623    field_name: &str,
13624    source: &str,
13625) -> Option<DeclaredFieldTypeFact> {
13626    if !matches!(node.kind(), "declaration" | "field_declaration") {
13627        return None;
13628    }
13629    let type_node = node
13630        .child_by_field_name("type")
13631        .or_else(|| first_type_child(node))?;
13632    cpp_declared_field_type_fact(node, type_node, field_name, source)
13633}
13634
13635pub(crate) fn cpp_declared_field_type_fact(
13636    node: Node<'_>,
13637    type_node: Node<'_>,
13638    field_name: &str,
13639    source: &str,
13640) -> Option<DeclaredFieldTypeFact> {
13641    let binding = declared_name_binding(node, type_node, field_name, source)?;
13642    let declared_type = if matches!(
13643        type_node.kind(),
13644        "class_specifier" | "struct_specifier" | "union_specifier"
13645    ) {
13646        type_node.child_by_field_name("name")
13647    } else {
13648        Some(type_node)
13649    };
13650    Some(DeclaredFieldTypeFact {
13651        type_text: declared_type.map_or_else(
13652            || field_name.to_string(),
13653            |declared_type| node_text(declared_type, source).to_string(),
13654        ),
13655        indirection: binding.pointer_depth,
13656        binds_indirectly: binding.indirect,
13657        template_arguments: declared_type
13658            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13659    })
13660}
13661
13662/// Text of the type that a C or C++ alias declaration names, read from the
13663/// `type_definition` or `alias_declaration` node's `type` field.
13664///
13665/// The declaration text is never scanned. A function-pointer typedef
13666/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
13667/// so no prefix or suffix of the spelling isolates the target.
13668///
13669/// An alias whose declarator is a function declarator names a function type:
13670/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
13671/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
13672/// so such an alias has no canonical target. Its `type` field holds the return
13673/// type `R`, which is a different type from the alias, so this returns `None`
13674/// rather than that return type.
13675pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13676    let mut parser = Parser::new();
13677    parser
13678        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13679        .ok()?;
13680    let tree = parser.parse(declaration, None)?;
13681    let mut stack = vec![tree.root_node()];
13682    while let Some(node) = stack.pop() {
13683        let type_node = match node.kind() {
13684            "type_definition" => {
13685                let mut cursor = node.walk();
13686                if node
13687                    .children_by_field_name("declarator", &mut cursor)
13688                    .any(declarator_names_function_type)
13689                {
13690                    return None;
13691                }
13692                node.child_by_field_name("type")?
13693            }
13694            "alias_declaration" => {
13695                let type_node = node.child_by_field_name("type")?;
13696                if type_node
13697                    .child_by_field_name("declarator")
13698                    .is_some_and(declarator_names_function_type)
13699                {
13700                    return None;
13701                }
13702                type_node
13703            }
13704            _ => {
13705                let mut cursor = node.walk();
13706                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13707                stack.extend(children.into_iter().rev());
13708                continue;
13709            }
13710        };
13711        return Some(node_text(type_node, declaration).to_string());
13712    }
13713    None
13714}
13715
13716/// True when an alias declarator names a function type.
13717///
13718/// The declarator chain is walked through the `declarator` field, so the
13719/// parameter list -- a sibling field -- is never entered and a parameter's own
13720/// function declarator cannot be mistaken for the alias's.
13721pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13722    let mut current = Some(declarator);
13723    while let Some(node) = current {
13724        match node.kind() {
13725            "function_declarator" | "abstract_function_declarator" => return true,
13726            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13727                current = node.named_child(0);
13728            }
13729            _ => current = node.child_by_field_name("declarator"),
13730        }
13731    }
13732    false
13733}
13734
13735fn decode_structured_alias_target(
13736    analyzer: &CppGraphSource<'_>,
13737    unit: &CodeUnit,
13738) -> Option<StructuredAliasTarget> {
13739    let facts = analyzer.declaration_source_properties(unit)?;
13740    let first = facts.first()?.alias_target.as_ref()?;
13741    facts
13742        .iter()
13743        .all(|fact| fact.alias_target.as_ref() == Some(first))
13744        .then(|| first.clone())
13745}
13746
13747pub(crate) fn structured_alias_type_target(
13748    mut type_node: Node<'_>,
13749    source: &str,
13750) -> Option<StructuredAliasTarget> {
13751    while type_node.kind() == "type_descriptor" {
13752        type_node = type_node.child_by_field_name("type")?;
13753    }
13754    if type_node.kind() == "primitive_type" {
13755        return Some(StructuredAliasTarget::Builtin);
13756    }
13757    if matches!(
13758        type_node.kind(),
13759        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13760    ) {
13761        type_node = type_node.child_by_field_name("name")?;
13762    }
13763    let global = type_node.child_by_field_name("scope").is_none()
13764        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13765    let mut components = Vec::new();
13766    append_structured_type_components(type_node, source, &mut components)?;
13767    let arguments = cpp_template_reference_arguments(type_node, source);
13768    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13769        components,
13770        global,
13771        arguments,
13772    })
13773}
13774
13775fn append_structured_type_components(
13776    node: Node<'_>,
13777    source: &str,
13778    out: &mut Vec<String>,
13779) -> Option<()> {
13780    match node.kind() {
13781        "identifier" | "namespace_identifier" | "type_identifier" => {
13782            out.push(node_text(node, source).to_string());
13783            Some(())
13784        }
13785        "template_type" | "template_function" => {
13786            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13787        }
13788        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13789            let (scope, name) = qualified_name_parts(node)?;
13790            if let Some(scope) = scope {
13791                append_structured_type_components(scope, source, out)?;
13792            }
13793            append_structured_type_components(name, source, out)
13794        }
13795        _ => None,
13796    }
13797}
13798
13799/// How the declarator that names a declared entity binds the declaration's
13800/// type.
13801pub(crate) struct DeclaredNameBinding {
13802    /// Pointer declarators between the type and the name.
13803    pub pointer_depth: i32,
13804    /// The name binds the declared type through a pointer or a reference, so
13805    /// the declared entity holds no subobject of that type. An array of the
13806    /// type does hold subobjects, so it is not indirect.
13807    pub indirect: bool,
13808}
13809
13810pub(crate) fn declared_name_binding(
13811    declaration: Node<'_>,
13812    type_node: Node<'_>,
13813    field_name: &str,
13814    source: &str,
13815) -> Option<DeclaredNameBinding> {
13816    let mut stack = Vec::new();
13817    let mut cursor = declaration.walk();
13818    stack.extend(
13819        declaration
13820            .named_children(&mut cursor)
13821            .filter(|child| !same_node(*child, type_node)),
13822    );
13823    while let Some(node) = stack.pop() {
13824        if matches!(node.kind(), "identifier" | "field_identifier")
13825            && node_text(node, source) == field_name
13826        {
13827            let mut binding = DeclaredNameBinding {
13828                pointer_depth: 0,
13829                indirect: false,
13830            };
13831            let mut current = node.parent();
13832            while let Some(parent) = current {
13833                if same_node(parent, declaration) {
13834                    return Some(binding);
13835                }
13836                match parent.kind() {
13837                    "pointer_declarator" => {
13838                        binding.pointer_depth += 1;
13839                        binding.indirect = true;
13840                    }
13841                    "reference_declarator" => binding.indirect = true,
13842                    _ => {}
13843                }
13844                current = parent.parent();
13845            }
13846            return None;
13847        }
13848        let mut cursor = node.walk();
13849        stack.extend(node.named_children(&mut cursor));
13850    }
13851    None
13852}
13853
13854fn normalize_field_type_text(type_text: &str) -> String {
13855    const FIELD_SPECIFIERS: [&str; 8] = [
13856        "extern ",
13857        "static ",
13858        "mutable ",
13859        "constexpr ",
13860        "constinit ",
13861        "inline ",
13862        "volatile ",
13863        "const ",
13864    ];
13865
13866    let mut normalized = normalize_type_text(type_text);
13867    loop {
13868        let Some(stripped) = FIELD_SPECIFIERS
13869            .iter()
13870            .find_map(|specifier| normalized.strip_prefix(specifier))
13871        else {
13872            return normalized;
13873        };
13874        normalized = normalize_type_text(stripped);
13875    }
13876}
13877
13878pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13879    let Some(type_node) = node.child_by_field_name("type") else {
13880        return false;
13881    };
13882    ctx.visibility.resolves_to_type(
13883        &ctx.analyzer,
13884        ctx.file,
13885        node_text(type_node, ctx.source),
13886        owner,
13887    )
13888}
13889
13890pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13891    !ctx.analyzer
13892        .declarations(ctx.file)
13893        .into_iter()
13894        .filter(|unit| unit.is_function())
13895        .any(|unit| {
13896            ctx.analyzer.ranges(&unit).iter().any(|range| {
13897                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13898            })
13899        })
13900}
13901
13902/// How a `T var ...;` declaration initializes its object.
13903pub enum DeclarationConstructorInitializer<'tree> {
13904    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13905    /// the declaration hands the constructor.
13906    Arguments(Node<'tree>),
13907    /// Copy initialization from one expression, `T var = expr`, which supplies a
13908    /// single constructor argument without spelling an argument list.
13909    Expression(Node<'tree>),
13910    /// `T var;`, which names no constructor argument at all.
13911    Empty,
13912}
13913
13914pub fn declaration_constructor_initializer(
13915    node: Node<'_>,
13916) -> DeclarationConstructorInitializer<'_> {
13917    let mut cursor = node.walk();
13918    for child in node.named_children(&mut cursor) {
13919        if child.kind() == "init_declarator" {
13920            let Some(value) = child
13921                .child_by_field_name("value")
13922                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13923                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13924            else {
13925                return DeclarationConstructorInitializer::Empty;
13926            };
13927            return match value.kind() {
13928                "argument_list" | "initializer_list" => {
13929                    DeclarationConstructorInitializer::Arguments(value)
13930                }
13931                "compound_literal_expression" => call_arguments_node(value)
13932                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13933                        DeclarationConstructorInitializer::Arguments(arguments)
13934                    }),
13935                _ => DeclarationConstructorInitializer::Expression(value),
13936            };
13937        }
13938        if let Some(declarator) = declaration_declarator(node, child) {
13939            return declarator_parameters(declarator)
13940                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13941                    DeclarationConstructorInitializer::Arguments(parameters)
13942                });
13943        }
13944    }
13945    DeclarationConstructorInitializer::Empty
13946}
13947
13948pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13949    match declaration_constructor_initializer(node) {
13950        DeclarationConstructorInitializer::Arguments(arguments) => {
13951            argument_children(arguments).count()
13952        }
13953        DeclarationConstructorInitializer::Expression(_) => 1,
13954        DeclarationConstructorInitializer::Empty => 0,
13955    }
13956}
13957
13958/// The parameter list of the innermost declarator, which is where a
13959/// `T var(args)` declaration parsed as a function declarator keeps the
13960/// constructor arguments.
13961fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13962    let mut current = node;
13963    loop {
13964        if let Some(parameters) = current.child_by_field_name("parameters") {
13965            return Some(parameters);
13966        }
13967        current = current.child_by_field_name("declarator")?;
13968    }
13969}
13970
13971fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13972    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13973}
13974
13975fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13976    if node.kind() == "identifier" {
13977        return true;
13978    }
13979    if node.kind() == "parenthesized_expression" {
13980        return false;
13981    }
13982    if node.kind() == "call_expression" {
13983        return node
13984            .child_by_field_name("function")
13985            .is_some_and(|function| function.kind() == "identifier");
13986    }
13987    let mut stack = vec![node];
13988    while let Some(descendant) = stack.pop() {
13989        if descendant != node && descendant.kind() == "parenthesized_expression" {
13990            continue;
13991        }
13992        if descendant.kind() == "identifier" {
13993            return true;
13994        }
13995        if descendant.kind() == "call_expression" {
13996            if descendant
13997                .child_by_field_name("function")
13998                .is_some_and(|function| function.kind() == "identifier")
13999            {
14000                return true;
14001            }
14002            continue;
14003        }
14004        push_named_children_reversed(descendant, &mut stack);
14005    }
14006    false
14007}
14008
14009fn macro_expansion_shape_is_safe(
14010    node: Node<'_>,
14011    source: &str,
14012    parameters: &[String],
14013    environment: &MacroEnvironment,
14014) -> bool {
14015    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14016        return true;
14017    }
14018    if node.kind() == "call_expression" {
14019        let Some(function) = node.child_by_field_name("function") else {
14020            return true;
14021        };
14022        if function.kind() != "identifier" {
14023            return true;
14024        }
14025        let function_name = node_text(function, source);
14026        if parameters
14027            .iter()
14028            .any(|parameter| parameter == function_name)
14029        {
14030            return false;
14031        }
14032        if !environment.may_bind(function_name) {
14033            return true;
14034        }
14035        let Some(arguments) = node.child_by_field_name("arguments") else {
14036            return false;
14037        };
14038        return argument_children(arguments).all(|argument| {
14039            if argument.kind() == "identifier"
14040                && parameters
14041                    .iter()
14042                    .any(|parameter| parameter == node_text(argument, source))
14043            {
14044                return false;
14045            }
14046            macro_expansion_shape_is_safe(argument, source, parameters, environment)
14047        });
14048    }
14049    let mut stack = vec![node];
14050    while let Some(descendant) = stack.pop() {
14051        if descendant != node {
14052            if descendant.kind() == "parenthesized_expression" {
14053                continue;
14054            }
14055            if descendant.kind() == "call_expression" {
14056                let expands = descendant
14057                    .child_by_field_name("function")
14058                    .filter(|function| function.kind() == "identifier")
14059                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
14060                if expands {
14061                    return false;
14062                }
14063                continue;
14064            }
14065        }
14066        if descendant.kind() == "identifier" {
14067            let identifier = node_text(descendant, source);
14068            if parameters.iter().any(|parameter| parameter == identifier)
14069                || environment.may_bind(identifier)
14070            {
14071                return false;
14072            }
14073        }
14074        push_named_children_reversed(descendant, &mut stack);
14075    }
14076    true
14077}
14078
14079fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14080    let source = prepared.source();
14081    let mut facts = Vec::new();
14082    let mut nodes = vec![prepared.tree().root_node()];
14083    while let Some(node) = nodes.pop() {
14084        if node.kind() == "preproc_include" {
14085            let Some(path) = node
14086                .child_by_field_name("path")
14087                .and_then(|path| crate::imports::include_path_literal_value(path, source))
14088                .map(str::to_owned)
14089            else {
14090                continue;
14091            };
14092            facts.push(StructuredIncludeFact {
14093                start_byte: node.start_byte(),
14094                end_byte: node.end_byte(),
14095                path,
14096            });
14097            continue;
14098        }
14099        push_named_children_reversed(node, &mut nodes);
14100    }
14101    Arc::from(facts.into_boxed_slice())
14102}
14103
14104fn has_unresolved_include_visible_before_in_prepared(
14105    file: &ProjectFile,
14106    prepared: &PreparedSyntaxTree,
14107    include_targets: &IncludeTargetIndex,
14108    facts: &[StructuredIncludeFact],
14109    before_byte: usize,
14110) -> bool {
14111    let guards = OnceCell::new();
14112    let reference = CallableReferenceContext {
14113        file,
14114        position: Some(CallableReferencePosition {
14115            prepared,
14116            byte: before_byte,
14117            guards: &guards,
14118        }),
14119    };
14120    let root = prepared.tree().root_node();
14121    facts
14122        .iter()
14123        .filter(|fact| fact.end_byte <= before_byte)
14124        .any(|fact| {
14125            let node = root
14126                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14127                .expect("structured include fact range must be in prepared tree");
14128            assert_eq!(
14129                node.kind(),
14130                "preproc_include",
14131                "structured include fact range must identify its include node"
14132            );
14133            callable_preprocessor_context_is_visible_for_reference(
14134                node,
14135                prepared.source(),
14136                &reference,
14137            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14138        })
14139}
14140
14141fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14142    let descendant = node;
14143    while let Some(parent) = node.parent() {
14144        if is_cpp_preprocessor_conditional(parent)
14145            && !is_file_covering_include_guard(parent, source)
14146            && preprocessor_conditional_contains_descendant(parent, descendant)
14147        {
14148            return true;
14149        }
14150        node = parent;
14151    }
14152    false
14153}
14154
14155/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
14156///
14157/// The descendant this walks up from is the one
14158/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
14159/// innermost node at the event's first byte, not the event node itself --
14160/// because containment compares that descendant's end against the recovered
14161/// conditional boundary, and the two nodes end in different places. An event
14162/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
14163/// predicate is what has always decided whether an event is conditional at
14164/// all, and answering it first also skips the walk for the ordinary
14165/// unconditional event.
14166fn owning_preprocessor_conditionals(
14167    root: Node<'_>,
14168    event: Node<'_>,
14169    source: &str,
14170) -> OwningPreprocessorConditionals {
14171    if !has_preprocessor_conditional_ancestor(event, source) {
14172        return OwningPreprocessorConditionals::default();
14173    }
14174    let start = event.start_byte();
14175    let descendant = root
14176        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14177        .expect("a byte inside the parsed tree names a descendant");
14178    let mut owners = Vec::new();
14179    let mut current = descendant.parent();
14180    while let Some(conditional) = current {
14181        if is_cpp_preprocessor_conditional(conditional)
14182            && !is_file_covering_include_guard(conditional, source)
14183            && preprocessor_conditional_contains_descendant(conditional, descendant)
14184        {
14185            owners.push(conditional.start_byte());
14186        }
14187        current = conditional.parent();
14188    }
14189    owners.into_boxed_slice()
14190}
14191
14192fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14193    node.parent()
14194        .filter(|parent| parent.kind() == "translation_unit")
14195        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14196        && is_canonical_include_guard(node, source)
14197}
14198
14199fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14200    if node.kind() != "preproc_ifdef"
14201        || node
14202            .child(0)
14203            .is_none_or(|directive| directive.kind() != "#ifndef")
14204        || node.child_by_field_name("alternative").is_some()
14205    {
14206        return false;
14207    }
14208    let Some(guard_name) = node.child_by_field_name("name") else {
14209        return false;
14210    };
14211    let mut cursor = node.walk();
14212    node.named_children(&mut cursor)
14213        .find(|child| *child != guard_name && child.kind() != "comment")
14214        .filter(|child| child.kind() == "preproc_def")
14215        .and_then(|definition| definition.child_by_field_name("name"))
14216        .is_some_and(|defined_name| {
14217            node_text(defined_name, source) == node_text(guard_name, source)
14218        })
14219}
14220
14221fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14222    let mut guard = None;
14223    for child in named_children_iter(root) {
14224        if child.kind() == "comment" || is_pragma_once(child, source) {
14225            continue;
14226        }
14227        if guard.is_none() && is_canonical_include_guard(child, source) {
14228            guard = Some(child);
14229        } else {
14230            return None;
14231        }
14232    }
14233    guard
14234        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14235        .map(|name| node_text(name, source).to_string())
14236}
14237
14238fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14239    if (0..root.named_child_count())
14240        .filter_map(|index| root.named_child(index))
14241        .any(|child| is_pragma_once(child, source))
14242    {
14243        return MacroIncludeProtection::PragmaOnce;
14244    }
14245    top_level_canonical_include_guard_name(root, source)
14246        .map(MacroIncludeProtection::MacroGuard)
14247        .unwrap_or(MacroIncludeProtection::None)
14248}
14249
14250fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14251    node.kind() == "preproc_call"
14252        && node
14253            .child_by_field_name("directive")
14254            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14255        && node
14256            .child_by_field_name("argument")
14257            .is_some_and(|argument| node_text(argument, source).trim() == "once")
14258}
14259
14260fn parse_preproc_identifier(argument: &str) -> Option<String> {
14261    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14262    let mut parser = Parser::new();
14263    parser
14264        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14265        .ok()?;
14266    let tree = parser.parse(&sentinel, None)?;
14267    if tree.root_node().has_error() {
14268        return None;
14269    }
14270    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14271    let identifier = statement.named_child(0)?;
14272    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14273        .then(|| node_text(identifier, &sentinel).to_string())
14274}
14275
14276pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14277    match node.kind() {
14278        "identifier" | "field_identifier" => {
14279            let name = node_text(node, source).trim();
14280            (!name.is_empty()).then(|| name.to_string())
14281        }
14282        "abstract_array_declarator"
14283        | "abstract_function_declarator"
14284        | "abstract_parenthesized_declarator"
14285        | "abstract_pointer_declarator"
14286        | "abstract_reference_declarator" => None,
14287        "function_declarator" => node
14288            .child_by_field_name("declarator")
14289            .or_else(|| node.child_by_field_name("name"))
14290            .and_then(|child| extract_variable_name(child, source)),
14291        _ => node
14292            .child_by_field_name("declarator")
14293            .or_else(|| node.child_by_field_name("name"))
14294            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14295            .and_then(|child| extract_variable_name(child, source)),
14296    }
14297}
14298
14299/// Whether `file` is proven to use plain-C source semantics.
14300///
14301/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
14302/// compilation dialect on their own, so only an exact `.c` source extension is
14303/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
14304/// identifiers.
14305///
14306/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
14307/// which extraction reads too (a `.c` file is extracted with C tag scope), so
14308/// the doctrine has exactly one definition.
14309pub fn is_c_source_file(file: &ProjectFile) -> bool {
14310    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14311}
14312
14313/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
14314/// identifier even though `T` may denote a typedef.
14315///
14316/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
14317/// semantic information. Keep this helper structural and narrow; callers must
14318/// still prove a visible type and reject an active ordinary-namespace shadow.
14319pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14320    if !is_c_source_file(file) || node.kind() != "identifier" {
14321        return false;
14322    }
14323    let mut operand = node;
14324    while let Some(parent) = operand.parent().filter(|parent| {
14325        parent.kind() == "parenthesized_expression"
14326            && parent.named_child_count() == 1
14327            && parent.named_child(0) == Some(operand)
14328    }) {
14329        operand = parent;
14330    }
14331    operand.parent().is_some_and(|parent| {
14332        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14333    })
14334}
14335
14336/// Return the type and member leaves of a C `offsetof` member designator.
14337///
14338/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
14339/// must be interpreted through their named fields.  In particular, do not
14340/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
14341/// name a member of an unrelated aggregate, including a field promoted from
14342/// an anonymous union.  A missing or unsupported operand is deliberately
14343/// rejected so callers can keep the reference unresolved.
14344pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14345    if !is_c_offsetof_member_node(node) {
14346        return None;
14347    }
14348    let expression = node.parent()?;
14349    if expression.has_error() {
14350        return None;
14351    }
14352    let closing = expression.child(expression.child_count().saturating_sub(1))?;
14353    if closing.kind() != ")" || closing.is_missing() {
14354        return None;
14355    }
14356    let type_descriptor = expression.child_by_field_name("type")?;
14357    if type_descriptor.kind() != "type_descriptor"
14358        || type_descriptor.is_missing()
14359        || type_descriptor.has_error()
14360    {
14361        return None;
14362    }
14363    let type_specifier = type_descriptor.child_by_field_name("type")?;
14364    if type_specifier.is_missing() || type_specifier.has_error() {
14365        return None;
14366    }
14367    let type_reference = match type_specifier.kind() {
14368        "class_specifier" | "struct_specifier" | "union_specifier" => {
14369            type_specifier.child_by_field_name("name")?
14370        }
14371        _ => type_specifier,
14372    };
14373    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14374}
14375
14376/// Whether `node` is the member leaf of an `offsetof_expression`, including a
14377/// malformed type operand.  Callers use this guard to prevent the ordinary
14378/// field-name heuristics from guessing an owner after structured resolution
14379/// has failed.
14380pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14381    node.kind() == "field_identifier"
14382        && node.parent().is_some_and(|parent| {
14383            parent.kind() == "offsetof_expression"
14384                && parent.child_by_field_name("member") == Some(node)
14385        })
14386}
14387
14388/// Admit an ABI 15 template-function shape to type resolution only through
14389/// the structured argument list of a type descriptor. This is syntax evidence;
14390/// callers must still resolve a visible type binding before proving identity.
14391pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14392    if node.kind() != "template_function" {
14393        return false;
14394    }
14395    let Some(arguments) = node
14396        .parent()
14397        .filter(|parent| parent.kind() == "template_argument_list")
14398    else {
14399        return false;
14400    };
14401    let Some(owner) = arguments.parent().filter(|parent| {
14402        parent.kind() == "template_type"
14403            && parent.child_by_field_name("arguments") == Some(arguments)
14404    }) else {
14405        return false;
14406    };
14407
14408    let mut current = owner;
14409    while let Some(parent) = current.parent() {
14410        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14411        {
14412            return true;
14413        }
14414        if parent.kind() == "qualified_identifier"
14415            && parent.child_by_field_name("name") == Some(current)
14416        {
14417            current = parent;
14418            continue;
14419        }
14420        return false;
14421    }
14422    false
14423}
14424
14425/// Whether `node` is a template argument name that tree-sitter spelled with
14426/// type syntax.
14427///
14428/// The grammar cannot tell a type argument from a non-type (value) argument, so
14429/// it gives both the same shape:
14430/// `template_argument_list -> type_descriptor -> type_identifier`. In
14431/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14432/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14433/// member used as a real type argument.
14434///
14435/// This helper reports only the syntactic position. A caller must still prove
14436/// which namespace explains the spelling: forward navigation asks the type
14437/// namespace first and reads the leaf as a value only when no type explains it,
14438/// and the inverse field scan admits the leaf only when no visible type does
14439/// (#2556).
14440pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14441    if node.kind() != "type_identifier" {
14442        return false;
14443    }
14444    let Some(descriptor) = node
14445        .parent()
14446        .filter(|parent| parent.kind() == "type_descriptor")
14447    else {
14448        return false;
14449    };
14450    if descriptor.child_by_field_name("type") != Some(node) {
14451        return false;
14452    }
14453    let Some(arguments) = descriptor
14454        .parent()
14455        .filter(|parent| parent.kind() == "template_argument_list")
14456    else {
14457        return false;
14458    };
14459    arguments.parent().is_some_and(|owner| {
14460        matches!(
14461            owner.kind(),
14462            "template_type" | "template_function" | "template_method"
14463        ) && owner.child_by_field_name("arguments") == Some(arguments)
14464    })
14465}
14466
14467/// Whether a reference written in `file` reads C++ source with C semantics.
14468///
14469/// [`is_c_source_file`] answers the half a path settles on its own. The other
14470/// half is a header, which has no dialect of its own: it is read as C exactly
14471/// when every workspace translation unit that provably compiles it compiles it
14472/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14473///
14474/// This is the gate for anything that is really about the compilation
14475/// language of the code being read -- which reading of an included header's
14476/// declarations is in scope, whether `this` is an ordinary identifier. It is
14477/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14478/// those keep calling [`is_c_source_file`].
14479pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14480    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14481}
14482
14483pub fn is_declarator_node(node: Node<'_>) -> bool {
14484    matches!(
14485        node.kind(),
14486        "identifier"
14487            | "field_identifier"
14488            | "qualified_identifier"
14489            | "scoped_identifier"
14490            | "pointer_declarator"
14491            | "reference_declarator"
14492            | "array_declarator"
14493            | "parenthesized_declarator"
14494            | "function_declarator"
14495    )
14496}
14497
14498/// Returns the declarator represented by one direct child of a declaration.
14499///
14500/// Tree-sitter exposes the first declarator through the `declarator` field,
14501/// but subsequent comma-separated declarators are unfielded direct children:
14502/// `int first, second;` therefore has an `identifier` child for `first` and a
14503/// second, otherwise identical, `identifier` child for `second`. Consumers
14504/// must inspect both shapes or a later declarator can be mistaken for a use of
14505/// an unrelated indexed symbol.
14506pub fn declaration_declarator<'tree>(
14507    declaration: Node<'tree>,
14508    child: Node<'tree>,
14509) -> Option<Node<'tree>> {
14510    if !matches!(
14511        declaration.kind(),
14512        "declaration"
14513            | "field_declaration"
14514            | "parameter_declaration"
14515            | "optional_parameter_declaration"
14516            | "function_definition"
14517            | "type_definition"
14518            | "alias_declaration"
14519            | "template_instantiation"
14520    ) {
14521        return None;
14522    }
14523    if declaration
14524        .child_by_field_name("type")
14525        .is_some_and(|type_node| same_node(type_node, child))
14526    {
14527        return None;
14528    }
14529    if child.kind() == "init_declarator" {
14530        return child.child_by_field_name("declarator");
14531    }
14532    let field = field_name_in_parent(declaration, child);
14533    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14534        || (declaration.kind() == "type_definition"
14535            && child.kind() == "type_identifier"
14536            && matches!(field, Some("declarator") | None))
14537    {
14538        Some(child)
14539    } else {
14540        None
14541    }
14542}
14543
14544/// Read the name, declarator, and optional type of a typed local binding.
14545pub fn cpp_binding_parts<'tree>(
14546    node: Node<'tree>,
14547    source: &str,
14548) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14549    let declarator = node.child_by_field_name("declarator")?;
14550    let name = extract_variable_name(declarator, source)?;
14551    let type_node = node
14552        .child_by_field_name("type")
14553        .or_else(|| first_type_child(node));
14554    Some((name, declarator, type_node))
14555}
14556
14557/// One run of a container's children whose parsed namespaces differ from
14558/// their lexical namespaces, with the complete lexical namespace path.
14559#[derive(Clone, Debug, PartialEq, Eq)]
14560pub struct RecoveredNamespaceRegion {
14561    /// Start byte of the first child in the run.
14562    pub start: usize,
14563    /// End byte of the last child in the run.
14564    pub end: usize,
14565    /// The complete enclosing namespace path of the run, outermost first.
14566    /// This can be empty when a parsed namespace extends past its real close.
14567    pub components: Vec<String>,
14568    /// The `namespace Name { ... }` construct that opens each of `components`,
14569    /// parallel to it, from the `namespace` keyword through the close the index
14570    /// proved for its `{`. A proven surviving namespace definition uses its
14571    /// body's direct close; a recovered head, or a namespace body exposed by
14572    /// malformed member recovery, uses the file-global brace stack. A level
14573    /// whose close the index could not prove -- the file never closes it, or
14574    /// the file's brace token stream lost an opening token (#3328) -- carries
14575    /// the head alone. This is the range the declaration walk records for the
14576    /// Module, so a namespace whose head collapsed into an `ERROR` reports
14577    /// where it is written rather than where its first member is (#3309).
14578    pub component_ranges: Vec<Range>,
14579    /// The name token each of `components` is written as, parallel to it. The
14580    /// declaration walk interns this subspan as the Module's name occurrence:
14581    /// the node the walk is visiting when it declares the level belongs to an
14582    /// unrelated construct inside the region, and deriving the name from it
14583    /// paired the level's range with that construct's name token (#4292).
14584    pub component_name_ranges: Vec<Range>,
14585    /// The close the index proved for each level's `{`, parallel to
14586    /// `components`. A node after that close is outside the level (#3633).
14587    /// `None` when the file never closes the level or the walk proved no
14588    /// pairing in the file (#3328); the level then applies to the whole region
14589    /// as a best-effort scope.
14590    pub component_closes: Vec<Option<Range>>,
14591}
14592
14593/// The namespaces C++ parse recovery drops from a file's tree.
14594///
14595/// When tree-sitter cannot parse a construct inside a namespace body it closes
14596/// an inner scope with a MISSING brace, or skips an opening brace into an
14597/// ERROR node. Every real `}` after that then closes one scope too early: a
14598/// class body's `}` closes the namespace, the namespace's own `}` closes its
14599/// parent, and the outermost real closes land in a trailing ERROR node. The
14600/// declarations between a stolen close and the real one keep their byte
14601/// positions but lose their `namespace_definition` ancestors (Catch2's
14602/// `catch_matchers_templated.hpp`, issue #1537).
14603///
14604/// A file-global stack over real AST brace tokens restores both lost and
14605/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14606/// balanced and can be skipped. For each child of a damaged container, compare
14607/// the namespaces still open on that stack with the parsed ancestor path. A
14608/// difference yields a region carrying the complete lexical path, including
14609/// an empty path when recovery swallowed file-scope declarations. Consecutive
14610/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14611/// pass retains matching brace positions for declaration partitioning (#3087).
14612/// A file without parse errors needs neither correction nor a brace index.
14613///
14614/// The stack restores the source's nesting only while the tree's brace tokens
14615/// are the source's braces. Severe recovery can break that even when the real
14616/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
14617/// the reflection case labels as `character` nodes but loses the outer
14618/// namespace's closing token. The global stack then cascades the function and
14619/// inner namespace closes into the wrong owners. A surviving
14620/// `namespace_definition` can supply stronger provenance when its body has a
14621/// genuine declaration boundary: its direct real `}` belongs to that
14622/// namespace, while a missing direct close proves that the namespace has no
14623/// close to claim. A body whose direct boundary is itself recovered from a
14624/// class/member error stays with the global fallback. The proven boundaries
14625/// override the global stack only when the file has no unpaired real close. An
14626/// unpaired close proves the stream lost an open at a position the stack cannot
14627/// recover, so all pairing in that file remains invalid:
14628/// [`Self::matching_close_brace`] then answers nothing and each restored level
14629/// keeps the head that writes it. The namespace paths remain, as the
14630/// best-effort structure recovery they are.
14631///
14632/// Every consumer reads a node's namespace path through one authority rule,
14633/// shared by [`Self::enclosing_namespace_levels`] and
14634/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
14635/// lookup directions agree:
14636///
14637/// - Outside every region, the node's parsed `namespace_definition` chain is
14638///   the whole answer.
14639/// - Inside a region, the region's restored levels come first and only the
14640///   parsed ancestors that start inside the region follow them. The region
14641///   overrides a parsed ancestor that starts before it, which is how a
14642///   namespace the tree kept open past its real close gives up the
14643///   declarations after that close (#3087).
14644/// - A restored level stops applying at the close the stack paired with its
14645///   `{`, and so does every level inside it. The walk compares a child with
14646///   the stack before it descends into that child, so a run can reach past a
14647///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
14648///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
14649///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
14650///   region that restores `type_tests` covers the whole `preproc_else`, and
14651///   without this check every namespace written after the close inherited
14652///   `type_tests` (#3633). A level whose close the stack did not prove applies
14653///   to the whole region, the best-effort answer #3328 leaves.
14654/// - A region whose restored path is empty keeps exactly the parsed ancestors
14655///   whose own `{` and `}` are both real tokens, wherever they start, and drops
14656///   the others. This holds whether or not the file's pairing is proven.
14657#[derive(Clone, Debug, Default)]
14658pub struct OrphanedNamespaceScopeIndex {
14659    regions: Vec<RecoveredNamespaceRegion>,
14660    /// Open-brace start byte to the close the file-global stack paired with it.
14661    /// Empty when the walk proved the brace token stream incomplete.
14662    brace_closes: HashMap<usize, Range>,
14663}
14664
14665impl OrphanedNamespaceScopeIndex {
14666    pub fn build(root: Node<'_>, source: &str) -> Self {
14667        if !root.has_error() {
14668            return Self::default();
14669        }
14670        /// One namespace level open on the brace stack: the `namespace Name {`
14671        /// head it is written as, and the `{`'s start byte, which is the
14672        /// `brace_closes` key its close lands under. The close is not known
14673        /// while the level is open, so the final ranges are assembled once the
14674        /// walk has paired every brace.
14675        #[derive(Clone, Copy)]
14676        struct OpenHead {
14677            head: Range,
14678            open: usize,
14679        }
14680        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
14681        /// braces rather than their closes.
14682        struct PendingRegion {
14683            start: usize,
14684            end: usize,
14685            components: Vec<String>,
14686            heads: Vec<OpenHead>,
14687            name_ranges: Vec<Range>,
14688        }
14689        struct Frame<'tree> {
14690            node: Node<'tree>,
14691            children: Vec<Node<'tree>>,
14692            next: usize,
14693            parsed_scope: Vec<String>,
14694            run: Option<PendingRegion>,
14695        }
14696        fn frame<'tree>(
14697            node: Node<'tree>,
14698            mut parsed_scope: Vec<String>,
14699            source: &str,
14700        ) -> Frame<'tree> {
14701            if node.kind() == "namespace_definition"
14702                && let Some(name) = node.child_by_field_name("name")
14703            {
14704                let mut components = Vec::new();
14705                if append_cpp_name_components(name, source, &mut components).is_some() {
14706                    parsed_scope.extend(components);
14707                }
14708            }
14709            let mut cursor = node.walk();
14710            Frame {
14711                node,
14712                children: node.children(&mut cursor).collect(),
14713                next: 0,
14714                parsed_scope,
14715                run: None,
14716            }
14717        }
14718        let mut regions = Vec::new();
14719        let mut brace_closes = HashMap::default();
14720        // A surviving namespace_definition may own a direct brace boundary of
14721        // its declaration_list. Keep that evidence separate from the file-
14722        // global stack: recovery can lose an outer namespace close while
14723        // retaining balanced braces from nested constructs, so token order
14724        // alone can assign a real close to the wrong namespace. The helper
14725        // rejects body boundaries exposed by malformed class/member recovery.
14726        let mut namespace_brace_proofs = Vec::new();
14727        // Real closes that arrived with nothing open. Each one proves the tree
14728        // is missing a scope-opening token the source writes, which is what
14729        // makes every pairing in the file unusable as an extent (#3328). The
14730        // complete set is kept, not a count, so a walk that has to be diagnosed
14731        // shows every position the stream went out of sync.
14732        let mut unpaired_closes: Vec<Range> = Vec::new();
14733        // The brace stack belongs to the file, not to a parser frame. A real
14734        // close inside a damaged child can close its parent's namespace. The
14735        // following children must see that removal immediately (#3087).
14736        let mut open = Vec::new();
14737        let mut lexical_scope = Vec::new();
14738        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
14739        // levels and both are written by that one head, exactly as the
14740        // ordinary namespace visit records one range for every level of the
14741        // C++17 shorthand (#1878).
14742        let mut lexical_heads: Vec<OpenHead> = Vec::new();
14743        // One name token per entry of `lexical_scope`: unlike the shared head,
14744        // each level of a `namespace a::b {` shorthand has its own token.
14745        let mut lexical_name_ranges: Vec<Range> = Vec::new();
14746        let mut frames = vec![frame(root, Vec::new(), source)];
14747        // A frame's node is the previous frame's direct child, so the frame
14748        // below answers what Node::parent would without re-descending from the
14749        // root; per-node parent or sibling climbs are quadratic over a wide
14750        // translation unit (#3141).
14751        while !frames.is_empty() {
14752            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14753            let current = frames.last_mut().expect("frames is non-empty");
14754            if current.next == current.children.len() {
14755                regions.extend(frames.pop().expect("the frame just borrowed").run);
14756                continue;
14757            }
14758            let child = current.children[current.next];
14759            current.next += 1;
14760            match child.kind() {
14761                "{" if !child.is_missing() => {
14762                    regions.extend(current.run.take());
14763                    if let Some(namespace) = parent_node.filter(|parent| {
14764                        parent.kind() == "namespace_definition"
14765                            && parent.child_by_field_name("body") == Some(current.node)
14766                            && child.start_byte() == current.node.start_byte()
14767                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
14768                    {
14769                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14770                    }
14771                    // The head is the construct that writes the namespace: the
14772                    // `namespace_definition` node when one survived, and the
14773                    // bare `namespace` keyword when recovery collapsed the head
14774                    // into flat `ERROR` children (#3084).
14775                    let mut named = parent_node.and_then(|parent| {
14776                        namespace_body_name_components(parent, current.node, source)
14777                    });
14778                    if named.is_none() {
14779                        named = recovered_namespace_open_components(
14780                            &current.children[..current.next - 1],
14781                            source,
14782                        );
14783                    }
14784                    open.push((child.start_byte(), lexical_scope.len()));
14785                    if let Some((written_at, components)) = named {
14786                        let head = OpenHead {
14787                            head: Range {
14788                                start_byte: written_at.start_byte(),
14789                                end_byte: child.end_byte(),
14790                                start_line: written_at.start_position().row + 1,
14791                                end_line: child.end_position().row + 1,
14792                            },
14793                            open: child.start_byte(),
14794                        };
14795                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14796                        lexical_name_ranges
14797                            .extend(components.iter().map(|(_, name_range)| *name_range));
14798                        lexical_scope
14799                            .extend(components.into_iter().map(|(component, _)| component));
14800                    }
14801                    continue;
14802                }
14803                "}" if !child.is_missing() => {
14804                    regions.extend(current.run.take());
14805                    let close = Range {
14806                        start_byte: child.start_byte(),
14807                        end_byte: child.end_byte(),
14808                        start_line: child.start_position().row + 1,
14809                        end_line: child.end_position().row + 1,
14810                    };
14811                    match open.pop() {
14812                        Some((start, namespace_len)) => {
14813                            debug_assert!(
14814                                start < close.start_byte,
14815                                "a close pairs with an open before it: {start} then {close:?}"
14816                            );
14817                            lexical_scope.truncate(namespace_len);
14818                            lexical_heads.truncate(namespace_len);
14819                            lexical_name_ranges.truncate(namespace_len);
14820                            brace_closes.insert(start, close);
14821                        }
14822                        None => unpaired_closes.push(close),
14823                    }
14824                    continue;
14825                }
14826                _ => {}
14827            }
14828            // The namespace head and body precede their opening brace; their
14829            // own parsed namespace is not yet on the lexical stack. Compare
14830            // the items inside the body, not this header bookkeeping.
14831            if current.node.kind() != "namespace_definition"
14832                && lexical_scope != current.parsed_scope
14833            {
14834                match &mut current.run {
14835                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14836                    run => {
14837                        regions.extend(run.take());
14838                        *run = Some(PendingRegion {
14839                            start: child.start_byte(),
14840                            end: child.end_byte(),
14841                            components: lexical_scope.clone(),
14842                            heads: lexical_heads.clone(),
14843                            name_ranges: lexical_name_ranges.clone(),
14844                        });
14845                    }
14846                }
14847            } else {
14848                regions.extend(current.run.take());
14849            }
14850            // Error-free subtrees are balanced. Keeping them intact also
14851            // leaves their valid nested namespaces to the ordinary ancestry
14852            // walk in restore_enclosing_namespaces. A clean
14853            // namespace-sentinel wrapper (the macro-declaration grammar reads
14854            // it as a function_definition whose type is the literal namespace
14855            // keyword) is the one exception: its body is a statement-context
14856            // compound whose members need the recovered scope.
14857            let descend = child.has_error()
14858                || (child.kind() == "function_definition"
14859                    && child.child_by_field_name("body").is_some_and(|body| {
14860                        namespace_body_name_components(child, body, source).is_some()
14861                    }));
14862            if descend {
14863                let parsed_scope = current.parsed_scope.clone();
14864                frames.push(frame(child, parsed_scope, source));
14865            }
14866        }
14867        // A close with nothing open is only possible when a `{` the source
14868        // writes is not a `{` token of the tree. The stack then paired some
14869        // earlier open with a close that belongs to an inner scope, and
14870        // nothing in the token stream says which pairs absorbed the loss: the
14871        // lost token sits at an unknown position before the close that
14872        // exposed it. Keep no pairing rather than an unprovable extent. An
14873        // open the file never closes is the opposite case and stays: it makes
14874        // no claim, so the levels it writes keep their heads either way.
14875        if !unpaired_closes.is_empty() {
14876            brace_closes.clear();
14877        } else {
14878            // Use AST ownership when the global walk has no unpaired close. A missing
14879            // direct close removes a misleading stack pairing; a real direct
14880            // close replaces any cascade from a damaged descendant. Apply this
14881            // only to the namespace chain containing a proven missing boundary;
14882            // unrelated or ambiguous namespaces retain the global fallback.
14883            let missing_namespaces = namespace_brace_proofs
14884                .iter()
14885                .filter_map(|(_, namespace, proof)| {
14886                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14887                })
14888                .collect::<HashSet<_>>();
14889            for (open, namespace, proof) in namespace_brace_proofs {
14890                if !std::iter::successors(Some(namespace), |node| node.parent())
14891                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14892                {
14893                    continue;
14894                }
14895                match proof {
14896                    NamespaceBodyBraceProof::Real(close) => {
14897                        brace_closes.insert(open, close);
14898                    }
14899                    NamespaceBodyBraceProof::Missing => {
14900                        brace_closes.remove(&open);
14901                    }
14902                }
14903            }
14904        }
14905        // Every surviving brace is paired now, so each level's head can grow to
14906        // the construct it writes. A level whose `{` never closes keeps its
14907        // head: the file ends inside the namespace and there is no real close
14908        // to reach for.
14909        let regions = regions
14910            .into_iter()
14911            .map(|region| {
14912                debug_assert_eq!(
14913                    region.components.len(),
14914                    region.heads.len(),
14915                    "every recovered namespace level carries the head that writes it: {:?}",
14916                    region.components
14917                );
14918                debug_assert_eq!(
14919                    region.components.len(),
14920                    region.name_ranges.len(),
14921                    "every recovered namespace level carries the name token that writes it: {:?}",
14922                    region.components
14923                );
14924                let component_closes: Vec<_> = region
14925                    .heads
14926                    .iter()
14927                    .map(|level| brace_closes.get(&level.open).copied())
14928                    .collect();
14929                let component_ranges = region
14930                    .heads
14931                    .iter()
14932                    .zip(&component_closes)
14933                    .map(|(level, close)| match close {
14934                        Some(close) => Range {
14935                            start_byte: level.head.start_byte,
14936                            end_byte: close.end_byte,
14937                            start_line: level.head.start_line,
14938                            end_line: close.end_line,
14939                        },
14940                        None => level.head,
14941                    })
14942                    .collect();
14943                RecoveredNamespaceRegion {
14944                    start: region.start,
14945                    end: region.end,
14946                    components: region.components,
14947                    component_ranges,
14948                    component_name_ranges: region.name_ranges,
14949                    component_closes,
14950                }
14951            })
14952            .collect();
14953        Self {
14954            regions,
14955            brace_closes,
14956        }
14957    }
14958
14959    /// The real AST brace matching an opening brace in a damaged subtree.
14960    /// Missing tokens and braces inside comments or literals do not participate.
14961    ///
14962    /// `None` when this open has no close in the file, and for every open in a
14963    /// file whose brace token stream lost an opening token: a caller cannot
14964    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
14965    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14966        self.brace_closes.get(&open).copied()
14967    }
14968
14969    pub fn is_empty(&self) -> bool {
14970        self.regions.is_empty()
14971    }
14972
14973    /// The bytes this index holds, for the analyzer cache's weight.
14974    pub fn approximate_size(&self) -> usize {
14975        self.regions.iter().fold(
14976            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14977            |total, region| {
14978                total
14979                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14980                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14981                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14982                    .saturating_add(
14983                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14984                    )
14985                    .saturating_add(
14986                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14987                    )
14988            },
14989        )
14990    }
14991
14992    /// The innermost recovered region containing `byte`.
14993    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14994        self.regions
14995            .iter()
14996            .filter(|region| region.start <= byte && byte < region.end)
14997            .min_by_key(|region| region.end - region.start)
14998    }
14999
15000    /// The enclosing namespaces of `node`, outermost first, restoring the ones
15001    /// parse recovery dropped from its ancestor chain. The one answer both
15002    /// lookup directions and declaration collection use (issue #1537).
15003    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15004        self.enclosing_namespace_levels(node, source)
15005            .into_iter()
15006            .map(|(component, ..)| component)
15007            .collect()
15008    }
15009
15010    /// [`Self::enclosing_namespace_components`] with the construct that writes
15011    /// each level and the level's own name token, for the declaration walk
15012    /// that has to record both ranges.
15013    ///
15014    /// A level the brace stack restored carries the ranges this index recorded
15015    /// for its head and name; a surviving `namespace_definition` ancestor
15016    /// carries its own node range and name token, which is what the ordinary
15017    /// namespace visit records.
15018    pub fn enclosing_namespace_levels(
15019        &self,
15020        node: Node<'_>,
15021        source: &str,
15022    ) -> Vec<(String, Range, Range)> {
15023        let mut parsed = Vec::new();
15024        let mut current = node.parent();
15025        while let Some(parent) = current {
15026            if parent.kind() == "namespace_definition"
15027                && let Some(name) = parent.child_by_field_name("name")
15028                && let Some(components) = cpp_name_component_ranges(name, source)
15029            {
15030                let range = node_range(parent);
15031                parsed.push((
15032                    parent.start_byte(),
15033                    components
15034                        .into_iter()
15035                        .map(|(component, name_range)| (component, range, name_range))
15036                        .collect(),
15037                    namespace_definition_has_real_braces(parent),
15038                ));
15039            }
15040            current = parent.parent();
15041        }
15042        parsed.reverse();
15043        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15044            region
15045                .components
15046                .iter()
15047                .take(active_components)
15048                .cloned()
15049                .zip(
15050                    region
15051                        .component_ranges
15052                        .iter()
15053                        .take(active_components)
15054                        .copied(),
15055                )
15056                .zip(
15057                    region
15058                        .component_name_ranges
15059                        .iter()
15060                        .take(active_components)
15061                        .copied(),
15062                )
15063                .map(|((component, range), name_range)| (component, range, name_range))
15064                .collect()
15065        })
15066    }
15067
15068    /// [`Self::enclosing_namespace_components`] for a caller that has already
15069    /// climbed the ancestor chain: `parsed` lists the node's named
15070    /// `namespace_definition` ancestors outermost first, each with its start
15071    /// byte and whether its own opening and closing braces survived parsing.
15072    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
15073    pub fn restore_enclosing_namespaces(
15074        &self,
15075        parsed: Vec<(usize, Vec<String>, bool)>,
15076        node_start: usize,
15077    ) -> Vec<String> {
15078        self.splice_recovered_path(parsed, node_start, |region, active_components| {
15079            region
15080                .components
15081                .iter()
15082                .take(active_components)
15083                .cloned()
15084                .collect()
15085        })
15086    }
15087
15088    /// Splice a node's parsed namespace ancestors onto the path a recovered
15089    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
15090    /// `recovered` receives the region and the number of its outermost levels
15091    /// still open at the node, and reads those levels in whatever shape the
15092    /// caller needs.
15093    fn splice_recovered_path<T>(
15094        &self,
15095        parsed: Vec<(usize, Vec<T>, bool)>,
15096        node_start: usize,
15097        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15098    ) -> Vec<T> {
15099        let Some(region) = self.region_at(node_start) else {
15100            return parsed
15101                .into_iter()
15102                .flat_map(|(_, levels, _)| levels)
15103                .collect();
15104        };
15105        debug_assert_eq!(
15106            region.components.len(),
15107            region.component_closes.len(),
15108            "every recovered namespace component carries its close proof: {region:?}"
15109        );
15110        debug_assert_eq!(
15111            region.components.len(),
15112            region.component_name_ranges.len(),
15113            "every recovered namespace component carries its name token: {region:?}"
15114        );
15115        if region.components.is_empty() {
15116            return parsed
15117                .into_iter()
15118                .filter(|(_, _, complete)| *complete)
15119                .flat_map(|(_, levels, _)| levels)
15120                .collect();
15121        }
15122        let active_components = region
15123            .component_closes
15124            .iter()
15125            .take_while(|close| {
15126                close
15127                    .as_ref()
15128                    .is_none_or(|close| node_start < close.end_byte)
15129            })
15130            .count();
15131        recovered(region, active_components)
15132            .into_iter()
15133            .chain(
15134                parsed
15135                    .into_iter()
15136                    .filter(|(start, _, _)| *start >= region.start)
15137                    .flat_map(|(_, levels, _)| levels),
15138            )
15139            .collect()
15140    }
15141}
15142
15143/// Whether a parsed namespace owns real AST tokens for both of its boundary
15144/// braces. Recovery may damage its body while leaving this boundary usable as
15145/// lexical ownership evidence.
15146pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15147    debug_assert_eq!(namespace.kind(), "namespace_definition");
15148    let Some(body) = namespace.child_by_field_name("body") else {
15149        return false;
15150    };
15151    let mut opening = false;
15152    let mut closing = false;
15153    let mut cursor = body.walk();
15154    for child in body.children(&mut cursor) {
15155        match child.kind() {
15156            "{" if !child.is_missing() => opening = true,
15157            "}" if !child.is_missing() => closing = true,
15158            _ => {}
15159        }
15160    }
15161    opening && closing
15162}
15163
15164/// The `namespace` keyword and name components of the namespace a stray `{`
15165/// opens, or `None` when the brace does not follow a namespace head.
15166///
15167/// When a namespace body holds a construct tree-sitter cannot parse, recovery
15168/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
15169/// a `namespace_definition`: the head survives as the node's own `namespace`
15170/// keyword, name and `{` tokens, in that order, and everything the namespace
15171/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
15172/// issue #3084). Read the head from those siblings so the brace stack names
15173/// the scope the brace opens. An anonymous namespace has no representable
15174/// name and keeps an opaque scope.
15175///
15176/// `preceding` is the open brace's preceding siblings, nearest last. The
15177/// caller already holds the container's child list; `Node::prev_sibling`
15178/// would re-descend from the root for each step (#3141).
15179fn recovered_namespace_open_components<'tree>(
15180    preceding: &[Node<'tree>],
15181    source: &str,
15182) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15183    let mut head = Vec::new();
15184    for &sibling in preceding.iter().rev() {
15185        if sibling.kind() != "comment" {
15186            head.push(sibling);
15187            if head.len() == 2 {
15188                break;
15189            }
15190        }
15191    }
15192    let [name, keyword] = head[..] else {
15193        return None;
15194    };
15195    if keyword.kind() != "namespace" {
15196        return None;
15197    }
15198    let components = cpp_name_component_ranges(name, source)?;
15199    (!components.is_empty()).then_some((keyword, components))
15200}
15201
15202/// The written head and name-token ranges of the namespace opened by `body`.
15203/// Ordinary namespace bodies and the two damaged literal heads share the
15204/// existing brace-stack lifetime; a recovered compound body's end is NOT a
15205/// namespace close proof (#5868).
15206fn namespace_body_name_components<'tree>(
15207    parent: Node<'tree>,
15208    body: Node<'tree>,
15209    source: &str,
15210) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15211    if parent.child_by_field_name("body") != Some(body) {
15212        return None;
15213    }
15214    let (written_at, name) = match (parent.kind(), body.kind()) {
15215        ("namespace_definition", "declaration_list") => {
15216            (parent, parent.child_by_field_name("name")?)
15217        }
15218        ("function_definition", "compound_statement") => {
15219            // The two wrapper shapes are mutually exclusive on child kinds: a
15220            // recovered head has an ERROR where the clean macro-modifier head
15221            // has the name. Recovery inside the body still flags has_error on
15222            // the clean shape, so the shape checks, not has_error, must pick
15223            // the reader.
15224            recovered_literal_namespace_head(parent, body, source)
15225                .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15226        }
15227        _ => return None,
15228    };
15229    let components = cpp_name_component_ranges(name, source)?;
15230    (!components.is_empty()).then_some((written_at, components))
15231}
15232
15233/// The written head and name of the literal namespace a damaged wrapper opens,
15234/// when recovery read the `namespace` keyword as a function declarator.
15235fn recovered_literal_namespace_head<'tree>(
15236    parent: Node<'tree>,
15237    body: Node<'tree>,
15238    source: &str,
15239) -> Option<(Node<'tree>, Node<'tree>)> {
15240    // An opaque prefix can make the parser read the literal namespace
15241    // keyword as a function declarator. Account for every direct token:
15242    // no parameters, attributes, extra names or punctuation may compete.
15243    let mut cursor = parent.walk();
15244    let children = parent
15245        .children(&mut cursor)
15246        .filter(|child| child.kind() != "comment")
15247        .collect::<Vec<_>>();
15248    let [prefix, keyword, error, actual_body] = children.as_slice() else {
15249        return None;
15250    };
15251    if *actual_body != body
15252        || parent.child_by_field_name("type") != Some(*prefix)
15253        || parent.child_by_field_name("declarator") != Some(*keyword)
15254        || prefix.kind() != "type_identifier"
15255        || prefix.is_missing()
15256        || prefix.has_error()
15257        || prefix.child_count() != 0
15258        || keyword.kind() != "identifier"
15259        || keyword.is_missing()
15260        || keyword.has_error()
15261        || keyword.child_count() != 0
15262        || node_text(*keyword, source) != "namespace"
15263        || error.kind() != "ERROR"
15264        || error.is_missing()
15265        || body.is_missing()
15266    {
15267        return None;
15268    }
15269    let mut cursor = error.walk();
15270    let names = error
15271        .children(&mut cursor)
15272        .filter(|child| child.kind() != "comment")
15273        .collect::<Vec<_>>();
15274    let [name] = names.as_slice() else {
15275        return None;
15276    };
15277    if !matches!(name.kind(), "identifier" | "namespace_identifier")
15278        || name.is_missing()
15279        || name.has_error()
15280        || name.child_count() != 0
15281    {
15282        return None;
15283    }
15284    let open = body.child(0)?;
15285    if open.kind() != "{"
15286        || open.is_missing()
15287        || open.has_error()
15288        || open.start_byte() != body.start_byte()
15289    {
15290        return None;
15291    }
15292    // Keep the literal keyword's provenance, never the unrelated prefix
15293    // or the prematurely closed function envelope (#3309, #4292).
15294    Some((*keyword, *name))
15295}
15296
15297/// The written head and name of the literal namespace a macro-modifier
15298/// wrapper opens, when the macro-declaration grammar read the same construct
15299/// without recovery.
15300fn macro_modifier_namespace_head<'tree>(
15301    parent: Node<'tree>,
15302    body: Node<'tree>,
15303    source: &str,
15304) -> Option<(Node<'tree>, Node<'tree>)> {
15305    // The macro-declaration grammar reads the same construct without
15306    // recovery: the opaque prefix is a `macro_modifier`, the literal
15307    // `namespace` keyword occupies the type field, and the namespace
15308    // name is the declarator. The keyword cannot be a type, so its
15309    // presence in the type field is the recovery evidence the older
15310    // grammar's `ERROR` carried; account for every direct token the
15311    // same way.
15312    let mut cursor = parent.walk();
15313    let children = parent
15314        .children(&mut cursor)
15315        .filter(|child| child.kind() != "comment")
15316        .collect::<Vec<_>>();
15317    let [prefix, keyword, name, actual_body] = children.as_slice() else {
15318        return None;
15319    };
15320    if *actual_body != body
15321        || parent.child_by_field_name("type") != Some(*keyword)
15322        || parent.child_by_field_name("declarator") != Some(*name)
15323        || prefix.kind() != "macro_modifier"
15324        || prefix.is_missing()
15325        || prefix.has_error()
15326        || macro_modifier_identifier(*prefix).is_none()
15327        || keyword.kind() != "type_identifier"
15328        || keyword.is_missing()
15329        || keyword.has_error()
15330        || keyword.child_count() != 0
15331        || node_text(*keyword, source) != "namespace"
15332        || !matches!(name.kind(), "identifier" | "namespace_identifier")
15333        || name.is_missing()
15334        || name.has_error()
15335        || name.child_count() != 0
15336        || body.is_missing()
15337    {
15338        return None;
15339    }
15340    let open = body.child(0)?;
15341    if open.kind() != "{"
15342        || open.is_missing()
15343        || open.has_error()
15344        || open.start_byte() != body.start_byte()
15345    {
15346        return None;
15347    }
15348    // Keep the literal keyword's provenance, never the unrelated prefix
15349    // or the prematurely closed function envelope (#3309, #4292).
15350    Some((*keyword, *name))
15351}
15352
15353#[derive(Clone, Copy)]
15354enum NamespaceBodyBraceProof {
15355    Real(Range),
15356    Missing,
15357}
15358
15359/// Prove a namespace body's boundary from its direct AST children. Require the
15360/// body's boundary as well as direct ownership, so a stray brace inside a
15361/// recovered body cannot become namespace ownership evidence. Unknown is
15362/// distinct from a proven missing token: unknown leaves the global pairing in
15363/// place, while a direct MISSING `}` removes a misleading pairing.
15364fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15365    if body.kind() != "declaration_list" {
15366        return None;
15367    }
15368    let mut cursor = body.walk();
15369    let children = body.children(&mut cursor).collect::<Vec<_>>();
15370    let close_index = children
15371        .iter()
15372        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15373    let close = children[close_index];
15374    if close.is_missing() {
15375        return Some(NamespaceBodyBraceProof::Missing);
15376    }
15377    let namespace_terminator_sibling = body
15378        .parent()
15379        .filter(|namespace| namespace.kind() == "namespace_definition")
15380        .and_then(|namespace| namespace.next_sibling())
15381        .is_some_and(|sibling| {
15382            sibling.kind() == ";"
15383                || (sibling.kind() == "expression_statement"
15384                    && sibling.child_count() == 1
15385                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15386        });
15387    // A recovered class close can become the namespace body boundary while
15388    // its class terminator remains as a sibling semicolon. A malformed class
15389    // can also leak its final expression or label directly into the namespace
15390    // body. Neither shape proves that this `}` belongs to the namespace.
15391    if namespace_terminator_sibling
15392        || children
15393            .get(close_index + 1)
15394            .is_some_and(|sibling| sibling.kind() == ";")
15395        || children[..close_index]
15396            .iter()
15397            .rev()
15398            .find(|child| !child.is_extra() && child.kind() != "{")
15399            .is_some_and(|previous| {
15400                matches!(
15401                    previous.kind(),
15402                    "expression_statement" | "labeled_statement"
15403                )
15404            })
15405    {
15406        return None;
15407    }
15408    Some(NamespaceBodyBraceProof::Real(node_range(close)))
15409}
15410
15411#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15412pub enum RecoveredDeclaratorTypeContext {
15413    Declaration,
15414    FunctionDefinition,
15415    Parameter,
15416}
15417
15418/// Recognize a real type displaced into a qualified declarator by parser
15419/// recovery.
15420///
15421/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
15422/// type and `Result` were the scope of a qualified declarator with a missing
15423/// `::`. A template return such as `API Result<T> make()` uses a
15424/// `template_type` for the same recovered scope. The same recovery occurs for
15425/// macro-prefixed definitions, extern variables, and macro-decorated
15426/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
15427/// the macro). Keep this intentionally structural: the recovered scope must
15428/// have the grammar's missing separator, the qualified node must occupy the
15429/// declaration's declarator chain, a separate nonempty type must occupy the
15430/// normal type field, and the recovered name must unwrap to a real declarator
15431/// name.
15432pub fn recovered_macro_decorated_declarator_type(
15433    node: Node<'_>,
15434) -> Option<RecoveredDeclaratorTypeContext> {
15435    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15436}
15437
15438/// Return the declaration/function `type` displaced by a macro-shaped
15439/// qualified declarator, together with the enclosing declaration context.
15440/// Callers use the macro scope only as structural admission evidence; the
15441/// returned node is the real type reference to resolve and record.
15442pub fn recovered_macro_decorated_type_node(
15443    node: Node<'_>,
15444) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15445    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15446        return None;
15447    }
15448    let qualified = node.parent()?;
15449    if qualified.kind() != "qualified_identifier"
15450        || qualified.child_by_field_name("scope") != Some(node)
15451        || !(0..qualified.child_count())
15452            .filter_map(|index| qualified.child(index))
15453            .any(|child| child.kind() == "::" && child.is_missing())
15454    {
15455        return None;
15456    }
15457    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15458        return None;
15459    }
15460
15461    let (declaration, context) = recovered_declarator_container(qualified)?;
15462    let type_node = declaration
15463        .child_by_field_name("type")
15464        .filter(|type_node| {
15465            *type_node != qualified
15466                && !type_node.is_missing()
15467                && type_node.start_byte() != type_node.end_byte()
15468        })?;
15469    Some((type_node, context))
15470}
15471
15472/// The macro-declaration grammar's clean reading of the head
15473/// [`recovered_macro_decorated_type_node`] recovers: `API static inline T
15474/// make()` parses as a leading `macro_modifier` and `T` as the declaration's
15475/// `type`. Accepts the same type kinds and declaration containers as the
15476/// recovered form, so a rule keyed on that macro-decorated head keeps its
15477/// scope across both grammars.
15478pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
15479    matches!(node.kind(), "type_identifier" | "template_type")
15480        && node.parent().is_some_and(|declaration| {
15481            matches!(
15482                declaration.kind(),
15483                "declaration"
15484                    | "function_definition"
15485                    | "parameter_declaration"
15486                    | "optional_parameter_declaration"
15487            ) && declaration.child_by_field_name("type") == Some(node)
15488                && declaration.child_by_field_name("declarator").is_some()
15489                && crate::declarations::cpp_declaration_type_follows_macro_modifier(
15490                    declaration,
15491                    node,
15492                )
15493        })
15494}
15495
15496fn recovered_declarator_container(
15497    mut declarator: Node<'_>,
15498) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15499    loop {
15500        let parent = declarator.parent()?;
15501        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15502            return Some((
15503                parent
15504                    .parent()
15505                    .filter(|declaration| declaration.kind() == "declaration")?,
15506                RecoveredDeclaratorTypeContext::Declaration,
15507            ));
15508        }
15509        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15510            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15511        }
15512        if parent.kind() == "function_definition"
15513            && has_field_child(parent, "declarator", declarator)
15514        {
15515            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15516        }
15517        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
15518        // level down: the parameter's `type` field takes the macro token and
15519        // the real type `T` becomes the recovered scope of the declarator.
15520        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
15521        // candidate at all (#1830).
15522        if matches!(
15523            parent.kind(),
15524            "parameter_declaration" | "optional_parameter_declaration"
15525        ) && has_field_child(parent, "declarator", declarator)
15526        {
15527            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15528        }
15529        if !matches!(
15530            parent.kind(),
15531            "array_declarator"
15532                | "function_declarator"
15533                | "parenthesized_declarator"
15534                | "pointer_declarator"
15535                | "pointer_type_declarator"
15536                | "reference_declarator"
15537        ) || !has_field_child(parent, "declarator", declarator)
15538        {
15539            return None;
15540        }
15541        declarator = parent;
15542    }
15543}
15544
15545fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15546    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15547}
15548
15549fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15550    loop {
15551        if node.is_missing() || node.start_byte() == node.end_byte() {
15552            return false;
15553        }
15554        match node.kind() {
15555            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15556                return true;
15557            }
15558            "array_declarator"
15559            | "function_declarator"
15560            | "parenthesized_declarator"
15561            | "pointer_declarator"
15562            | "pointer_type_declarator"
15563            | "reference_declarator" => {
15564                let Some(declarator) = node.child_by_field_name("declarator") else {
15565                    return false;
15566                };
15567                node = declarator;
15568            }
15569            _ => return false,
15570        }
15571    }
15572}
15573
15574/// Aggregate-owner proof for a structurally recognized designated initializer.
15575pub enum DesignatedInitializerOwner {
15576    Resolved(CodeUnit),
15577    Unresolved,
15578}
15579
15580enum InitializerOwnerStep {
15581    Field(String),
15582    AggregateWrapper,
15583}
15584
15585/// Recognize a designated-initializer field and, when possible, resolve its
15586/// aggregate owner.
15587///
15588/// Covers both the grammar's ordinary `field_designator` shape and the exact
15589/// recovery used for `.field = value` after a preprocessor-split array
15590/// initializer. Ordered multi-component designators follow each preceding
15591/// field's declared aggregate type. `None` means the node is not a designator
15592/// at all; an unresolved designator remains classified so callers cannot fall
15593/// through to unrelated global/member heuristics.
15594pub fn designated_initializer_owner(
15595    analyzer: &CppGraphSource<'_>,
15596    visibility: &VisibilityIndex<'_>,
15597    file: &ProjectFile,
15598    source: &str,
15599    node: Node<'_>,
15600) -> Option<DesignatedInitializerOwner> {
15601    if let Some(designator) = node
15602        .parent()
15603        .filter(|parent| parent.kind() == "field_designator")
15604    {
15605        let pair = designator.parent()?;
15606        if pair.kind() != "initializer_pair" {
15607            return None;
15608        }
15609        let mut cursor = pair.walk();
15610        let designators = pair
15611            .children_by_field_name("designator", &mut cursor)
15612            .collect::<Vec<_>>();
15613        let position = designators
15614            .iter()
15615            .position(|candidate| same_node(*candidate, designator))?;
15616        let initializer = pair.parent()?;
15617        if initializer.kind() != "initializer_list" {
15618            return None;
15619        }
15620        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15621        for prior in &designators[..position] {
15622            let field = prior
15623                .child_by_field_name("field")
15624                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15625            owner = owner.and_then(|owner| {
15626                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15627            });
15628        }
15629        return Some(classified_designated_owner(owner));
15630    }
15631
15632    let init_declarator = node.parent()?;
15633    if init_declarator.child_by_field_name("declarator") != Some(node)
15634        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15635    {
15636        return None;
15637    }
15638    Some(classified_designated_owner(declaration_owner(
15639        analyzer,
15640        visibility,
15641        file,
15642        source,
15643        init_declarator.parent()?,
15644    )))
15645}
15646
15647fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15648    owner.map_or(
15649        DesignatedInitializerOwner::Unresolved,
15650        DesignatedInitializerOwner::Resolved,
15651    )
15652}
15653
15654fn initializer_list_owner(
15655    analyzer: &CppGraphSource<'_>,
15656    visibility: &VisibilityIndex<'_>,
15657    file: &ProjectFile,
15658    source: &str,
15659    initializer: Node<'_>,
15660) -> Option<CodeUnit> {
15661    let mut current = initializer;
15662    let mut steps = Vec::new();
15663    loop {
15664        let parent = current.parent()?;
15665        match parent.kind() {
15666            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15667                let designator = parent.child_by_field_name("designator")?;
15668                let step = designator
15669                    .child_by_field_name("field")
15670                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15671                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15672                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15673                steps.push(step);
15674                current = parent.parent()?;
15675            }
15676            "initializer_list" => {
15677                current = parent;
15678            }
15679            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15680                let declaration = parent.parent()?;
15681                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15682                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15683            }
15684            "compound_literal_expression"
15685                if parent.child_by_field_name("value") == Some(current) =>
15686            {
15687                let type_node = parent.child_by_field_name("type")?;
15688                let owner =
15689                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15690                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15691            }
15692            "ERROR" => current = parent,
15693            _ => return None,
15694        }
15695    }
15696}
15697
15698fn apply_initializer_owner_steps(
15699    analyzer: &CppGraphSource<'_>,
15700    visibility: &VisibilityIndex<'_>,
15701    file: &ProjectFile,
15702    mut owner: CodeUnit,
15703    steps: Vec<InitializerOwnerStep>,
15704) -> Option<CodeUnit> {
15705    for step in steps.into_iter().rev() {
15706        if let InitializerOwnerStep::Field(field_name) = step {
15707            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15708        }
15709    }
15710    Some(owner)
15711}
15712
15713fn initializer_field_owner(
15714    analyzer: &CppGraphSource<'_>,
15715    visibility: &VisibilityIndex<'_>,
15716    file: &ProjectFile,
15717    owner: CodeUnit,
15718    field_name: &str,
15719) -> Option<CodeUnit> {
15720    let fields = visibility
15721        .visible_members_for_owner_name(file, &owner, field_name)
15722        .into_iter()
15723        .filter(|field| field.is_field())
15724        .collect::<Vec<_>>();
15725    let field = match fields.as_slice() {
15726        [field] => *field,
15727        _ => return None,
15728    };
15729    field_declared_binding(analyzer, visibility, file, field)?.unit
15730}
15731
15732fn declaration_owner(
15733    analyzer: &CppGraphSource<'_>,
15734    visibility: &VisibilityIndex<'_>,
15735    file: &ProjectFile,
15736    source: &str,
15737    declaration: Node<'_>,
15738) -> Option<CodeUnit> {
15739    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15740        return None;
15741    }
15742    let type_node = declaration
15743        .child_by_field_name("type")
15744        .or_else(|| first_type_child(declaration))?;
15745    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15746}
15747
15748fn resolve_designated_owner_type(
15749    analyzer: &CppGraphSource<'_>,
15750    visibility: &VisibilityIndex<'_>,
15751    file: &ProjectFile,
15752    source: &str,
15753    type_node: Node<'_>,
15754) -> Option<CodeUnit> {
15755    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15756        return Some(owner);
15757    }
15758    let type_name = normalize_type_text(node_text(type_node, source));
15759    visibility
15760        .resolve_type(file, &type_name)
15761        .filter(CodeUnit::is_class)
15762}
15763
15764pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15765    let mut cursor = node.walk();
15766    node.named_children(&mut cursor).find(|child| {
15767        matches!(
15768            child.kind(),
15769            "type_identifier"
15770                | "primitive_type"
15771                | "qualified_identifier"
15772                | "scoped_type_identifier"
15773                | "struct_specifier"
15774                | "union_specifier"
15775                | "enum_specifier"
15776        )
15777    })
15778}
15779
15780pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15781    visibility: &VisibilityIndex<'_>,
15782    file: &ProjectFile,
15783    source: &str,
15784    declarator: Node<'_>,
15785    type_text: Option<&str>,
15786    bindings: &LocalInferenceEngine<T>,
15787) -> bool {
15788    if !has_ancestor_kind(declarator, "compound_statement") {
15789        return false;
15790    }
15791    if declarator
15792        .child_by_field_name("declarator")
15793        .is_none_or(|declarator| declarator.kind() != "identifier")
15794    {
15795        return false;
15796    }
15797    if !type_text
15798        .and_then(|text| visibility.resolve_type(file, text))
15799        .is_some_and(|unit| unit.is_class())
15800    {
15801        return false;
15802    }
15803    declarator
15804        .child_by_field_name("parameters")
15805        .is_some_and(|parameters| {
15806            constructor_parameters_look_like_expressions(parameters, source, bindings)
15807        })
15808}
15809
15810fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15811    parameters: Node<'_>,
15812    source: &str,
15813    bindings: &LocalInferenceEngine<T>,
15814) -> bool {
15815    let mut cursor = parameters.walk();
15816    parameters.named_children(&mut cursor).any(|parameter| {
15817        !matches!(
15818            parameter.kind(),
15819            "parameter_declaration" | "optional_parameter_declaration"
15820        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15821    })
15822}
15823
15824fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15825    parameter: Node<'_>,
15826    source: &str,
15827    bindings: &LocalInferenceEngine<T>,
15828) -> bool {
15829    let text = node_text(parameter, source).trim();
15830    if text
15831        .chars()
15832        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15833        && bindings.is_shadowed(text)
15834    {
15835        return true;
15836    }
15837
15838    let Some(base) = parameter
15839        .child_by_field_name("type")
15840        .filter(|base| base.kind() == "type_identifier")
15841    else {
15842        return false;
15843    };
15844    let Some(subscript) = parameter
15845        .child_by_field_name("declarator")
15846        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15847    else {
15848        return false;
15849    };
15850    subscript.child_by_field_name("size").is_some()
15851        && bindings.is_shadowed(node_text(base, source).trim())
15852}
15853
15854pub fn is_declaration_name(node: Node<'_>) -> bool {
15855    let Some(parent) = node.parent() else {
15856        return false;
15857    };
15858    if parent
15859        .child_by_field_name("name")
15860        .is_some_and(|name| same_node(name, node))
15861    {
15862        if matches!(
15863            parent.kind(),
15864            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15865        ) {
15866            return cpp_tag_specifier_declares_name(parent);
15867        }
15868        if matches!(
15869            parent.kind(),
15870            "namespace_definition"
15871                | "namespace_alias_definition"
15872                | "alias_declaration"
15873                | "enumerator"
15874        ) {
15875            return true;
15876        }
15877    }
15878
15879    let mut current = Some(parent);
15880    while let Some(ancestor) = current {
15881        let type_definition = ancestor.kind() == "type_definition";
15882        let mut child_cursor = ancestor.walk();
15883        if ancestor.named_children(&mut child_cursor).any(|child| {
15884            declaration_declarator(ancestor, child).is_some_and(|declarator| {
15885                declarator_name_path_contains(declarator, node, type_definition)
15886            })
15887        }) {
15888            return true;
15889        }
15890        if matches!(
15891            ancestor.kind(),
15892            "declaration"
15893                | "field_declaration"
15894                | "parameter_declaration"
15895                | "optional_parameter_declaration"
15896                | "function_definition"
15897                | "type_definition"
15898                | "alias_declaration"
15899                | "template_instantiation"
15900                | "class_specifier"
15901                | "struct_specifier"
15902                | "union_specifier"
15903                | "enum_specifier"
15904        ) {
15905            return false;
15906        }
15907        current = ancestor.parent();
15908    }
15909    false
15910}
15911
15912/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
15913/// declaration's declarator inside a malformed class body.
15914///
15915/// An export macro between `class` and the class name can make the containing
15916/// body parse as a function body. A source declaration such as
15917/// `friend class internal::Friend;` then retains this exact structure:
15918/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
15919/// The declarator is a type reference despite its field role.
15920pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15921    if !matches!(
15922        node.kind(),
15923        "qualified_identifier" | "scoped_type_identifier"
15924    ) {
15925        return false;
15926    }
15927    let Some(declaration) = node
15928        .parent()
15929        .filter(|parent| parent.kind() == "declaration")
15930    else {
15931        return false;
15932    };
15933    if declaration.child_by_field_name("declarator") != Some(node)
15934        || !declaration
15935            .child_by_field_name("type")
15936            .is_some_and(|friend| {
15937                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15938            })
15939    {
15940        return false;
15941    }
15942    let mut cursor = declaration.walk();
15943    let mut errors = declaration
15944        .named_children(&mut cursor)
15945        .filter(|child| child.kind() == "ERROR");
15946    let Some(error) = errors.next() else {
15947        return false;
15948    };
15949    errors.next().is_none()
15950        && error.named_child_count() == 1
15951        && error.named_child(0).is_some_and(|class| {
15952            class.kind() == "identifier" && node_text(class, source) == "class"
15953        })
15954}
15955
15956pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15957    if !matches!(node.kind(), "identifier" | "field_identifier") {
15958        return false;
15959    }
15960    if let Some(parent) = node.parent() {
15961        if parent.kind() == "call_expression"
15962            && parent.child_by_field_name("function") == Some(node)
15963        {
15964            return false;
15965        }
15966        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15967            && parent.child_by_field_name("label") == Some(node)
15968        {
15969            return false;
15970        }
15971    }
15972    if is_declaration_name(node) {
15973        return false;
15974    }
15975    let mut current = node.parent();
15976    while let Some(ancestor) = current {
15977        match ancestor.kind() {
15978            "preproc_ifdef" | "preproc_ifndef" => {
15979                if ancestor
15980                    .child_by_field_name("name")
15981                    .is_some_and(|name| node_range_contains(name, node))
15982                {
15983                    return false;
15984                }
15985            }
15986            "preproc_if" | "preproc_elif" => {
15987                if ancestor
15988                    .child_by_field_name("condition")
15989                    .is_some_and(|condition| node_range_contains(condition, node))
15990                {
15991                    return false;
15992                }
15993            }
15994            "preproc_else" => {}
15995            kind if kind.starts_with("preproc_") => return false,
15996            _ => {}
15997        }
15998        if matches!(
15999            ancestor.kind(),
16000            "translation_unit" | "function_definition" | "compound_statement"
16001        ) {
16002            break;
16003        }
16004        current = ancestor.parent();
16005    }
16006    true
16007}
16008
16009fn recovered_c_reference_node(
16010    visibility: &VisibilityIndex<'_>,
16011    file: &ProjectFile,
16012    node: Node<'_>,
16013    source: &str,
16014) -> bool {
16015    if node.start_byte() >= node.end_byte()
16016        || node.is_error()
16017        || node.is_missing()
16018        || !matches!(
16019            node.kind(),
16020            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16021        )
16022        || recovered_c_macro_binding_role(node)
16023        || recovered_c_label_role(node)
16024    {
16025        return false;
16026    }
16027    // A declaration name can be an identifier child of a recovered ERROR
16028    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
16029    // let a same-named macro turn that binder into a reference. An
16030    // assignment whose C callee follows the recovered `explicit` token is
16031    // the one expression-shaped exception: the generic declarator walk sees
16032    // its function_declarator as a declaration path, but the ERROR sibling
16033    // proves it is a call.
16034    let name = node_text(node, source);
16035    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16036    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16037        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16038    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16039    if is_declaration_name(node)
16040        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16041        && !recovered_function_call
16042        && !recovered_macro_call
16043        && !recovered_parenthesized_reference
16044    {
16045        return false;
16046    }
16047
16048    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16049        return true;
16050    }
16051    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16052        return true;
16053    }
16054    if recovered_parenthesized_reference {
16055        return true;
16056    }
16057    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16058        if recovered_function_call {
16059            return true;
16060        }
16061        return visibility
16062            .visible_identifier_candidates(file, name)
16063            .any(|candidate| candidate.is_class() || candidate.is_module());
16064    }
16065    let visible = visibility
16066        .visible_identifier_candidates(file, name)
16067        .next()
16068        .is_some();
16069    visible
16070        && (recovered_c_reference_anchor(node)
16071            || recovered_c_error_expression_leaf(node)
16072            || recovered_function_call)
16073}
16074
16075fn push_recovered_c_range(
16076    ranges: &mut Vec<Range>,
16077    seen: &mut HashSet<(usize, usize)>,
16078    start_byte: usize,
16079    end_byte: usize,
16080    node: Node<'_>,
16081    limit: usize,
16082) -> bool {
16083    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16084        return true;
16085    }
16086    if ranges.len() >= limit {
16087        return false;
16088    }
16089    ranges.push(Range {
16090        start_byte,
16091        end_byte,
16092        start_line: node.start_position().row,
16093        end_line: node.end_position().row,
16094    });
16095    true
16096}
16097
16098/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
16099/// is still attached to a real expression (most often a recovered macro call
16100/// argument). The expression parent is the structured proof; an unindexed
16101/// identifier beneath a bare recovery envelope has no such proof.
16102fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16103    let mut current = node.parent();
16104    while let Some(parent) = current {
16105        if parent.is_error() {
16106            let Some(anchor) = parent.parent() else {
16107                return false;
16108            };
16109            return anchor.kind().ends_with("_expression")
16110                || matches!(
16111                    anchor.kind(),
16112                    "argument_list"
16113                        | "return_statement"
16114                        | "expression_statement"
16115                        | "case_statement"
16116                        | "initializer_list"
16117                        | "field_designator"
16118                        | "enumerator"
16119                );
16120        }
16121        if matches!(
16122            parent.kind(),
16123            "translation_unit" | "function_definition" | "compound_statement"
16124        ) {
16125            return false;
16126        }
16127        current = parent.parent();
16128    }
16129    false
16130}
16131
16132/// C recovery may represent a call as `identifier > function_declarator >
16133/// function_declarator > ERROR > compound_statement`. This shape is only a
16134/// call when the malformed declarator is attached to a real function body and
16135/// the name is an indexed visible callable. A declaration's
16136/// `ERROR > function_declarator > declaration` shape deliberately fails this
16137/// test.
16138fn recovered_c_function_call(
16139    visibility: &VisibilityIndex<'_>,
16140    file: &ProjectFile,
16141    node: Node<'_>,
16142    name: &str,
16143) -> bool {
16144    if !matches!(
16145        node.kind(),
16146        "identifier" | "field_identifier" | "type_identifier"
16147    ) {
16148        return false;
16149    }
16150    // C++ keywords used as C arguments can leave only the call prefix under
16151    // ERROR, with the remaining arguments in a following expression statement.
16152    // Require a statement boundary in a real block, an opening parenthesis,
16153    // and a recovered argument. A bare ERROR identifier supplies no call role.
16154    let error_call_prefix = node.parent().is_some_and(|error| {
16155        error.is_error()
16156            && error
16157                .parent()
16158                .is_some_and(|parent| parent.kind() == "compound_statement")
16159    }) && node
16160        .prev_sibling()
16161        .is_none_or(|previous| previous.kind() == ";")
16162        && node.next_sibling().is_some_and(|open| {
16163            open.kind() == "("
16164                && open.next_named_sibling().is_some_and(|argument| {
16165                    argument.kind() == "parameter_declaration" && !argument.has_error()
16166                })
16167        });
16168    (error_call_prefix || recovered_c_function_declarator_invocation(node))
16169        && visibility
16170            .visible_identifier_candidates(file, name)
16171            .any(CodeUnit::is_function)
16172}
16173
16174/// The bogus `function_definition` the macro-declaration grammar builds for a
16175/// C call whose argument list a Clang block literal breaks: `f(first, ^(T b)
16176/// { ... })` reads as a definition whose declarator ends at the block's `(`,
16177/// with the block body as the function body. The older grammar kept the
16178/// `call_expression` and left the damage inside its argument list. The
16179/// declarator must carry the damage, and the envelope must sit in statement
16180/// context: a clean type-less definition is a real one (a constructor).
16181pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16182    if declarator.kind() != "function_declarator" || !declarator.has_error() {
16183        return None;
16184    }
16185    let definition = declarator.parent().filter(|parent| {
16186        parent.kind() == "function_definition"
16187            && parent.child_by_field_name("declarator") == Some(declarator)
16188    })?;
16189    if definition.child_by_field_name("type").is_some()
16190        || definition.named_child(0) != Some(declarator)
16191        || definition.child_by_field_name("body").is_none()
16192        || definition.parent()?.kind() != "compound_statement"
16193    {
16194        return None;
16195    }
16196    Some(definition)
16197}
16198
16199/// Return whether a C identifier belongs to a call-shaped declarator that the
16200/// C++ grammar put under an `ERROR` node.
16201///
16202/// The malformed call can be direct (`f(arg)`) or nested in a parameter
16203/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
16204/// In both cases the CST retains the function-declarator and its enclosing
16205/// recovery envelope. We walk only those declarator/parameter nodes and stop
16206/// at a real expression-bearing boundary; declarations therefore cannot pass
16207/// this predicate merely because they have a parameter list.
16208fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16209    // The macro-declaration grammar keeps a function-like macro invocation at
16210    // declaration scope as a `macro_type_specifier` (a function-like macro
16211    // wrapping a type), admitted only where the parse would otherwise contain
16212    // an error. Its name is the invocation.
16213    let mut current = if node.parent().is_some_and(|parent| {
16214        parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16215    }) {
16216        node.parent().expect("checked macro_type_specifier parent")
16217    } else {
16218        let mut function_declarator = if node.parent().is_some_and(|parent| {
16219            parent.kind() == "function_declarator"
16220                && parent.child_by_field_name("declarator") == Some(node)
16221        }) {
16222            node.parent().expect("checked function declarator parent")
16223        } else {
16224            let Some(parameter) = node.parent().filter(|parent| {
16225                parent.kind() == "parameter_declaration"
16226                    && parent.child_by_field_name("type") == Some(node)
16227            }) else {
16228                return false;
16229            };
16230            if !parameter
16231                .child_by_field_name("declarator")
16232                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16233            {
16234                return false;
16235            }
16236            let Some(parameters) = parameter
16237                .parent()
16238                .filter(|parent| parent.kind() == "parameter_list")
16239            else {
16240                return false;
16241            };
16242            let Some(function_declarator) = parameters
16243                .parent()
16244                .filter(|parent| parent.kind() == "function_declarator")
16245            else {
16246                return false;
16247            };
16248            function_declarator
16249        };
16250
16251        // Recovery may absorb the next statement's parenthesized cast into a
16252        // second function declarator. Follow only the original declarator chain.
16253        while let Some(parent) = function_declarator.parent().filter(|parent| {
16254            parent.kind() == "function_declarator"
16255                && parent.child_by_field_name("declarator") == Some(function_declarator)
16256        }) {
16257            function_declarator = parent;
16258        }
16259        // The macro-declaration grammar can attach the malformed call's
16260        // declarator directly to the bogus `function_definition` (a block
16261        // literal argument reads as the body) without the older grammar's
16262        // ERROR wrapper.
16263        let Some(current) = function_declarator.parent().filter(|parent| {
16264            parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
16265        }) else {
16266            return false;
16267        };
16268        current
16269    };
16270    loop {
16271        let Some(parent) = current.parent() else {
16272            return false;
16273        };
16274        if matches!(
16275            parent.kind(),
16276            "translation_unit"
16277                | "compound_statement"
16278                | "preproc_if"
16279                | "preproc_ifdef"
16280                | "preproc_ifndef"
16281                | "preproc_else"
16282                | "preproc_elif"
16283        ) {
16284            return true;
16285        }
16286        if parent.kind() == "function_definition"
16287            && parent.child_by_field_name("declarator") == Some(current)
16288            && parent.named_child(0) == Some(current)
16289            && parent.child_by_field_name("body").is_some()
16290        {
16291            return true;
16292        }
16293        if parent.is_error()
16294            || matches!(
16295                parent.kind(),
16296                "parameter_declaration"
16297                    | "parameter_list"
16298                    | "function_declarator"
16299                    | "abstract_function_declarator"
16300                    | "parenthesized_declarator"
16301            )
16302        {
16303            current = parent;
16304            continue;
16305        }
16306        return false;
16307    }
16308}
16309
16310/// C permits an identifier named `typename`. The C++ grammar can recover an
16311/// assignment using that identifier as a declaration whose declarator is a
16312/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
16313/// the argument retained beneath the nested `ERROR` is a reference; sibling
16314/// declarator identifiers remain binders/grammar artifacts.
16315fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16316    // The macro-declaration grammar keeps the same line as a `dependent_type`
16317    // whose `macro_type_specifier` argument list retains the first argument
16318    // beneath the nested `ERROR`; that argument's `type_descriptor` is the
16319    // reference, and the specifier's own `type` field is the following
16320    // sibling, never a reference.
16321    if node
16322        .parent()
16323        .is_some_and(|parent| parent.kind() == "type_descriptor")
16324    {
16325        let Some(type_descriptor) = node.parent() else {
16326            return false;
16327        };
16328        let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
16329            return false;
16330        };
16331        if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
16332            return false;
16333        }
16334        let Some(dependent) = error
16335            .parent()
16336            .and_then(|parent| parent.parent())
16337            .filter(|parent| parent.kind() == "dependent_type")
16338        else {
16339            return false;
16340        };
16341        return dependent
16342            .child(0)
16343            .is_some_and(|keyword| keyword.kind() == "typename");
16344    }
16345    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16346        return false;
16347    };
16348    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16349        return false;
16350    }
16351    let Some(declarator) = error
16352        .parent()
16353        .filter(|parent| parent.kind() == "parenthesized_declarator")
16354    else {
16355        return false;
16356    };
16357    let Some(declaration) = declarator
16358        .parent()
16359        .filter(|parent| parent.kind() == "declaration")
16360    else {
16361        return false;
16362    };
16363    if declaration.child_by_field_name("declarator") != Some(declarator) {
16364        return false;
16365    }
16366    let Some(type_node) = declaration.child_by_field_name("type") else {
16367        return false;
16368    };
16369    type_node.kind() == "dependent_type"
16370        && type_node
16371            .child(0)
16372            .is_some_and(|keyword| keyword.kind() == "typename")
16373}
16374
16375fn recovered_c_explicit_assignment_callee(
16376    visibility: &VisibilityIndex<'_>,
16377    file: &ProjectFile,
16378    node: Node<'_>,
16379    name: &str,
16380) -> bool {
16381    let mut current = node;
16382    let error = loop {
16383        let Some(parent) = current.parent() else {
16384            return false;
16385        };
16386        if parent.is_error() {
16387            break parent;
16388        }
16389        current = parent;
16390    };
16391    let mut cursor = error.walk();
16392    let explicit_recovery_precedes_callee = error
16393        .named_children(&mut cursor)
16394        .take_while(|child| child.start_byte() < node.start_byte())
16395        .any(|child| child.kind() == "explicit_function_specifier");
16396    if !explicit_recovery_precedes_callee {
16397        return false;
16398    }
16399    visibility
16400        .visible_identifier_candidates(file, name)
16401        .any(CodeUnit::is_function)
16402}
16403
16404fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16405    while let Some(parent) = node.parent() {
16406        if matches!(
16407            parent.kind(),
16408            "preproc_def" | "preproc_function_def" | "preproc_params"
16409        ) {
16410            return true;
16411        }
16412        if parent.is_error()
16413            || matches!(
16414                parent.kind(),
16415                "translation_unit" | "function_definition" | "compound_statement"
16416            )
16417        {
16418            return false;
16419        }
16420        node = parent;
16421    }
16422    false
16423}
16424
16425fn recovered_c_label_role(node: Node<'_>) -> bool {
16426    node.parent().is_some_and(|parent| {
16427        matches!(parent.kind(), "labeled_statement" | "goto_statement")
16428            && parent.child_by_field_name("label") == Some(node)
16429    })
16430}
16431
16432fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16433    while let Some(parent) = node.parent() {
16434        if parent.is_error() {
16435            return false;
16436        }
16437        // A C macro call recovered as a function declarator can parse an
16438        // assignment-shaped argument as an optional parameter. Its
16439        // `default_value` field remains an expression role even though the
16440        // surrounding call shape is beneath ERROR.
16441        if parent.kind() == "optional_parameter_declaration"
16442            && parent
16443                .child_by_field_name("default_value")
16444                .is_some_and(|value| node_range_contains(value, node))
16445        {
16446            return true;
16447        }
16448        if parent.kind().ends_with("_expression")
16449            || matches!(
16450                parent.kind(),
16451                "argument_list"
16452                    | "return_statement"
16453                    | "expression_statement"
16454                    | "case_statement"
16455                    | "initializer_list"
16456                    | "init_declarator"
16457                    | "array_declarator"
16458                    | "field_designator"
16459                    | "enumerator"
16460            )
16461        {
16462            return true;
16463        }
16464        if matches!(
16465            parent.kind(),
16466            "translation_unit"
16467                | "function_definition"
16468                | "compound_statement"
16469                | "declaration"
16470                | "field_declaration"
16471                | "parameter_declaration"
16472        ) {
16473            return false;
16474        }
16475        node = parent;
16476    }
16477    false
16478}
16479
16480/// Whether a parameter declaration belongs to the callable scope whose body can
16481/// contain references to it.
16482///
16483/// Error recovery can wrap a macro-decorated class body in a synthetic outer
16484/// `function_definition`. Merely finding any callable ancestor would then leak
16485/// parameters from member prototypes into later member bodies. Require the
16486/// parameter to be inside that definition's own declarator instead.
16487pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16488    let mut current = parameter.parent();
16489    while let Some(ancestor) = current {
16490        if ancestor.kind() == "lambda_expression" {
16491            return ancestor
16492                .child_by_field_name("declarator")
16493                .is_some_and(|declarator| {
16494                    declarator.start_byte() <= parameter.start_byte()
16495                        && parameter.end_byte() <= declarator.end_byte()
16496                });
16497        }
16498        if ancestor.kind() == "function_definition" {
16499            return ancestor
16500                .child_by_field_name("declarator")
16501                .is_some_and(|declarator| {
16502                    declarator.start_byte() <= parameter.start_byte()
16503                        && parameter.end_byte() <= declarator.end_byte()
16504                });
16505        }
16506        current = ancestor.parent();
16507    }
16508    false
16509}
16510
16511pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16512    let mut current = node.parent();
16513    while let Some(ancestor) = current {
16514        if matches!(
16515            ancestor.kind(),
16516            "parameter_declaration" | "optional_parameter_declaration"
16517        ) {
16518            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16519        }
16520        if matches!(
16521            ancestor.kind(),
16522            "function_definition" | "lambda_expression" | "compound_statement"
16523        ) {
16524            return false;
16525        }
16526        current = ancestor.parent();
16527    }
16528    false
16529}
16530
16531/// Walk tag-specifier ancestors until a type-reference context makes its name
16532/// an operand rather than a declaration. Callers retain their own terminal
16533/// declaration boundaries after this shared exclusion scan.
16534pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16535    specifier: Node<'_>,
16536) -> impl Iterator<Item = Node<'_>> {
16537    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16538        !matches!(
16539            ancestor.kind(),
16540            "type_descriptor"
16541                | "parameter_declaration"
16542                | "optional_parameter_declaration"
16543                | "template_argument_list"
16544                | "cast_expression"
16545        )
16546    })
16547}
16548
16549pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16550    if specifier.child_by_field_name("body").is_some() {
16551        return true;
16552    }
16553    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16554        match ancestor.kind() {
16555            "declaration" | "field_declaration" => {
16556                let mut cursor = ancestor.walk();
16557                return ancestor
16558                    .children_by_field_name("declarator", &mut cursor)
16559                    .next()
16560                    .is_none();
16561            }
16562            "translation_unit" => return true,
16563            _ => {}
16564        }
16565    }
16566    false
16567}
16568
16569pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16570    match node.kind() {
16571        "identifier"
16572        | "field_identifier"
16573        | "qualified_identifier"
16574        | "scoped_identifier"
16575        | "operator_name"
16576        | "destructor_name"
16577        | "literal_operator_name" => Some(node),
16578        "reference_declarator" | "parenthesized_declarator" => {
16579            node.named_child(0).and_then(declarator_name_node)
16580        }
16581        _ => node
16582            .child_by_field_name("declarator")
16583            .or_else(|| node.child_by_field_name("name"))
16584            .or_else(|| node.child_by_field_name("field"))
16585            .and_then(declarator_name_node),
16586    }
16587}
16588
16589/// Whether `candidate` occurs on the declarator path before its selected name.
16590pub fn declarator_path_contains_before_name(
16591    declarator: Node<'_>,
16592    name: Node<'_>,
16593    candidate: Node<'_>,
16594) -> bool {
16595    let mut current = Some(declarator);
16596    while let Some(node) = current {
16597        if same_node(node, candidate) {
16598            return true;
16599        }
16600        if same_node(node, name) {
16601            return false;
16602        }
16603        current = node
16604            .child_by_field_name("declarator")
16605            .or_else(|| node.child_by_field_name("name"))
16606            .or_else(|| node.child_by_field_name("field"));
16607    }
16608    false
16609}
16610
16611fn declarator_name_path_contains(
16612    declarator: Node<'_>,
16613    candidate: Node<'_>,
16614    allow_type_identifier: bool,
16615) -> bool {
16616    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16617        return false;
16618    };
16619    declarator_path_contains_before_name(declarator, name, candidate)
16620}
16621
16622fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16623    match node.kind() {
16624        "identifier"
16625        | "field_identifier"
16626        | "operator_name"
16627        | "destructor_name"
16628        | "literal_operator_name" => Some(node),
16629        "type_identifier" if allow_type_identifier => Some(node),
16630        _ => node
16631            .child_by_field_name("declarator")
16632            .or_else(|| node.child_by_field_name("name"))
16633            .or_else(|| node.child_by_field_name("field"))
16634            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16635    }
16636}
16637
16638/// True when `node` is a component of a larger structured type node whose outer
16639/// range is the single reference surfaced to callers.
16640pub fn is_nested_type_node(node: Node<'_>) -> bool {
16641    node.parent().is_some_and(|parent| {
16642        matches!(
16643            parent.kind(),
16644            "qualified_identifier" | "scoped_type_identifier" | "template_type"
16645        )
16646    })
16647}
16648
16649pub struct OutOfLineMemberDefinitionOwners<'tree> {
16650    pub owners: Vec<(Node<'tree>, CodeUnit)>,
16651    innermost: Option<(Node<'tree>, CodeUnit)>,
16652}
16653
16654impl OutOfLineMemberDefinitionOwners<'_> {
16655    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16656        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16657    }
16658}
16659
16660pub struct QualifiedOwnerComponents<'tree> {
16661    pub nodes: Vec<Node<'tree>>,
16662    pub names: Vec<String>,
16663    pub global: bool,
16664}
16665
16666/// True when each structured qualifier on the callable-name path has a real
16667/// `::` token. A macro-prefixed return type can make tree-sitter insert a
16668/// zero-width missing separator and parse `TYPE Result<T> method()` as the
16669/// false qualified declarator `Result<T>::method`.
16670pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16671    let mut stack = vec![node];
16672    let mut found_separator = false;
16673    while let Some(current) = stack.pop() {
16674        if !matches!(
16675            current.kind(),
16676            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16677        ) {
16678            continue;
16679        }
16680        let mut current_has_separator = false;
16681        for child in children_iter(current) {
16682            if child.kind() == "::" {
16683                if child.is_missing() {
16684                    return false;
16685                }
16686                current_has_separator = true;
16687                found_separator = true;
16688            }
16689        }
16690        if !current_has_separator {
16691            return false;
16692        }
16693        for field in ["scope", "name"] {
16694            if let Some(child) = current.child_by_field_name(field)
16695                && matches!(
16696                    child.kind(),
16697                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16698                )
16699            {
16700                stack.push(child);
16701            }
16702        }
16703    }
16704    found_separator
16705}
16706
16707pub fn qualified_owner_components<'tree>(
16708    node: Node<'tree>,
16709    source: &str,
16710) -> Option<QualifiedOwnerComponents<'tree>> {
16711    if !qualified_name_has_concrete_scope_separators(node) {
16712        return None;
16713    }
16714    let mut nodes = cpp_name_component_nodes(node)?;
16715    nodes.pop()?;
16716    if nodes.is_empty() {
16717        return None;
16718    }
16719    let names = nodes
16720        .iter()
16721        .map(|component| node_text(*component, source).to_string())
16722        .collect();
16723    Some(QualifiedOwnerComponents {
16724        nodes,
16725        names,
16726        global: is_globally_qualified_cpp_name(node),
16727    })
16728}
16729
16730pub fn out_of_line_member_definition_owner<'tree>(
16731    analyzer: &CppGraphSource<'_>,
16732    visibility: &VisibilityIndex<'_>,
16733    file: &ProjectFile,
16734    source: &str,
16735    node: Node<'tree>,
16736) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16737    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16738        || !has_ancestor_kind(node, "function_definition")
16739        || !is_function_declarator_name_root(node)
16740    {
16741        return None;
16742    }
16743    let qualified = qualified_owner_components(node, source)?;
16744    let lexical_scope = enclosing_namespace_components(node, source)?;
16745    let mut owners = Vec::new();
16746    let mut innermost = None;
16747
16748    for component_count in 1..=qualified.names.len() {
16749        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16750            .resolve_type_components_lexically(
16751                analyzer,
16752                file,
16753                &qualified.names[..component_count],
16754                qualified.global,
16755                &lexical_scope,
16756            )
16757            && !owners
16758                .iter()
16759                .any(|(_, existing)| same_visible_symbol(existing, &unit))
16760        {
16761            if component_count == qualified.names.len() {
16762                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16763            }
16764            owners.push((qualified.nodes[component_count - 1], unit));
16765        }
16766    }
16767
16768    // The C++ analyzer has already reconciled an indexed out-of-line callable
16769    // against the include-visible class table. Consult that canonical owner
16770    // chain only when ordinary lexical lookup could not recover the innermost
16771    // owner.  A one-segment qualifier is safe here only when the enclosing
16772    // indexed callable has an authoritative class owner and the parser's
16773    // namespace path is a (possibly sparse) subsequence of that owner path.
16774    // The latter is what lets macro-wrapped namespace sentinels recover a
16775    // missing `time_internal`/`cord_internal` component without guessing an
16776    // unrelated short name.
16777    if innermost.is_none() {
16778        let indexed_owner_components = visibility
16779            .indexed_enclosing_owner_scope(analyzer, file, node)
16780            .or_else(|| {
16781                // Retain the legacy rendered-name fallback for the existing
16782                // multi-segment path when an enclosing owner chain is not
16783                // available (for example, cache-loaded units without parent
16784                // links).  One-segment recovery must stay canonical-only.
16785                if qualified.names.len() <= 1 {
16786                    return None;
16787                }
16788                let range = Range {
16789                    start_byte: node.start_byte(),
16790                    end_byte: node.end_byte(),
16791                    start_line: node.start_position().row,
16792                    end_line: node.end_position().row,
16793                };
16794                let start = analyzer.enclosing_code_unit(file, &range)?;
16795                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16796                    brokk_bifrost_core::analyzer::Language::Cpp,
16797                    &cpp_name_for(&start),
16798                );
16799                components.pop();
16800                Some(components)
16801            });
16802        if let Some(indexed_owner_components) = indexed_owner_components
16803            && indexed_owner_components.len() > qualified.names.len()
16804            && indexed_owner_components.ends_with(&qualified.names)
16805            && indexed_namespace_path_is_recoverable(
16806                &lexical_scope,
16807                &indexed_owner_components,
16808                qualified.names.len(),
16809            )
16810            // A globally-qualified one-segment owner is an explicit request
16811            // for the top-level binding; do not reinterpret it as a missing
16812            // namespace component.  Existing multi-segment global lookups
16813            // retain their historical indexed recovery.
16814            && (qualified.names.len() > 1 || !qualified.global)
16815        {
16816            let namespace_count = indexed_owner_components.len() - qualified.names.len();
16817            for component_count in 1..=qualified.names.len() {
16818                let expected = &indexed_owner_components[..namespace_count + component_count];
16819                let owner_node = qualified.nodes[component_count - 1];
16820                for owner in visibility
16821                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16822                    .filter(|candidate| candidate.is_class())
16823                    .filter(|candidate| {
16824                        canonical_cpp_scope_components(candidate) == expected
16825                            && visibility.external_type_candidate_visible_in_context(
16826                                analyzer, file, candidate, node,
16827                            )
16828                    })
16829                {
16830                    if component_count == qualified.names.len() && innermost.is_none() {
16831                        innermost = Some((owner_node, owner.clone()));
16832                    }
16833                    if !owners
16834                        .iter()
16835                        .any(|(_, existing)| same_symbol(existing, owner))
16836                    {
16837                        owners.push((owner_node, owner.clone()));
16838                    }
16839                }
16840            }
16841        }
16842    }
16843    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16844}
16845
16846fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16847    let mut current = node;
16848    while let Some(parent) = current.parent() {
16849        if parent.kind() == "function_declarator" {
16850            return parent.child_by_field_name("declarator") == Some(current);
16851        }
16852        if matches!(
16853            parent.kind(),
16854            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16855        ) && parent.child_by_field_name("declarator") == Some(current)
16856        {
16857            current = parent;
16858            continue;
16859        }
16860        return false;
16861    }
16862    false
16863}
16864
16865pub fn append_cpp_name_components(
16866    node: Node<'_>,
16867    source: &str,
16868    out: &mut Vec<String>,
16869) -> Option<()> {
16870    out.extend(
16871        cpp_name_component_nodes(node)?
16872            .into_iter()
16873            .map(|component| node_text(component, source).to_string()),
16874    );
16875    Some(())
16876}
16877
16878/// The components of a C++ name with the source range of the token that
16879/// writes each one, outermost first.
16880fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16881    cpp_name_component_nodes(node).map(|components| {
16882        components
16883            .into_iter()
16884            .map(|component| {
16885                (
16886                    node_text(component, source).to_string(),
16887                    node_range(component),
16888                )
16889            })
16890            .collect()
16891    })
16892}
16893
16894pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16895    let mut components = Vec::new();
16896    append_cpp_name_components(node, source, &mut components)?;
16897    Some(components)
16898}
16899
16900/// Resolve a structured type spelling from an object-like macro replacement
16901/// when definition-site source order has no answer.
16902///
16903/// Macro replacement tokens are looked up where the macro is expanded, so a
16904/// type declared later in the defining header can still be their destination.
16905/// Without expanding every invocation, accept only one include-visible logical
16906/// class or alias whose structured path ends in the replacement components.
16907/// An ordinary lexical answer always takes precedence at the call site.
16908pub fn unique_macro_replacement_type_candidate(
16909    analyzer: &CppGraphSource<'_>,
16910    visibility: &VisibilityIndex<'_>,
16911    file: &ProjectFile,
16912    components: &[String],
16913) -> Option<CodeUnit> {
16914    let terminal = components.last()?;
16915    let mut candidates = Vec::new();
16916    for candidate in visibility
16917        .visible_identifier_candidates(file, terminal)
16918        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16919        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16920    {
16921        if !candidates
16922            .iter()
16923            .any(|existing| same_logical_symbol(existing, candidate))
16924        {
16925            candidates.push(candidate.clone());
16926        }
16927    }
16928    (candidates.len() == 1).then(|| candidates.remove(0))
16929}
16930
16931/// The base scopes named by member using-declarations for `member` in one
16932/// class source range.
16933///
16934/// The grammar supplies the qualified identifier and each component. Keep
16935/// this interpretation shared between forward overload lookup and inverse
16936/// owner routing rather than reparsing a rendered `Base::member` string at
16937/// either call site.
16938pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16939    let mut parser = Parser::new();
16940    if parser
16941        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16942        .is_err()
16943    {
16944        return Vec::new();
16945    }
16946    let Some(tree) = parser.parse(source, None) else {
16947        return Vec::new();
16948    };
16949    let mut scopes = Vec::new();
16950    let mut pending = vec![tree.root_node()];
16951    while let Some(node) = pending.pop() {
16952        if node.kind() == "using_declaration" {
16953            let Some(imported) = node.named_child(0) else {
16954                continue;
16955            };
16956            let Some(mut components) = cpp_type_name_components(imported, source) else {
16957                continue;
16958            };
16959            if components.pop().as_deref() == Some(member) && !components.is_empty() {
16960                scopes.push(components.join("::"));
16961            }
16962            continue;
16963        }
16964        push_named_children_reversed(node, &mut pending);
16965    }
16966    scopes
16967}
16968
16969/// Whether a structured using-declaration scope can name `qualified` as an
16970/// ancestor class. The boundary check prevents `Base` from matching
16971/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
16972pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16973    qualified == scope
16974        || qualified
16975            .strip_suffix(scope)
16976            .is_some_and(|prefix| prefix.ends_with("::"))
16977}
16978
16979/// Whether `node` is the direct structured type payload of a template
16980/// argument. This role remains meaningful even when a surrounding expression
16981/// is below tree-sitter recovery, because both the `template_argument_list`
16982/// and the `type_descriptor` retain their named fields.
16983pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16984    let Some(type_descriptor) = node.parent() else {
16985        return false;
16986    };
16987    if type_descriptor.kind() != "type_descriptor"
16988        || type_descriptor.child_by_field_name("type") != Some(node)
16989    {
16990        return false;
16991    }
16992    let Some(arguments) = type_descriptor.parent() else {
16993        return false;
16994    };
16995    if arguments.kind() != "template_argument_list" {
16996        return false;
16997    }
16998    arguments.parent().is_some_and(|parent| {
16999        matches!(parent.kind(), "template_type" | "template_function")
17000            && parent.child_by_field_name("arguments") == Some(arguments)
17001    })
17002}
17003
17004pub fn cpp_template_reference_arguments(
17005    mut node: Node<'_>,
17006    source: &str,
17007) -> Option<Vec<CppTemplateExpression>> {
17008    loop {
17009        match node.kind() {
17010            "template_type" | "template_function" => {
17011                let arguments = node.child_by_field_name("arguments")?;
17012                let mut cursor = arguments.walk();
17013                return Some(
17014                    arguments
17015                        .named_children(&mut cursor)
17016                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17017                        .map(|argument| CppTemplateExpression {
17018                            text: normalize_cpp_whitespace(node_text(argument, source)),
17019                            // One template term from a resolver query; see `ParentIndex::unindexed`.
17020                            term: cpp_template_term(
17021                                argument,
17022                                source,
17023                                &[],
17024                                &ParentIndex::unindexed(),
17025                            ),
17026                        })
17027                        .collect(),
17028                );
17029            }
17030            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17031                node = node
17032                    .child_by_field_name("name")
17033                    .or_else(|| node.child_by_field_name("type"))?;
17034            }
17035            _ => return None,
17036        }
17037    }
17038}
17039
17040fn cpp_reconcile_primary_template_parameters(
17041    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17042    preferred: &CodeUnit,
17043) -> Option<Vec<CppTemplateParameterMetadata>> {
17044    let canonical = candidates
17045        .iter()
17046        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17047    let mut merged = canonical
17048        .parameters
17049        .iter()
17050        .map(|parameter| CppTemplateParameterMetadata {
17051            name: parameter.name.clone(),
17052            kind: parameter.kind,
17053            variadic: parameter.variadic,
17054            default: None,
17055        })
17056        .collect::<Vec<_>>();
17057
17058    for (_, metadata) in candidates {
17059        if metadata.parameters.len() != merged.len() {
17060            return None;
17061        }
17062        let rename_bindings = metadata
17063            .parameters
17064            .iter()
17065            .zip(&merged)
17066            .map(|(parameter, canonical)| {
17067                (
17068                    parameter.name.clone(),
17069                    CppTemplateTerm::Parameter(canonical.name.clone()),
17070                )
17071            })
17072            .collect::<HashMap<_, _>>();
17073        for ((parameter, canonical), merged_parameter) in metadata
17074            .parameters
17075            .iter()
17076            .zip(&canonical.parameters)
17077            .zip(&mut merged)
17078        {
17079            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17080                return None;
17081            }
17082            let Some(default) = &parameter.default else {
17083                continue;
17084            };
17085            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17086            if let Some(existing) = &merged_parameter.default {
17087                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17088                    return None;
17089                }
17090            } else {
17091                merged_parameter.default = Some(CppTemplateExpression {
17092                    text: default.text.clone(),
17093                    term: normalized_term,
17094                });
17095            }
17096        }
17097    }
17098    Some(merged)
17099}
17100
17101pub fn cpp_bind_template_arguments(
17102    parameters: &[CppTemplateParameterMetadata],
17103    explicit_arguments: &[CppTemplateExpression],
17104) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17105    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17106    if variadic_index.is_some_and(|index| {
17107        index + 1 != parameters.len()
17108            || parameters[index + 1..]
17109                .iter()
17110                .any(|parameter| parameter.variadic)
17111    }) {
17112        return None;
17113    }
17114    let fixed_count = variadic_index.unwrap_or(parameters.len());
17115    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17116        return None;
17117    }
17118    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17119    let mut expanded = explicit_arguments[..explicit_fixed_count]
17120        .iter()
17121        .map(cpp_clone_template_expression_iterative)
17122        .collect::<Vec<_>>();
17123    let mut bindings = HashMap::default();
17124    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17125        bindings.insert(
17126            parameter.name.clone(),
17127            cpp_clone_template_term_iterative(&argument.term),
17128        );
17129    }
17130    for parameter in &parameters[explicit_fixed_count..fixed_count] {
17131        let default = parameter.default.as_ref()?;
17132        let term = cpp_substitute_template_term(&default.term, &bindings)?;
17133        bindings.insert(parameter.name.clone(), term.clone());
17134        expanded.push(CppTemplateExpression {
17135            text: default.text.clone(),
17136            term,
17137        });
17138    }
17139    if let Some(index) = variadic_index {
17140        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17141        expanded.extend(
17142            packed_arguments
17143                .iter()
17144                .map(cpp_clone_template_expression_iterative),
17145        );
17146        bindings.insert(
17147            parameters[index].name.clone(),
17148            CppTemplateTerm::Node {
17149                kind: "parameter_pack".to_string(),
17150                children: packed_arguments
17151                    .iter()
17152                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17153                    .collect(),
17154            },
17155        );
17156    }
17157    Some((expanded, bindings))
17158}
17159
17160fn cpp_specialization_matches(
17161    metadata: &CppTemplateMetadata,
17162    arguments: &[CppTemplateExpression],
17163) -> bool {
17164    cpp_specialization_terms_match(
17165        &metadata.parameters,
17166        &metadata.specialization_arguments,
17167        arguments,
17168    )
17169}
17170
17171fn cpp_specialization_terms_match(
17172    parameters: &[CppTemplateParameterMetadata],
17173    patterns: &[CppTemplateExpression],
17174    arguments: &[CppTemplateExpression],
17175) -> bool {
17176    if patterns.len() != arguments.len() {
17177        return false;
17178    }
17179    let parameter_names = parameters
17180        .iter()
17181        .map(|parameter| parameter.name.as_str())
17182        .collect::<HashSet<_>>();
17183    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17184    patterns.iter().zip(arguments).all(|(pattern, argument)| {
17185        cpp_unify_template_term(
17186            &pattern.term,
17187            &argument.term,
17188            &parameter_names,
17189            &mut bindings,
17190        )
17191    })
17192}
17193
17194fn cpp_specialization_more_specialized(
17195    candidate: &CppTemplateMetadata,
17196    other: &CppTemplateMetadata,
17197) -> bool {
17198    cpp_specialization_pattern_accepts(other, candidate)
17199        && !cpp_specialization_pattern_accepts(candidate, other)
17200}
17201
17202fn cpp_specialization_pattern_accepts(
17203    broader: &CppTemplateMetadata,
17204    narrower: &CppTemplateMetadata,
17205) -> bool {
17206    cpp_specialization_terms_match(
17207        &broader.parameters,
17208        &broader.specialization_arguments,
17209        &narrower.specialization_arguments,
17210    )
17211}
17212
17213pub fn cpp_substitute_template_term(
17214    term: &CppTemplateTerm,
17215    bindings: &HashMap<String, CppTemplateTerm>,
17216) -> Option<CppTemplateTerm> {
17217    enum Work<'a> {
17218        Visit(&'a CppTemplateTerm),
17219        Build { kind: String, child_count: usize },
17220    }
17221
17222    let mut work = vec![Work::Visit(term)];
17223    let mut substituted = Vec::new();
17224    while let Some(next) = work.pop() {
17225        match next {
17226            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17227                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17228            }
17229            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17230                substituted.push(CppTemplateTerm::Atom {
17231                    kind: kind.clone(),
17232                    text: text.clone(),
17233                });
17234            }
17235            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17236                work.push(Work::Build {
17237                    kind: kind.clone(),
17238                    child_count: children.len(),
17239                });
17240                work.extend(children.iter().rev().map(Work::Visit));
17241            }
17242            Work::Build { kind, child_count } => {
17243                let children = substituted.split_off(substituted.len() - child_count);
17244                substituted.push(CppTemplateTerm::Node { kind, children });
17245            }
17246        }
17247    }
17248    substituted.pop()
17249}
17250
17251pub fn cpp_substitute_template_arguments(
17252    arguments: &[CppTemplateExpression],
17253    bindings: &HashMap<String, CppTemplateTerm>,
17254) -> Option<Vec<CppTemplateExpression>> {
17255    let mut substituted = Vec::new();
17256    for argument in arguments {
17257        let CppTemplateTerm::Node { kind, children } = &argument.term else {
17258            substituted.push(CppTemplateExpression {
17259                text: argument.text.clone(),
17260                term: cpp_substitute_template_term(&argument.term, bindings)?,
17261            });
17262            continue;
17263        };
17264        if kind != "parameter_pack_expansion" {
17265            substituted.push(CppTemplateExpression {
17266                text: argument.text.clone(),
17267                term: cpp_substitute_template_term(&argument.term, bindings)?,
17268            });
17269            continue;
17270        }
17271        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17272            return None;
17273        };
17274        if ellipsis != "..." {
17275            return None;
17276        }
17277
17278        let mut pack_names = Vec::new();
17279        let mut work = vec![pattern];
17280        while let Some(term) = work.pop() {
17281            match term {
17282                CppTemplateTerm::Parameter(name)
17283                    if matches!(
17284                        bindings.get(name),
17285                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
17286                    ) =>
17287                {
17288                    if !pack_names.contains(name) {
17289                        pack_names.push(name.clone());
17290                    }
17291                }
17292                CppTemplateTerm::Node { children, .. } => work.extend(children),
17293                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
17294            }
17295        }
17296        let first_pack = pack_names.first()?;
17297        let CppTemplateTerm::Node {
17298            children: first_elements,
17299            ..
17300        } = bindings.get(first_pack)?
17301        else {
17302            return None;
17303        };
17304        let pack_len = first_elements.len();
17305        for pack_name in &pack_names {
17306            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17307                return None;
17308            };
17309            if children.len() != pack_len {
17310                return None;
17311            }
17312        }
17313        for index in 0..pack_len {
17314            let mut element_bindings = bindings.clone();
17315            for pack_name in &pack_names {
17316                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17317                    return None;
17318                };
17319                element_bindings.insert(
17320                    pack_name.clone(),
17321                    cpp_clone_template_term_iterative(&children[index]),
17322                );
17323            }
17324            substituted.push(CppTemplateExpression {
17325                text: argument.text.clone(),
17326                term: cpp_substitute_template_term(pattern, &element_bindings)?,
17327            });
17328        }
17329    }
17330    Some(substituted)
17331}
17332
17333fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17334    enum Work<'a> {
17335        Visit(&'a CppTemplateTerm),
17336        Build { kind: String, child_count: usize },
17337    }
17338
17339    let mut work = vec![Work::Visit(term)];
17340    let mut cloned = Vec::new();
17341    while let Some(next) = work.pop() {
17342        match next {
17343            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17344                cloned.push(CppTemplateTerm::Parameter(name.clone()));
17345            }
17346            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17347                cloned.push(CppTemplateTerm::Atom {
17348                    kind: kind.clone(),
17349                    text: text.clone(),
17350                });
17351            }
17352            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17353                work.push(Work::Build {
17354                    kind: kind.clone(),
17355                    child_count: children.len(),
17356                });
17357                work.extend(children.iter().rev().map(Work::Visit));
17358            }
17359            Work::Build { kind, child_count } => {
17360                let children = cloned.split_off(cloned.len() - child_count);
17361                cloned.push(CppTemplateTerm::Node { kind, children });
17362            }
17363        }
17364    }
17365    cloned
17366        .pop()
17367        .expect("template term traversal emits one root")
17368}
17369
17370fn cpp_clone_template_expression_iterative(
17371    expression: &CppTemplateExpression,
17372) -> CppTemplateExpression {
17373    CppTemplateExpression {
17374        text: expression.text.clone(),
17375        term: cpp_clone_template_term_iterative(&expression.term),
17376    }
17377}
17378
17379pub fn cpp_unify_template_term(
17380    pattern: &CppTemplateTerm,
17381    argument: &CppTemplateTerm,
17382    parameters: &HashSet<&str>,
17383    bindings: &mut HashMap<String, CppTemplateTerm>,
17384) -> bool {
17385    let mut work = vec![(pattern, argument)];
17386    while let Some((pattern, argument)) = work.pop() {
17387        match pattern {
17388            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17389                if let Some(bound) = bindings.get(name) {
17390                    if !cpp_template_terms_equal(bound, argument) {
17391                        return false;
17392                    }
17393                } else {
17394                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17395                }
17396            }
17397            CppTemplateTerm::Atom {
17398                kind: pattern_kind,
17399                text: pattern_text,
17400            } => {
17401                if !matches!(
17402                    argument,
17403                    CppTemplateTerm::Atom { kind, text }
17404                        if kind == pattern_kind && text == pattern_text
17405                ) {
17406                    return false;
17407                }
17408            }
17409            CppTemplateTerm::Node {
17410                kind: pattern_kind,
17411                children: pattern_children,
17412            } => {
17413                let CppTemplateTerm::Node { kind, children } = argument else {
17414                    return false;
17415                };
17416                if kind != pattern_kind || children.len() != pattern_children.len() {
17417                    return false;
17418                }
17419                work.extend(pattern_children.iter().zip(children).rev());
17420            }
17421            CppTemplateTerm::Parameter(_) => return false,
17422        }
17423    }
17424    true
17425}
17426
17427fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17428    let mut work = vec![(left, right)];
17429    while let Some((left, right)) = work.pop() {
17430        match (left, right) {
17431            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17432                if left != right {
17433                    return false;
17434                }
17435            }
17436            (
17437                CppTemplateTerm::Atom {
17438                    kind: left_kind,
17439                    text: left_text,
17440                },
17441                CppTemplateTerm::Atom {
17442                    kind: right_kind,
17443                    text: right_text,
17444                },
17445            ) => {
17446                if left_kind != right_kind || left_text != right_text {
17447                    return false;
17448                }
17449            }
17450            (
17451                CppTemplateTerm::Node {
17452                    kind: left_kind,
17453                    children: left_children,
17454                },
17455                CppTemplateTerm::Node {
17456                    kind: right_kind,
17457                    children: right_children,
17458                },
17459            ) => {
17460                if left_kind != right_kind || left_children.len() != right_children.len() {
17461                    return false;
17462                }
17463                work.extend(left_children.iter().zip(right_children).rev());
17464            }
17465            _ => return false,
17466        }
17467    }
17468    true
17469}
17470
17471pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17472    let mut components = Vec::new();
17473    let mut stack = vec![node];
17474    while let Some(current) = stack.pop() {
17475        match current.kind() {
17476            "identifier"
17477            | "field_identifier"
17478            | "namespace_identifier"
17479            | "type_identifier"
17480            | "operator_name"
17481            | "destructor_name" => components.push(current),
17482            "template_type" | "template_function" => {
17483                stack.push(current.child_by_field_name("name")?);
17484            }
17485            "dependent_name" => stack.push(current.named_child(0)?),
17486            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17487                stack.push(current.child_by_field_name("name")?);
17488                if let Some(scope) = current.child_by_field_name("scope") {
17489                    stack.push(scope);
17490                }
17491            }
17492            "nested_namespace_specifier" => {
17493                for index in (0..current.named_child_count()).rev() {
17494                    stack.push(current.named_child(index)?);
17495                }
17496            }
17497            _ => return None,
17498        }
17499    }
17500    Some(components)
17501}
17502
17503pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17504    node.child_by_field_name("scope").is_none()
17505        && node.child(0).is_some_and(|child| child.kind() == "::")
17506}
17507
17508fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17509    let mut namespaces = Vec::new();
17510    for parent in enclosing_cpp_namespace_nodes(node) {
17511        if let Some(name) = parent.child_by_field_name("name") {
17512            let mut components = Vec::new();
17513            append_cpp_name_components(name, source, &mut components)?;
17514            namespaces.push(components);
17515        }
17516    }
17517    Some(namespaces.into_iter().flatten().collect())
17518}
17519
17520/// Whether a parser-derived namespace path can be reconciled with an indexed
17521/// owner scope without inventing an unrelated short-name binding.
17522///
17523/// Macro namespace sentinels can make tree-sitter omit one or more namespace
17524/// definitions from the ancestor chain. Preserve the order of every namespace
17525/// that did survive parsing, but allow indexed components between them. An
17526/// empty path is accepted only when the declarator itself supplies a nested
17527/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
17528/// owner chain, that suffix is structural evidence that a namespace was lost.
17529/// A one-segment owner at the translation-unit root remains insufficient.
17530fn indexed_namespace_path_is_recoverable(
17531    lexical_scope: &[String],
17532    indexed_owner_scope: &[String],
17533    explicit_owner_component_count: usize,
17534) -> bool {
17535    if lexical_scope.is_empty() {
17536        return explicit_owner_component_count > 1;
17537    }
17538    if lexical_scope.len() >= indexed_owner_scope.len() {
17539        return false;
17540    }
17541    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17542}
17543
17544pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17545
17546/// Whether a declaration type is initialized with a pointer cast.
17547///
17548/// This structured shape has an independent qualified occurrence in addition
17549/// to the cast descriptor below it. Other declarations must keep their normal
17550/// full-range occurrence only.
17551pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17552    let mut current = Some(node);
17553    while let Some(candidate) = current {
17554        if candidate.kind() == "declaration" {
17555            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17556                return false;
17557            }
17558            let mut cursor = candidate.walk();
17559            return candidate.named_children(&mut cursor).any(|child| {
17560                child.kind() == "init_declarator"
17561                    && child
17562                        .child_by_field_name("value")
17563                        .is_some_and(|value| value.kind() == "cast_expression")
17564            });
17565        }
17566        current = candidate.parent();
17567    }
17568    false
17569}
17570
17571#[derive(Clone, Copy, PartialEq, Eq)]
17572pub(crate) enum QualifiedAliasReferenceKind {
17573    Ordinary,
17574    ConstructorWithExpressionArgument,
17575    ExhaustiveTemplate,
17576}
17577
17578/// Whether a qualified alias reference preserves the requested target.
17579///
17580/// The complete qualified spelling and its terminal identifier are both valid
17581/// occurrences when the visible alias path is structurally proven to name the
17582/// target. Template aliases use their bound arguments; ordinary aliases use
17583/// their structured primary chain.
17584pub(crate) fn qualified_alias_reference_preserves_target(
17585    node: Node<'_>,
17586    target: &CodeUnit,
17587    analyzer: &CppGraphSource<'_>,
17588    visibility: &VisibilityIndex<'_>,
17589    file: &ProjectFile,
17590    source: &str,
17591) -> Option<QualifiedAliasReferenceKind> {
17592    if !matches!(
17593        node.kind(),
17594        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17595    ) {
17596        return None;
17597    }
17598    let components = cpp_type_name_components(node, source)?;
17599    let name = components.last()?;
17600    analyzer.type_alias_provider().and_then(|provider| {
17601        visibility
17602            .visible_identifier_candidates(file, name)
17603            .find_map(|candidate| {
17604                let proof = provider.is_type_alias(candidate)
17605                    && canonical_cpp_scope_components(candidate) == components
17606                    && visibility.external_type_candidate_visible_in_context(
17607                        analyzer, file, candidate, node,
17608                    )
17609                    && match cpp_template_reference_arguments(node, source) {
17610                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
17611                            analyzer, file, candidate, &arguments, target,
17612                        ),
17613                        None => visibility.structured_alias_primary_preserves_target(
17614                            analyzer, file, candidate, target,
17615                        ),
17616                    };
17617                proof.then(|| {
17618                    if cpp_template_reference_arguments(node, source).is_some()
17619                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
17620                            analyzer, file, candidate,
17621                        )
17622                    {
17623                        QualifiedAliasReferenceKind::ExhaustiveTemplate
17624                    } else if qualified_alias_constructor_has_expression_argument(node)
17625                        || qualified_alias_local_constructor_declaration(node)
17626                    {
17627                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17628                    } else {
17629                        QualifiedAliasReferenceKind::Ordinary
17630                    }
17631                })
17632            })
17633    })
17634}
17635
17636pub(crate) fn qualified_alias_reference_requires_terminal(
17637    reference: Option<QualifiedAliasReferenceKind>,
17638) -> bool {
17639    matches!(
17640        reference,
17641        Some(
17642            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17643                | QualifiedAliasReferenceKind::ExhaustiveTemplate
17644        )
17645    )
17646}
17647
17648fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17649    let Some(declaration) = node.parent().filter(|parent| {
17650        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17651    }) else {
17652        return false;
17653    };
17654    let mut cursor = declaration.walk();
17655    declaration.named_children(&mut cursor).any(|child| {
17656        child.kind() == "init_declarator"
17657            && child
17658                .child_by_field_name("value")
17659                .filter(|value| value.kind() == "argument_list")
17660                .is_some_and(|arguments| {
17661                    let mut cursor = arguments.walk();
17662                    arguments.named_children(&mut cursor).any(|argument| {
17663                        let is_parameter = matches!(
17664                            argument.kind(),
17665                            "parameter_declaration" | "optional_parameter_declaration"
17666                        );
17667                        if is_parameter {
17668                            argument
17669                                .child_by_field_name("type")
17670                                .is_some_and(|type_node| {
17671                                    type_node.kind() == "type_identifier"
17672                                        && argument.child_by_field_name("declarator").is_none()
17673                                })
17674                        } else {
17675                            !argument.kind().ends_with("_literal")
17676                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
17677                        }
17678                    })
17679                })
17680    })
17681}
17682
17683/// Tree-sitter represents a local C++ direct construction such as
17684/// `Alias value(argument)` as a function declarator. Restrict that recovery to
17685/// declarations inside a compound statement so namespace-scope function
17686/// declarations with the same qualified return type stay full-range only.
17687fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17688    let Some(declaration) = node.parent().filter(|parent| {
17689        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17690    }) else {
17691        return false;
17692    };
17693    if declaration
17694        .parent()
17695        .is_none_or(|parent| parent.kind() != "compound_statement")
17696    {
17697        return false;
17698    }
17699    let mut cursor = declaration.walk();
17700    declaration
17701        .named_children(&mut cursor)
17702        .any(|child| child.kind() == "function_declarator")
17703}
17704
17705/// Return the terminal identifier represented by a callable or type callee.
17706///
17707/// Qualified, scoped, template, and field wrappers are traversed through their
17708/// grammar fields so both function calls and type constructions emit the token
17709/// that names the referenced declaration.
17710pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17711    loop {
17712        let next = match node.kind() {
17713            "qualified_identifier"
17714            | "scoped_identifier"
17715            | "template_method"
17716            | "template_function"
17717            | "template_type" => node.child_by_field_name("name"),
17718            "field_expression" => node.child_by_field_name("field"),
17719            _ => None,
17720        };
17721        let Some(next) = next else {
17722            return node;
17723        };
17724        node = next;
17725    }
17726}
17727
17728#[derive(Clone, Copy)]
17729pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17730    pub receiver: Node<'tree>,
17731    pub member: Node<'tree>,
17732    pub arity: usize,
17733}
17734
17735/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
17736/// nested relational expressions instead of a `template_method` call.
17737///
17738/// The recovery uses only grammar fields: the selected field must be the left
17739/// side of `<`, that expression must be the left side of `>`, and the right
17740/// side of `>` must be the parenthesized call arguments. Semantic callers must
17741/// additionally prove the receiver owner and the member's template status.
17742pub fn recovered_relational_template_member_call(
17743    field: Node<'_>,
17744) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17745    if field.kind() != "field_expression" {
17746        return None;
17747    }
17748    let receiver = field
17749        .child_by_field_name("argument")
17750        .or_else(|| field.child_by_field_name("object"))?;
17751    let member = field.child_by_field_name("field")?;
17752    let less = field.parent()?;
17753    if less.kind() != "binary_expression"
17754        || less.child_by_field_name("left") != Some(field)
17755        || less
17756            .child_by_field_name("operator")
17757            .is_none_or(|operator| operator.kind() != "<")
17758        || less.child_by_field_name("right").is_none()
17759    {
17760        return None;
17761    }
17762    let greater = less.parent()?;
17763    if greater.kind() != "binary_expression"
17764        || greater.child_by_field_name("left") != Some(less)
17765        || greater
17766            .child_by_field_name("operator")
17767            .is_none_or(|operator| operator.kind() != ">")
17768    {
17769        return None;
17770    }
17771    let arguments = greater.child_by_field_name("right")?;
17772    if arguments.kind() != "parenthesized_expression" {
17773        return None;
17774    }
17775    let arity = parenthesized_call_argument_arity(arguments)?;
17776    Some(RecoveredRelationalTemplateMemberCall {
17777        receiver,
17778        member,
17779        arity,
17780    })
17781}
17782
17783fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17784    let expression = arguments.named_child(0)?;
17785    if expression.kind() != "comma_expression" {
17786        return Some(1);
17787    }
17788    let mut arity = 0usize;
17789    let mut stack = vec![expression];
17790    while let Some(node) = stack.pop() {
17791        if node.kind() == "comma_expression" {
17792            stack.push(node.child_by_field_name("right")?);
17793            stack.push(node.child_by_field_name("left")?);
17794        } else {
17795            arity += 1;
17796        }
17797    }
17798    Some(arity)
17799}
17800
17801/// Whether `node` is part of a call's callee expression, walking only through
17802/// the grammar wrappers that can structurally contain that callee.
17803pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17804    while let Some(parent) = node.parent() {
17805        match parent.kind() {
17806            "call_expression" => {
17807                return parent
17808                    .child_by_field_name("function")
17809                    .or_else(|| parent.named_child(0))
17810                    == Some(node);
17811            }
17812            "qualified_identifier"
17813            | "scoped_identifier"
17814            | "template_function"
17815            | "template_type"
17816            | "field_expression" => node = parent,
17817            _ => return false,
17818        }
17819    }
17820    false
17821}
17822
17823/// Expand a C++ type-name component to the complete written reference that
17824/// owns it. Qualified path wrappers that stop before a later component remain
17825/// separate, so a type used as an owner keeps its prefix range.
17826pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17827    loop {
17828        let Some(parent) = node.parent() else {
17829            return node;
17830        };
17831        if parent.kind() == "template_type"
17832            && parent
17833                .child_by_field_name("name")
17834                .is_some_and(|name| same_node(name, node))
17835        {
17836            node = parent;
17837            continue;
17838        }
17839        if matches!(
17840            parent.kind(),
17841            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17842        ) && (parent
17843            .child_by_field_name("scope")
17844            .is_some_and(|scope| same_node(scope, node))
17845            || parent
17846                .child_by_field_name("name")
17847                .is_some_and(|name| same_node(name, node)))
17848            && parent.end_byte() == node.end_byte()
17849        {
17850            node = parent;
17851            continue;
17852        }
17853        return node;
17854    }
17855}
17856
17857pub fn normalize_type_text(value: &str) -> String {
17858    strip_tag_type_prefix(
17859        normalize_cpp_whitespace(value)
17860            .trim_start_matches("const ")
17861            .trim_end_matches('*')
17862            .trim_end_matches('&')
17863            .trim(),
17864    )
17865    .to_string()
17866}
17867
17868use crate::declarations::strip_tag_type_prefix;
17869
17870pub fn normalize_reference_name(value: &str) -> Option<String> {
17871    let normalized = normalize_cpp_reference_text(value);
17872    (!normalized.is_empty()).then_some(normalized)
17873}
17874
17875pub fn normalize_cpp_reference_text(value: &str) -> String {
17876    let mut text = normalize_cpp_whitespace(value)
17877        .trim_start_matches("new ")
17878        .trim()
17879        .to_string();
17880    if let Some(index) = text.find(['(', '{']) {
17881        text.truncate(index);
17882    }
17883    if let Some(index) = text.find('<') {
17884        text.truncate(index);
17885    }
17886    let normalized = text
17887        .trim()
17888        .trim_start_matches("const ")
17889        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17890        .trim_matches(':')
17891        .trim();
17892    strip_tag_type_prefix(normalized).to_string()
17893}
17894
17895pub fn cpp_name_for(unit: &CodeUnit) -> String {
17896    let short = unit.short_name().replace(['.', '$'], "::");
17897    if unit.package_name().is_empty() {
17898        short
17899    } else {
17900        format!("{}::{}", unit.package_name(), short)
17901    }
17902}
17903
17904/// Render an indexed C++ qualified name from its authoritative FqName
17905/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
17906/// that belong to a template argument (for example `Args...`).
17907fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17908    let fq = unit.fq();
17909    if fq.is_empty() {
17910        return None;
17911    }
17912    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17913    Some(
17914        fq.segments()
17915            .iter()
17916            .map(|&segment| interner.resolve(segment).0)
17917            .collect::<Vec<_>>()
17918            .join("::"),
17919    )
17920}
17921
17922fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17923    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17924        || unit.fq().is_empty() && cpp_name_for(unit) == expected
17925}
17926
17927/// Return the indexed C++ owner scope without reparsing its rendered name.
17928///
17929/// Template spellings are opaque within an indexed `FqName` segment.  In
17930/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
17931/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
17932/// through `parse_symbol_path` would mistake those dots for component
17933/// separators.  Cache-loaded/legacy units may still have an empty structured
17934/// name, so retain the parser only as that explicit fallback.
17935pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17936    let fq = unit.fq();
17937    if !fq.is_empty() {
17938        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17939        let scope = fq
17940            .segments()
17941            .iter()
17942            .filter_map(|&segment| {
17943                let (text, kind) = interner.resolve(segment);
17944                matches!(
17945                    kind,
17946                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17947                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17948                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17949                )
17950                .then(|| text.to_string())
17951            })
17952            .collect();
17953        return scope;
17954    }
17955    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17956        brokk_bifrost_core::analyzer::Language::Cpp,
17957        &cpp_name_for(unit),
17958    )
17959}
17960
17961// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
17962// (not the substring "->"), which deliberately reduces an `operator->`-style
17963// terminal segment to an empty tail rather than keeping it intact; the shared
17964// structured splitter's cpp operator-token merge would keep `operator->`
17965// whole instead, changing this function's result — `name_matches_callable`'s
17966// `expected.starts_with("operator")` fallback exists specifically to
17967// compensate for that reduction, and a pinned regression test
17968// (`operator-> must not be reduced with terminal_name-style punctuation
17969// splitting`) asserts today's char-class behavior. Not equivalence-provable;
17970// revisit alongside that pinned test if it is ever relaxed.
17971pub fn terminal_name(value: &str) -> &str {
17972    value
17973        .rsplit("::")
17974        .next()
17975        .unwrap_or(value)
17976        .rsplit(['.', '-', '>'])
17977        .next()
17978        .unwrap_or(value)
17979        .trim()
17980}
17981
17982pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17983    terminal_name(&normalize_cpp_reference_text(value)) == expected
17984}
17985
17986pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17987    name_matches_terminal(value, expected)
17988        || expected.starts_with("operator")
17989            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17990}
17991
17992pub fn name_mentions(value: &str, expected: &str) -> bool {
17993    normalize_cpp_reference_text(value)
17994        .split("::")
17995        .any(|part| part == expected)
17996}
17997
17998pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17999    let cpp_name = cpp_name_for(unit);
18000    if reference.contains("::") {
18001        return reference == cpp_name;
18002    }
18003    reference == cpp_name
18004        || terminal_name(reference) == unit.identifier()
18005            && (unit.package_name().is_empty() || reference == unit.identifier())
18006}
18007
18008pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18009    match kind {
18010        TargetKind::Type
18011        | TargetKind::Constructor
18012        | TargetKind::Method
18013        | TargetKind::MemberField => true,
18014        TargetKind::FreeFunction => unit.is_function(),
18015        TargetKind::GlobalField => unit.is_field(),
18016        TargetKind::Macro => unit.is_macro(),
18017    }
18018}
18019
18020fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18021    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18022    let target_name = cpp_name_for(target);
18023    if normalized.contains("::") {
18024        return normalized == target_name;
18025    }
18026    if let Some(namespace) = alias.namespace.as_deref() {
18027        return namespace_prefixes(namespace)
18028            .into_iter()
18029            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18030    }
18031    target.package_name().is_empty() && normalized == target.identifier()
18032}
18033
18034/// The declared return type text of a C++ function unit, with leading declaration specifiers
18035/// stripped, e.g. `T*` for `T* operator->()`.
18036pub fn cpp_function_return_type_text(
18037    analyzer: &CppGraphSource<'_>,
18038    function: &CodeUnit,
18039) -> Option<String> {
18040    let metadata = analyzer.signature_metadata(function);
18041    let first = metadata.first()?.return_type_text()?;
18042    metadata
18043        .iter()
18044        .all(|metadata| metadata.return_type_text() == Some(first))
18045        .then(|| first.to_owned())
18046}
18047
18048pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18049    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
18050    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
18051    // the same string `default_parent_fq_name`/`fq().parent()` would render:
18052    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
18053    // `::`) between a trailing `Package` segment and a following `Type`
18054    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
18055    // popping the unit's own `fq()` segment would NOT reproduce this
18056    // fully-`::`-joined string. Left as a split on the locally-built
18057    // all-colon string rather than the unit's structured name.
18058    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18059        namespace
18060            .strip_prefix("anonymous_namespace::")
18061            .unwrap_or(namespace)
18062            .to_string()
18063    })
18064}
18065
18066/// The namespace lookup tiers, from the innermost namespace outward.
18067pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18068    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
18069    // non-`::` separator already converted to `::`, so re-tokenizing it with
18070    // the shared structured splitter and progressively popping the last
18071    // component reproduces the `rsplit_once("::")` outward walk exactly (same
18072    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
18073    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18074        brokk_bifrost_core::analyzer::Language::Cpp,
18075        namespace,
18076    );
18077    let mut prefixes = Vec::new();
18078    while !parts.is_empty() {
18079        prefixes.push(parts.join("::"));
18080        parts.pop();
18081    }
18082    prefixes
18083}
18084
18085fn nearest_namespace_candidates(
18086    candidates: Vec<CodeUnit>,
18087    normalized: &str,
18088    lexical_namespace: Option<&str>,
18089) -> Vec<CodeUnit> {
18090    if normalized.contains("::") {
18091        return candidates;
18092    }
18093    if let Some(namespace) = lexical_namespace {
18094        for prefix in namespace_prefixes(namespace) {
18095            let scoped = candidates
18096                .iter()
18097                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18098                .cloned()
18099                .collect::<Vec<_>>();
18100            if !scoped.is_empty() {
18101                return scoped;
18102            }
18103        }
18104    }
18105    candidates
18106        .into_iter()
18107        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18108        .collect()
18109}
18110
18111pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18112    let mut namespaces = Vec::new();
18113    for parent in enclosing_cpp_namespace_nodes(node) {
18114        if let Some(name) = parent.child_by_field_name("name") {
18115            let namespace = normalize_cpp_reference_text(node_text(name, source));
18116            if !namespace.is_empty() {
18117                namespaces.push(namespace);
18118            }
18119        }
18120    }
18121    if namespaces.is_empty() {
18122        None
18123    } else {
18124        Some(namespaces.join("::"))
18125    }
18126}
18127
18128/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
18129/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
18130/// globals rather than members.
18131pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18132    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18133}
18134
18135fn type_owner_resolution(
18136    analyzer: &CppGraphSource<'_>,
18137    code_unit: &CodeUnit,
18138) -> Option<ResolvedTypeOwner> {
18139    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18140}
18141
18142fn target_type_owner_resolution(
18143    analyzer: &CppGraphSource<'_>,
18144    code_unit: &CodeUnit,
18145) -> Option<ResolvedTypeOwner> {
18146    match type_owner_resolution(analyzer, code_unit) {
18147        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18148        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18149    }
18150}
18151
18152/// Recover method identity for an indexed out-of-line definition when the
18153/// ordinary parent edge is absent. Prefer the unique include-visible forward
18154/// declaration, then classify exact-FQN class declarations elsewhere in the
18155/// workspace. A unique complete declaration wins; otherwise multiple forward
18156/// declarations are one owner only when they all share one logical identity.
18157/// The qualified callable FQN proves that owner spelling even when its defining
18158/// header is outside the scan file's include closure, while unknown or competing
18159/// complete declarations remain ambiguous.
18160/// This is deliberately target-only: canonical declaration resolution must
18161/// continue to prefer the callable definition rather than replacing it with
18162/// the recovered owner.
18163fn target_forward_owner_resolution(
18164    analyzer: &CppGraphSource<'_>,
18165    code_unit: &CodeUnit,
18166) -> Option<ResolvedTypeOwner> {
18167    if !code_unit.is_function() {
18168        return None;
18169    }
18170    // A top-level free function has no owner at all, and `FqName::parent`
18171    // answers the empty name rather than `None` for a one-segment identity.
18172    // `default_parent_fq_name`, which this replaced, filtered that case out;
18173    // asking the relational store for the empty name is a batch error that
18174    // fails the whole target frontier.
18175    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18176    let cpp = analyzer.cpp?;
18177    let mut visible_files = HashSet::default();
18178    collect_include_closure(
18179        analyzer,
18180        cpp.include_target_index(),
18181        code_unit.source(),
18182        &mut visible_files,
18183        None,
18184    );
18185    let candidates = analyzer.workspace_definitions().exact(&owner_name);
18186    let visible_candidates = candidates
18187        .iter()
18188        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18189        .cloned()
18190        .collect::<Vec<_>>();
18191    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18192        DirectOwnerResolution::UniqueFull(unit) => {
18193            return Some(ResolvedTypeOwner {
18194                unit,
18195                is_forward_declaration: false,
18196            });
18197        }
18198        DirectOwnerResolution::ForwardsOnly(forwards) => {
18199            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18200                unit: forwards.into_iter().next().unwrap(),
18201                is_forward_declaration: true,
18202            });
18203        }
18204        DirectOwnerResolution::Ambiguous => return None,
18205        DirectOwnerResolution::None => {}
18206    }
18207
18208    let candidates = candidates
18209        .into_iter()
18210        .filter(|candidate| candidate.is_class())
18211        .collect::<Vec<_>>();
18212    let (unit, is_forward_declaration) =
18213        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18214            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18215            DirectOwnerResolution::ForwardsOnly(forwards) => {
18216                (unique_logical_forward_owner(forwards)?, true)
18217            }
18218            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18219        };
18220    Some(ResolvedTypeOwner {
18221        unit,
18222        is_forward_declaration,
18223    })
18224}
18225
18226pub fn precise_parent_of(
18227    analyzer: &CppGraphSource<'_>,
18228    visibility: &VisibilityIndex<'_>,
18229    code_unit: &CodeUnit,
18230) -> Option<CodeUnit> {
18231    visibility.cached_precise_parent_of(analyzer, code_unit)
18232}
18233
18234fn precise_parent_resolution(
18235    analyzer: &CppGraphSource<'_>,
18236    code_unit: &CodeUnit,
18237) -> Option<ResolvedTypeOwner> {
18238    #[cfg(any(test, feature = "test-support"))]
18239    if let Some(cpp) = analyzer.cpp {
18240        cpp.record_cpp_parent_resolution_for_test();
18241    }
18242    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18243        return Some(ResolvedTypeOwner {
18244            unit,
18245            is_forward_declaration: false,
18246        });
18247    }
18248    let fallback = analyzer.parent_of(code_unit);
18249    if !code_unit.owner_is_type_scope() {
18250        return fallback.map(|unit| ResolvedTypeOwner {
18251            unit,
18252            is_forward_declaration: false,
18253        });
18254    }
18255    let owner_fq = code_unit
18256        .fq()
18257        .parent()
18258        .expect("a unit with an owner identifier has a structured parent");
18259    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18260    match same_source_owner(analyzer, code_unit, &owner_candidates) {
18261        DirectOwnerResolution::UniqueFull(owner) => {
18262            return Some(ResolvedTypeOwner {
18263                unit: owner,
18264                is_forward_declaration: false,
18265            });
18266        }
18267        DirectOwnerResolution::Ambiguous => return None,
18268        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18269    }
18270    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18271        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18272            unit: owner,
18273            is_forward_declaration: false,
18274        }),
18275        DirectOwnerResolution::Ambiguous => None,
18276        DirectOwnerResolution::ForwardsOnly(forwards) => {
18277            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18278                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18279                    unit: owner,
18280                    is_forward_declaration: false,
18281                }),
18282                FullOwnerResolution::None => {
18283                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
18284                        unit,
18285                        is_forward_declaration: true,
18286                    })
18287                }
18288                FullOwnerResolution::Ambiguous => None,
18289            }
18290        }
18291        DirectOwnerResolution::None => {
18292            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18293                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18294                    unit: owner,
18295                    is_forward_declaration: false,
18296                }),
18297                FullOwnerResolution::Ambiguous => None,
18298                FullOwnerResolution::None => fallback
18299                    .filter(|parent| {
18300                        parent.source() == code_unit.source()
18301                            && parent.fq() == &owner_fq
18302                            && (!parent.is_class()
18303                                || cpp_class_declaration_strength(analyzer, parent)
18304                                    == CppClassDeclarationStrength::Full)
18305                    })
18306                    .map(|unit| ResolvedTypeOwner {
18307                        unit,
18308                        is_forward_declaration: false,
18309                    }),
18310            }
18311        }
18312    }
18313}
18314
18315fn exact_structural_type_parent(
18316    analyzer: &CppGraphSource<'_>,
18317    code_unit: &CodeUnit,
18318) -> Option<CodeUnit> {
18319    if !code_unit.is_function() && !code_unit.is_field() {
18320        return None;
18321    }
18322    let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; // fqname-M4: package-less short_name owner used as an encoded key; fq.parent() would render the `::`-headed package-qualified owner
18323    let cpp = analyzer.cpp?;
18324    let parent = cpp.structural_parent_of(code_unit)?;
18325    (!parent.is_module()
18326        && parent.source() == code_unit.source()
18327        && parent.package_name() == code_unit.package_name()
18328        && parent.short_name() == encoded_owner)
18329        .then_some(parent)
18330}
18331
18332fn same_source_owner(
18333    analyzer: &CppGraphSource<'_>,
18334    code_unit: &CodeUnit,
18335    owner_candidates: &[CodeUnit],
18336) -> DirectOwnerResolution {
18337    let candidates = owner_candidates
18338        .iter()
18339        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18340        .cloned()
18341        .collect::<Vec<_>>();
18342    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18343    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18344}
18345
18346fn visible_full_cpp_owner(
18347    analyzer: &CppGraphSource<'_>,
18348    code_unit: &CodeUnit,
18349    owner_candidates: &[CodeUnit],
18350) -> FullOwnerResolution {
18351    let Some(cpp) = analyzer.cpp else {
18352        return FullOwnerResolution::None;
18353    };
18354    let mut visible_files = HashSet::default();
18355    collect_include_closure(
18356        analyzer,
18357        cpp.include_target_index(),
18358        code_unit.source(),
18359        &mut visible_files,
18360        None,
18361    );
18362    let candidates = owner_candidates
18363        .iter()
18364        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18365        .cloned()
18366        .collect::<Vec<_>>();
18367    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18368    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18369        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18370        (candidate, strength)
18371    })) {
18372        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18373        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18374            FullOwnerResolution::None
18375        }
18376        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18377    }
18378}
18379
18380pub enum DirectOwnerResolution {
18381    None,
18382    ForwardsOnly(Vec<CodeUnit>),
18383    UniqueFull(CodeUnit),
18384    Ambiguous,
18385}
18386
18387enum FullOwnerResolution {
18388    None,
18389    Unique(CodeUnit),
18390    Ambiguous,
18391}
18392
18393fn directly_included_owner(
18394    analyzer: &CppGraphSource<'_>,
18395    code_unit: &CodeUnit,
18396    owner_candidates: &[CodeUnit],
18397) -> DirectOwnerResolution {
18398    let Some(cpp) = analyzer.cpp else {
18399        return DirectOwnerResolution::None;
18400    };
18401    let direct_includes: HashSet<ProjectFile> = analyzer
18402        .canonical_include_paths(code_unit.source())
18403        .unwrap_or_default()
18404        .into_iter()
18405        .flat_map(|include| {
18406            resolve_include_targets_with_index(
18407                code_unit.source(),
18408                &include,
18409                cpp.include_target_index(),
18410            )
18411        })
18412        .collect();
18413    let candidates = owner_candidates
18414        .iter()
18415        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18416        .cloned()
18417        .collect::<Vec<_>>();
18418    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18419    classify_direct_owner_candidates(analyzer, candidates.into_iter())
18420}
18421
18422fn prefer_member_declaring_owners(
18423    analyzer: &CppGraphSource<'_>,
18424    member: &CodeUnit,
18425    candidates: Vec<CodeUnit>,
18426) -> Vec<CodeUnit> {
18427    let matching = candidates
18428        .iter()
18429        .filter(|owner| owner_declares_member(analyzer, owner, member))
18430        .cloned()
18431        .collect::<Vec<_>>();
18432    if matching.is_empty() {
18433        candidates
18434    } else {
18435        matching
18436    }
18437}
18438
18439fn owner_declares_member(
18440    analyzer: &CppGraphSource<'_>,
18441    owner: &CodeUnit,
18442    member: &CodeUnit,
18443) -> bool {
18444    analyzer.direct_children(owner).into_iter().any(|child| {
18445        child.kind() == member.kind()
18446            && child.identifier() == member.identifier()
18447            && child.signature() == member.signature()
18448    })
18449}
18450
18451fn classify_direct_owner_candidates(
18452    analyzer: &CppGraphSource<'_>,
18453    candidates: impl Iterator<Item = CodeUnit>,
18454) -> DirectOwnerResolution {
18455    collapse_owner_candidates(candidates.map(|candidate| {
18456        let strength = cpp_class_declaration_strength(analyzer, &candidate);
18457        (candidate, strength)
18458    }))
18459}
18460
18461pub fn collapse_owner_candidates(
18462    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18463) -> DirectOwnerResolution {
18464    let mut full_definition = None;
18465    let mut forwards = Vec::new();
18466    for (candidate, strength) in candidates {
18467        match strength {
18468            CppClassDeclarationStrength::Full if full_definition.is_some() => {
18469                return DirectOwnerResolution::Ambiguous;
18470            }
18471            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18472            CppClassDeclarationStrength::Forward => forwards.push(candidate),
18473            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18474        }
18475    }
18476    if let Some(owner) = full_definition {
18477        DirectOwnerResolution::UniqueFull(owner)
18478    } else if !forwards.is_empty() {
18479        DirectOwnerResolution::ForwardsOnly(forwards)
18480    } else {
18481        DirectOwnerResolution::None
18482    }
18483}
18484
18485#[cfg(any(test, feature = "test-support"))]
18486pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18487    unique_logical_forward_owner(forwards)
18488}
18489
18490fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18491    let first = forwards.pop()?;
18492    forwards
18493        .iter()
18494        .all(|forward| same_logical_symbol(forward, &first))
18495        .then_some(first)
18496}
18497
18498pub fn cpp_class_declaration_strength(
18499    analyzer: &CppGraphSource<'_>,
18500    candidate: &CodeUnit,
18501) -> CppClassDeclarationStrength {
18502    // The answer is a pure function of the unit's ranges and its file's tree,
18503    // and the inverse scan asks it once per declaration seed. On a translation
18504    // unit the parser could not fully recover, each ask re-derives the
18505    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
18506    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
18507    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
18508    // (#1496).
18509    let Some(cpp) = analyzer.cpp else {
18510        return uncached_cpp_class_declaration_strength(analyzer, candidate);
18511    };
18512    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18513        return strength;
18514    }
18515    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18516    cpp.cache_class_declaration_strength(candidate, strength);
18517    strength
18518}
18519
18520fn uncached_cpp_class_declaration_strength(
18521    analyzer: &CppGraphSource<'_>,
18522    candidate: &CodeUnit,
18523) -> CppClassDeclarationStrength {
18524    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18525        if facts
18526            .iter()
18527            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18528        {
18529            return CppClassDeclarationStrength::Full;
18530        }
18531        if facts
18532            .iter()
18533            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18534        {
18535            return CppClassDeclarationStrength::Forward;
18536        }
18537    }
18538    if let Some(cpp) = analyzer.cpp
18539        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18540    {
18541        return cpp_class_declaration_strength_in_tree(
18542            analyzer,
18543            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18544            candidate,
18545            prepared.source(),
18546            prepared.tree().root_node(),
18547        );
18548    }
18549    let Some(source) = analyzer.indexed_source(candidate.source()) else {
18550        return CppClassDeclarationStrength::Unknown;
18551    };
18552    let mut parser = Parser::new();
18553    if parser
18554        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18555        .is_err()
18556    {
18557        return CppClassDeclarationStrength::Unknown;
18558    }
18559    let Some(tree) = parser.parse(&source, None) else {
18560        return CppClassDeclarationStrength::Unknown;
18561    };
18562    // This branch reparses a file the analyzer has no prepared tree for, so its
18563    // recovery index is that one tree's and cannot be shared.
18564    let recovered_export_classes =
18565        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18566    cpp_class_declaration_strength_in_tree(
18567        analyzer,
18568        &recovered_export_classes,
18569        candidate,
18570        &source,
18571        tree.root_node(),
18572    )
18573}
18574
18575fn cpp_class_declaration_strength_in_tree(
18576    analyzer: &CppGraphSource<'_>,
18577    recovered_export_classes: &CppRecoveredExportClassIndex,
18578    candidate: &CodeUnit,
18579    source: &str,
18580    root: Node<'_>,
18581) -> CppClassDeclarationStrength {
18582    let ranges = analyzer.ranges(candidate);
18583    let mut saw_forward = false;
18584    for range in ranges {
18585        // The recovered export-macro shapes answer for their own ranges; only a
18586        // range no recovery claims is read as a plain specifier.
18587        match recovered_class_body_at(
18588            recovered_export_classes,
18589            root,
18590            source,
18591            candidate.identifier(),
18592            &range,
18593        ) {
18594            Some(true) => return CppClassDeclarationStrength::Full,
18595            Some(false) => {
18596                saw_forward = true;
18597                continue;
18598            }
18599            None => {}
18600        }
18601        // Only a node covering the range's start byte can be the specifier for
18602        // this range, so apply that test where nodes enter the stack rather
18603        // than where they leave it. Pushing first meant one ask enqueued every
18604        // sibling at every level it descended, which on a translation unit with
18605        // thousands of top-level declarations is a per-ask cost proportional to
18606        // the file (#1496).
18607        let covers_range_start = |node: &Node<'_>| {
18608            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18609        };
18610        let mut stack = Vec::new();
18611        if covers_range_start(&root) {
18612            stack.push(root);
18613        }
18614        while let Some(node) = stack.pop() {
18615            if node.start_byte() == range.start_byte
18616                && node.end_byte() == range.end_byte
18617                && matches!(
18618                    node.kind(),
18619                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18620                )
18621            {
18622                if cpp_class_node_has_body(node) {
18623                    return CppClassDeclarationStrength::Full;
18624                }
18625                saw_forward = true;
18626            }
18627            let mut cursor = node.walk();
18628            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18629        }
18630    }
18631    if saw_forward {
18632        CppClassDeclarationStrength::Forward
18633    } else {
18634        CppClassDeclarationStrength::Unknown
18635    }
18636}
18637
18638fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18639    child_by_field_or_named_child_of_kinds(
18640        node,
18641        "body",
18642        &[
18643            "declaration_list",
18644            "field_declaration_list",
18645            "enumerator_list",
18646        ],
18647    )
18648    .is_some()
18649}
18650
18651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18652enum CppCTagKind {
18653    Struct,
18654    Union,
18655}
18656
18657fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18658    let declaration = analyzer.get_source(code_unit, false)?;
18659    let mut parser = Parser::new();
18660    parser
18661        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18662        .ok()?;
18663    let tree = parser.parse(&declaration, None)?;
18664    let mut stack = vec![tree.root_node()];
18665    while let Some(node) = stack.pop() {
18666        let kind = match node.kind() {
18667            "struct_specifier" => CppCTagKind::Struct,
18668            "union_specifier" => CppCTagKind::Union,
18669            _ => {
18670                let mut cursor = node.walk();
18671                stack.extend(node.named_children(&mut cursor));
18672                continue;
18673            }
18674        };
18675        if node
18676            .child_by_field_name("name")
18677            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18678        {
18679            return Some(kind);
18680        }
18681        let mut cursor = node.walk();
18682        stack.extend(node.named_children(&mut cursor));
18683    }
18684    None
18685}
18686
18687pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18688    if !code_unit.owner_is_type_scope() {
18689        return None;
18690    }
18691    let owner_fq = code_unit.fq().parent()?;
18692    ctx.analyzer
18693        .workspace_definitions()
18694        .exact(&owner_fq)
18695        .into_iter()
18696        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18697}
18698
18699pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18700    same_logical_symbol(left, right) && left.source() == right.source()
18701}
18702
18703pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18704    same_symbol(left, right) || same_logical_symbol(left, right)
18705}
18706
18707pub fn same_visible_global_field_symbol(
18708    analyzer: &CppGraphSource<'_>,
18709    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18710    left: &CodeUnit,
18711    right: &CodeUnit,
18712) -> bool {
18713    if same_symbol(left, right) {
18714        return true;
18715    }
18716    if !same_logical_symbol(left, right) {
18717        return false;
18718    }
18719    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18720        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18721    {
18722        left.source() == right.source()
18723    } else {
18724        true
18725    }
18726}
18727
18728fn cpp_global_field_has_internal_linkage_cached(
18729    analyzer: &CppGraphSource<'_>,
18730    cache: &mut HashMap<CodeUnit, bool>,
18731    candidate: &CodeUnit,
18732) -> bool {
18733    if let Some(internal) = cache.get(candidate) {
18734        return *internal;
18735    }
18736    #[cfg(any(test, feature = "test-support"))]
18737    note_cpp_global_field_internal_linkage_classification_for_test();
18738    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18739    cache.insert(candidate.clone(), internal);
18740    internal
18741}
18742
18743#[cfg(any(test, feature = "test-support"))]
18744thread_local! {
18745    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18746}
18747
18748#[cfg(any(test, feature = "test-support"))]
18749fn note_cpp_global_field_internal_linkage_classification_for_test() {
18750    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18751        count.set(count.get() + 1);
18752    });
18753}
18754
18755#[cfg(any(test, feature = "test-support"))]
18756pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18757    body: impl FnOnce() -> T,
18758) -> (T, usize) {
18759    with_thread_local_counter_for_test(
18760        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18761        body,
18762    )
18763}
18764
18765#[cfg(any(test, feature = "test-support"))]
18766fn with_thread_local_counter_for_test<T>(
18767    counter: &'static std::thread::LocalKey<Cell<usize>>,
18768    body: impl FnOnce() -> T,
18769) -> (T, usize) {
18770    counter.with(|count| {
18771        count.set(0);
18772        let result = body();
18773        let observed = count.get();
18774        count.set(0);
18775        (result, observed)
18776    })
18777}
18778
18779pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18780    left.kind() == right.kind()
18781        && left.fq_name() == right.fq_name()
18782        && left.signature() == right.signature()
18783}
18784
18785/// Select one visible spelling from an alias family only when its members
18786/// agree on one structured target. A single candidate needs no target proof.
18787pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18788    candidates: &[&'a CodeUnit],
18789    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18790) -> Option<&'a CodeUnit> {
18791    let first = *candidates.first()?;
18792    if candidates.len() == 1 {
18793        return Some(first);
18794    }
18795    let first_target = structured_target(first)?;
18796    candidates
18797        .iter()
18798        .skip(1)
18799        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18800        .then_some(first)
18801}
18802
18803pub fn cpp_global_field_has_internal_linkage(
18804    analyzer: &CppGraphSource<'_>,
18805    candidate: &CodeUnit,
18806) -> bool {
18807    if !candidate.is_field() || candidate.short_name().contains('.') {
18808        return false;
18809    }
18810    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18811        return false;
18812    };
18813    match local_linkage {
18814        CppFieldLinkage::Internal => true,
18815        CppFieldLinkage::External => false,
18816        CppFieldLinkage::InternalUnlessExternalPeer => {
18817            !cpp_global_field_linkage_peers(analyzer, candidate)
18818                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18819                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18820        }
18821    }
18822}
18823
18824fn cpp_global_field_linkage_peers<'a>(
18825    analyzer: &CppGraphSource<'a>,
18826    candidate: &'a CodeUnit,
18827) -> impl Iterator<Item = CodeUnit> + 'a {
18828    let name = candidate.fq().clone();
18829    analyzer
18830        .workspace_definitions()
18831        .exact(&name)
18832        .into_iter()
18833        .filter(move |peer| {
18834            if peer == candidate {
18835                return false;
18836            }
18837            #[cfg(any(test, feature = "test-support"))]
18838            note_cpp_global_field_linkage_peer_inspection_for_test();
18839            same_logical_symbol(peer, candidate)
18840        })
18841}
18842
18843#[cfg(any(test, feature = "test-support"))]
18844thread_local! {
18845    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18846}
18847
18848#[cfg(any(test, feature = "test-support"))]
18849fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18850    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18851        count.set(count.get() + 1);
18852    });
18853}
18854
18855#[cfg(any(test, feature = "test-support"))]
18856pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18857    body: impl FnOnce() -> T,
18858) -> (T, usize) {
18859    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18860}
18861
18862fn cpp_global_field_declaration_linkage(
18863    analyzer: &CppGraphSource<'_>,
18864    candidate: &CodeUnit,
18865) -> Option<CppFieldLinkage> {
18866    analyzer.cpp_field_linkage(candidate)
18867}
18868
18869/// Agreed alias target spelling captured from the declaring AST type field.
18870pub fn cpp_canonical_alias_target_text(
18871    analyzer: &CppGraphSource<'_>,
18872    unit: &CodeUnit,
18873) -> Option<String> {
18874    let facts = analyzer.declaration_source_properties(unit)?;
18875    let first = facts.first()?.alias_target_text.as_ref()?;
18876    facts
18877        .iter()
18878        .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18879        .then(|| first.clone())
18880}
18881
18882#[cfg(test)]
18883mod tests {
18884    #[test]
18885    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18886    fn issue_3089_statement_formal_at_end_of_replacement() {
18887        let parameters = vec!["handle".to_owned(), "block".to_owned()];
18888        for replacement in [
18889            "do { header_event_t* event; if ((handle)->active) block } while (0)",
18890            "do { header_event_t* event; block } while (0)",
18891        ] {
18892            assert!(
18893                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
18894                    .is_some(),
18895                "{replacement}"
18896            );
18897        }
18898    }
18899    use super::*;
18900    use brokk_bifrost_test_macros::hardwon;
18901
18902    #[test]
18903    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18904    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18905        let source = "int size(void) { return sizeof(((Payload))); }\n";
18906        let mut parser = Parser::new();
18907        parser
18908            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18909            .expect("C++ grammar");
18910        let tree = parser.parse(source, None).expect("fixture tree");
18911        let start = source.find("Payload").expect("sizeof operand");
18912        let node = tree
18913            .root_node()
18914            .named_descendant_for_byte_range(start, start + "Payload".len())
18915            .expect("focused operand");
18916        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18917        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18918
18919        assert_eq!(node.kind(), "identifier");
18920        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18921        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18922    }
18923
18924    fn parse_cpp(source: &str) -> Tree {
18925        crate::diagnostics::parse_cpp_tree(source)
18926    }
18927
18928    #[test]
18929    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18930    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18931        let source = "auto value = new  ns::Box<int> [ 3 ];";
18932        let tree = parse_cpp(source);
18933        let mut pending = vec![tree.root_node()];
18934        let allocation = loop {
18935            let node = pending.pop().expect("new expression in fixture");
18936            if node.kind() == "new_expression" {
18937                break node;
18938            }
18939            let mut cursor = node.walk();
18940            pending.extend(
18941                node.named_children(&mut cursor)
18942                    .collect::<Vec<_>>()
18943                    .into_iter()
18944                    .rev(),
18945            );
18946        };
18947
18948        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18949        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18950        assert_eq!(node_text(type_node, source), "ns::Box<int>");
18951        assert_eq!(
18952            allocation
18953                .child_by_field_name("declarator")
18954                .expect("array declarator")
18955                .kind(),
18956            "new_declarator"
18957        );
18958
18959        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18960        let old_split = old_split
18961            .strip_prefix("new ")
18962            .unwrap_or(old_split.as_str())
18963            .split(['(', '{'])
18964            .next()
18965            .expect("old split result");
18966        assert_ne!(old_split.trim(), node_text(type_node, source));
18967    }
18968
18969    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18970        let start = source.find(needle).expect("fixture needle");
18971        tree.root_node()
18972            .named_descendant_for_byte_range(start, start + needle.len())
18973            .expect("node at needle")
18974    }
18975
18976    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18977        let tree = crate::diagnostics::parse_cpp_tree(source);
18978        PreparedSyntaxTree::new(
18979            PreparedSyntaxSource::Exact(Arc::from(source)),
18980            tree,
18981            compute_line_starts(source),
18982            LanguageDialect::Standard(Language::Cpp),
18983            PreparedSourceOrigin::Disk,
18984            None,
18985        )
18986    }
18987
18988    fn unresolved_include_before(source: &str, reference: &str) -> bool {
18989        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18990        let prepared = prepared_cpp(source);
18991        let facts = collect_structured_include_facts(&prepared);
18992        let include_targets = IncludeTargetIndex::build([&file]);
18993        has_unresolved_include_visible_before_in_prepared(
18994            &file,
18995            &prepared,
18996            &include_targets,
18997            &facts,
18998            source.find(reference).expect("reference fixture"),
18999        )
19000    }
19001
19002    #[test]
19003    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19004    fn unresolved_include_before_reference_is_visible() {
19005        let source = "#include \"missing.h\"\nint use = Missing;\n";
19006        assert!(unresolved_include_before(source, "Missing"));
19007    }
19008
19009    #[test]
19010    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19011    fn unresolved_include_after_reference_is_not_visible() {
19012        let source = "int use = Missing;\n#include \"missing.h\"\n";
19013        assert!(!unresolved_include_before(source, "Missing"));
19014    }
19015
19016    #[test]
19017    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19018    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
19019        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
19020        assert!(!unresolved_include_before(source, "Missing"));
19021    }
19022
19023    #[test]
19024    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19025    fn unresolved_include_in_current_branch_is_visible() {
19026        let source =
19027            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
19028        assert!(unresolved_include_before(source, "Missing"));
19029    }
19030
19031    /// Two macro-decorated class heads make tree-sitter close `detail` at the
19032    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
19033    /// real `}`; the tail parses at translation-unit level and the two real
19034    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
19035    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
19036namespace matchers {
19037    namespace detail {
19038        class API [[nodiscard]] First {
19039        public:
19040            int value() const { return count_ + 1; }
19041        private:
19042            int count_;
19043        };
19044        class API [[nodiscard]] Second {
19045        public:
19046            int value() const { return count_ + 2; }
19047        private:
19048            int count_;
19049        };
19050    } // namespace detail
19051
19052    template <typename T>
19053    void tail_function(MatcherBase<T> const& value);
19054
19055    class TailClass {};
19056} // namespace matchers
19057} // namespace app
19058
19059struct AfterAll {};
19060"#;
19061
19062    #[test]
19063    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19064    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
19065        let source = STOLEN_BRACE_CASCADE;
19066        let tree = parse_cpp(source);
19067        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19068
19069        let tail_class = named_node_at(&tree, source, "TailClass");
19070        assert!(
19071            !has_ancestor_kind(tail_class, "namespace_definition"),
19072            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
19073        );
19074        let displaced = named_node_at(&tree, source, "Second");
19075        assert_eq!(
19076            enclosing_namespace_components(displaced, source),
19077            Some(vec!["app".to_string(), "matchers".to_string()]),
19078            "the fixture must displace the second class out of detail"
19079        );
19080
19081        let components = |needle: &str| {
19082            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
19083        };
19084        assert_eq!(components("First"), ["app", "matchers", "detail"]);
19085        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
19086        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
19087        assert_eq!(components("tail_function"), ["app", "matchers"]);
19088        assert_eq!(components("TailClass"), ["app", "matchers"]);
19089        assert!(components("AfterAll").is_empty());
19090    }
19091
19092    #[test]
19093    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19094    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
19095        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
19096        let tree = parse_cpp(clean);
19097        assert!(!tree.root_node().has_error());
19098        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
19099
19100        // A namespace that merely contains a parse error closes where its
19101        // brace says; the declarations after it keep their parsed scope.
19102        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
19103        let tree = parse_cpp(damaged);
19104        assert!(tree.root_node().has_error());
19105        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
19106        assert_eq!(
19107            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
19108            ["a"]
19109        );
19110    }
19111
19112    /// A function-like `#define` in a class body, whose replacement returns a
19113    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
19114    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
19115    /// tokens are the only trace of the head, and every declaration the
19116    /// namespace holds becomes their flat sibling. This is Catch2's
19117    /// `catch_decomposer.hpp`, reduced (issue #3084).
19118    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
19119
19120    class Target {
19121        int value_;
19122    };
19123
19124    template <typename T>
19125    class Holder {
19126    public:
19127        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
19128
19129#define HOLDER_DEFINE_OP( id, op )                                             \
19130    template <typename U>                                                      \
19131    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
19132        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
19133        return Target{};                                                       \
19134    }
19135
19136        HOLDER_DEFINE_OP( equal, == )
19137#undef HOLDER_DEFINE_OP
19138        T m_lhs;
19139    };
19140
19141    class Tail {};
19142}
19143"#;
19144
19145    #[test]
19146    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
19147    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
19148        let source = COLLAPSED_NAMESPACE_HEAD;
19149        let tree = parse_cpp(source);
19150        let target = named_node_at(&tree, source, "class Target");
19151
19152        // The macro-declaration grammar keeps the namespace head and its
19153        // members inside a surviving `namespace_definition` even with the
19154        // macro invocation shredding one declaration inside the body, so the
19155        // scope authority answers from parsed ancestry; the recovered-region
19156        // path this fixture exercised is no longer needed for it.
19157        assert!(
19158            tree.root_node().has_error(),
19159            "the fixture must retain the macro invocation's parse damage"
19160        );
19161        assert!(
19162            has_ancestor_kind(target, "namespace_definition"),
19163            "the namespace head survives: the class keeps its parsed namespace ancestor"
19164        );
19165
19166        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19167        assert_eq!(
19168            index.enclosing_namespace_components(target, source),
19169            ["app"]
19170        );
19171        assert_eq!(
19172            index.enclosing_namespace_levels(target, source),
19173            vec![(
19174                "app".to_string(),
19175                Range {
19176                    start_byte: 0,
19177                    end_byte: source.len() - 1,
19178                    start_line: 1,
19179                    end_line: 25,
19180                },
19181                Range {
19182                    start_byte: 10,
19183                    end_byte: 13,
19184                    start_line: 1,
19185                    end_line: 1,
19186                },
19187            )],
19188            "the recovered namespace owns its full construct, not its first member or just its header"
19189        );
19190    }
19191
19192    #[test]
19193    #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
19194    fn namespace_body_name_components_recovers_literal_function_head() {
19195        // Independently authored direct-helper contract, not an index or
19196        // short-body reproduction. The index deliberately leaves clean
19197        // subtrees intact; head recognition alone does not make this simple
19198        // body an indexed recovery region. Corpus index/short-body/outside
19199        // ownership remains required by the private root golden prewriter.
19200        let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
19201        let tree = parse_cpp(source);
19202        let keyword_start = source.find("namespace recovered").unwrap();
19203        let keyword = tree
19204            .root_node()
19205            .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
19206            .expect("literal keyword node");
19207        // The macro-declaration grammar reads the opaque prefix as a
19208        // `macro_modifier` and the literal `namespace` keyword as the bogus
19209        // function's type: a keyword cannot be a type, so the type field is
19210        // the same recovery evidence the older grammar's `ERROR` carried.
19211        assert_eq!(keyword.kind(), "type_identifier");
19212        assert_eq!(keyword.child_count(), 0);
19213        assert!(!keyword.is_missing() && !keyword.has_error());
19214        assert_eq!(node_text(keyword, source), "namespace");
19215        let wrapper = keyword.parent().expect("actual wrapper");
19216        assert_eq!(wrapper.kind(), "function_definition");
19217        let mut cursor = wrapper.walk();
19218        let children = wrapper
19219            .children(&mut cursor)
19220            .filter(|child| child.kind() != "comment")
19221            .collect::<Vec<_>>();
19222        assert_eq!(
19223            children.len(),
19224            4,
19225            "all named and unnamed children accounted: {children:?}"
19226        );
19227        let prefix = children[0];
19228        assert_eq!(prefix.kind(), "macro_modifier");
19229        assert!(!prefix.is_missing() && !prefix.has_error());
19230        assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
19231        assert_eq!(children[1], keyword);
19232        assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
19233        let name = children[2];
19234        assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
19235        assert_eq!(name.child_count(), 0);
19236        assert!(!name.is_missing() && !name.has_error());
19237        assert_eq!(node_text(name, source), "recovered");
19238        assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
19239        let body = wrapper.child_by_field_name("body").expect("actual body");
19240        assert_eq!(children[3], body);
19241        assert_eq!(body.kind(), "compound_statement");
19242        assert!(!body.is_missing());
19243        let open = body.child(0).expect("actual first child");
19244        assert_eq!(open.kind(), "{");
19245        assert!(!open.is_missing() && !open.has_error());
19246        assert_eq!(open.start_byte(), body.start_byte());
19247        let (head, names) = namespace_body_name_components(wrapper, body, source)
19248            .expect("strict synthetic literal head recognized");
19249        assert_eq!(head, keyword);
19250        assert_eq!(
19251            names
19252                .iter()
19253                .map(|(name, _)| name.as_str())
19254                .collect::<Vec<_>>(),
19255            ["recovered"]
19256        );
19257        assert_eq!(
19258            (names[0].1.start_byte, names[0].1.end_byte),
19259            (name.start_byte(), name.end_byte())
19260        );
19261        let inside = named_node_at(&tree, source, "int member;");
19262        assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
19263    }
19264
19265    #[test]
19266    #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
19267    fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
19268        for refusal in [
19269            "int ordinary(int value) { return value; }",
19270            "OPAQUE_PREFIX namespace_ recovered { int member; }",
19271            "OPAQUE_PREFIX namespace recovered other { int member; }",
19272            "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
19273        ] {
19274            let tree = parse_cpp(refusal);
19275            let mut stack = vec![tree.root_node()];
19276            let mut helper_calls = 0;
19277            while let Some(node) = stack.pop() {
19278                if node.kind() == "function_definition"
19279                    && let Some(body) = node.child_by_field_name("body")
19280                {
19281                    helper_calls += 1;
19282                    assert_eq!(
19283                        (node.start_byte(), node.end_byte()),
19284                        (0, refusal.len()),
19285                        "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
19286                    );
19287                    assert!(
19288                        namespace_body_name_components(node, body, refusal).is_none(),
19289                        "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
19290                    );
19291                }
19292                let mut cursor = node.walk();
19293                stack.extend(node.named_children(&mut cursor));
19294            }
19295            assert_eq!(
19296                helper_calls,
19297                1,
19298                "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
19299                tree.root_node()
19300            );
19301        }
19302
19303        // The measured '=' case has no function/body envelope. A missing
19304        // body is likewise parser-absence coverage, not a passed helper guard.
19305        // The macro-declaration grammar reads both as clean declarations whose
19306        // specifier words are `macro_modifier`s, so the parser-absence premise
19307        // is now their lack of a function/body envelope rather than an ERROR.
19308        for absent in [
19309            "OPAQUE_PREFIX namespace recovered = other { int member; }",
19310            "OPAQUE_PREFIX namespace recovered int member;",
19311        ] {
19312            let tree = parse_cpp(absent);
19313            let mut stack = vec![tree.root_node()];
19314            let mut helper_candidates = 0;
19315            while let Some(node) = stack.pop() {
19316                if node.kind() == "function_definition"
19317                    && node.child_by_field_name("body").is_some()
19318                {
19319                    helper_candidates += 1;
19320                }
19321                let mut cursor = node.walk();
19322                stack.extend(node.named_children(&mut cursor));
19323            }
19324            assert_eq!(
19325                helper_candidates,
19326                0,
19327                "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
19328                tree.root_node()
19329            );
19330        }
19331    }
19332
19333    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
19334    /// so the `{` of a scope the source really opens is part of the macro's
19335    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
19336    /// one more real `}` than real `{`, and a file-global stack has to assign
19337    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
19338    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
19339    /// line 16 arrives with nothing open. This is the state simdjson's
19340    /// `compile_time_json-inl.h` reaches eight times over (#3328).
19341    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
19342
19343namespace app {
19344namespace detail {
19345
19346bool check(bool active) {
19347  APP_IF_ACTIVE
19348    return true;
19349  }
19350  return false;
19351}
19352
19353class Tail {};
19354
19355} // namespace detail
19356} // namespace app
19357"#;
19358
19359    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
19360    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
19361    /// literal and the nested namespace definitions. The malformed function
19362    /// body still causes a missing outer namespace close. Keep the original
19363    /// source as a positive for real inner closes, without inventing the outer
19364    /// close that the parser could not read (#3328).
19365    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
19366namespace compile_time {
19367namespace number_parsing {
19368consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
19369    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
19370      auto digit = [](uint8_t c) -> uint32_t {
19371      }
19372    }
19373    if (a) {
19374      while (a) {
19375      }
19376    }
19377    switch (c) {
19378    case '{': {
19379    }
19380    }
19381  }
19382  while (a) {
19383    cursor += field.second;
19384    if (a) {
19385    case '{': {
19386      if (a) {
19387      }
19388    }
19389    case 'n': {
19390      if (a) {
19391      }
19392    }
19393    }
19394  }
19395  if (a) {
19396    simdjson_consteval_error("Expected '}'");
19397  }
19398}
19399}
19400}
19401"#;
19402
19403    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
19404    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
19405        let mut stack = vec![root];
19406        let mut counts = (0, 0);
19407        while let Some(node) = stack.pop() {
19408            match (node.kind(), node.is_missing()) {
19409                ("{", false) => counts.0 += 1,
19410                ("}", false) => counts.1 += 1,
19411                _ => {}
19412            }
19413            let mut cursor = node.walk();
19414            stack.extend(node.children(&mut cursor));
19415        }
19416        counts
19417    }
19418
19419    /// The open brace of `head`, which is the key its close lands under.
19420    fn namespace_open_byte(source: &str, head: &str) -> usize {
19421        let at = source.find(head).expect("fixture namespace head");
19422        at + head.len() - 1
19423    }
19424
19425    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
19426        let index = OrphanedNamespaceScopeIndex::build(root, source);
19427        for head in heads {
19428            assert_eq!(
19429                index.matching_close_brace(namespace_open_byte(source, head)),
19430                None,
19431                "{head} must not claim a close belonging to an inner scope"
19432            );
19433        }
19434    }
19435
19436    #[test]
19437    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19438    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
19439        let source = MACRO_OPENED_SCOPE;
19440        let tree = parse_cpp(source);
19441        let root = tree.root_node();
19442        assert_eq!(
19443            real_brace_token_counts(root),
19444            (4, 5),
19445            "the fixture must reproduce the loss: one scope the source opens \
19446             has no `{{` token, so the stream holds an extra real close"
19447        );
19448
19449        assert_namespace_heads_have_no_matching_close(
19450            root,
19451            source,
19452            &["namespace app {", "namespace detail {"],
19453        );
19454    }
19455
19456    #[test]
19457    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
19458    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
19459        // The stack proves no pairing in this file, so every restored level
19460        // keeps its best-effort scope. None of it may reach a declaration the
19461        // file writes after its last close.
19462        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
19463        let tree = parse_cpp(&source);
19464        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
19465        let after_all = named_node_at(&tree, &source, "struct AfterAll");
19466        assert!(
19467            index
19468                .enclosing_namespace_components(after_all, &source)
19469                .is_empty()
19470        );
19471    }
19472
19473    #[test]
19474    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19475    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
19476        let source = REFLECTION_CASE_LABEL_RECOVERY;
19477        let tree = parse_cpp(source);
19478        let root = tree.root_node();
19479        let mut stack = vec![root];
19480        let mut mis_lexed_opens = Vec::new();
19481        while let Some(node) = stack.pop() {
19482            if node.kind() == "character"
19483                && &source[node.start_byte()..node.end_byte()] == "{"
19484                && node
19485                    .parent()
19486                    .is_some_and(|parent| parent.kind() != "char_literal")
19487            {
19488                mis_lexed_opens.push(node.start_position().row + 1);
19489            }
19490            let mut cursor = node.walk();
19491            stack.extend(node.children(&mut cursor));
19492        }
19493        assert!(
19494            mis_lexed_opens.is_empty(),
19495            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
19496             the old ABI14 mis-lex is no longer a valid recovery witness"
19497        );
19498        assert_eq!(
19499            real_brace_token_counts(root),
19500            (17, 17),
19501            "the improved parse retains balanced real brace tokens"
19502        );
19503        assert_eq!(root.kind(), "translation_unit");
19504        assert!(root.has_error(), "the malformed body remains incomplete");
19505        let index = OrphanedNamespaceScopeIndex::build(root, source);
19506        assert_eq!(
19507            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
19508            None,
19509            "a missing outer close must not borrow an inner namespace's real close"
19510        );
19511        for (head, close) in [
19512            ("namespace compile_time {", source.len() - 2),
19513            ("namespace number_parsing {", source.len() - 4),
19514        ] {
19515            assert_eq!(
19516                index
19517                    .matching_close_brace(namespace_open_byte(source, head))
19518                    .map(|range| range.start_byte),
19519                Some(close),
19520                "{head} retains its own real close"
19521            );
19522        }
19523    }
19524
19525    #[test]
19526    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
19527    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
19528        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
19529        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
19530        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
19531        assert!(indexed_namespace_path_is_recoverable(
19532            &["cache".to_string()],
19533            &indexed,
19534            1,
19535        ));
19536    }
19537
19538    #[test]
19539    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
19540    fn sort_lookup_units_totally_orders_every_identity_field() {
19541        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
19542        let base = CodeUnit::with_signature(
19543            file.clone(),
19544            CodeUnitType::Function,
19545            "scope",
19546            "value",
19547            Some("()".to_string()),
19548            false,
19549        );
19550        let different_kind = CodeUnit::with_signature(
19551            file.clone(),
19552            CodeUnitType::Field,
19553            "scope",
19554            "value",
19555            Some("()".to_string()),
19556            false,
19557        );
19558        let synthetic = base.with_synthetic(true);
19559
19560        let interner = segment_interner();
19561        let mut member_fq = FqName::new();
19562        member_fq.push(interner.intern("scope", SegmentKind::Package));
19563        member_fq.push(interner.intern("value", SegmentKind::Member));
19564        let different_package_boundary = CodeUnit::from_fq(
19565            file.clone(),
19566            CodeUnitType::Function,
19567            member_fq,
19568            0,
19569            Some("()".to_string()),
19570            false,
19571        );
19572
19573        let mut unknown_fq = FqName::new();
19574        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19575        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19576        let different_segment_kind = CodeUnit::from_fq(
19577            file,
19578            CodeUnitType::Function,
19579            unknown_fq,
19580            1,
19581            Some("()".to_string()),
19582            false,
19583        );
19584
19585        let input = vec![
19586            base,
19587            different_kind,
19588            synthetic,
19589            different_package_boundary,
19590            different_segment_kind,
19591        ];
19592        let mut expected = input.clone();
19593        sort_lookup_units(&mut expected);
19594        assert!(expected.windows(2).all(|pair| {
19595            let mut ordered = pair.to_vec();
19596            sort_lookup_units(&mut ordered);
19597            ordered == pair && pair[0] != pair[1]
19598        }));
19599
19600        let mut reversed = input.clone();
19601        reversed.reverse();
19602        sort_lookup_units(&mut reversed);
19603        assert_eq!(reversed, expected);
19604
19605        let mut rotated = input;
19606        rotated.rotate_left(2);
19607        sort_lookup_units(&mut rotated);
19608        assert_eq!(rotated, expected);
19609    }
19610
19611    #[test]
19612    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19613    fn structured_conditional_array_bound_preserves_the_real_guard() {
19614        let damaged = "#ifndef API_H\n#define API_H\nextern char option_buffer[\n#ifdef FEATURE_X\n    16 +\n#endif\n    1];\n\nvoid target(void);\n#endif\n";
19615        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19616        let parse = |source: &str| {
19617            let mut parser = Parser::new();
19618            parser
19619                .set_language(&tree_sitter_cpp::LANGUAGE.into())
19620                .expect("C++ grammar");
19621            parser.parse(source, None).expect("fixture tree")
19622        };
19623
19624        let tree = parse(damaged);
19625        let root = tree.root_node();
19626        assert!(
19627            !root.has_error(),
19628            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19629            root.to_sexp()
19630        );
19631        let target = damaged.find("target").expect("target byte");
19632        let declaration = root
19633            .descendant_for_byte_range(target, target + "target".len())
19634            .and_then(|mut node| {
19635                loop {
19636                    if node.kind() == "declaration" {
19637                        break Some(node);
19638                    }
19639                    node = node.parent()?;
19640                }
19641            })
19642            .expect("declaration after the displaced terminator");
19643        let outer = declaration
19644            .parent()
19645            .filter(|node| node.kind() == "preproc_ifdef")
19646            .expect("ordinary outer include guard");
19647        let bound = outer
19648            .named_children(&mut outer.walk())
19649            .filter(|node| node.kind() == "declaration")
19650            .filter_map(|node| node.child_by_field_name("declarator"))
19651            .find(|node| node.kind() == "array_declarator")
19652            .and_then(|node| node.child_by_field_name("size"))
19653            .expect("structured conditional array bound");
19654        assert_eq!(bound.kind(), "preproc_array_size");
19655        let conditional = bound
19656            .named_children(&mut bound.walk())
19657            .find(|node| node.kind() == "preproc_ifdef")
19658            .expect("array-size conditional");
19659        assert!(conditional.end_byte() <= declaration.start_byte());
19660        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19661        assert!(
19662            !std::iter::successors(Some(declaration), |node| node.parent())
19663                .any(|ancestor| same_node(ancestor, conditional)),
19664            "the declaration must not inherit the array-size feature guard"
19665        );
19666        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19667        assert!(preprocessor_conditional_contains_descendant(
19668            outer,
19669            declaration
19670        ));
19671
19672        let tree = parse(guarded);
19673        let conditional = tree
19674            .root_node()
19675            .named_child(0)
19676            .filter(|node| node.kind() == "preproc_ifdef")
19677            .expect("ordinary conditional");
19678        let declaration = conditional
19679            .named_children(&mut conditional.walk())
19680            .find(|node| node.kind() == "declaration")
19681            .expect("guarded declaration");
19682        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19683        assert!(preprocessor_conditional_contains_descendant(
19684            conditional,
19685            declaration
19686        ));
19687
19688        let damaged_alternative = format!(
19689            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19690            "UNUSED(value)\n".repeat(64)
19691        );
19692        let tree = parse(&damaged_alternative);
19693        let conditional = tree
19694            .root_node()
19695            .named_child(0)
19696            .filter(|node| node.kind() == "preproc_ifdef")
19697            .expect("outer conditional with an alternative");
19698        assert!(conditional.has_error());
19699        assert!(conditional.child_by_field_name("alternative").is_some());
19700        assert!(
19701            conditional
19702                .child(conditional.child_count() - 1)
19703                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19704        );
19705        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19706
19707        let split_declaration = "struct Node;\n\ntypedef\n  #ifdef FEATURE_X\n    struct Node *\n  #else\n    UInt32\n  #endif\n  NodeRef;\n\nstatic int target(void) { return 1; }\n#ifdef LATER\nint later;\n#endif\n";
19708        let tree = parse(split_declaration);
19709        let root = tree.root_node();
19710        // The macro-declaration grammar folds the whole split declaration
19711        // into one damaged `type_definition` instead of keeping a
19712        // `preproc_ifdef` with a displaced terminator: the boundary the
19713        // recovery supplied is now the declaration node itself. What must not
19714        // change is the outcome -- the function written after the split
19715        // declaration is outside the damaged span and inherits no guard.
19716        let split = root
19717            .named_children(&mut root.walk())
19718            .find(|node| node.kind() == "type_definition")
19719            .expect("the split declaration folds into one type definition");
19720        assert_eq!(
19721            split.end_position().row + 1,
19722            9,
19723            "the split declaration ends at its own declarator line"
19724        );
19725        let target = split_declaration
19726            .find("static int target")
19727            .expect("target byte");
19728        assert!(split.end_byte() <= target);
19729        let target_node = root
19730            .descendant_for_byte_range(target, target + "static".len())
19731            .expect("target node");
19732        assert!(
19733            !std::iter::successors(Some(target_node), |node| node.parent())
19734                .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
19735            "the declaration after the shredded split declaration must not \
19736             inherit a damaged conditional ancestor"
19737        );
19738    }
19739
19740    #[test]
19741    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19742    fn fragmented_reference_guard_is_recovered() {
19743        let source = "#if HAVE_ONE && HAVE_TWO\nstatic int helper(int value) { return value; }\n#endif\n\nint fragmented(int value) {\n    if (value == 0) {\n        return 0;\n#if HAVE_ONE && HAVE_TWO\n    } else if (value == 1) {\n        return helper(value);\n#endif\n    }\n    return 0;\n}\n";
19744        let mut parser = Parser::new();
19745        parser
19746            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19747            .expect("C++ grammar");
19748        let tree = parser.parse(source, None).expect("fixture tree");
19749        let start = source.rfind("helper").expect("reference byte");
19750        let node = tree
19751            .root_node()
19752            .descendant_for_byte_range(start, start + "helper".len())
19753            .expect("reference node");
19754        let mut expected = HashSet::default();
19755        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19756            vec![
19757                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19758                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19759            ],
19760        )));
19761        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19762    }
19763
19764    #[test]
19765    #[hardwon(
19766        "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
19767    )]
19768    fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
19769        let parse = |source: &str| {
19770            let mut parser = Parser::new();
19771            parser
19772                .set_language(&tree_sitter_cpp::LANGUAGE.into())
19773                .expect("C++ grammar");
19774            parser.parse(source, None).expect("fixture tree")
19775        };
19776        fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
19777            let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
19778            root.descendant_for_byte_range(start, start + token.len())
19779                .expect("token node")
19780        }
19781        let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
19782        let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
19783
19784        // `#ifdef`: the `#else` is an ERROR child of the declaration that spans
19785        // both sentinels, and a `#define` after it stays a structured child of
19786        // the same conditional.
19787        let source = "namespace n {\n#ifdef A\nBEGIN_NS\nint x;\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#define MODE 2\n#endif\n}\n";
19788        let tree = parse(source);
19789        let root = tree.root_node();
19790        let x = at(root, source, "int x", "x");
19791        let conditional = std::iter::successors(Some(x), |node| node.parent())
19792            .find(|node| node.kind() == "preproc_ifdef")
19793            .expect("ifdef");
19794        assert!(conditional.child_by_field_name("alternative").is_none());
19795        let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
19796        assert_eq!(directive.kind(), "#else");
19797        assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
19798        let defined = PreprocessorGuard::Defined("A".to_string());
19799        assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
19800        let class_name = at(root, source, "class T", "T");
19801        assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
19802        let define = at(root, source, "#define MODE", "MODE")
19803            .parent()
19804            .filter(|node| node.kind() == "preproc_def")
19805            .expect("macro event");
19806        let mut cache = PreprocessorContextCache::default();
19807        assert!(matches!(
19808            preprocessor_branch_of_descendant(conditional, define, &mut cache),
19809            PreprocessorBranch::DisplacedElse
19810        ));
19811        assert_eq!(
19812            preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
19813            Some(conditional),
19814            "a displaced #else completes the family"
19815        );
19816        assert_eq!(
19817            callable_guard_completion_byte(class_name, source),
19818            Some(conditional.end_byte())
19819        );
19820
19821        // A displaced `#elif B` reads `B` as the declaration's type: its
19822        // condition has no structured node, so nothing after it, including the
19823        // later displaced `#else`, may be read as `!A`.
19824        let source = "namespace n {\n#if A\nBEGIN_NS\nint x;\nEND_NS\n#elif B\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nstruct T {};\nEND_NS\n#endif\n}\n";
19825        let tree = parse(source);
19826        let root = tree.root_node();
19827        let x = at(root, source, "int x", "x");
19828        let conditional = std::iter::successors(Some(x), |node| node.parent())
19829            .find(|node| node.kind() == "preproc_if")
19830            .expect("if");
19831        assert!(conditional.child_by_field_name("alternative").is_none());
19832        assert_eq!(
19833            cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
19834            Some("#elif")
19835        );
19836        let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
19837        assert_eq!(guards(x, source), Some(set(&[truthy])));
19838        let mut cache = PreprocessorContextCache::default();
19839        for needle in ["class T", "struct T"] {
19840            let name = at(root, source, needle, "T");
19841            assert_eq!(
19842                guards(name, source),
19843                None,
19844                "{needle} after a displaced #elif"
19845            );
19846            assert!(matches!(
19847                preprocessor_branch_of_descendant(conditional, name, &mut cache),
19848                PreprocessorBranch::Unreadable
19849            ));
19850            assert_eq!(
19851                preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
19852                None,
19853                "{needle}: an unreadable #elif branch does not complete the family"
19854            );
19855            assert_eq!(callable_guard_completion_byte(name, source), None);
19856        }
19857
19858        // A structured `#elif` whose own `#else` was displaced: the content
19859        // after it excludes both readable conditions.
19860        let source = "namespace n {\n#if defined(A)\nBEGIN_NS\nint x;\nEND_NS\n#elif defined(B)\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#endif\n}\n";
19861        let tree = parse(source);
19862        let root = tree.root_node();
19863        let start = source.rfind("class T").expect("else class") + "class ".len();
19864        let name = root
19865            .descendant_for_byte_range(start, start + 1)
19866            .expect("else class name");
19867        let elif = std::iter::successors(Some(name), |node| node.parent())
19868            .find(|node| node.kind() == "preproc_elif")
19869            .expect("structured elif");
19870        assert_eq!(
19871            cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
19872            Some("#else")
19873        );
19874        assert_eq!(
19875            guards(name, source),
19876            Some(set(&[
19877                PreprocessorGuard::Defined("A".to_string()).negated(),
19878                PreprocessorGuard::Defined("B".to_string()).negated(),
19879            ]))
19880        );
19881        let mut cache = PreprocessorContextCache::default();
19882        assert!(
19883            preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
19884        );
19885    }
19886
19887    #[test]
19888    #[hardwon(
19889        "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
19890    )]
19891    fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
19892        let source = "API static inline T make(void);\nAPI T value;\nvoid take(API T p);\nstatic inline T plain(void);\nAPI ns::T qualified(void);\nAPI static inline T body(void) { return 0; }\n";
19893        let mut parser = Parser::new();
19894        parser
19895            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19896            .expect("C++ grammar");
19897        let tree = parser.parse(source, None).expect("fixture tree");
19898        let root = tree.root_node();
19899        let type_before = |declared: &str| {
19900            let end = source.find(declared).expect("declared name");
19901            let start = source[..end].rfind('T').expect("type token");
19902            root.descendant_for_byte_range(start, start + 1)
19903                .expect("type node")
19904        };
19905        for declared in ["make(", "value;", "p)", "body("] {
19906            let node = type_before(declared);
19907            assert_eq!(node.kind(), "type_identifier", "{declared}");
19908            assert!(
19909                macro_modifier_decorated_type_node(node),
19910                "{declared}: {}",
19911                node.parent().expect("declaration").to_sexp()
19912            );
19913        }
19914        for declared in ["plain(", "qualified("] {
19915            let node = type_before(declared);
19916            assert!(
19917                !macro_modifier_decorated_type_node(node),
19918                "{declared} is outside the macro-decorated head scope: {}",
19919                node.parent().expect("parent").to_sexp()
19920            );
19921        }
19922    }
19923
19924    #[test]
19925    fn expression_defined_and_ifndef_guards_are_incompatible() {
19926        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19927        let mut parser = Parser::new();
19928        parser
19929            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19930            .expect("C++ grammar");
19931        let tree = parser.parse(source, None).expect("fixture tree");
19932        let root = tree.root_node();
19933        let selected_start = source.find("selected").expect("selected declaration");
19934        let rejected_start = source.find("rejected").expect("rejected declaration");
19935        let selected = root
19936            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19937            .expect("selected node");
19938        let rejected = root
19939            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19940            .expect("rejected node");
19941        let selected_guards =
19942            preprocessor_guard_environment(selected, source).expect("selected guards");
19943        let rejected_guards =
19944            preprocessor_guard_environment(rejected, source).expect("rejected guards");
19945
19946        assert!(
19947            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19948            "opposite spellings of one macro guard must contradict"
19949        );
19950    }
19951
19952    #[test]
19953    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19954    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19955        let source = r#"#ifdef _WIN32
19956#if defined(__cplusplus)
19957extern "C"
19958#endif
19959int platform_api(void);
19960#endif
19961
19962#ifdef _WIN32
19963static int entropy_target(void) { return 0; }
19964#else
19965#ifdef HAVE_COMMON_RANDOM
19966static int other_target(void) { return 0; }
19967#elif defined(HAVE_GETENTROPY)
19968static int entropy_target(void) { return 1; }
19969static int use_entropy(void) { return entropy_target(); }
19970#endif
19971#endif
19972"#;
19973        let mut parser = Parser::new();
19974        parser
19975            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19976            .expect("C++ grammar");
19977        let tree = parser.parse(source, None).expect("fixture tree");
19978        let start = source.rfind("entropy_target()").expect("reference");
19979        let node = tree
19980            .root_node()
19981            .descendant_for_byte_range(start, start + "entropy_target".len())
19982            .expect("reference node");
19983        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19984        assert!(
19985            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19986            "{guards:#?}"
19987        );
19988        assert!(
19989            guards.contains(&PreprocessorGuard::Undefined(
19990                "HAVE_COMMON_RANDOM".to_string()
19991            )),
19992            "{guards:#?}"
19993        );
19994        assert!(
19995            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19996            "{guards:#?}"
19997        );
19998        assert!(
19999            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
20000            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
20001        );
20002    }
20003
20004    #[test]
20005    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
20006    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
20007        let source = "#define KEY 42\n#ifdef ENABLE_KEYS\nint classify(int value) {\n    switch (value) {\n        case KEY: return 1;\n        default: return 0;\n    }\n}\n#endif\n";
20008        let mut parser = Parser::new();
20009        parser
20010            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20011            .expect("C++ grammar");
20012        let tree = parser.parse(source, None).expect("fixture tree");
20013        let root = tree.root_node();
20014        let node_at = |text: &str, start: usize| {
20015            root.descendant_for_byte_range(start, start + text.len())
20016                .expect("token node")
20017        };
20018
20019        let key_start = source.find("case KEY").expect("case label") + "case ".len();
20020        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
20021        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
20022        assert!(!is_ordinary_macro_reference_node(node_at(
20023            "ENABLE_KEYS",
20024            guard_start,
20025        )));
20026    }
20027
20028    #[test]
20029    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20030    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
20031        let source = "#if HAVE_ARM_NEON\nstatic int target(void) { return 1; }\n#endif\n#if HAVE_ARM_NEON && ENABLE_FAST_PATH\nint use(void) { return target(); }\n#endif\n";
20032        let mut parser = Parser::new();
20033        parser
20034            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20035            .expect("C++ grammar");
20036        let tree = parser.parse(source, None).expect("fixture tree");
20037        let root = tree.root_node();
20038        let definition_start = source.find("target(void)").expect("definition");
20039        let reference_start = source.rfind("target()").expect("reference");
20040        let definition = root
20041            .descendant_for_byte_range(definition_start, definition_start + "target".len())
20042            .expect("definition node");
20043        let reference = root
20044            .descendant_for_byte_range(reference_start, reference_start + "target".len())
20045            .expect("reference node");
20046        let required =
20047            preprocessor_guard_environment(definition, source).expect("definition guard");
20048        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
20049        assert!(guard_requirements_hold_at_reference(
20050            &required,
20051            Some(&active)
20052        ));
20053    }
20054
20055    #[test]
20056    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20057    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
20058        let source = "g_autoptr(FuChunkArray) self = make_array();";
20059        let mut parser = Parser::new();
20060        parser
20061            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20062            .expect("C++ grammar");
20063        let tree = parser.parse(source, None).expect("fixture tree");
20064        let statement = tree.root_node().named_child(0).expect("statement");
20065        let binding =
20066            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
20067        assert_eq!(binding.name, "self");
20068        assert_eq!(binding.type_name, "FuChunkArray");
20069        assert_eq!(binding.pointer_depth, 1);
20070
20071        let near_miss = "holder(FuChunkArray) self = make_array();";
20072        let tree = parser.parse(near_miss, None).expect("near-miss tree");
20073        let statement = tree.root_node().named_child(0).expect("statement");
20074        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
20075    }
20076
20077    #[test]
20078    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
20079    fn boolean_guard_normalization_proves_equivalence_and_implication() {
20080        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
20081        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
20082        let negated_windows_branch =
20083            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
20084        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
20085        assert_eq!(negated_windows_branch, portable);
20086
20087        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
20088        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
20089        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
20090        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
20091        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
20092        assert!(fallback_branch.implies(&fallback_declaration));
20093        assert!(
20094            BooleanGuardExpression::Truthy("FEATURE".to_string())
20095                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
20096        );
20097        assert!(
20098            BooleanGuardExpression::Undefined("FEATURE".to_string())
20099                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
20100        );
20101        assert!(
20102            !BooleanGuardExpression::Defined("FEATURE".to_string())
20103                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
20104        );
20105        assert!(!fallback_declaration.implies(&fallback_branch));
20106    }
20107
20108    #[test]
20109    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
20110    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
20111        let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n    if (1) {\n        return helper(NULL, template); /* nested bound */\n    }\n    return helper(NULL, template); /* bound */\n}\nint lambda_scope(wchar_t *template) {\n    return [=]() { return helper(NULL, template); }(); /* lambda scope */\n}\nint unbound(void) {\n    return helper(NULL, template); /* unbound */\n}\n";
20112        let mut parser = Parser::new();
20113        parser
20114            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20115            .expect("C++ grammar");
20116        let tree = parser.parse(source, None).expect("fixture tree");
20117        let root = tree.root_node();
20118        let call = |marker: &str| {
20119            let start = source.find(marker).expect("call marker");
20120            let mut node = root
20121                .descendant_for_byte_range(start, start + "helper".len())
20122                .expect("call name node");
20123            loop {
20124                if node.kind() == "call_expression" {
20125                    break node;
20126                }
20127                node = node.parent().expect("call expression ancestor");
20128            }
20129        };
20130        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
20131        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
20132        let keyword_call = call("helper(NULL, template); /* bound */");
20133        let keyword_arguments = keyword_call
20134            .child_by_field_name("arguments")
20135            .expect("keyword argument list");
20136        assert_eq!(
20137            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
20138            1
20139        );
20140
20141        let nested_call = call("helper(NULL, template); /* nested bound */");
20142        let nested_arguments = nested_call
20143            .child_by_field_name("arguments")
20144            .expect("nested keyword argument list");
20145        assert_eq!(
20146            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
20147            1
20148        );
20149
20150        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
20151        let lambda_arguments = lambda_call
20152            .child_by_field_name("arguments")
20153            .expect("lambda keyword argument list");
20154        assert_eq!(
20155            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
20156            0,
20157            "a lambda body must not borrow the enclosing collapsed C parameter"
20158        );
20159
20160        assert_eq!(
20161            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
20162            0
20163        );
20164
20165        let unbound_call = call("helper(NULL, template); /* unbound */");
20166        let unbound_arguments = unbound_call
20167            .child_by_field_name("arguments")
20168            .expect("unbound argument list");
20169        assert_eq!(
20170            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
20171            0
20172        );
20173    }
20174
20175    #[test]
20176    #[brokk_bifrost_test_macros::hardwon(
20177        "#4407: collapsed C keyword parameters need structural position proof"
20178    )]
20179    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
20180        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
20181int type_keyword(template *value) {
20182    return helper(NULL, template); /* type */
20183}
20184int qualifier_keyword(const *template) {
20185    return helper(NULL, template); /* qualifier */
20186}
20187int annotation_keyword(wchar_t *value __attribute__((template))) {
20188    return helper(NULL, template); /* annotation */
20189}
20190int default_keyword(wchar_t *value = template) {
20191    return helper(NULL, template); /* default */
20192}
20193int nested_expression(wchar_t *value, (template)) {
20194    return helper(NULL, template); /* nested */
20195}
20196int malformed_head = (wchar_t *template) {
20197    return helper(NULL, template); /* malformed */
20198};
20199"#;
20200        let tree = parse_cpp(source);
20201        let root = tree.root_node();
20202        let call = |marker: &str| {
20203            let start = source.find(marker).expect("call marker");
20204            let mut node = root
20205                .descendant_for_byte_range(start, start + "helper".len())
20206                .expect("call name node");
20207            loop {
20208                if node.kind() == "call_expression" {
20209                    break Some(node);
20210                }
20211                let Some(parent) = node.parent() else {
20212                    break None;
20213                };
20214                node = parent;
20215            }
20216        };
20217        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
20218        let markers = [
20219            ("helper(NULL, template); /* type */", true),
20220            ("helper(NULL, template); /* qualifier */", false),
20221            ("helper(NULL, template); /* annotation */", false),
20222            ("helper(NULL, template); /* default */", false),
20223            ("helper(NULL, template); /* nested */", false),
20224            ("helper(NULL, template); /* malformed */", false),
20225        ];
20226        let mut exercised = 0;
20227        for (marker, expect_call) in markers {
20228            // The macro-declaration grammar admits the misplaced keyword's
20229            // neighbors as `macro_modifier`s, which flattens these heads and
20230            // their bodies: where no `call_expression` survives, the recovery
20231            // has no invocation to fire on, which is itself the non-admission
20232            // proof. Pin the split so the near-miss matrix cannot silently
20233            // decay: only the `type` marker retains an invocation.
20234            let Some(keyword_call) = call(marker) else {
20235                assert!(
20236                    !expect_call,
20237                    "{marker} lost its call_expression; the near-miss matrix changed"
20238                );
20239                continue;
20240            };
20241            assert!(
20242                expect_call,
20243                "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
20244            );
20245            exercised += 1;
20246            let keyword_arguments = keyword_call
20247                .child_by_field_name("arguments")
20248                .expect("keyword argument list");
20249            assert_eq!(
20250                recovered_c_keyword_argument_count(
20251                    &c_file,
20252                    keyword_call,
20253                    keyword_arguments,
20254                    source,
20255                ),
20256                0,
20257                "keyword in {marker} must not prove a parameter binding"
20258            );
20259        }
20260        assert!(
20261            exercised > 0,
20262            "at least one near-miss marker must exercise the count itself"
20263        );
20264    }
20265
20266    #[test]
20267    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20268    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
20269        let source = r#"#define MAKE(type) type *value
20270MAKE(int *);
20271typedef struct Item Item;
20272struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
20273void gen_op_movl(void *s, int first, int second) { }
20274const char *strZ(const char *value) { return value; }
20275int body(void *s) {
20276    MAKE(int *);
20277    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
20278                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
20279    execvp(strZ(value), UNCONSTIFY(char **, args));
20280}
20281 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
20282    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
20283int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
20284    if (d->units) {
20285        switch (target->nodetype) {
20286        case 1:
20287        case 2:
20288            break;
20289        default:
20290            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
20291        }
20292        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
20293        lysdict_remove(ctx, ((struct Item *)target)->units);
20294        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
20295    }
20296     return 0;
20297 }
20298STATIC EFI_STATUS Encode () { return 0; }
20299"#;
20300        let tree = parse_cpp(source);
20301        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
20302        let top_macro = tree
20303            .root_node()
20304            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
20305            .expect("top macro node");
20306        let body_macro_start = source
20307            .match_indices("MAKE(int *);")
20308            .nth(1)
20309            .expect("body macro")
20310            .0;
20311        let body_macro = tree
20312            .root_node()
20313            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
20314            .expect("body macro node");
20315        let function_call_start = source
20316            .find("gen_op_movl(s, offsetof(CPUX86State")
20317            .expect("function call");
20318        let function_call = tree
20319            .root_node()
20320            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
20321            .expect("function call node");
20322        let strz_start = source.find("strZ(value)").expect("nested function call");
20323        let strz = tree
20324            .root_node()
20325            .named_descendant_for_byte_range(strz_start, strz_start + 4)
20326            .expect("nested function call node");
20327        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
20328        let recovered_call = tree
20329            .root_node()
20330            .named_descendant_for_byte_range(
20331                recovered_call_start,
20332                recovered_call_start + "lysdict_remove".len(),
20333            )
20334            .expect("recovered call node");
20335        let binder_start = source.find("Encode").expect("binder");
20336        let binder = tree
20337            .root_node()
20338            .named_descendant_for_byte_range(binder_start, binder_start + 6)
20339            .expect("binder node");
20340
20341        assert!(recovered_c_function_declarator_invocation(top_macro));
20342        assert!(recovered_c_function_declarator_invocation(body_macro));
20343        assert!(recovered_c_function_declarator_invocation(function_call));
20344        assert!(recovered_c_function_declarator_invocation(strz));
20345        assert!(recovered_c_function_declarator_invocation(recovered_call));
20346        assert!(!recovered_c_function_declarator_invocation(binder));
20347    }
20348
20349    #[test]
20350    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20351    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
20352        let source = r#"typedef int krb5_context;
20353int helper(int first, int second) { return first + second; }
20354static krb5_context ctx;
20355int main(int argc, char **argv) {
20356    int ccinitial;
20357    const char *collection_name, *typename;
20358    typename = helper(ctx, ccinitial);
20359    return 0;
20360}
20361"#;
20362        let tree = parse_cpp(source);
20363        let ctx = tree
20364            .root_node()
20365            .descendant_for_byte_range(
20366                source.find("ctx, ccinitial").expect("ctx argument"),
20367                source.find("ctx, ccinitial").expect("ctx argument") + 3,
20368            )
20369            .expect("ctx node");
20370        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
20371        let ccinitial = tree
20372            .root_node()
20373            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
20374            .expect("sibling node");
20375        let typename = named_node_at(&tree, source, "typename = helper");
20376        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
20377
20378        // The macro-declaration grammar retains the argument beneath the
20379        // nested `ERROR` as a `type_descriptor`, so its type position is a
20380        // `type_identifier`; the recovery contract below is unchanged.
20381        assert_eq!(ctx.kind(), "type_identifier");
20382        assert!(recovered_c_parenthesized_declarator_reference(ctx));
20383        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
20384        assert!(!recovered_c_parenthesized_declarator_reference(typename));
20385        assert!(!recovered_c_parenthesized_declarator_reference(helper));
20386    }
20387
20388    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
20389        let mut parser = Parser::new();
20390        parser
20391            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20392            .expect("C++ grammar");
20393        let tree = parser.parse(source, None).expect("C++ fixture tree");
20394        let mut stack = vec![tree.root_node()];
20395        while let Some(node) = stack.pop() {
20396            if node.kind() == "enum_specifier" {
20397                return flattened_macro_namespace_components(node, source);
20398            }
20399            let mut cursor = node.walk();
20400            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
20401            stack.extend(children.into_iter().rev());
20402        }
20403        None
20404    }
20405
20406    #[test]
20407    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
20408    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
20409        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
20410namespace detail
20411{
20412enum class value_t { null };
20413}
20414NLOHMANN_JSON_NAMESPACE_END
20415NLOHMANN_JSON_NAMESPACE_BEGIN
20416namespace next
20417{
20418struct next_type {};
20419}
20420NLOHMANN_JSON_NAMESPACE_END
20421"#;
20422        assert_eq!(
20423            first_enum_flattened_namespace(complete),
20424            Some(vec!["detail".to_string()])
20425        );
20426
20427        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
20428        assert_eq!(
20429            first_enum_flattened_namespace(&stale_end),
20430            Some(vec!["detail".to_string()]),
20431            "a stale end marker before the begin marker must not replace the intended namespace"
20432        );
20433
20434        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
20435namespace detail
20436{
20437enum class value_t { null };
20438}
20439struct next_type {};
20440"#;
20441        assert_eq!(first_enum_flattened_namespace(incomplete), None);
20442    }
20443}
20444
20445/// Comparator laws for the total C++ lookup order introduced by #1876.
20446///
20447/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
20448/// any "first wins" selection (template families in #1836, the visible
20449/// identifier index, the type-candidate lists). If its comparator is not a
20450/// total order over CodeUnit identity, some pair stays tied and the survivor
20451/// falls back to the order the units arrived in -- which is FxHash iteration
20452/// order over keys whose hash covers the absolute workspace root. That is the
20453/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
20454/// checked generatively rather than on one hand-picked list.
20455///
20456/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
20457/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
20458/// carries no range, so declaration ranges are covered by the workspace-level
20459/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
20460#[cfg(test)]
20461mod lookup_order_properties {
20462    use super::*;
20463    use proptest::prelude::*;
20464
20465    /// Segment spellings the C++ extractor and the shared renderer actually
20466    /// produce, including the `$`-joined nested spellings and non-ASCII
20467    /// identifiers that a byte-wise comparison has to keep apart.
20468    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
20469    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
20470    /// Two roots so the order is pinned across workspaces as well as inside
20471    /// one: the root path is precisely the byte string that used to leak into
20472    /// iteration order.
20473    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
20474    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
20475    const KINDS: [CodeUnitType; 6] = [
20476        CodeUnitType::Class,
20477        CodeUnitType::Function,
20478        CodeUnitType::Field,
20479        CodeUnitType::Module,
20480        CodeUnitType::Macro,
20481        CodeUnitType::FileScope,
20482    ];
20483
20484    /// Where one unit sits relative to another under the comparator that
20485    /// `sort_lookup_units` owns.
20486    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
20487    enum ProbedOrder {
20488        Before,
20489        Tied,
20490        After,
20491        /// Both directions reported "strictly first": the comparator is not
20492        /// dual, and no sort over it can be order-independent.
20493        Contradictory,
20494    }
20495
20496    impl ProbedOrder {
20497        fn mirror(self) -> Self {
20498            match self {
20499                ProbedOrder::Before => ProbedOrder::After,
20500                ProbedOrder::After => ProbedOrder::Before,
20501                other => other,
20502            }
20503        }
20504
20505        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
20506        fn signum(self) -> i8 {
20507            match self {
20508                ProbedOrder::Before => -1,
20509                ProbedOrder::Tied => 0,
20510                ProbedOrder::After => 1,
20511                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
20512            }
20513        }
20514    }
20515
20516    /// Read the comparator through its only caller.
20517    ///
20518    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
20519    /// output says exactly whether the comparator put the second element
20520    /// strictly first. Sorting both arrangements of one pair therefore reports
20521    /// the comparator's verdict in both directions, including the contradictory
20522    /// case a single sort would hide.
20523    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
20524        if left == right {
20525            // A stable sort cannot distinguish two equal values, and `Equal` is
20526            // the only verdict a total order can give them.
20527            return ProbedOrder::Tied;
20528        }
20529        let mut forward = vec![left.clone(), right.clone()];
20530        sort_lookup_units(&mut forward);
20531        let mut backward = vec![right.clone(), left.clone()];
20532        sort_lookup_units(&mut backward);
20533        let left_first = backward[0] == *left;
20534        let right_first = forward[0] == *right;
20535        match (left_first, right_first) {
20536            (true, true) => ProbedOrder::Contradictory,
20537            (true, false) => ProbedOrder::Before,
20538            (false, true) => ProbedOrder::After,
20539            (false, false) => ProbedOrder::Tied,
20540        }
20541    }
20542
20543    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
20544        (
20545            0..ROOT_NAMES.len(),
20546            0..REL_PATHS.len(),
20547            0..KINDS.len(),
20548            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
20549            0..3usize,
20550            0..SIGNATURES.len(),
20551            any::<bool>(),
20552        )
20553            .prop_map(
20554                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
20555                    let source = ProjectFile::new(
20556                        std::env::temp_dir().join(ROOT_NAMES[root]),
20557                        REL_PATHS[rel_path],
20558                    );
20559                    let interner = segment_interner();
20560                    let mut fq = FqName::new();
20561                    for (atom, segment_kind) in &segments {
20562                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
20563                    }
20564                    // `from_fq` requires a non-empty declaration tail.
20565                    let package_segment_count = package_prefix % fq.len();
20566                    CodeUnit::from_fq(
20567                        source,
20568                        KINDS[kind],
20569                        fq,
20570                        package_segment_count,
20571                        SIGNATURES[signature].map(str::to_string),
20572                        synthetic,
20573                    )
20574                },
20575            )
20576    }
20577
20578    proptest! {
20579        #![proptest_config(ProptestConfig::with_cases(256))]
20580
20581        /// Reflexivity and duality: a unit ties with itself, and no pair is
20582        /// strictly first in both directions.
20583        #[test]
20584        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20585        fn lookup_order_is_reflexive_and_dual(
20586            left in code_unit_strategy(),
20587            right in code_unit_strategy(),
20588        ) {
20589            prop_assert_eq!(
20590                probe_order(&left, &left),
20591                ProbedOrder::Tied,
20592                "a unit must tie with itself: {:?}",
20593                left
20594            );
20595            let forward = probe_order(&left, &right);
20596            prop_assert_ne!(
20597                forward,
20598                ProbedOrder::Contradictory,
20599                "comparator put each of these strictly first: left={:?} right={:?}",
20600                left,
20601                right
20602            );
20603            prop_assert_eq!(
20604                probe_order(&right, &left),
20605                forward.mirror(),
20606                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
20607                left,
20608                right
20609            );
20610        }
20611
20612        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
20613        #[test]
20614        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20615        fn lookup_order_is_transitive(
20616            a in code_unit_strategy(),
20617            b in code_unit_strategy(),
20618            c in code_unit_strategy(),
20619        ) {
20620            let ab = probe_order(&a, &b);
20621            let bc = probe_order(&b, &c);
20622            let ac = probe_order(&a, &c);
20623            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
20624                prop_assert_ne!(
20625                    probed,
20626                    ProbedOrder::Contradictory,
20627                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
20628                    pair,
20629                    a,
20630                    b,
20631                    c
20632                );
20633            }
20634            if ab.signum() <= 0 && bc.signum() <= 0 {
20635                prop_assert!(
20636                    ac.signum() <= 0,
20637                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
20638                     a={:?} b={:?} c={:?}",
20639                    ab,
20640                    bc,
20641                    ac,
20642                    a,
20643                    b,
20644                    c
20645                );
20646            }
20647        }
20648
20649        /// The property #1876 exists for: only identical identities may tie.
20650        /// A tie between distinct units is the residual hash-order dependence.
20651        #[test]
20652        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20653        fn lookup_order_separates_distinct_identities(
20654            left in code_unit_strategy(),
20655            right in code_unit_strategy(),
20656        ) {
20657            if probe_order(&left, &right) == ProbedOrder::Tied {
20658                prop_assert_eq!(
20659                    &left,
20660                    &right,
20661                    "distinct identities tied, so their order is whatever order they \
20662                     arrived in: left_segments={:?} right_segments={:?}",
20663                    left.fq_segments_debug(),
20664                    right.fq_segments_debug()
20665                );
20666            }
20667        }
20668
20669        /// The consequence the resolver relies on: the sorted list is a
20670        /// function of the SET of units, not of the order they were pushed in.
20671        #[test]
20672        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20673        fn lookup_sort_is_permutation_invariant(
20674            units in prop::collection::vec(code_unit_strategy(), 1..=8),
20675        ) {
20676            let mut sorted = units.clone();
20677            sort_lookup_units(&mut sorted);
20678            for rotation in 0..units.len() {
20679                for reversed in [false, true] {
20680                    let mut permuted = units.clone();
20681                    permuted.rotate_left(rotation);
20682                    if reversed {
20683                        permuted.reverse();
20684                    }
20685                    sort_lookup_units(&mut permuted);
20686                    prop_assert_eq!(
20687                        &permuted,
20688                        &sorted,
20689                        "sorting a permutation gave a different list \
20690                         (rotation={}, reversed={}): input={:?}",
20691                        rotation,
20692                        reversed,
20693                        units
20694                    );
20695                }
20696            }
20697        }
20698    }
20699}