Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{
2    CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::declarations::{
5    cpp_displaced_preprocessor_terminator, cpp_export_macro_token, cpp_field_declaration_linkage,
6    cpp_template_term, node_text, normalize_cpp_whitespace, recovered_exported_class_has_body,
7};
8use crate::graph::CppGraphSource;
9use crate::graph::extractor::ScanCtx;
10use crate::graph_support::CppSource;
11use crate::imports::{
12    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
13};
14use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
15use brokk_bifrost_core::analyzer::model::{
16    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
17    CppTemplateParameterMetadata, CppTemplateTerm,
18};
19use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
20use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
21use brokk_bifrost_core::analyzer::tree_walk::node_for_exact_range;
22use brokk_bifrost_core::analyzer::usages::common::same_node;
23use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
24use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
25use brokk_bifrost_core::cancellation::CancellationToken;
26use brokk_bifrost_core::hash::{HashMap, HashSet};
27use std::borrow::Cow;
28#[cfg(any(test, feature = "test-support"))]
29use std::cell::Cell;
30use std::cell::OnceCell;
31use std::cmp::Ordering as CmpOrdering;
32use std::collections::BTreeSet;
33use std::hash::Hash;
34#[cfg(any(test, feature = "test-support"))]
35use std::sync::atomic::{AtomicUsize, Ordering};
36use std::sync::{Arc, Mutex, OnceLock, RwLock};
37use std::thread::ThreadId;
38use tree_sitter::{Node, Parser, Tree};
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub enum TargetKind {
42    Type,
43    Constructor,
44    FreeFunction,
45    Method,
46    GlobalField,
47    MemberField,
48    Macro,
49}
50
51pub enum LexicalTypeResolution {
52    Resolved {
53        unit: CodeUnit,
54        components: Vec<String>,
55        candidates: Vec<CodeUnit>,
56    },
57    Ambiguous,
58    Missing,
59}
60
61#[derive(Clone, Copy)]
62enum TypeCandidateResolution<'a> {
63    Canonical,
64    PreserveAlias,
65    PreserveTarget(&'a CodeUnit),
66}
67
68/// Why a name did not reduce to one indexed type declaration.
69///
70/// The two answers are not interchangeable. `Ambiguous` means the index holds
71/// several declarations and the caller must choose; `Unresolvable` means the
72/// index holds none, which is a boundary the workspace cannot see past. A
73/// `using`/`typedef` alias to a template parameter or to a standard-library
74/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
75/// answer with an empty candidate list (#1828).
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77enum TypeCandidateFailure {
78    Ambiguous,
79    Unresolvable,
80}
81
82impl TypeCandidateFailure {
83    fn lexical_resolution(self) -> LexicalTypeResolution {
84        match self {
85            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
86            Self::Unresolvable => LexicalTypeResolution::Missing,
87        }
88    }
89}
90
91pub enum LexicalCallableValueResolution {
92    Type(CodeUnit),
93    FreeFunction(CodeUnit),
94    Ambiguous,
95    Missing,
96}
97
98pub enum UsingEnumMemberResolution {
99    Resolved { owner: CodeUnit, member: CodeUnit },
100    Ambiguous,
101    Missing,
102}
103
104pub enum NamespaceValueResolution {
105    Resolved,
106    Ambiguous,
107    Missing,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub enum OrdinaryMacroReferenceResolution {
112    Resolved(CodeUnit),
113    Ambiguous,
114    Missing,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum RecoveredCReferenceRanges {
119    Complete(Vec<Range>),
120    LimitExceeded,
121}
122
123pub fn resolve_namespace_value(
124    analyzer: &CppGraphSource<'_>,
125    visibility: &VisibilityIndex<'_>,
126    file: &ProjectFile,
127    namespace: &str,
128    name: &str,
129    before_byte: usize,
130) -> NamespaceValueResolution {
131    let mut matches = Vec::new();
132    for candidate in visibility.visible_identifier_candidates(file, name) {
133        if type_owner_of(analyzer, candidate).is_some()
134            || candidate.package_name() != namespace
135            || (candidate.source() == file
136                && !analyzer
137                    .ranges(candidate)
138                    .iter()
139                    .any(|range| range.start_byte < before_byte))
140            || matches
141                .iter()
142                .any(|existing| same_visible_symbol(existing, candidate))
143        {
144            continue;
145        }
146        matches.push(candidate.clone());
147        if matches.len() > 1 {
148            return NamespaceValueResolution::Ambiguous;
149        }
150    }
151    matches
152        .pop()
153        .map(|_| NamespaceValueResolution::Resolved)
154        .unwrap_or(NamespaceValueResolution::Missing)
155}
156
157pub(crate) struct ScopedUsingEnumOwners {
158    scopes: Vec<Vec<CodeUnit>>,
159}
160
161/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
162/// Cross-file and inherited class imports are deliberately not inferred without persisted
163/// evidence; a missing imported enumerator therefore remains unproven rather than being
164/// misresolved.
165pub(crate) struct SemanticUsingEnumOwners {
166    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
167    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
168}
169
170pub(crate) enum SemanticUsingEnumMemberResolution {
171    Class(UsingEnumMemberResolution),
172    Namespace(UsingEnumMemberResolution),
173    Missing,
174}
175
176impl SemanticUsingEnumOwners {
177    pub(crate) fn new() -> Self {
178        Self {
179            class_imports: HashMap::default(),
180            namespace_imports: HashMap::default(),
181        }
182    }
183
184    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
185        let imports = self.class_imports.entry(class).or_default();
186        if !imports
187            .iter()
188            .any(|existing| same_visible_symbol(existing, &enum_owner))
189        {
190            imports.push(enum_owner);
191        }
192    }
193
194    pub fn import_namespace(
195        &mut self,
196        namespace: Vec<String>,
197        declaration_byte: usize,
198        enum_owner: CodeUnit,
199    ) {
200        let imports = self.namespace_imports.entry(namespace).or_default();
201        if !imports
202            .iter()
203            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
204        {
205            imports.push((declaration_byte, enum_owner));
206        }
207    }
208
209    pub fn resolve_member(
210        &self,
211        visibility: &VisibilityIndex<'_>,
212        file: &ProjectFile,
213        class: Option<&CodeUnit>,
214        namespace: &[String],
215        before_byte: usize,
216        name: &str,
217    ) -> SemanticUsingEnumMemberResolution {
218        if let Some(class) = class
219            && let Some((_, imports)) = self
220                .class_imports
221                .iter()
222                .find(|(owner, _)| same_visible_symbol(owner, class))
223        {
224            let resolution =
225                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
226            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
227                return SemanticUsingEnumMemberResolution::Class(resolution);
228            }
229        }
230        for prefix_len in (0..=namespace.len()).rev() {
231            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
232                continue;
233            };
234            let owners = imports
235                .iter()
236                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
237                .map(|(_, owner)| owner);
238            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
239            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
240                return SemanticUsingEnumMemberResolution::Namespace(resolution);
241            }
242        }
243        SemanticUsingEnumMemberResolution::Missing
244    }
245}
246
247fn resolve_using_enum_member_for_owners<'a>(
248    visibility: &VisibilityIndex<'_>,
249    file: &ProjectFile,
250    owners: impl IntoIterator<Item = &'a CodeUnit>,
251    name: &str,
252) -> UsingEnumMemberResolution {
253    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
254    for owner in owners {
255        for member in visibility.visible_members_for_owner_name(file, owner, name) {
256            if !member.is_field()
257                || matches.iter().any(|(existing_owner, existing_member)| {
258                    same_visible_symbol(existing_owner, owner)
259                        && same_visible_symbol(existing_member, member)
260                })
261            {
262                continue;
263            }
264            matches.push((owner.clone(), member.clone()));
265        }
266    }
267    match matches.len() {
268        0 => UsingEnumMemberResolution::Missing,
269        1 => {
270            let (owner, member) = matches.pop().expect("one using-enum match");
271            UsingEnumMemberResolution::Resolved { owner, member }
272        }
273        _ => UsingEnumMemberResolution::Ambiguous,
274    }
275}
276
277impl ScopedUsingEnumOwners {
278    pub(crate) fn new() -> Self {
279        Self {
280            scopes: vec![Vec::new()],
281        }
282    }
283
284    pub fn enter_scope(&mut self) {
285        self.scopes.push(Vec::new());
286    }
287
288    pub fn exit_scope(&mut self) {
289        if self.scopes.len() > 1 {
290            self.scopes.pop();
291        }
292    }
293
294    pub fn import(&mut self, owner: CodeUnit) {
295        let scope = self
296            .scopes
297            .last_mut()
298            .expect("using-enum scope stack is never empty");
299        if !scope
300            .iter()
301            .any(|existing| same_visible_symbol(existing, &owner))
302        {
303            scope.push(owner);
304        }
305    }
306
307    pub fn resolve_member(
308        &self,
309        visibility: &VisibilityIndex<'_>,
310        file: &ProjectFile,
311        name: &str,
312    ) -> UsingEnumMemberResolution {
313        for scope in self.scopes.iter().rev() {
314            let resolution =
315                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
316            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
317                return resolution;
318            }
319        }
320        UsingEnumMemberResolution::Missing
321    }
322}
323
324#[derive(Clone)]
325pub struct TargetSpec {
326    pub target: CodeUnit,
327    pub kind: TargetKind,
328    pub owner: Option<CodeUnit>,
329    pub member_name: String,
330    pub callable_arity: Option<CallableArity>,
331    pub activated_callable_arities: Vec<ActivatedCallableArity>,
332    pub param_types: Option<Vec<String>>,
333    pub enum_owner_kind: EnumOwnerKind,
334    pub owner_is_forward_declaration: bool,
335}
336
337#[derive(Clone, Copy)]
338pub struct ActivatedCallableArity {
339    pub activation_byte: usize,
340    pub arity: CallableArity,
341}
342
343#[derive(Debug, PartialEq, Eq, Hash)]
344pub struct TypeScanKey {
345    target: LogicalSymbolKey,
346    member_name: String,
347}
348
349#[derive(Clone, Debug, PartialEq, Eq, Hash)]
350struct LogicalSymbolKey {
351    kind: CodeUnitType,
352    fq_name: String,
353    signature: Option<String>,
354}
355
356struct ResolvedTypeOwner {
357    unit: CodeUnit,
358    is_forward_declaration: bool,
359}
360
361#[derive(Clone, Copy, PartialEq, Eq)]
362pub enum EnumOwnerKind {
363    Scoped,
364    Unscoped,
365    NonEnum,
366}
367
368impl TargetSpec {
369    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
370        (self.kind == TargetKind::Type).then(|| TypeScanKey {
371            target: logical_symbol_key(&self.target),
372            member_name: self.member_name.clone(),
373        })
374    }
375
376    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
377        if target.is_class() {
378            return Some(Self::new(
379                target.clone(),
380                TargetKind::Type,
381                Some(target.clone()),
382                target.identifier().to_string(),
383                None,
384                None,
385            ));
386        }
387
388        if target.is_field() {
389            // A namespace (module) is not a receiver: a namespace-scoped constant such as
390            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
391            // qualified from outside, exactly like a global. Treating a module owner as a
392            // member-field owner makes the receiver/owner-context match reject every valid
393            // reference, so resolve it as a global field instead.
394            let owner = type_owner_of(analyzer, target);
395            let kind = if owner.is_some() {
396                TargetKind::MemberField
397            } else {
398                TargetKind::GlobalField
399            };
400            let enum_owner_kind = owner
401                .as_ref()
402                .map(|owner| classify_enum_owner(analyzer, owner))
403                .unwrap_or(EnumOwnerKind::NonEnum);
404            let mut spec = Self::new(
405                target.clone(),
406                kind,
407                owner,
408                target.identifier().to_string(),
409                None,
410                None,
411            );
412            spec.enum_owner_kind = enum_owner_kind;
413            return Some(spec);
414        }
415
416        if target.is_function() {
417            // Free functions declared inside a namespace have a module owner; that namespace is
418            // not a call receiver, so resolve them as free functions rather than methods.
419            let owner_resolution = target_type_owner_resolution(analyzer, target);
420            let owner_is_forward_declaration = owner_resolution
421                .as_ref()
422                .is_some_and(|owner| owner.is_forward_declaration);
423            let owner = owner_resolution.map(|owner| owner.unit);
424            let kind = if owner.as_ref().is_some_and(|owner| {
425                target.identifier() == owner.identifier()
426                    || analyzer
427                        .cpp
428                        .and_then(|cpp| cpp.template_metadata(owner))
429                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
430            }) {
431                TargetKind::Constructor
432            } else if owner.is_some() {
433                TargetKind::Method
434            } else {
435                TargetKind::FreeFunction
436            };
437            let mut spec = Self::new(
438                target.clone(),
439                kind,
440                owner,
441                target.identifier().to_string(),
442                Some(cpp_callable_arity(analyzer, target)),
443                target.signature().and_then(cpp_signature_param_types),
444            );
445            spec.owner_is_forward_declaration = owner_is_forward_declaration;
446            return Some(spec);
447        }
448
449        if target.is_macro() {
450            return Some(Self::new(
451                target.clone(),
452                TargetKind::Macro,
453                None,
454                target.identifier().to_string(),
455                None,
456                None,
457            ));
458        }
459
460        None
461    }
462
463    pub fn with_visible_callable_arities<'a>(
464        &'a self,
465        analyzer: &CppGraphSource<'_>,
466        cpp: &dyn CppSource,
467        visibility: &VisibilityIndex<'_>,
468        file: &ProjectFile,
469        prepared: &PreparedSyntaxTree,
470    ) -> Cow<'a, Self> {
471        let macro_parameter_arity =
472            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
473        let activated_callable_arities =
474            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
475        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
476            return Cow::Borrowed(self);
477        }
478        let mut effective = self.clone();
479        if let Some(macro_parameter_arity) = macro_parameter_arity {
480            effective.callable_arity = Some(macro_parameter_arity);
481        }
482        effective.activated_callable_arities = activated_callable_arities;
483        Cow::Owned(effective)
484    }
485
486    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
487        let base = self.callable_arity?;
488        Some(
489            self.activated_callable_arities
490                .iter()
491                .filter(|candidate| candidate.activation_byte <= byte)
492                .fold(base, |arity, candidate| {
493                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
494                }),
495        )
496    }
497
498    pub fn new(
499        target: CodeUnit,
500        kind: TargetKind,
501        owner: Option<CodeUnit>,
502        member_name: String,
503        callable_arity: Option<CallableArity>,
504        param_types: Option<Vec<String>>,
505    ) -> Self {
506        Self {
507            target,
508            kind,
509            owner,
510            member_name,
511            callable_arity,
512            activated_callable_arities: Vec::new(),
513            param_types,
514            enum_owner_kind: EnumOwnerKind::NonEnum,
515            owner_is_forward_declaration: false,
516        }
517    }
518}
519
520fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
521    LogicalSymbolKey {
522        kind: unit.kind(),
523        fq_name: unit.fq_name(),
524        signature: unit.signature().map(str::to_string),
525    }
526}
527
528fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
529    let classify = |source: &str| {
530        let source = source.trim_start();
531        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
532            Some(EnumOwnerKind::Scoped)
533        } else if source.starts_with("enum ") {
534            Some(EnumOwnerKind::Unscoped)
535        } else {
536            None
537        }
538    };
539    owner
540        .signature()
541        .and_then(classify)
542        .or_else(|| {
543            analyzer
544                .get_source(owner, false)
545                .as_deref()
546                .and_then(classify)
547        })
548        .unwrap_or(EnumOwnerKind::NonEnum)
549}
550
551#[derive(Clone, PartialEq, Eq, Hash)]
552pub struct CppScanBinding {
553    pub unit: Option<CodeUnit>,
554    pub type_name: Option<String>,
555    pub indirection: i32,
556}
557
558impl CppScanBinding {
559    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
560        Self {
561            type_name: Some(cpp_name_for(&unit)),
562            unit: Some(unit),
563            indirection,
564        }
565    }
566
567    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
568        Self {
569            type_name: Some(type_name),
570            unit,
571            indirection,
572        }
573    }
574
575    pub fn as_arg_type(&self) -> Option<CppArgType> {
576        let name = self
577            .type_name
578            .clone()
579            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
580        Some(CppArgType {
581            name,
582            unit: self.unit.clone(),
583            indirection: self.indirection,
584            pointee_const: false,
585        })
586    }
587}
588
589type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
590type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
591pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
592pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
593type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
594pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
595type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
596type MacroLocalBindingTemplateCache =
597    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
598
599#[derive(Clone, Default)]
600pub struct MacroEnvironment {
601    bindings: HashMap<String, MacroBinding>,
602    unknown_names: bool,
603    applied_pragma_once_files: HashSet<ProjectFile>,
604    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
605}
606
607#[derive(Default)]
608pub struct MacroEnvironmentCursor {
609    frontier: usize,
610    environment: Arc<MacroEnvironment>,
611}
612
613impl MacroEnvironment {
614    fn binding(&self, name: &str) -> Option<&MacroBinding> {
615        self.bindings.get(name)
616    }
617
618    fn may_bind(&self, name: &str) -> bool {
619        self.bindings.contains_key(name) || self.unknown_names
620    }
621
622    fn insert(&mut self, name: String, binding: MacroBinding) {
623        self.bindings.insert(name, binding);
624    }
625
626    fn remove(&mut self, name: &str) {
627        self.bindings.remove(name);
628    }
629
630    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
631        for binding in self.bindings.values_mut() {
632            *binding = MacroBinding::uncertain_from(binding, source, byte);
633        }
634        self.unknown_names = true;
635    }
636}
637
638#[derive(Clone)]
639pub enum EffectiveUsingTarget {
640    Ordinary {
641        name: String,
642        target_components: Vec<String>,
643        global: bool,
644    },
645    Namespace {
646        namespace_components: Vec<String>,
647        global: bool,
648    },
649}
650
651#[derive(Clone)]
652pub struct OrdinaryTypeImport {
653    pub target: EffectiveUsingTarget,
654    pub source: ProjectFile,
655    pub declaration_byte: usize,
656    pub scope_start: usize,
657    pub scope_end: usize,
658    pub scope_depth: usize,
659    pub block_scope: bool,
660    pub lexical_depth: usize,
661    pub declaration_namespace: Vec<String>,
662    pub namespace_scope: Option<Vec<String>>,
663    pub resolved_target_components: Option<Vec<String>>,
664    pub required_guards: HashSet<PreprocessorGuard>,
665}
666
667#[derive(Clone)]
668pub struct ConditionalIncludeProjection {
669    pub activation_byte: usize,
670    pub required_guards: HashSet<PreprocessorGuard>,
671}
672
673#[derive(Default)]
674pub struct SourceUsingIndex {
675    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
676    pub directives: Vec<OrdinaryTypeImport>,
677}
678
679#[derive(Default)]
680pub struct ProjectUsingIndex {
681    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
682    pub directives: Vec<OrdinaryTypeImport>,
683}
684
685type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
686
687pub struct EffectiveUsingIndex {
688    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
689}
690
691impl EffectiveUsingIndex {
692    fn new(_root: ProjectFile) -> Self {
693        Self {
694            projected_by_name: Mutex::new(HashMap::default()),
695        }
696    }
697
698    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
699        self.projected_by_name
700            .lock()
701            .expect("C++ effective-using projection cache poisoned")
702            .entry(name.to_string())
703            .or_default()
704            .clone()
705    }
706}
707
708pub enum OrdinaryTypeImportResolution {
709    Resolved {
710        target: CodeUnit,
711        target_components: Vec<String>,
712        lexical_depth: usize,
713        is_direct: bool,
714    },
715    Ambiguous {
716        lexical_depth: usize,
717    },
718    Missing,
719}
720
721type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
722type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
723type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
724type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
725type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
726type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
727type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
728
729/// Per-query C++ visibility facts.
730///
731/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
732/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
733/// generations and overlays, where another generation's hydrated states would
734/// be wrong). An index that owned a clone would therefore see an inactive read
735/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
736/// the same source from the store once per candidate instead of once per query
737/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
738/// tens of thousands of times.
739pub struct VisibilityIndex<'a> {
740    cpp: &'a dyn CppSource,
741    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
742    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
743    global_field_internal_linkage: HashMap<CodeUnit, bool>,
744    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
745    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
746    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
747    visible_parser_alias_target_names:
748        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
749    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
750    project_using_index: OnceLock<ProjectUsingIndex>,
751    callable_reference_specs:
752        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
753    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
754    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
755    #[cfg(any(test, feature = "test-support"))]
756    conditional_include_projection_index_build_count: AtomicUsize,
757    #[cfg(any(test, feature = "test-support"))]
758    conditional_include_projection_state_count: AtomicUsize,
759    #[cfg(any(test, feature = "test-support"))]
760    include_activation_build_count: AtomicUsize,
761    #[cfg(any(test, feature = "test-support"))]
762    using_donor_activation_count: AtomicUsize,
763    #[cfg(any(test, feature = "test-support"))]
764    using_namespace_lookup_count: AtomicUsize,
765    #[cfg(any(test, feature = "test-support"))]
766    using_name_candidate_inspection_count: AtomicUsize,
767    #[cfg(any(test, feature = "test-support"))]
768    using_source_index_walk_count: AtomicUsize,
769    #[cfg(any(test, feature = "test-support"))]
770    using_guard_context_inspection_count: AtomicUsize,
771    #[cfg(any(test, feature = "test-support"))]
772    callable_reference_spec_build_count: AtomicUsize,
773    #[cfg(any(test, feature = "test-support"))]
774    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
775    #[cfg(any(test, feature = "test-support"))]
776    visible_parser_alias_name_set_build_count: AtomicUsize,
777    #[cfg(any(test, feature = "test-support"))]
778    visible_parser_alias_target_names_build_count: AtomicUsize,
779    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
780    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
781    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
782    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
783    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
784    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
785    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
786    // A forward cursor is useful only while its caller visits one source in byte order. The
787    // authoritative differential shares this index across target workers, whose frontiers can
788    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
789    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
790    // immutable event and parse caches above remain shared.
791    pub macro_environment_cursors:
792        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
793    macro_replacements: Mutex<MacroReplacementCache>,
794    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
795    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
796    #[cfg(any(test, feature = "test-support"))]
797    pub macro_replacement_parse_count: AtomicUsize,
798    #[cfg(any(test, feature = "test-support"))]
799    pub macro_event_application_count: AtomicUsize,
800    #[cfg(any(test, feature = "test-support"))]
801    pub macro_environment_copy_count: AtomicUsize,
802    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
803    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
804    #[cfg(any(test, feature = "test-support"))]
805    qualified_candidate_inspections: AtomicUsize,
806    #[cfg(any(test, feature = "test-support"))]
807    target_preserving_type_resolution_count: AtomicUsize,
808}
809
810#[derive(Clone, Debug, PartialEq, Eq, Hash)]
811pub enum PreprocessorGuard {
812    Defined(String),
813    Undefined(String),
814    Expression(String),
815    NegatedExpression(String),
816    Constant(bool),
817}
818
819impl PreprocessorGuard {
820    fn negated(&self) -> Self {
821        match self {
822            Self::Defined(name) => Self::Undefined(name.clone()),
823            Self::Undefined(name) => Self::Defined(name.clone()),
824            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
825            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
826            Self::Constant(value) => Self::Constant(!value),
827        }
828    }
829
830    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
831        match self {
832            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
833            // The expression has already been isolated structurally by
834            // tree-sitter, but its full preprocessor semantics are outside the
835            // analyzer's guard model. Any macro mutation can therefore change
836            // its truth value.
837            Self::Expression(_) | Self::NegatedExpression(_) => true,
838            Self::Constant(_) => false,
839        }
840    }
841}
842
843#[derive(Clone, PartialEq, Eq)]
844pub enum MacroDefinition {
845    Object {
846        replacement: String,
847    },
848    Function {
849        parameters: Vec<String>,
850        replacement: String,
851    },
852    Unsupported,
853}
854
855#[derive(Clone, Debug, PartialEq, Eq)]
856pub enum MacroIncludeProtection {
857    MacroGuard(String),
858    PragmaOnce,
859    None,
860}
861
862enum ParsedMacroReplacement {
863    Parsed { source: String, tree: Tree },
864    Unsupported,
865}
866
867#[derive(Clone)]
868enum MacroLocalBindingTypeTemplate {
869    Parameter(usize),
870    Fixed(String),
871}
872
873#[derive(Clone)]
874struct MacroLocalBindingTemplate {
875    name: String,
876    declared_type: MacroLocalBindingTypeTemplate,
877    pointer_depth: i32,
878}
879
880/// A local declaration contributed by one structurally known function-like macro.
881///
882/// `type_node` points into the invocation syntax when the replacement's type
883/// is one of the macro parameters. Consumers can therefore use their normal
884/// lexical type resolver without parsing replacement text themselves.
885pub struct MacroLocalBinding<'tree> {
886    pub name: String,
887    pub type_name: String,
888    pub type_node: Option<Node<'tree>>,
889    pub pointer_depth: i32,
890}
891
892#[derive(Clone, PartialEq, Eq)]
893pub struct MacroBinding {
894    source: ProjectFile,
895    declaration_byte: usize,
896    definition: MacroDefinition,
897    exact: bool,
898}
899
900impl MacroBinding {
901    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
902        Self {
903            source: source.clone(),
904            declaration_byte,
905            definition: MacroDefinition::Unsupported,
906            exact: false,
907        }
908    }
909
910    fn is_exact(&self) -> bool {
911        self.exact
912    }
913
914    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
915        Self {
916            source: source.clone(),
917            declaration_byte,
918            definition: current.definition.clone(),
919            exact: false,
920        }
921    }
922}
923
924#[derive(Clone)]
925pub enum MacroEvent {
926    Define {
927        name: String,
928        binding: MacroBinding,
929        byte: usize,
930        conditional: bool,
931    },
932    Undef {
933        name: String,
934        byte: usize,
935        conditional: bool,
936    },
937    Include {
938        targets: Vec<ProjectFile>,
939        byte: usize,
940        conditional: bool,
941    },
942    Invalidate {
943        byte: usize,
944    },
945}
946
947impl MacroEvent {
948    pub fn byte(&self) -> usize {
949        match self {
950            Self::Define { byte, .. }
951            | Self::Undef { byte, .. }
952            | Self::Include { byte, .. }
953            | Self::Invalidate { byte } => *byte,
954        }
955    }
956}
957
958#[derive(Clone, Copy, Debug, PartialEq, Eq)]
959pub enum CallArityEvidence {
960    Exact(usize),
961    Unknown,
962}
963
964impl CallArityEvidence {
965    pub fn exact(self) -> Option<usize> {
966        match self {
967            Self::Exact(arity) => Some(arity),
968            Self::Unknown => None,
969        }
970    }
971
972    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
973        self.exact().map(|arity| expected.accepts(arity))
974    }
975}
976
977#[derive(Clone)]
978struct DeclaredFieldTypeFact {
979    type_text: String,
980    indirection: i32,
981    template_arguments: Option<Vec<CppTemplateExpression>>,
982}
983
984#[derive(Clone)]
985enum StructuredAliasTarget {
986    Builtin,
987    Named {
988        components: Vec<String>,
989        global: bool,
990        arguments: Option<Vec<CppTemplateExpression>>,
991    },
992}
993
994struct CppAlias {
995    name: String,
996    target: String,
997    namespace: Option<String>,
998}
999
1000type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1001
1002/// Why template-argument resolution failed. Definition diagnostics render
1003/// each mode differently; graph scans only care that the resolution is
1004/// unproven and match `Err(_)`.
1005#[derive(Debug, Clone, PartialEq, Eq)]
1006pub enum CppTemplateResolutionError {
1007    /// A template alias expansion revisited `alias`.
1008    AliasCycle { alias: CodeUnit },
1009    /// The explicit arguments do not bind to the declared template parameters.
1010    ArgumentBinding,
1011    /// Bound arguments do not substitute into the alias target's arguments.
1012    Substitution,
1013    /// No visible primary template declaration could be selected and
1014    /// reconciled for the specialization family.
1015    PrimarySelection,
1016    /// More than one applicable specialization remains and none is strictly
1017    /// more specialized than every other candidate.
1018    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1019}
1020
1021/// The ambiguity candidates, deduplicated to one representative per visible
1022/// symbol so a diagnostic lists each contender once.
1023fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1024    let mut distinct: Vec<CodeUnit> = Vec::new();
1025    for unit in units {
1026        if !distinct
1027            .iter()
1028            .any(|existing| same_visible_symbol(existing, unit))
1029        {
1030            distinct.push(unit.clone());
1031        }
1032    }
1033    distinct
1034}
1035
1036impl<'a> VisibilityIndex<'a> {
1037    pub fn cpp(&self) -> &'a dyn CppSource {
1038        self.cpp
1039    }
1040
1041    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1042    /// bypassing the include-closure walk [`Self::build`] performs.
1043    ///
1044    /// The resolver's own unit tests drive the type-resolution paths against a
1045    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1046    /// because they need a real `CppAnalyzer`, so the struct literal they used
1047    /// to write inline is here instead of thirty-three public fields.
1048    #[cfg(any(test, feature = "test-support"))]
1049    pub fn from_visible_files_for_test(
1050        cpp: &'a dyn CppSource,
1051        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1052    ) -> Self {
1053        let visible_source_files_by_root = visible_by_file
1054            .iter()
1055            .map(|(file, visible)| {
1056                (
1057                    file.clone(),
1058                    visible
1059                        .iter()
1060                        .map(|unit| unit.source().clone())
1061                        .chain(std::iter::once(file.clone()))
1062                        .collect(),
1063                )
1064            })
1065            .collect();
1066        let mut global_field_internal_linkage = HashMap::default();
1067        Self {
1068            cpp,
1069            visible_by_identifier: build_visible_identifier_index(
1070                &CppGraphSource::from_source(cpp),
1071                &visible_by_file,
1072                &visible_source_files_by_root,
1073                &mut global_field_internal_linkage,
1074            ),
1075            global_field_internal_linkage,
1076            visible_by_file,
1077            visible_source_files_by_root,
1078            alias_cells: Mutex::new(HashMap::default()),
1079            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1080            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1081            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1082            project_using_index: OnceLock::new(),
1083            callable_reference_specs: Mutex::new(HashMap::default()),
1084            include_activation_cells: Mutex::new(HashMap::default()),
1085            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1086            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1087            conditional_include_projection_state_count: AtomicUsize::new(0),
1088            include_activation_build_count: AtomicUsize::new(0),
1089            using_donor_activation_count: AtomicUsize::new(0),
1090            using_namespace_lookup_count: AtomicUsize::new(0),
1091            using_name_candidate_inspection_count: AtomicUsize::new(0),
1092            using_source_index_walk_count: AtomicUsize::new(0),
1093            using_guard_context_inspection_count: AtomicUsize::new(0),
1094            callable_reference_spec_build_count: AtomicUsize::new(0),
1095            alias_source_parse_counts: Mutex::new(HashMap::default()),
1096            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1097            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1098            field_type_facts: Mutex::new(HashMap::default()),
1099            structured_alias_targets: Mutex::new(HashMap::default()),
1100            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1101            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1102            precise_parent_cache: Mutex::new(HashMap::default()),
1103            macro_event_cells: Mutex::new(HashMap::default()),
1104            macro_include_protection_cells: Mutex::new(HashMap::default()),
1105            macro_environment_cursors: Mutex::new(HashMap::default()),
1106            macro_replacements: Mutex::new(HashMap::default()),
1107            macro_local_binding_templates: Mutex::new(HashMap::default()),
1108            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1109            macro_replacement_parse_count: AtomicUsize::new(0),
1110            macro_event_application_count: AtomicUsize::new(0),
1111            macro_environment_copy_count: AtomicUsize::new(0),
1112            cpp_template_metadata: HashMap::default(),
1113            cpp_template_families: HashMap::default(),
1114            qualified_candidate_inspections: AtomicUsize::new(0),
1115            target_preserving_type_resolution_count: AtomicUsize::new(0),
1116        }
1117    }
1118
1119    /// The index's own C++ source, in the dispatching-analyzer shape.
1120    ///
1121    /// Four resolution paths reach the workspace through the C++ analyzer they
1122    /// already hold rather than through the analyzer the query was issued
1123    /// against; before the move they passed `&CppAnalyzer` straight into a
1124    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1125    fn cpp_source(&self) -> CppGraphSource<'a> {
1126        CppGraphSource::from_source(self.cpp)
1127    }
1128
1129    pub fn build(
1130        cpp: &'a dyn CppSource,
1131        analyzer: &CppGraphSource<'_>,
1132        roots: &HashSet<ProjectFile>,
1133    ) -> Self {
1134        Self::build_with_cancellation(cpp, analyzer, roots, None)
1135    }
1136
1137    pub fn build_with_cancellation(
1138        cpp: &'a dyn CppSource,
1139        analyzer: &CppGraphSource<'_>,
1140        roots: &HashSet<ProjectFile>,
1141        cancellation: Option<&CancellationToken>,
1142    ) -> Self {
1143        let include_targets = cpp.include_target_index();
1144        let VisibilityData {
1145            mut visible_by_file,
1146            visible_source_files_by_root,
1147        } = build_visibility_data(
1148            roots,
1149            cancellation,
1150            |file| {
1151                let imports = analyzer.import_statements(file);
1152                cpp_include_paths(&imports)
1153                    .into_iter()
1154                    .flat_map(|include| {
1155                        resolve_include_targets_with_index(file, &include, include_targets)
1156                    })
1157                    .collect()
1158            },
1159            |file| analyzer.declarations(file),
1160        );
1161        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1162        let mut global_field_internal_linkage = HashMap::default();
1163        let visible_by_identifier = build_visible_identifier_index(
1164            analyzer,
1165            &visible_by_file,
1166            &visible_source_files_by_root,
1167            &mut global_field_internal_linkage,
1168        );
1169        let mut cpp_template_metadata = HashMap::default();
1170        for unit in visible_by_file
1171            .values()
1172            .flatten()
1173            .filter(|unit| unit.is_class())
1174        {
1175            if cpp_template_metadata.contains_key(unit) {
1176                continue;
1177            }
1178            if let Some(metadata) = cpp.template_metadata(unit) {
1179                cpp_template_metadata.insert(unit.clone(), metadata);
1180            }
1181        }
1182        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1183        for (unit, metadata) in &cpp_template_metadata {
1184            cpp_template_families
1185                .entry(metadata.primary_fq_name.clone())
1186                .or_default()
1187                .push(unit.clone());
1188        }
1189        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1190        // order above is a function of those hashes. Two mirrored headers can
1191        // declare one specialization; `select_template_specialization` treats
1192        // them as interchangeable and returns the family's first entry, so an
1193        // unsorted family made the reported declaration depend on the
1194        // workspace's absolute path and on unrelated files (#1836). Order the
1195        // family exactly as `build_visible_identifier_index` orders its
1196        // per-identifier candidate lists.
1197        for family in cpp_template_families.values_mut() {
1198            sort_lookup_units(family);
1199        }
1200        Self {
1201            cpp,
1202            visible_by_file,
1203            visible_by_identifier,
1204            global_field_internal_linkage,
1205            visible_source_files_by_root,
1206            alias_cells: Mutex::new(HashMap::default()),
1207            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1208            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1209            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1210            project_using_index: OnceLock::new(),
1211            callable_reference_specs: Mutex::new(HashMap::default()),
1212            include_activation_cells: Mutex::new(HashMap::default()),
1213            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1214            #[cfg(any(test, feature = "test-support"))]
1215            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1216            #[cfg(any(test, feature = "test-support"))]
1217            conditional_include_projection_state_count: AtomicUsize::new(0),
1218            #[cfg(any(test, feature = "test-support"))]
1219            include_activation_build_count: AtomicUsize::new(0),
1220            #[cfg(any(test, feature = "test-support"))]
1221            using_donor_activation_count: AtomicUsize::new(0),
1222            #[cfg(any(test, feature = "test-support"))]
1223            using_namespace_lookup_count: AtomicUsize::new(0),
1224            #[cfg(any(test, feature = "test-support"))]
1225            using_name_candidate_inspection_count: AtomicUsize::new(0),
1226            #[cfg(any(test, feature = "test-support"))]
1227            using_source_index_walk_count: AtomicUsize::new(0),
1228            #[cfg(any(test, feature = "test-support"))]
1229            using_guard_context_inspection_count: AtomicUsize::new(0),
1230            #[cfg(any(test, feature = "test-support"))]
1231            callable_reference_spec_build_count: AtomicUsize::new(0),
1232            #[cfg(any(test, feature = "test-support"))]
1233            alias_source_parse_counts: Mutex::new(HashMap::default()),
1234            #[cfg(any(test, feature = "test-support"))]
1235            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1236            #[cfg(any(test, feature = "test-support"))]
1237            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1238            field_type_facts: Mutex::new(HashMap::default()),
1239            structured_alias_targets: Mutex::new(HashMap::default()),
1240            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1241            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1242            precise_parent_cache: Mutex::new(HashMap::default()),
1243            macro_event_cells: Mutex::new(HashMap::default()),
1244            macro_include_protection_cells: Mutex::new(HashMap::default()),
1245            macro_environment_cursors: Mutex::new(HashMap::default()),
1246            macro_replacements: Mutex::new(HashMap::default()),
1247            macro_local_binding_templates: Mutex::new(HashMap::default()),
1248            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1249            #[cfg(any(test, feature = "test-support"))]
1250            macro_replacement_parse_count: AtomicUsize::new(0),
1251            #[cfg(any(test, feature = "test-support"))]
1252            macro_event_application_count: AtomicUsize::new(0),
1253            #[cfg(any(test, feature = "test-support"))]
1254            macro_environment_copy_count: AtomicUsize::new(0),
1255            cpp_template_metadata,
1256            cpp_template_families,
1257            #[cfg(any(test, feature = "test-support"))]
1258            qualified_candidate_inspections: AtomicUsize::new(0),
1259            #[cfg(any(test, feature = "test-support"))]
1260            target_preserving_type_resolution_count: AtomicUsize::new(0),
1261        }
1262    }
1263
1264    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1265        if file == target.source() {
1266            return true;
1267        }
1268        if self.global_field_has_internal_linkage(target) {
1269            return self
1270                .visible_source_files_by_root
1271                .get(file)
1272                .is_some_and(|sources| sources.contains(target.source()));
1273        }
1274        self.visible_by_file
1275            .get(file)
1276            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1277    }
1278
1279    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1280        self.global_field_internal_linkage
1281            .get(unit)
1282            .copied()
1283            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1284    }
1285
1286    pub fn call_arity_evidence(
1287        &self,
1288        file: &ProjectFile,
1289        call: Node<'_>,
1290        source: &str,
1291    ) -> CallArityEvidence {
1292        let Some(arguments) = call
1293            .child_by_field_name("arguments")
1294            .or_else(|| call.child_by_field_name("parameters"))
1295            .or_else(|| call.child_by_field_name("value"))
1296            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1297            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1298        else {
1299            return CallArityEvidence::Exact(0);
1300        };
1301        let arguments = argument_children(arguments).collect::<Vec<_>>();
1302        if arguments
1303            .iter()
1304            .all(|argument| !argument_shape_may_change_arity(*argument))
1305        {
1306            return CallArityEvidence::Exact(arguments.len());
1307        }
1308        let environment = self.macro_environment(file, call.start_byte());
1309        let mut stack = Vec::new();
1310        let mut total = 0usize;
1311        for argument in arguments {
1312            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1313                return CallArityEvidence::Unknown;
1314            }
1315            let CallArityEvidence::Exact(spread) =
1316                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1317            else {
1318                return CallArityEvidence::Unknown;
1319            };
1320            total += spread;
1321        }
1322        CallArityEvidence::Exact(total)
1323    }
1324
1325    fn argument_arity_evidence(
1326        &self,
1327        argument: Node<'_>,
1328        source: &str,
1329        environment: &MacroEnvironment,
1330        stack: &mut Vec<(ProjectFile, usize)>,
1331    ) -> CallArityEvidence {
1332        let (name, invocation_arguments, function_like) = match argument.kind() {
1333            "identifier" => (node_text(argument, source), None, false),
1334            "call_expression" => {
1335                let Some(function) = argument.child_by_field_name("function") else {
1336                    return CallArityEvidence::Exact(1);
1337                };
1338                if function.kind() != "identifier" {
1339                    return CallArityEvidence::Exact(1);
1340                }
1341                let Some(arguments) = argument.child_by_field_name("arguments") else {
1342                    return CallArityEvidence::Exact(1);
1343                };
1344                (node_text(function, source), Some(arguments), true)
1345            }
1346            _ => return CallArityEvidence::Exact(1),
1347        };
1348        let Some(binding) = environment.binding(name) else {
1349            return if environment.unknown_names {
1350                CallArityEvidence::Unknown
1351            } else {
1352                CallArityEvidence::Exact(1)
1353            };
1354        };
1355        if !binding.is_exact() {
1356            return CallArityEvidence::Unknown;
1357        }
1358        match (&binding.definition, invocation_arguments, function_like) {
1359            (MacroDefinition::Object { replacement }, None, false) => self
1360                .replacement_arity_evidence(
1361                    replacement,
1362                    &[],
1363                    &[],
1364                    source,
1365                    environment,
1366                    stack,
1367                    binding,
1368                ),
1369            (
1370                MacroDefinition::Function {
1371                    parameters,
1372                    replacement,
1373                },
1374                Some(arguments),
1375                true,
1376            ) => {
1377                let actuals = argument_children(arguments).collect::<Vec<_>>();
1378                if actuals.len() != parameters.len() {
1379                    CallArityEvidence::Unknown
1380                } else {
1381                    self.replacement_arity_evidence(
1382                        replacement,
1383                        parameters,
1384                        &actuals,
1385                        source,
1386                        environment,
1387                        stack,
1388                        binding,
1389                    )
1390                }
1391            }
1392            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1393            _ => CallArityEvidence::Unknown,
1394        }
1395    }
1396
1397    #[allow(clippy::too_many_arguments)]
1398    fn replacement_arity_evidence(
1399        &self,
1400        replacement: &str,
1401        parameters: &[String],
1402        actuals: &[Node<'_>],
1403        actual_source: &str,
1404        environment: &MacroEnvironment,
1405        stack: &mut Vec<(ProjectFile, usize)>,
1406        binding: &MacroBinding,
1407    ) -> CallArityEvidence {
1408        let identity = (binding.source.clone(), binding.declaration_byte);
1409        if stack.contains(&identity) || replacement.trim().is_empty() {
1410            return CallArityEvidence::Unknown;
1411        }
1412        stack.push(identity);
1413        let parsed = self.parsed_macro_replacement(binding, replacement);
1414        let evidence = (|| {
1415            let ParsedMacroReplacement::Parsed {
1416                source: sentinel,
1417                tree,
1418            } = parsed.as_ref()
1419            else {
1420                return None;
1421            };
1422            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1423            let arguments = call.child_by_field_name("arguments")?;
1424            let mut total = 0usize;
1425            for argument in argument_children(arguments) {
1426                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1427                    return None;
1428                }
1429                if argument.kind() == "identifier"
1430                    && let Some(parameter_index) = parameters
1431                        .iter()
1432                        .position(|parameter| parameter == node_text(argument, sentinel))
1433                {
1434                    if !macro_expansion_shape_is_safe(
1435                        actuals[parameter_index],
1436                        actual_source,
1437                        &[],
1438                        environment,
1439                    ) {
1440                        return None;
1441                    }
1442                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1443                        actuals[parameter_index],
1444                        actual_source,
1445                        environment,
1446                        stack,
1447                    ) else {
1448                        return None;
1449                    };
1450                    total += spread;
1451                    continue;
1452                }
1453                let CallArityEvidence::Exact(spread) =
1454                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1455                else {
1456                    return None;
1457                };
1458                total += spread;
1459            }
1460            Some(CallArityEvidence::Exact(total))
1461        })()
1462        .unwrap_or(CallArityEvidence::Unknown);
1463        stack.pop();
1464        evidence
1465    }
1466
1467    fn parsed_macro_replacement(
1468        &self,
1469        binding: &MacroBinding,
1470        replacement: &str,
1471    ) -> Arc<ParsedMacroReplacement> {
1472        let key = (binding.source.clone(), binding.declaration_byte);
1473        let mut cache = self
1474            .macro_replacements
1475            .lock()
1476            .expect("C++ macro replacement cache poisoned");
1477        if let Some(parsed) = cache.get(&key) {
1478            return Arc::clone(parsed);
1479        }
1480        #[cfg(any(test, feature = "test-support"))]
1481        self.macro_replacement_parse_count
1482            .fetch_add(1, Ordering::Relaxed);
1483        let source =
1484            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1485        let mut parser = Parser::new();
1486        let parsed = parser
1487            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1488            .ok()
1489            .and_then(|()| parser.parse(&source, None))
1490            .filter(|tree| !tree.root_node().has_error())
1491            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1492                ParsedMacroReplacement::Parsed { source, tree }
1493            });
1494        let parsed = Arc::new(parsed);
1495        cache.insert(key, Arc::clone(&parsed));
1496        parsed
1497    }
1498
1499    /// Recover a typed local declared by an active C function-like macro.
1500    ///
1501    /// This is intentionally narrower than macro expansion. The replacement
1502    /// must parse as one declaration, and the invocation must bind every
1503    /// formal parameter to one structured argument. That is sufficient for
1504    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1505    /// can make the binding provisional without erasing its last known
1506    /// definition; an explicit conflicting definition still replaces it with
1507    /// Unsupported. Malformed and statement-producing macros also fail closed.
1508    pub fn function_macro_local_binding<'tree>(
1509        &self,
1510        file: &ProjectFile,
1511        statement: Node<'tree>,
1512        source: &str,
1513    ) -> Option<MacroLocalBinding<'tree>> {
1514        if !is_c_source_file(file) {
1515            return None;
1516        }
1517        let call = match statement.kind() {
1518            "call_expression" => statement,
1519            "expression_statement" if statement.named_child_count() == 1 => {
1520                statement.named_child(0)?
1521            }
1522            _ => return None,
1523        };
1524        if call.kind() != "call_expression" {
1525            return None;
1526        }
1527        let function = call.child_by_field_name("function")?;
1528        if function.kind() != "identifier" {
1529            return None;
1530        }
1531        let arguments = call.child_by_field_name("arguments")?;
1532        let actuals = argument_children(arguments).collect::<Vec<_>>();
1533        let environment = self.macro_environment(file, call.start_byte());
1534        let function_name = node_text(function, source);
1535        let binding = environment.binding(function_name)?;
1536        let MacroDefinition::Function {
1537            parameters,
1538            replacement,
1539        } = &binding.definition
1540        else {
1541            return None;
1542        };
1543        if actuals.len() != parameters.len() {
1544            return None;
1545        }
1546        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1547        let (type_name, type_node) = match &template.declared_type {
1548            MacroLocalBindingTypeTemplate::Parameter(index) => {
1549                let actual = *actuals.get(*index)?;
1550                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1551                    return None;
1552                }
1553                (node_text(actual, source).trim().to_string(), Some(actual))
1554            }
1555            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1556        };
1557        if type_name.is_empty() {
1558            return None;
1559        }
1560        Some(MacroLocalBinding {
1561            name: template.name.clone(),
1562            type_name,
1563            type_node,
1564            pointer_depth: template.pointer_depth,
1565        })
1566    }
1567
1568    fn macro_local_binding_template(
1569        &self,
1570        binding: &MacroBinding,
1571        parameters: &[String],
1572        replacement: &str,
1573    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1574        let key = (binding.source.clone(), binding.declaration_byte);
1575        let mut cache = self
1576            .macro_local_binding_templates
1577            .lock()
1578            .expect("C++ macro local-binding cache poisoned");
1579        if let Some(template) = cache.get(&key) {
1580            return template.clone();
1581        }
1582        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1583        let template = (|| {
1584            let mut parser = Parser::new();
1585            parser
1586                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1587                .ok()?;
1588            let tree = parser.parse(&sentinel, None)?;
1589            if tree.root_node().has_error() {
1590                return None;
1591            }
1592            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1593            let body = function.child_by_field_name("body")?;
1594            if body.named_child_count() != 1 {
1595                return None;
1596            }
1597            let declaration = body.named_child(0)?;
1598            if declaration.kind() != "declaration" {
1599                return None;
1600            }
1601            let type_node = declaration
1602                .child_by_field_name("type")
1603                .or_else(|| first_type_child(declaration))?;
1604            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1605                let mut cursor = declaration.walk();
1606                declaration.named_children(&mut cursor).find_map(|child| {
1607                    if child.kind() == "init_declarator" {
1608                        child.child_by_field_name("declarator")
1609                    } else {
1610                        is_declarator_node(child).then_some(child)
1611                    }
1612                })
1613            })?;
1614            let name = extract_variable_name(declarator, &sentinel)?;
1615            let pointer_depth =
1616                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1617            let type_text = node_text(type_node, &sentinel).trim();
1618            let declared_type = parameters
1619                .iter()
1620                .position(|parameter| parameter == type_text)
1621                .map(MacroLocalBindingTypeTemplate::Parameter)
1622                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1623            Some(Arc::new(MacroLocalBindingTemplate {
1624                name,
1625                declared_type,
1626                pointer_depth,
1627            }))
1628        })();
1629        cache.insert(key, template.clone());
1630        template
1631    }
1632
1633    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1634        let Some(value) = node.child_by_field_name("value") else {
1635            return MacroDefinition::Unsupported;
1636        };
1637        let replacement = node_text(value, source).to_string();
1638        if node.kind() == "preproc_def" {
1639            return MacroDefinition::Object { replacement };
1640        }
1641        let Some(parameters) = node.child_by_field_name("parameters") else {
1642            return MacroDefinition::Unsupported;
1643        };
1644        if (0..parameters.child_count()).any(|index| {
1645            parameters
1646                .child(index)
1647                .is_some_and(|child| child.kind() == "...")
1648        }) {
1649            return MacroDefinition::Unsupported;
1650        }
1651        let parameters = (0..parameters.named_child_count())
1652            .filter_map(|index| parameters.named_child(index))
1653            .map(|parameter| node_text(parameter, source).to_string())
1654            .collect();
1655        MacroDefinition::Function {
1656            parameters,
1657            replacement,
1658        }
1659    }
1660
1661    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1662        self.macro_event_cells
1663            .lock()
1664            .expect("C++ macro event cache poisoned")
1665            .entry(file.clone())
1666            .or_default()
1667            .clone()
1668    }
1669
1670    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1671        let key = (file.clone(), std::thread::current().id());
1672        self.macro_environment_cursors
1673            .lock()
1674            .expect("C++ macro environment cursor cache poisoned")
1675            .entry(key)
1676            .or_default()
1677            .clone()
1678    }
1679
1680    pub fn macro_environment(
1681        &self,
1682        file: &ProjectFile,
1683        before_byte: usize,
1684    ) -> Arc<MacroEnvironment> {
1685        let cell = self.macro_event_cell(file);
1686        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1687        let frontier = events.partition_point(|event| event.byte() < before_byte);
1688        let cursor_cell = self.macro_environment_cursor_cell(file);
1689        let mut cursor = cursor_cell
1690            .lock()
1691            .expect("C++ macro environment cursor poisoned");
1692        if frontier < cursor.frontier {
1693            *cursor = MacroEnvironmentCursor::default();
1694        }
1695        if frontier > cursor.frontier {
1696            #[cfg(any(test, feature = "test-support"))]
1697            if Arc::strong_count(&cursor.environment) > 1 {
1698                self.macro_environment_copy_count
1699                    .fetch_add(1, Ordering::Relaxed);
1700            }
1701            let start = cursor.frontier;
1702            let environment = Arc::make_mut(&mut cursor.environment);
1703            let mut include_stack = HashSet::from_iter([file.clone()]);
1704            for event in &events[start..frontier] {
1705                self.apply_macro_event(file, event, environment, &mut include_stack);
1706            }
1707            cursor.frontier = frontier;
1708        }
1709        Arc::clone(&cursor.environment)
1710    }
1711
1712    /// Whether `name` is bound as a macro at `before_byte` in `file`,
1713    /// including a binding this environment cannot pin to one replacement
1714    /// (a conditional `#define`, or a function-like macro).
1715    ///
1716    /// [`Self::object_macro_replacement_at`] collapses every such binding to
1717    /// `None`, which is indistinguishable from "not a macro at all". A caller
1718    /// that must not read a macro token as an ordinary type name needs the two
1719    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
1720    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
1721        self.macro_environment(file, before_byte)
1722            .binding(name)
1723            .is_some()
1724    }
1725
1726    pub fn macro_name_may_be_bound_at(
1727        &self,
1728        file: &ProjectFile,
1729        name: &str,
1730        before_byte: usize,
1731    ) -> bool {
1732        self.macro_environment(file, before_byte).may_bind(name)
1733    }
1734
1735    /// Whether the active macro binding at this reference is the requested
1736    /// indexed definition. Name equality alone is not enough because two
1737    /// headers can define the same macro for different translation units.
1738    pub fn macro_binding_matches_target_at(
1739        &self,
1740        analyzer: &CppGraphSource<'_>,
1741        file: &ProjectFile,
1742        name: &str,
1743        before_byte: usize,
1744        target: &CodeUnit,
1745    ) -> bool {
1746        let environment = self.macro_environment(file, before_byte);
1747        let Some(binding) = environment.binding(name) else {
1748            return false;
1749        };
1750        // A normal header guard makes the replacement text conditional, but
1751        // it does not erase the definition site's source and byte identity.
1752        // Keep that identity even when expansion details are not exact.
1753        if binding.source != *target.source() {
1754            return false;
1755        }
1756        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
1757            return false;
1758        };
1759        analyzer.ranges(target).iter().any(|range| {
1760            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
1761                return false;
1762            };
1763            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
1764                let Some(parent) = node.parent() else {
1765                    return false;
1766                };
1767                node = parent;
1768            }
1769            node.start_byte() == binding.declaration_byte
1770        })
1771    }
1772
1773    /// Resolve an ordinary expression-position macro token at its exact byte.
1774    ///
1775    /// Calls and preprocessor-condition tokens have separate resolution
1776    /// surfaces. Declaration names, macro parameters, and labels are not
1777    /// references. Keeping that role policy here makes forward and both
1778    /// inverse graph builders consume the same activation verdict (#2093).
1779    pub fn resolve_ordinary_macro_reference(
1780        &self,
1781        analyzer: &CppGraphSource<'_>,
1782        file: &ProjectFile,
1783        node: Node<'_>,
1784        source: &str,
1785    ) -> OrdinaryMacroReferenceResolution {
1786        if !is_ordinary_macro_reference_node(node) {
1787            return OrdinaryMacroReferenceResolution::Missing;
1788        }
1789        let name = node_text(node, source);
1790        if name.is_empty() {
1791            return OrdinaryMacroReferenceResolution::Missing;
1792        }
1793        let visible = self
1794            .visible_identifier_candidates(file, name)
1795            .filter(|candidate| candidate.is_macro())
1796            .cloned()
1797            .collect::<Vec<_>>();
1798        let mut exact = Vec::new();
1799        for candidate in &visible {
1800            if self.macro_binding_matches_target_at(
1801                analyzer,
1802                file,
1803                name,
1804                node.start_byte(),
1805                candidate,
1806            ) && !exact
1807                .iter()
1808                .any(|existing| same_visible_symbol(existing, candidate))
1809            {
1810                exact.push(candidate.clone());
1811            }
1812        }
1813        match exact.len() {
1814            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
1815            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
1816            0 if !visible.is_empty()
1817                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
1818            {
1819                OrdinaryMacroReferenceResolution::Ambiguous
1820            }
1821            0 => OrdinaryMacroReferenceResolution::Missing,
1822        }
1823    }
1824
1825    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
1826    ///
1827    /// The ordinary census deliberately skips every `ERROR` subtree. This
1828    /// separate, precision-only frontier admits only roles that retain enough
1829    /// structure for the C usage graph to interpret independently (#2089).
1830    /// Macro evidence comes from this visibility index at the exact byte; no
1831    /// source-text parsing or terminal-name fallback is used.
1832    pub fn recovered_c_reference_ranges(
1833        &self,
1834        file: &ProjectFile,
1835        root: Node<'_>,
1836        source: &str,
1837        limit: usize,
1838    ) -> RecoveredCReferenceRanges {
1839        if !is_c_source_file(file) {
1840            return RecoveredCReferenceRanges::Complete(Vec::new());
1841        }
1842        let mut ranges = Vec::new();
1843        let mut seen = HashSet::default();
1844        let mut stack = vec![(root, root.is_error())];
1845        while let Some((node, inside_error)) = stack.pop() {
1846            let inside_error = inside_error || node.is_error();
1847            if inside_error
1848                && recovered_c_reference_node(self, file, node, source)
1849                && seen.insert((node.start_byte(), node.end_byte()))
1850            {
1851                if ranges.len() == limit {
1852                    return RecoveredCReferenceRanges::LimitExceeded;
1853                }
1854                ranges.push(Range {
1855                    start_byte: node.start_byte(),
1856                    end_byte: node.end_byte(),
1857                    start_line: node.start_position().row,
1858                    end_line: node.end_position().row,
1859                });
1860            }
1861            let mut cursor = node.walk();
1862            for child in node.named_children(&mut cursor) {
1863                stack.push((child, inside_error));
1864            }
1865        }
1866        ranges.sort_unstable();
1867        RecoveredCReferenceRanges::Complete(ranges)
1868    }
1869
1870    /// Whether this target is an indexed macro visible from this file.
1871    ///
1872    /// An unresolved conditional can make more than one same-name macro a
1873    /// possible active binding. Each possible target can keep the site as an
1874    /// unproven hit. A macro in an unrelated translation unit stays excluded.
1875    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1876        self.visible_identifier_candidates(file, target.identifier())
1877            .filter(|candidate| candidate.is_macro())
1878            .any(|candidate| {
1879                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
1880            })
1881    }
1882
1883    pub fn object_macro_replacement_at(
1884        &self,
1885        file: &ProjectFile,
1886        name: &str,
1887        before_byte: usize,
1888    ) -> Option<String> {
1889        let environment = self.macro_environment(file, before_byte);
1890        let binding = environment.binding(name)?;
1891        if !binding.exact {
1892            return None;
1893        }
1894        match &binding.definition {
1895            MacroDefinition::Object { replacement } => Some(replacement.clone()),
1896            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
1897        }
1898    }
1899
1900    fn apply_macro_events(
1901        &self,
1902        file: &ProjectFile,
1903        before_byte: Option<usize>,
1904        environment: &mut MacroEnvironment,
1905        include_stack: &mut HashSet<ProjectFile>,
1906    ) {
1907        if !include_stack.insert(file.clone()) {
1908            return;
1909        }
1910        if self.cpp.prepared_syntax(file).is_none() {
1911            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
1912            include_stack.remove(file);
1913            return;
1914        }
1915        match self.macro_include_protection(file) {
1916            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
1917                Some(binding) if binding.is_exact() => {
1918                    include_stack.remove(file);
1919                    return;
1920                }
1921                Some(_) | None if environment.unknown_names => {
1922                    let mut ambiguous_seen = HashSet::default();
1923                    self.mark_macro_events_ambiguous(
1924                        file,
1925                        environment,
1926                        &mut ambiguous_seen,
1927                        file,
1928                        before_byte.unwrap_or_default(),
1929                    );
1930                    include_stack.remove(file);
1931                    return;
1932                }
1933                Some(_) => {
1934                    let mut ambiguous_seen = HashSet::default();
1935                    self.mark_macro_events_ambiguous(
1936                        file,
1937                        environment,
1938                        &mut ambiguous_seen,
1939                        file,
1940                        before_byte.unwrap_or_default(),
1941                    );
1942                    include_stack.remove(file);
1943                    return;
1944                }
1945                None => {}
1946            },
1947            MacroIncludeProtection::PragmaOnce => {
1948                if !environment.applied_pragma_once_files.insert(file.clone()) {
1949                    include_stack.remove(file);
1950                    return;
1951                }
1952                if environment.maybe_applied_pragma_once_files.remove(file) {
1953                    // A prior conditional include may already have consumed the pragma-once
1954                    // header. This unconditional include guarantees it is consumed now, but
1955                    // cannot prove whether its events occur before or after intervening local
1956                    // macro changes, so preserve the union as ambiguous.
1957                    let mut ambiguous_seen = HashSet::default();
1958                    environment.applied_pragma_once_files.remove(file);
1959                    self.mark_macro_events_ambiguous(
1960                        file,
1961                        environment,
1962                        &mut ambiguous_seen,
1963                        file,
1964                        before_byte.unwrap_or_default(),
1965                    );
1966                    environment.maybe_applied_pragma_once_files.remove(file);
1967                    environment.applied_pragma_once_files.insert(file.clone());
1968                    include_stack.remove(file);
1969                    return;
1970                }
1971            }
1972            MacroIncludeProtection::None => {}
1973        }
1974        let cell = self.macro_event_cell(file);
1975        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1976        for event in events {
1977            if before_byte.is_some_and(|limit| event.byte() >= limit) {
1978                break;
1979            }
1980            self.apply_macro_event(file, event, environment, include_stack);
1981        }
1982        include_stack.remove(file);
1983    }
1984
1985    fn apply_macro_event(
1986        &self,
1987        file: &ProjectFile,
1988        event: &MacroEvent,
1989        environment: &mut MacroEnvironment,
1990        include_stack: &mut HashSet<ProjectFile>,
1991    ) {
1992        #[cfg(any(test, feature = "test-support"))]
1993        self.macro_event_application_count
1994            .fetch_add(1, Ordering::Relaxed);
1995        match event {
1996            MacroEvent::Define {
1997                name,
1998                binding,
1999                conditional,
2000                byte,
2001            } => {
2002                if *conditional {
2003                    Self::merge_conditional_macro_definition(
2004                        environment,
2005                        name,
2006                        binding,
2007                        file,
2008                        *byte,
2009                    );
2010                } else {
2011                    environment.insert(name.clone(), binding.clone());
2012                }
2013            }
2014            MacroEvent::Undef {
2015                name,
2016                conditional,
2017                byte,
2018            } => {
2019                if *conditional {
2020                    if environment.binding(name).is_some() {
2021                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2022                    }
2023                } else {
2024                    environment.remove(name);
2025                }
2026            }
2027            MacroEvent::Include {
2028                targets,
2029                conditional,
2030                byte,
2031            } => {
2032                if targets.is_empty() {
2033                    environment.mark_unknown_names(file, *byte);
2034                    return;
2035                }
2036                if *conditional || targets.len() > 1 {
2037                    let mut ambiguous_seen = HashSet::default();
2038                    for target in targets {
2039                        self.mark_macro_events_ambiguous(
2040                            target,
2041                            environment,
2042                            &mut ambiguous_seen,
2043                            file,
2044                            *byte,
2045                        );
2046                    }
2047                } else if let Some(target) = targets.first() {
2048                    self.apply_macro_events(target, None, environment, include_stack);
2049                }
2050            }
2051            MacroEvent::Invalidate { byte } => {
2052                for binding in environment.bindings.values_mut() {
2053                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2054                }
2055            }
2056        }
2057    }
2058
2059    fn mark_macro_events_ambiguous(
2060        &self,
2061        file: &ProjectFile,
2062        environment: &mut MacroEnvironment,
2063        include_stack: &mut HashSet<ProjectFile>,
2064        conditional_file: &ProjectFile,
2065        conditional_byte: usize,
2066    ) {
2067        if !include_stack.insert(file.clone()) {
2068            return;
2069        }
2070        if self.cpp.prepared_syntax(file).is_none() {
2071            environment.mark_unknown_names(conditional_file, conditional_byte);
2072            return;
2073        }
2074        match self.macro_include_protection(file) {
2075            MacroIncludeProtection::MacroGuard(guard) => {
2076                if environment
2077                    .binding(&guard)
2078                    .is_some_and(MacroBinding::is_exact)
2079                {
2080                    return;
2081                }
2082            }
2083            MacroIncludeProtection::PragmaOnce => {
2084                if environment.applied_pragma_once_files.contains(file) {
2085                    return;
2086                }
2087                environment
2088                    .maybe_applied_pragma_once_files
2089                    .insert(file.clone());
2090            }
2091            MacroIncludeProtection::None => {}
2092        }
2093        let cell = self.macro_event_cell(file);
2094        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2095        for event in events {
2096            #[cfg(any(test, feature = "test-support"))]
2097            self.macro_event_application_count
2098                .fetch_add(1, Ordering::Relaxed);
2099            match event {
2100                MacroEvent::Define { name, binding, .. } => {
2101                    Self::merge_conditional_macro_definition(
2102                        environment,
2103                        name,
2104                        binding,
2105                        conditional_file,
2106                        conditional_byte,
2107                    );
2108                }
2109                MacroEvent::Undef { name, .. } => {
2110                    if environment.binding(name).is_some() {
2111                        environment.insert(
2112                            name.clone(),
2113                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2114                        );
2115                    }
2116                }
2117                MacroEvent::Include { targets, .. } => {
2118                    if targets.is_empty() {
2119                        environment.mark_unknown_names(conditional_file, conditional_byte);
2120                        continue;
2121                    }
2122                    for target in targets {
2123                        self.mark_macro_events_ambiguous(
2124                            target,
2125                            environment,
2126                            include_stack,
2127                            conditional_file,
2128                            conditional_byte,
2129                        );
2130                    }
2131                }
2132                MacroEvent::Invalidate { .. } => {
2133                    for binding in environment.bindings.values_mut() {
2134                        *binding = MacroBinding::uncertain_from(
2135                            binding,
2136                            conditional_file,
2137                            conditional_byte,
2138                        );
2139                    }
2140                }
2141            }
2142        }
2143    }
2144
2145    fn merge_conditional_macro_definition(
2146        environment: &mut MacroEnvironment,
2147        name: &str,
2148        possible_binding: &MacroBinding,
2149        conditional_file: &ProjectFile,
2150        conditional_byte: usize,
2151    ) {
2152        // A conditional include can revisit an already-active guarded header.
2153        // If the possible branch defines the exact same macro, both outcomes
2154        // leave the binding unchanged; degrading it to Unknown would discard
2155        // proof because of an unrelated unresolved macro name (#2092).
2156        if environment.binding(name).is_some_and(|current| {
2157            current.definition != MacroDefinition::Unsupported
2158                && current.definition == possible_binding.definition
2159        }) {
2160            return;
2161        }
2162        environment.insert(
2163            name.to_string(),
2164            MacroBinding::ambiguous(conditional_file, conditional_byte),
2165        );
2166    }
2167
2168    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2169        let cell = self
2170            .macro_include_protection_cells
2171            .lock()
2172            .expect("C++ include protection cache poisoned")
2173            .entry(file.clone())
2174            .or_default()
2175            .clone();
2176        cell.get_or_init(|| {
2177            self.cpp
2178                .prepared_syntax(file)
2179                .map_or(MacroIncludeProtection::None, |prepared| {
2180                    top_level_macro_include_protection(
2181                        prepared.tree().root_node(),
2182                        prepared.source(),
2183                    )
2184                })
2185        })
2186        .clone()
2187    }
2188
2189    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2190        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2191            return Vec::new();
2192        };
2193        let source = prepared.source();
2194        let mut events = Vec::new();
2195        let mut stack = vec![prepared.tree().root_node()];
2196        while let Some(node) = stack.pop() {
2197            let conditional = has_preprocessor_conditional_ancestor(node, source);
2198            match node.kind() {
2199                "preproc_def" | "preproc_function_def" => {
2200                    let Some(name) = node.child_by_field_name("name") else {
2201                        continue;
2202                    };
2203                    let name = node_text(name, source).to_string();
2204                    events.push(MacroEvent::Define {
2205                        name,
2206                        binding: MacroBinding {
2207                            source: file.clone(),
2208                            declaration_byte: node.start_byte(),
2209                            definition: Self::decode_macro_definition(node, source),
2210                            exact: true,
2211                        },
2212                        byte: node.start_byte(),
2213                        conditional,
2214                    });
2215                    continue;
2216                }
2217                "preproc_include" => {
2218                    let Some(path) = node.child_by_field_name("path") else {
2219                        events.push(MacroEvent::Include {
2220                            targets: Vec::new(),
2221                            byte: node.start_byte(),
2222                            conditional,
2223                        });
2224                        continue;
2225                    };
2226                    let targets =
2227                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2228                            resolve_include_targets_with_index(
2229                                file,
2230                                path,
2231                                self.cpp.include_target_index(),
2232                            )
2233                        });
2234                    // An unresolved angle-bracket include crosses into an external system
2235                    // boundary that is absent from the source index. It must not poison all
2236                    // later local macro evidence. Quoted/project-local and computed includes,
2237                    // by contrast, may hide indexed macro state and therefore fail closed.
2238                    if targets.is_empty() && path.kind() == "system_lib_string" {
2239                        continue;
2240                    }
2241                    events.push(MacroEvent::Include {
2242                        targets,
2243                        byte: node.start_byte(),
2244                        conditional,
2245                    });
2246                    continue;
2247                }
2248                "preproc_call" => {
2249                    let Some(directive) = node.child_by_field_name("directive") else {
2250                        continue;
2251                    };
2252                    if node_text(directive, source) != "#undef" {
2253                        continue;
2254                    }
2255                    let name = node
2256                        .child_by_field_name("argument")
2257                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2258                    if let Some(name) = name {
2259                        events.push(MacroEvent::Undef {
2260                            name,
2261                            byte: node.start_byte(),
2262                            conditional,
2263                        });
2264                    } else {
2265                        events.push(MacroEvent::Invalidate {
2266                            byte: node.start_byte(),
2267                        });
2268                    }
2269                    continue;
2270                }
2271                _ => {}
2272            }
2273            for index in (0..node.named_child_count()).rev() {
2274                if let Some(child) = node.named_child(index) {
2275                    stack.push(child);
2276                }
2277            }
2278        }
2279        events.sort_by_key(MacroEvent::byte);
2280        events
2281    }
2282
2283    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2284        self.ordinary_type_import_cells
2285            .lock()
2286            .expect("C++ ordinary type import cache poisoned")
2287            .entry(file.clone())
2288            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2289            .clone()
2290    }
2291
2292    pub fn project_using_index(
2293        &self,
2294        build: impl FnOnce() -> ProjectUsingIndex,
2295    ) -> &ProjectUsingIndex {
2296        self.project_using_index.get_or_init(build)
2297    }
2298
2299    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2300        let mut files = self
2301            .visible_source_files_by_root
2302            .values()
2303            .flatten()
2304            .cloned()
2305            .collect::<HashSet<_>>()
2306            .into_iter()
2307            .collect::<Vec<_>>();
2308        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2309        files
2310    }
2311
2312    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2313        self.visible_source_files_by_root
2314            .get(root)
2315            .is_some_and(|files| files.contains(source))
2316    }
2317
2318    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2319        let cached = self
2320            .visible_parser_alias_name_sets
2321            .read()
2322            .expect("visible parser alias-name cache poisoned")
2323            .get(file)
2324            .cloned();
2325        let cell = if let Some(cached) = cached {
2326            cached
2327        } else {
2328            let mut cells = self
2329                .visible_parser_alias_name_sets
2330                .write()
2331                .expect("visible parser alias-name cache poisoned");
2332            Arc::clone(
2333                cells
2334                    .entry(file.clone())
2335                    .or_insert_with(|| Arc::new(OnceLock::new())),
2336            )
2337        };
2338        cell.get_or_init(|| {
2339            #[cfg(any(test, feature = "test-support"))]
2340            self.visible_parser_alias_name_set_build_count
2341                .fetch_add(1, Ordering::Relaxed);
2342            let mut names = HashSet::default();
2343            let visible_files = self
2344                .visible_source_files_by_root
2345                .get(file)
2346                .cloned()
2347                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2348            for visible_file in visible_files {
2349                let aliases = {
2350                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2351                    Arc::clone(
2352                        cells
2353                            .entry(visible_file.clone())
2354                            .or_insert_with(|| Arc::new(OnceLock::new())),
2355                    )
2356                };
2357                for alias in aliases
2358                    .get_or_init(|| {
2359                        #[cfg(any(test, feature = "test-support"))]
2360                        {
2361                            *self
2362                                .alias_source_parse_counts
2363                                .lock()
2364                                .expect("alias source parse count lock")
2365                                .entry(visible_file.clone())
2366                                .or_default() += 1;
2367                        }
2368                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2369                    })
2370                    .iter()
2371                {
2372                    names.insert(alias.name.clone());
2373                }
2374            }
2375            names
2376        })
2377        .contains(name)
2378    }
2379
2380    fn visible_parser_alias_names_for_target(
2381        &self,
2382        file: &ProjectFile,
2383        target: &CodeUnit,
2384    ) -> HashSet<String> {
2385        let cell = {
2386            let mut cells = self
2387                .visible_parser_alias_target_names
2388                .lock()
2389                .expect("visible parser alias-target cache poisoned");
2390            Arc::clone(
2391                cells
2392                    .entry(file.clone())
2393                    .or_insert_with(|| Arc::new(OnceLock::new())),
2394            )
2395        };
2396        let target_name = cpp_name_for(target);
2397        cell.get_or_init(|| {
2398            #[cfg(any(test, feature = "test-support"))]
2399            self.visible_parser_alias_target_names_build_count
2400                .fetch_add(1, Ordering::Relaxed);
2401            let visible_files = self
2402                .visible_source_files_by_root
2403                .get(file)
2404                .cloned()
2405                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2406            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2407            for visible_file in visible_files {
2408                let aliases = {
2409                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2410                    Arc::clone(
2411                        cells
2412                            .entry(visible_file.clone())
2413                            .or_insert_with(|| Arc::new(OnceLock::new())),
2414                    )
2415                };
2416                for alias in aliases
2417                    .get_or_init(|| {
2418                        #[cfg(any(test, feature = "test-support"))]
2419                        {
2420                            *self
2421                                .alias_source_parse_counts
2422                                .lock()
2423                                .expect("alias source parse count lock")
2424                                .entry(visible_file.clone())
2425                                .or_default() += 1;
2426                        }
2427                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2428                    })
2429                    .iter()
2430                {
2431                    for target_name in parser_alias_target_names(alias) {
2432                        names_by_target
2433                            .entry(target_name)
2434                            .or_default()
2435                            .insert(alias.name.clone());
2436                    }
2437                }
2438            }
2439            names_by_target
2440        })
2441        .get(&target_name)
2442        .cloned()
2443        .unwrap_or_default()
2444    }
2445
2446    fn callable_arities_for_target(
2447        &self,
2448        analyzer: &CppGraphSource<'_>,
2449        cpp: &dyn CppSource,
2450        file: &ProjectFile,
2451        prepared: &PreparedSyntaxTree,
2452        spec: &TargetSpec,
2453    ) -> Vec<ActivatedCallableArity> {
2454        let Some(signature) = spec.target.signature() else {
2455            return Vec::new();
2456        };
2457        let Some(candidates) = self
2458            .visible_by_identifier
2459            .get(file)
2460            .and_then(|by_name| by_name.get(&spec.member_name))
2461        else {
2462            return Vec::new();
2463        };
2464        let differing_candidates = candidates
2465            .iter()
2466            .filter(|candidate| {
2467                candidate.is_function()
2468                    && candidate.fq_name() == spec.target.fq_name()
2469                    && candidate.signature() == Some(signature)
2470            })
2471            .filter_map(|candidate| {
2472                analyzer
2473                    .signature_metadata(candidate)
2474                    .into_iter()
2475                    .find_map(|metadata| metadata.callable_arity())
2476                    .filter(|arity| Some(*arity) != spec.callable_arity)
2477                    .map(|arity| (candidate, arity))
2478            })
2479            .collect::<Vec<_>>();
2480        if differing_candidates.is_empty() {
2481            return Vec::new();
2482        }
2483        let mut arities = Vec::with_capacity(differing_candidates.len());
2484        // The activation ranges here describe the whole file rather than one
2485        // reference, so there is no reference guard environment to consult.
2486        let reference = CallableReferenceContext {
2487            file,
2488            position: None,
2489        };
2490        for (candidate, candidate_arity) in differing_candidates {
2491            let declaration_activation = if candidate.source() == file {
2492                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2493            } else {
2494                cpp.prepared_syntax(candidate.source()).and_then(|syntax| {
2495                    callable_declaration_activation_in_file(
2496                        analyzer,
2497                        syntax.as_ref(),
2498                        candidate,
2499                        &reference,
2500                    )
2501                })
2502            };
2503            let Some(declaration_activation) = declaration_activation else {
2504                continue;
2505            };
2506            let activation_byte = if candidate.source() == file {
2507                Some(declaration_activation)
2508            } else {
2509                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2510            };
2511            if let Some(activation_byte) = activation_byte {
2512                arities.push(ActivatedCallableArity {
2513                    activation_byte,
2514                    arity: candidate_arity,
2515                });
2516            }
2517        }
2518        arities
2519    }
2520
2521    fn callable_parameter_macro_arity(
2522        &self,
2523        target: &CodeUnit,
2524        signature: Option<&str>,
2525    ) -> Option<CallableArity> {
2526        let parameter_types = cpp_signature_param_types(signature?)?;
2527        let [macro_name] = parameter_types.as_slice() else {
2528            return None;
2529        };
2530        if macro_name.is_empty()
2531            || !macro_name
2532                .chars()
2533                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2534        {
2535            return None;
2536        }
2537        let cache_key = (target.source().clone(), macro_name.clone());
2538        if let Some(cached) = self
2539            .callable_parameter_macro_arities
2540            .lock()
2541            .expect("C++ callable parameter-macro arity cache poisoned")
2542            .get(&cache_key)
2543            .copied()
2544        {
2545            return cached;
2546        }
2547        let mut visible_files = HashSet::default();
2548        collect_include_closure(
2549            &self.cpp_source(),
2550            self.cpp.include_target_index(),
2551            target.source(),
2552            &mut visible_files,
2553            None,
2554        );
2555        let mut arities = Vec::new();
2556        for visible_file in visible_files {
2557            let cell = self.macro_event_cell(&visible_file);
2558            for event in
2559                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2560            {
2561                let MacroEvent::Define { name, binding, .. } = event else {
2562                    continue;
2563                };
2564                if name != macro_name {
2565                    continue;
2566                }
2567                let MacroDefinition::Object { replacement } = &binding.definition else {
2568                    continue;
2569                };
2570                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2571                    continue;
2572                };
2573                if !arities.contains(&arity) {
2574                    arities.push(arity);
2575                }
2576            }
2577        }
2578        let resolved = (|| {
2579            let required = arities
2580                .iter()
2581                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2582                .min()?;
2583            let total = arities.iter().map(|arity| arity.total()).max()?;
2584            let repeated = arities
2585                .iter()
2586                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2587            // Preprocessor conditions can leave more than one object-like parameter
2588            // bundle active in the target header's include closure. Preserve their
2589            // conservative callable envelope instead of choosing whichever definition
2590            // happened to be visited first.
2591            Some(CallableArity::new(required, total, repeated))
2592        })();
2593        self.callable_parameter_macro_arities
2594            .lock()
2595            .expect("C++ callable parameter-macro arity cache poisoned")
2596            .insert(cache_key, resolved);
2597        resolved
2598    }
2599
2600    pub fn include_activation_for_source(
2601        &self,
2602        cpp: &dyn CppSource,
2603        file: &ProjectFile,
2604        prepared: &PreparedSyntaxTree,
2605        donor_source: &ProjectFile,
2606    ) -> Option<usize> {
2607        let key = (file.clone(), donor_source.clone());
2608        if let Some(cached) = self
2609            .include_activation_cells
2610            .lock()
2611            .expect("C++ include activation cache poisoned")
2612            .get(&key)
2613            .copied()
2614        {
2615            return cached;
2616        }
2617        #[cfg(any(test, feature = "test-support"))]
2618        self.include_activation_build_count
2619            .fetch_add(1, Ordering::Relaxed);
2620        let activation = find_include_activation(cpp, file, prepared, donor_source);
2621        let mut cells = self
2622            .include_activation_cells
2623            .lock()
2624            .expect("C++ include activation cache poisoned");
2625        *cells.entry(key).or_insert(activation)
2626    }
2627
2628    pub fn conditional_include_projections_for_source(
2629        &self,
2630        file: &ProjectFile,
2631        prepared: &PreparedSyntaxTree,
2632        donor_source: &ProjectFile,
2633    ) -> Arc<[ConditionalIncludeProjection]> {
2634        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2635        let cell = self
2636            .conditional_include_projection_cells
2637            .lock()
2638            .expect("C++ conditional include projection cache poisoned")
2639            .entry(file.clone())
2640            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2641            .clone();
2642        let index = cell.get_or_build_pool_independent(|| {
2643            #[cfg(any(test, feature = "test-support"))]
2644            self.conditional_include_projection_index_build_count
2645                .fetch_add(1, Ordering::Relaxed);
2646            find_conditional_include_projection_index(self.cpp, file, prepared, &|| {
2647                #[cfg(any(test, feature = "test-support"))]
2648                self.conditional_include_projection_state_count
2649                    .fetch_add(1, Ordering::Relaxed);
2650            })
2651        });
2652        index
2653            .get(donor_source)
2654            .cloned()
2655            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2656    }
2657
2658    #[cfg(any(test, feature = "test-support"))]
2659    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2660        (
2661            self.conditional_include_projection_index_build_count
2662                .load(Ordering::Relaxed),
2663            self.conditional_include_projection_state_count
2664                .load(Ordering::Relaxed),
2665        )
2666    }
2667
2668    #[cfg(any(test, feature = "test-support"))]
2669    pub fn include_activation_build_count_for_test(&self) -> usize {
2670        self.include_activation_build_count.load(Ordering::Relaxed)
2671    }
2672
2673    #[cfg(any(test, feature = "test-support"))]
2674    pub fn note_using_donor_activation_for_test(&self) {
2675        self.using_donor_activation_count
2676            .fetch_add(1, Ordering::Relaxed);
2677    }
2678
2679    #[cfg(not(any(test, feature = "test-support")))]
2680    pub fn note_using_donor_activation_for_test(&self) {}
2681
2682    #[cfg(any(test, feature = "test-support"))]
2683    pub fn note_using_namespace_lookup_for_test(&self) {
2684        self.using_namespace_lookup_count
2685            .fetch_add(1, Ordering::Relaxed);
2686    }
2687
2688    #[cfg(not(any(test, feature = "test-support")))]
2689    pub fn note_using_namespace_lookup_for_test(&self) {}
2690
2691    #[cfg(any(test, feature = "test-support"))]
2692    pub fn note_using_name_candidate_inspection_for_test(&self) {
2693        self.using_name_candidate_inspection_count
2694            .fetch_add(1, Ordering::Relaxed);
2695    }
2696
2697    #[cfg(not(any(test, feature = "test-support")))]
2698    pub fn note_using_name_candidate_inspection_for_test(&self) {}
2699
2700    #[cfg(any(test, feature = "test-support"))]
2701    pub fn note_using_source_index_walk_for_test(&self) {
2702        self.using_source_index_walk_count
2703            .fetch_add(1, Ordering::Relaxed);
2704    }
2705
2706    #[cfg(not(any(test, feature = "test-support")))]
2707    pub fn note_using_source_index_walk_for_test(&self) {}
2708
2709    #[cfg(any(test, feature = "test-support"))]
2710    pub fn note_using_guard_context_inspection_for_test(&self) {
2711        self.using_guard_context_inspection_count
2712            .fetch_add(1, Ordering::Relaxed);
2713    }
2714
2715    #[cfg(not(any(test, feature = "test-support")))]
2716    pub fn note_using_guard_context_inspection_for_test(&self) {}
2717
2718    #[cfg(any(test, feature = "test-support"))]
2719    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize, usize, usize) {
2720        (
2721            self.using_source_index_walk_count.load(Ordering::Relaxed),
2722            self.using_donor_activation_count.load(Ordering::Relaxed),
2723            self.using_namespace_lookup_count.load(Ordering::Relaxed),
2724            self.callable_reference_spec_build_count
2725                .load(Ordering::Relaxed),
2726            self.using_name_candidate_inspection_count
2727                .load(Ordering::Relaxed),
2728            self.using_guard_context_inspection_count
2729                .load(Ordering::Relaxed),
2730        )
2731    }
2732
2733    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2734        file == target.source()
2735            || self
2736                .visible_by_file
2737                .get(file)
2738                .is_some_and(|visible| visible.contains(target))
2739    }
2740
2741    pub fn declaration_visible_at(
2742        &self,
2743        analyzer: &CppGraphSource<'_>,
2744        file: &ProjectFile,
2745        declaration: &CodeUnit,
2746        reference_byte: usize,
2747    ) -> bool {
2748        let reference_guards = OnceCell::new();
2749        self.visible_identifier_candidates(file, declaration.identifier())
2750            .filter(|candidate| {
2751                same_logical_symbol(candidate, declaration)
2752                    || flattened_macro_namespace_declaration_matches(
2753                        analyzer,
2754                        self.cpp,
2755                        file,
2756                        candidate,
2757                        declaration,
2758                        reference_byte,
2759                    )
2760            })
2761            .any(|candidate| {
2762                self.physical_declaration_visible_at(
2763                    analyzer,
2764                    file,
2765                    candidate,
2766                    reference_byte,
2767                    &reference_guards,
2768                )
2769            })
2770    }
2771
2772    pub fn callable_arity_at_reference(
2773        &self,
2774        analyzer: &CppGraphSource<'_>,
2775        file: &ProjectFile,
2776        candidate: &CodeUnit,
2777        reference_byte: usize,
2778    ) -> Option<CallableArity> {
2779        let key = (file.clone(), logical_symbol_key(candidate));
2780        let cell = self
2781            .callable_reference_specs
2782            .lock()
2783            .expect("C++ callable reference-spec cache poisoned")
2784            .entry(key)
2785            .or_default()
2786            .clone();
2787        let spec = cell.get_or_init(|| {
2788            let prepared = self.cpp.prepared_syntax(file)?;
2789            let spec = TargetSpec::from_target(analyzer, candidate)?;
2790            let spec = spec
2791                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
2792                .into_owned();
2793            #[cfg(any(test, feature = "test-support"))]
2794            self.callable_reference_spec_build_count
2795                .fetch_add(1, Ordering::Relaxed);
2796            Some(spec)
2797        });
2798        spec.as_ref()?.callable_arity_at(reference_byte)
2799    }
2800
2801    fn physical_declaration_visible_at(
2802        &self,
2803        analyzer: &CppGraphSource<'_>,
2804        file: &ProjectFile,
2805        declaration: &CodeUnit,
2806        reference_byte: usize,
2807        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
2808    ) -> bool {
2809        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2810            return false;
2811        };
2812        let reference = CallableReferenceContext {
2813            file,
2814            position: Some(CallableReferencePosition {
2815                prepared: prepared.as_ref(),
2816                byte: reference_byte,
2817                guards: reference_guards,
2818            }),
2819        };
2820        if declaration.source() == file {
2821            return callable_declaration_activation_in_file(
2822                analyzer,
2823                prepared.as_ref(),
2824                declaration,
2825                &reference,
2826            )
2827            .or_else(|| {
2828                self.exhaustive_guard_family_activation(
2829                    analyzer,
2830                    prepared.as_ref(),
2831                    declaration,
2832                    &reference,
2833                )
2834            })
2835            .is_some_and(|activation| activation < reference_byte);
2836        }
2837        let Some(donor_syntax) = self.cpp.prepared_syntax(declaration.source()) else {
2838            return false;
2839        };
2840        if callable_declaration_activation_in_file(
2841            analyzer,
2842            donor_syntax.as_ref(),
2843            declaration,
2844            &reference,
2845        )
2846        .or_else(|| {
2847            self.exhaustive_guard_family_activation(
2848                analyzer,
2849                donor_syntax.as_ref(),
2850                declaration,
2851                &reference,
2852            )
2853        })
2854        .is_none()
2855        {
2856            return false;
2857        }
2858        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), declaration.source())
2859            .is_some_and(|activation| activation < reference_byte)
2860    }
2861
2862    pub fn external_type_candidate_visible_at(
2863        &self,
2864        file: &ProjectFile,
2865        candidate: &CodeUnit,
2866        reference_byte: usize,
2867    ) -> bool {
2868        if candidate.source() == file {
2869            return true;
2870        }
2871        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2872            return false;
2873        };
2874        self.visible_identifier_candidates(file, candidate.identifier())
2875            .filter(|peer| same_logical_symbol(candidate, peer))
2876            .any(|peer| {
2877                peer.source() == file
2878                    || self
2879                        .include_activation_for_source(
2880                            self.cpp,
2881                            file,
2882                            prepared.as_ref(),
2883                            peer.source(),
2884                        )
2885                        .is_some_and(|activation| activation <= reference_byte)
2886            })
2887    }
2888
2889    pub fn external_type_declaration_visible_at(
2890        &self,
2891        file: &ProjectFile,
2892        candidate: &CodeUnit,
2893        reference_byte: usize,
2894    ) -> bool {
2895        if candidate.source() == file {
2896            return true;
2897        }
2898        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2899            return false;
2900        };
2901        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
2902            .is_some_and(|activation| activation <= reference_byte)
2903    }
2904
2905    /// Decide whether a declaration that lives in another file reaches a
2906    /// reference in `file`.
2907    ///
2908    /// An external header selects its declaration branch before the reference
2909    /// file is parsed. Require compatible reference guards, but do not test
2910    /// the header's guard expression for stability in the reference file: a
2911    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
2912    /// wraps every declaration of a portable C header, and demanding it would
2913    /// hide the whole header. Guards that the reference file imposes on its
2914    /// own `#include` still have to hold, and still have to be stable.
2915    fn foreign_declaration_reachable_at_reference(
2916        &self,
2917        file: &ProjectFile,
2918        prepared: &PreparedSyntaxTree,
2919        declaration_source: &ProjectFile,
2920        declaration_guards: &HashSet<PreprocessorGuard>,
2921        reference_guards: Option<&HashSet<PreprocessorGuard>>,
2922        reference_byte: usize,
2923    ) -> bool {
2924        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
2925            return false;
2926        }
2927        if self
2928            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
2929            .is_some_and(|activation| activation <= reference_byte)
2930        {
2931            return true;
2932        }
2933        self.conditional_include_projections_for_source(file, prepared, declaration_source)
2934            .iter()
2935            .any(|projection| {
2936                projection.activation_byte <= reference_byte
2937                    && guard_requirements_hold_at_reference(
2938                        &projection.required_guards,
2939                        reference_guards,
2940                    )
2941                    && self.preprocessor_guards_stable_between(
2942                        file,
2943                        0,
2944                        reference_byte,
2945                        &projection.required_guards,
2946                    )
2947            })
2948    }
2949
2950    pub fn external_type_candidate_visible_in_context(
2951        &self,
2952        analyzer: &CppGraphSource<'_>,
2953        file: &ProjectFile,
2954        candidate: &CodeUnit,
2955        reference: Node<'_>,
2956    ) -> bool {
2957        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2958            return false;
2959        };
2960        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
2961
2962        let directly_visible = self
2963            .visible_identifier_candidates(file, candidate.identifier())
2964            .filter(|peer| same_logical_symbol(candidate, peer))
2965            .any(|peer| {
2966                declaration_guard_requirements(analyzer, self.cpp, peer)
2967                    .into_iter()
2968                    .any(|(declaration_byte, declaration_guards)| {
2969                        if peer.source() == file {
2970                            return declaration_byte < reference.start_byte()
2971                                && guard_requirements_hold_at_reference(
2972                                    &declaration_guards,
2973                                    reference_guards.as_ref(),
2974                                )
2975                                && self.preprocessor_guards_stable_between(
2976                                    file,
2977                                    declaration_byte,
2978                                    reference.start_byte(),
2979                                    &declaration_guards,
2980                                );
2981                        }
2982                        self.foreign_declaration_reachable_at_reference(
2983                            file,
2984                            prepared.as_ref(),
2985                            peer.source(),
2986                            &declaration_guards,
2987                            reference_guards.as_ref(),
2988                            reference.start_byte(),
2989                        )
2990                    })
2991            });
2992        let complementary = self
2993            .visible_identifier_candidates(file, candidate.identifier())
2994            .filter(|peer| {
2995                peer.kind() == candidate.kind()
2996                    && peer.fq_name() == candidate.fq_name()
2997                    && peer.source() == candidate.source()
2998            })
2999            .collect::<Vec<_>>();
3000        // A completed #if/#else family declares the shared source-level name
3001        // before this reference. A later macro mutation cannot revoke that
3002        // declaration. The family gate below rejects declarations split across
3003        // separate conditional blocks, where mutation can change coverage.
3004        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3005            declaration_guard_requirements(analyzer, self.cpp, candidate)
3006                .iter()
3007                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3008        });
3009        let complementary_visible = candidate_branch_compatible
3010            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3011            && if candidate.source() == file {
3012                declaration_guard_requirements(analyzer, self.cpp, candidate)
3013                    .iter()
3014                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3015            } else {
3016                self.include_activation_for_source(
3017                    self.cpp,
3018                    file,
3019                    prepared.as_ref(),
3020                    candidate.source(),
3021                )
3022                .is_some_and(|activation| activation <= reference.start_byte())
3023            };
3024        directly_visible || complementary_visible
3025    }
3026
3027    pub fn is_exhaustive_same_fqn_type_declaration_family(
3028        &self,
3029        analyzer: &CppGraphSource<'_>,
3030        file: &ProjectFile,
3031        candidate: &CodeUnit,
3032    ) -> bool {
3033        let candidates = self
3034            .visible_identifier_candidates(file, candidate.identifier())
3035            .filter(|peer| {
3036                peer.kind() == candidate.kind()
3037                    && peer.fq_name() == candidate.fq_name()
3038                    && peer.source() == candidate.source()
3039            })
3040            .collect::<Vec<_>>();
3041        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3042    }
3043
3044    /// Prove a nested type alias used as a dependent member-pointer owner when
3045    /// its owning class has mutually-exclusive declarations.  A common C++11
3046    /// compatibility shape provides the owning class in one preprocessor
3047    /// branch and aliases it to a standard-library type in the other branch;
3048    /// the nested fallback alias is therefore not itself active in every
3049    /// branch even though the qualified owner API is.
3050    ///
3051    /// This is deliberately narrower than ordinary type visibility.  The
3052    /// caller has already recovered a member-pointer owner path from the CST;
3053    /// this helper additionally requires the target's structured parent to
3054    /// match that path, physical source visibility, and exact preprocessor
3055    /// guard agreement with the parent declaration.  Only then may the
3056    /// parent's direct/complementary same-FQN visibility stand in for the
3057    /// nested terminal's active-branch check.
3058    pub fn dependent_member_pointer_alias_visible_in_context(
3059        &self,
3060        analyzer: &CppGraphSource<'_>,
3061        file: &ProjectFile,
3062        candidate: &CodeUnit,
3063        owner_components: &[String],
3064        reference: Node<'_>,
3065    ) -> bool {
3066        if !analyzer
3067            .type_alias_provider()
3068            .is_some_and(|provider| provider.is_type_alias(candidate))
3069        {
3070            return false;
3071        }
3072        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3073            return false;
3074        };
3075        if terminal != candidate.identifier()
3076            || canonical_cpp_scope_components(candidate) != owner_components
3077        {
3078            return false;
3079        }
3080        let Some(expected_parent_fq_name) =
3081            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3082        else {
3083            return false;
3084        };
3085        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3086            return false;
3087        };
3088        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3089            || parent_anchor.source() != candidate.source()
3090            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3091        {
3092            return false;
3093        }
3094
3095        // The ordinary path already handles unguarded aliases (and preserves
3096        // same-file declaration ordering).  This fallback is only for a
3097        // physically visible declaration whose guard is the owning branch's
3098        // guard, so reject a same-file declaration that appears after the
3099        // reference before considering guard compatibility.
3100        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3101            || candidate.source() == file
3102                && !analyzer
3103                    .ranges(candidate)
3104                    .iter()
3105                    .any(|range| range.start_byte < reference.start_byte())
3106        {
3107            return false;
3108        }
3109
3110        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3111        if candidate_guards.is_empty() {
3112            return false;
3113        }
3114        let same_guard_sets =
3115            |left: &[(usize, HashSet<PreprocessorGuard>)],
3116             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3117                left.iter().all(|(_, left_guards)| {
3118                    right
3119                        .iter()
3120                        .any(|(_, right_guards)| left_guards == right_guards)
3121                })
3122            };
3123        let parent_candidates = self
3124            .visible_identifier_candidates(file, parent_anchor.identifier())
3125            .filter(|peer| {
3126                peer.kind() == parent_anchor.kind()
3127                    && peer.fq_name() == expected_parent_fq_name.as_str()
3128                    && peer.source() == parent_anchor.source()
3129                    && canonical_cpp_scope_components(peer) == owner_prefix
3130            })
3131            .filter_map(|peer| {
3132                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3133                (candidate_guards.len() == parent_guards.len()
3134                    && same_guard_sets(&candidate_guards, &parent_guards)
3135                    && same_guard_sets(&parent_guards, &candidate_guards))
3136                .then(|| (peer.clone(), parent_guards))
3137            })
3138            .collect::<Vec<_>>();
3139        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3140            return false;
3141        };
3142
3143        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3144            return false;
3145        };
3146        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3147        else {
3148            return false;
3149        };
3150        // An external header selects its declaration branch before the
3151        // reference file is parsed. Require compatible reference guards, but
3152        // do not test the header's guard expression for stability in the
3153        // reference file. Same-file aliases still require that stability.
3154        if !candidate_guards.iter().any(|(_, target_guards)| {
3155            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3156                && (candidate.source() != file
3157                    || self.preprocessor_guards_stable_between(
3158                        file,
3159                        0,
3160                        reference.start_byte(),
3161                        target_guards,
3162                    ))
3163        }) {
3164            return false;
3165        }
3166
3167        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3168    }
3169
3170    /// Check a type candidate's preprocessor/import context without imposing
3171    /// ordinary declaration-before-reference ordering for same-file peers.
3172    ///
3173    /// C++ class scope makes member names visible throughout the complete
3174    /// class, including a trailing return type that appears before the member
3175    /// alias declaration in source order. Callers must first prove that the
3176    /// reference is inside the candidate's indexed class owner; this helper
3177    /// only relaxes the byte-order predicate while retaining guard and include
3178    /// activation checks.
3179    pub fn external_type_candidate_guard_compatible_in_context(
3180        &self,
3181        analyzer: &CppGraphSource<'_>,
3182        file: &ProjectFile,
3183        candidate: &CodeUnit,
3184        reference: Node<'_>,
3185    ) -> bool {
3186        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3187            return false;
3188        };
3189        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3190
3191        self.visible_identifier_candidates(file, candidate.identifier())
3192            .filter(|peer| same_logical_symbol(candidate, peer))
3193            .any(|peer| {
3194                declaration_guard_requirements(analyzer, self.cpp, peer)
3195                    .into_iter()
3196                    .any(|(declaration_byte, declaration_guards)| {
3197                        if peer.source() == file {
3198                            let (start, end) = if declaration_byte <= reference.start_byte() {
3199                                (declaration_byte, reference.start_byte())
3200                            } else {
3201                                (reference.start_byte(), declaration_byte)
3202                            };
3203                            return guard_requirements_hold_at_reference(
3204                                &declaration_guards,
3205                                reference_guards.as_ref(),
3206                            ) && self.preprocessor_guards_stable_between(
3207                                file,
3208                                start,
3209                                end,
3210                                &declaration_guards,
3211                            );
3212                        }
3213                        self.foreign_declaration_reachable_at_reference(
3214                            file,
3215                            prepared.as_ref(),
3216                            peer.source(),
3217                            &declaration_guards,
3218                            reference_guards.as_ref(),
3219                            reference.start_byte(),
3220                        )
3221                    })
3222            })
3223    }
3224
3225    pub fn type_candidate_may_be_visible_before_reference(
3226        &self,
3227        analyzer: &CppGraphSource<'_>,
3228        file: &ProjectFile,
3229        candidate: &CodeUnit,
3230        reference_byte: usize,
3231    ) -> bool {
3232        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3233            return false;
3234        };
3235        let root = prepared.tree().root_node();
3236        let end_byte = reference_byte
3237            .saturating_add(1)
3238            .min(prepared.source().len());
3239        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3240            return false;
3241        };
3242        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3243    }
3244
3245    pub fn preprocessor_guards_stable_between(
3246        &self,
3247        file: &ProjectFile,
3248        start_byte: usize,
3249        end_byte: usize,
3250        guards: &HashSet<PreprocessorGuard>,
3251    ) -> bool {
3252        if guards.is_empty() || start_byte >= end_byte {
3253            return true;
3254        }
3255        let cell = self.macro_event_cell(file);
3256        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3257        let mut visited = HashSet::from_iter([file.clone()]);
3258        !events.iter().any(|event| {
3259            event.byte() >= start_byte
3260                && event.byte() < end_byte
3261                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3262        })
3263    }
3264
3265    fn macro_event_may_mutate_guards(
3266        &self,
3267        event: &MacroEvent,
3268        guards: &HashSet<PreprocessorGuard>,
3269        visited: &mut HashSet<ProjectFile>,
3270    ) -> bool {
3271        match event {
3272            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3273                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3274            }
3275            MacroEvent::Include { targets, .. } => {
3276                targets.is_empty()
3277                    || targets
3278                        .iter()
3279                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3280            }
3281            MacroEvent::Invalidate { .. } => true,
3282        }
3283    }
3284
3285    fn source_may_mutate_guards(
3286        &self,
3287        file: &ProjectFile,
3288        guards: &HashSet<PreprocessorGuard>,
3289        visited: &mut HashSet<ProjectFile>,
3290    ) -> bool {
3291        if !visited.insert(file.clone()) {
3292            return false;
3293        }
3294        let cell = self.macro_event_cell(file);
3295        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3296        events
3297            .iter()
3298            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3299    }
3300
3301    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3302        let normalized = normalize_reference_name(raw_name)?;
3303        self.type_candidates(file, &normalized)
3304            .into_iter()
3305            .next()
3306            .cloned()
3307    }
3308
3309    pub fn resolve_type_node_result(
3310        &self,
3311        file: &ProjectFile,
3312        node: Node<'_>,
3313        source: &str,
3314    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3315        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3316            return Ok(None);
3317        };
3318        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3319            return Ok(Some(primary));
3320        };
3321        self.resolve_template_arguments(file, primary, &arguments)
3322            .map(Some)
3323    }
3324
3325    pub fn resolve_type_node_primary(
3326        &self,
3327        file: &ProjectFile,
3328        node: Node<'_>,
3329        source: &str,
3330    ) -> Option<CodeUnit> {
3331        let components = cpp_type_name_components(node, source)?;
3332        self.resolve_type(file, &components.join("::"))
3333    }
3334
3335    pub fn resolve_template_arguments(
3336        &self,
3337        file: &ProjectFile,
3338        primary: CodeUnit,
3339        arguments: &[CppTemplateExpression],
3340    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3341        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3342    }
3343
3344    fn resolve_template_arguments_inner(
3345        &self,
3346        file: &ProjectFile,
3347        primary: CodeUnit,
3348        arguments: &[CppTemplateExpression],
3349        seen_aliases: &mut HashSet<CodeUnit>,
3350    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3351        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3352            && let Some(alias_target) = &metadata.alias_target
3353        {
3354            if !seen_aliases.insert(primary.clone()) {
3355                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3356            }
3357            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3358                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3359            let target_name = alias_target.components.join("::");
3360            let target_primary = if alias_target.global {
3361                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3362            } else {
3363                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3364            };
3365            let Some(target_primary) = target_primary else {
3366                // A dependent or external RHS cannot be canonicalized from the
3367                // indexed graph. Preserve the alias's direct identity instead
3368                // of inventing a target from its source spelling.
3369                return Ok(primary);
3370            };
3371            let Some(target_arguments) = &alias_target.arguments else {
3372                return Ok(target_primary);
3373            };
3374            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3375                .ok_or(CppTemplateResolutionError::Substitution)?;
3376            return self.resolve_template_arguments_inner(
3377                file,
3378                target_primary,
3379                &target_arguments,
3380                seen_aliases,
3381            );
3382        }
3383
3384        let primary_fq_name = self
3385            .cpp_template_metadata
3386            .get(&primary)
3387            .map(|metadata| metadata.primary_fq_name.clone())
3388            .unwrap_or_else(|| primary.fq_name());
3389        let has_specialization_metadata = self
3390            .cpp_template_families
3391            .get(&primary_fq_name)
3392            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3393        if !has_specialization_metadata {
3394            return Ok(primary);
3395        }
3396        self.select_template_specialization(file, &primary, arguments)
3397    }
3398
3399    fn select_template_specialization(
3400        &self,
3401        file: &ProjectFile,
3402        resolved: &CodeUnit,
3403        explicit_arguments: &[CppTemplateExpression],
3404    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3405        let primary_fq_name = self
3406            .cpp_template_metadata
3407            .get(resolved)
3408            .map(|metadata| metadata.primary_fq_name.clone())
3409            .unwrap_or_else(|| resolved.fq_name());
3410        let family = self
3411            .cpp_template_families
3412            .get(&primary_fq_name)
3413            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3414        let primary_candidates = family
3415            .iter()
3416            .filter_map(|unit| {
3417                let metadata = self.cpp_template_metadata.get(unit)?;
3418                (metadata.specialization_arguments.is_empty() && self.is_visible(file, unit))
3419                    .then_some((unit, metadata))
3420            })
3421            .collect::<Vec<_>>();
3422        let primary_unit = primary_candidates
3423            .iter()
3424            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3425            .or_else(|| {
3426                primary_candidates
3427                    .iter()
3428                    .map(|(unit, _)| *unit)
3429                    .min_by_key(|unit| {
3430                        (
3431                            unit.source().to_string(),
3432                            unit.signature().unwrap_or_default(),
3433                        )
3434                    })
3435            })
3436            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3437        let primary_parameters =
3438            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3439                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3440        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3441            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3442
3443        let mut applicable = Vec::new();
3444        for unit in family {
3445            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3446                continue;
3447            };
3448            if metadata.specialization_arguments.is_empty() || !self.is_visible(file, unit) {
3449                continue;
3450            }
3451            if !cpp_specialization_matches(metadata, &expanded) {
3452                continue;
3453            }
3454            applicable.push((unit, metadata));
3455        }
3456        if applicable.is_empty() {
3457            return Ok(primary_unit.clone());
3458        }
3459
3460        // A scalar constraint count cannot represent C++ partial ordering:
3461        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3462        // Select only a logical candidate whose structural pattern is strictly
3463        // more specialized than every other distinct applicable candidate.
3464        let winners = applicable
3465            .iter()
3466            .filter(|(candidate, candidate_metadata)| {
3467                applicable.iter().all(|(other, other_metadata)| {
3468                    same_visible_symbol(candidate, other)
3469                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3470                })
3471            })
3472            .copied()
3473            .collect::<Vec<_>>();
3474        let Some((selected, _)) = winners.first() else {
3475            // Mutually incomparable applicable candidates: every one of them
3476            // is a live contender.
3477            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3478                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3479            });
3480        };
3481        if winners
3482            .iter()
3483            .any(|(unit, _)| !same_visible_symbol(unit, selected))
3484        {
3485            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3486                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3487            });
3488        }
3489        Ok((*selected).clone())
3490    }
3491
3492    pub fn resolve_type_components_lexically(
3493        &self,
3494        analyzer: &CppGraphSource<'_>,
3495        file: &ProjectFile,
3496        components: &[String],
3497        global: bool,
3498        lexical_scope: &[String],
3499    ) -> LexicalTypeResolution {
3500        self.resolve_type_components_lexically_inner(
3501            analyzer,
3502            file,
3503            components,
3504            global,
3505            lexical_scope,
3506            TypeCandidateResolution::Canonical,
3507        )
3508    }
3509
3510    pub fn resolve_type_components_lexically_for_forward(
3511        &self,
3512        analyzer: &CppGraphSource<'_>,
3513        file: &ProjectFile,
3514        components: &[String],
3515        global: bool,
3516        lexical_scope: &[String],
3517    ) -> LexicalTypeResolution {
3518        self.resolve_type_components_lexically_inner(
3519            analyzer,
3520            file,
3521            components,
3522            global,
3523            lexical_scope,
3524            TypeCandidateResolution::PreserveAlias,
3525        )
3526    }
3527
3528    pub fn resolve_type_components_lexically_for_target(
3529        &self,
3530        analyzer: &CppGraphSource<'_>,
3531        file: &ProjectFile,
3532        components: &[String],
3533        global: bool,
3534        lexical_scope: &[String],
3535        target: &CodeUnit,
3536    ) -> LexicalTypeResolution {
3537        #[cfg(any(test, feature = "test-support"))]
3538        self.target_preserving_type_resolution_count
3539            .fetch_add(1, Ordering::Relaxed);
3540        self.resolve_type_components_lexically_inner(
3541            analyzer,
3542            file,
3543            components,
3544            global,
3545            lexical_scope,
3546            TypeCandidateResolution::PreserveTarget(target),
3547        )
3548    }
3549
3550    pub fn coarse_unqualified_type_reference_may_resolve(
3551        &self,
3552        file: &ProjectFile,
3553        name: &str,
3554    ) -> bool {
3555        if name.is_empty() {
3556            return true;
3557        }
3558        self.visible_identifier_candidates(file, name)
3559            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
3560            || self.visible_parser_alias_name_is_visible(file, name)
3561    }
3562
3563    #[allow(clippy::too_many_arguments)]
3564    pub fn structured_type_reference_may_resolve_to_target(
3565        &self,
3566        analyzer: &CppGraphSource<'_>,
3567        file: &ProjectFile,
3568        components: &[String],
3569        global: bool,
3570        lexical_scope: &[String],
3571        target: &CodeUnit,
3572    ) -> bool {
3573        if components.is_empty() {
3574            return true;
3575        }
3576        let Some(terminal) = components.last() else {
3577            return true;
3578        };
3579        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
3580        if parser_alias_visible
3581            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
3582        {
3583            return true;
3584        }
3585        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
3586            .map(|qualified| qualified.join("::"))
3587            .collect::<Vec<_>>();
3588        let target_name = cpp_name_for(target);
3589        if qualified_tiers
3590            .iter()
3591            .any(|qualified| qualified == &target_name)
3592        {
3593            return true;
3594        }
3595
3596        let mut saw_shape_candidate = parser_alias_visible;
3597        for candidate in self.visible_identifier_candidates(file, terminal) {
3598            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3599            {
3600                continue;
3601            }
3602            let candidate_name = cpp_name_for(candidate);
3603            let shape_matches = if global || components.len() > 1 {
3604                qualified_tiers
3605                    .iter()
3606                    .any(|qualified| qualified == &candidate_name)
3607            } else {
3608                true
3609            };
3610            if !shape_matches {
3611                continue;
3612            }
3613            saw_shape_candidate = true;
3614            if same_visible_symbol(candidate, target)
3615                || self.compatible_primary_template_redeclarations(candidate, target)
3616                || (declared_type_alias(analyzer, candidate)
3617                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
3618            {
3619                return true;
3620            }
3621        }
3622
3623        !saw_shape_candidate
3624    }
3625
3626    pub fn target_preserving_reference_namespace(
3627        &self,
3628        analyzer: &CppGraphSource<'_>,
3629        file: &ProjectFile,
3630        identifier: &str,
3631        target: &CodeUnit,
3632    ) -> Option<Vec<String>> {
3633        let mut namespace = None;
3634        for candidate in self.visible_identifier_candidates(file, identifier) {
3635            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3636            {
3637                continue;
3638            }
3639            if !(same_visible_symbol(candidate, target)
3640                || self.compatible_primary_template_redeclarations(candidate, target)
3641                || declared_type_alias(analyzer, candidate)
3642                    && self.structured_alias_primary_preserves_target(
3643                        analyzer, file, candidate, target,
3644                    ))
3645            {
3646                continue;
3647            }
3648            if namespace
3649                .as_ref()
3650                .is_some_and(|existing| existing != candidate.package_name())
3651            {
3652                return None;
3653            }
3654            namespace = Some(candidate.package_name().to_string());
3655        }
3656        let namespace = namespace?;
3657        Some(
3658            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3659                brokk_bifrost_core::analyzer::Language::Cpp,
3660                &namespace,
3661            ),
3662        )
3663    }
3664
3665    pub fn resolve_imported_type_candidate(
3666        &self,
3667        analyzer: &CppGraphSource<'_>,
3668        file: &ProjectFile,
3669        target: &CodeUnit,
3670        target_components: &[String],
3671        direct_target: Option<&CodeUnit>,
3672        preserve_alias: bool,
3673    ) -> LexicalTypeResolution {
3674        let candidates = [target];
3675        let resolution = if preserve_alias {
3676            TypeCandidateResolution::PreserveAlias
3677        } else {
3678            direct_target.map_or(
3679                TypeCandidateResolution::Canonical,
3680                TypeCandidateResolution::PreserveTarget,
3681            )
3682        };
3683        // One candidate goes in, so a failure here is never "choose one of
3684        // these": it is the alias chain leaving the index, which must answer
3685        // missing rather than ambiguous (#1828).
3686        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3687            Ok(unit) => LexicalTypeResolution::Resolved {
3688                unit,
3689                components: target_components.to_vec(),
3690                candidates: vec![target.clone()],
3691            },
3692            Err(failure) => failure.lexical_resolution(),
3693        }
3694    }
3695
3696    fn resolve_type_components_lexically_inner(
3697        &self,
3698        analyzer: &CppGraphSource<'_>,
3699        file: &ProjectFile,
3700        components: &[String],
3701        global: bool,
3702        lexical_scope: &[String],
3703        resolution: TypeCandidateResolution<'_>,
3704    ) -> LexicalTypeResolution {
3705        if components.is_empty() {
3706            return LexicalTypeResolution::Missing;
3707        }
3708        // A C++ class injects its own name into the class scope.  The indexed
3709        // FqName for that declaration is the class path itself (for example,
3710        // `n::raw_hash_set`), not a synthetic child named
3711        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
3712        // requested identifier to every scope component, so they cannot
3713        // represent that injected binding when the enclosing class is the
3714        // closest scope.  Recover the binding from the structured class path
3715        // before allowing lookup to fall through to an outer same-spelled
3716        // declaration.
3717        let mut injected = self.resolve_injected_class_name(
3718            analyzer,
3719            file,
3720            components,
3721            global,
3722            lexical_scope,
3723            resolution,
3724        );
3725        for (tier_index, qualified) in
3726            lexical_component_tiers(components, global, lexical_scope).enumerate()
3727        {
3728            let prefix_len = qualified.len().saturating_sub(components.len());
3729            if injected
3730                .as_ref()
3731                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
3732            {
3733                return injected
3734                    .take()
3735                    .expect("injected class resolution was just present")
3736                    .1;
3737            }
3738            let qualified_name = qualified.join("::");
3739            let candidates = self
3740                .type_candidates(file, &qualified_name)
3741                .into_iter()
3742                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3743                .collect::<Vec<_>>();
3744            if candidates.is_empty() {
3745                if tier_index == 0 && !global && components.len() == 1 {
3746                    match self.resolve_inherited_type_for_lexical_scope(
3747                        analyzer,
3748                        file,
3749                        lexical_scope,
3750                        &components[0],
3751                        resolution,
3752                    ) {
3753                        LexicalTypeResolution::Missing => {}
3754                        inherited => return inherited,
3755                    }
3756                }
3757                continue;
3758            }
3759            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3760                Ok(unit) => unit,
3761                Err(failure) => return failure.lexical_resolution(),
3762            };
3763            return LexicalTypeResolution::Resolved {
3764                unit,
3765                components: qualified,
3766                candidates: candidates.into_iter().cloned().collect(),
3767            };
3768        }
3769        LexicalTypeResolution::Missing
3770    }
3771
3772    fn resolve_injected_class_name(
3773        &self,
3774        analyzer: &CppGraphSource<'_>,
3775        file: &ProjectFile,
3776        components: &[String],
3777        global: bool,
3778        lexical_scope: &[String],
3779        resolution: TypeCandidateResolution<'_>,
3780    ) -> Option<(usize, LexicalTypeResolution)> {
3781        if global
3782            || components.len() != 1
3783            || file.rel_path().extension().is_some_and(|ext| ext == "c")
3784            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
3785        {
3786            return None;
3787        }
3788        let name = components.first()?;
3789        let mut matches: Vec<&CodeUnit> = Vec::new();
3790        let mut owner_len = 0;
3791        for candidate in self.visible_identifier_candidates(file, name) {
3792            if !candidate.is_class()
3793                || declared_type_alias(analyzer, candidate)
3794                || candidate.identifier() != name
3795            {
3796                continue;
3797            }
3798            let candidate_scope = canonical_cpp_scope_components(candidate);
3799            if candidate_scope.len() > lexical_scope.len()
3800                || !lexical_scope.starts_with(&candidate_scope)
3801                || candidate_scope.last().is_none_or(|last| last != name)
3802            {
3803                continue;
3804            }
3805            if candidate_scope.len() > owner_len {
3806                owner_len = candidate_scope.len();
3807                matches.clear();
3808            }
3809            if candidate_scope.len() == owner_len
3810                && !matches
3811                    .iter()
3812                    .any(|existing| same_logical_symbol(existing, candidate))
3813            {
3814                matches.push(candidate);
3815            }
3816        }
3817        if matches.is_empty() {
3818            return None;
3819        }
3820        // A same-named class at the current lexical boundary is already
3821        // represented by the ordinary namespace/class tier.  The injected
3822        // recovery is only needed when lookup is occurring inside a nested
3823        // class, where the enclosing class name is injected across that
3824        // additional class boundary.  Keeping this boundary strict avoids
3825        // treating qualified receiver/static-qualifier context as an
3826        // injected-name reference.
3827        if owner_len >= lexical_scope.len() {
3828            return None;
3829        }
3830        let owner_components = lexical_scope[..owner_len].to_vec();
3831        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
3832            Ok(unit) => LexicalTypeResolution::Resolved {
3833                unit,
3834                components: owner_components,
3835                candidates: matches.into_iter().cloned().collect(),
3836            },
3837            Err(failure) => failure.lexical_resolution(),
3838        };
3839        Some((owner_len, resolution))
3840    }
3841
3842    fn resolve_inherited_type_for_lexical_scope(
3843        &self,
3844        analyzer: &CppGraphSource<'_>,
3845        file: &ProjectFile,
3846        lexical_scope: &[String],
3847        name: &str,
3848        resolution: TypeCandidateResolution<'_>,
3849    ) -> LexicalTypeResolution {
3850        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
3851            return LexicalTypeResolution::Missing;
3852        };
3853        let lexical_owner_name = lexical_scope.join("::");
3854        if lexical_owner_name.is_empty() {
3855            return LexicalTypeResolution::Missing;
3856        }
3857        let owner_candidates = self
3858            .type_candidates(file, &lexical_owner_name)
3859            .into_iter()
3860            .filter(|candidate| {
3861                canonical_cpp_name_matches(candidate, &lexical_owner_name)
3862                    && !declared_type_alias(analyzer, candidate)
3863            })
3864            .collect::<Vec<_>>();
3865        if owner_candidates.is_empty() {
3866            return LexicalTypeResolution::Missing;
3867        }
3868        let Some(lexical_owner) = unique_logical_type_candidate(owner_candidates) else {
3869            return LexicalTypeResolution::Ambiguous;
3870        };
3871
3872        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
3873        let mut visited_owners = HashSet::default();
3874        while !frontier.is_empty() {
3875            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
3876            let mut next_frontier = Vec::new();
3877            for owner in frontier {
3878                if !visited_owners.insert(owner.fq_name()) {
3879                    continue;
3880                }
3881                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
3882                let candidates = self
3883                    .type_candidates(file, &qualified_name)
3884                    .into_iter()
3885                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3886                    .collect::<Vec<_>>();
3887                if candidates.is_empty() {
3888                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
3889                        if !next_frontier
3890                            .iter()
3891                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
3892                        {
3893                            next_frontier.push(ancestor);
3894                        }
3895                    }
3896                    continue;
3897                }
3898                let unit =
3899                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3900                        Ok(unit) => unit,
3901                        Err(failure) => return failure.lexical_resolution(),
3902                    };
3903                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
3904            }
3905            if let Some((unit, candidates)) = level_matches.first().cloned() {
3906                let Some(first_declaration) = candidates.first() else {
3907                    return LexicalTypeResolution::Ambiguous;
3908                };
3909                if !level_matches.iter().all(|(_, declarations)| {
3910                    declarations
3911                        .iter()
3912                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
3913                }) {
3914                    return LexicalTypeResolution::Ambiguous;
3915                }
3916                let mut components = lexical_scope.to_vec();
3917                components.push(name.to_string());
3918                return LexicalTypeResolution::Resolved {
3919                    unit,
3920                    components,
3921                    candidates,
3922                };
3923            }
3924            frontier = next_frontier;
3925        }
3926        LexicalTypeResolution::Missing
3927    }
3928
3929    /// Resolve a base class through its injected class name at the nearest
3930    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
3931    pub fn inherited_injected_class_owner(
3932        &self,
3933        analyzer: &CppGraphSource<'_>,
3934        file: &ProjectFile,
3935        enclosing_owner: &CodeUnit,
3936        injected_name: &str,
3937    ) -> Option<CodeUnit> {
3938        let hierarchy = analyzer.type_hierarchy_provider()?;
3939        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
3940        let mut visited = HashSet::default();
3941        while !frontier.is_empty() {
3942            let mut level_matches = Vec::new();
3943            let mut next_frontier = Vec::new();
3944            for raw_owner in frontier {
3945                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
3946                if !visited.insert(owner.clone()) {
3947                    continue;
3948                }
3949                if owner.identifier() == injected_name
3950                    && !level_matches
3951                        .iter()
3952                        .any(|existing| same_logical_symbol(existing, &owner))
3953                {
3954                    level_matches.push(owner.clone());
3955                }
3956                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
3957            }
3958            if let Some(first) = level_matches.first() {
3959                return level_matches
3960                    .iter()
3961                    .all(|candidate| same_logical_symbol(candidate, first))
3962                    .then(|| first.clone());
3963            }
3964            frontier = next_frontier;
3965        }
3966        None
3967    }
3968
3969    /// The one type the candidates name under `resolution`, or why they do not
3970    /// name one. The two preserving modes only ever reject candidates that
3971    /// disagree with each other, which is ambiguity; canonicalization can also
3972    /// fail because the alias chain leaves the index (#1828).
3973    fn resolve_type_candidates(
3974        &self,
3975        analyzer: &CppGraphSource<'_>,
3976        file: &ProjectFile,
3977        candidates: &[&CodeUnit],
3978        resolution: TypeCandidateResolution<'_>,
3979    ) -> Result<CodeUnit, TypeCandidateFailure> {
3980        match resolution {
3981            TypeCandidateResolution::Canonical => {
3982                self.canonical_type_candidate_resolution(analyzer, file, candidates)
3983            }
3984            TypeCandidateResolution::PreserveAlias => {
3985                unique_type_candidate_preserving_alias(analyzer, candidates)
3986                    .ok_or(TypeCandidateFailure::Ambiguous)
3987            }
3988            TypeCandidateResolution::PreserveTarget(target) => self
3989                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
3990                .ok_or(TypeCandidateFailure::Ambiguous),
3991        }
3992    }
3993
3994    pub fn resolve_callable_value_components_lexically(
3995        &self,
3996        analyzer: &CppGraphSource<'_>,
3997        file: &ProjectFile,
3998        owner_components: &[String],
3999        member_name: &str,
4000        global: bool,
4001        lexical_scope: &[String],
4002    ) -> LexicalCallableValueResolution {
4003        if owner_components.is_empty() || member_name.is_empty() {
4004            return LexicalCallableValueResolution::Missing;
4005        }
4006        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4007            let owner_name = qualified_owner.join("::");
4008            let type_candidates = self
4009                .type_candidates(file, &owner_name)
4010                .into_iter()
4011                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4012                .collect::<Vec<_>>();
4013            let resolved_type = if type_candidates.is_empty() {
4014                None
4015            } else {
4016                let Some(unit) =
4017                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4018                else {
4019                    return LexicalCallableValueResolution::Ambiguous;
4020                };
4021                Some(unit)
4022            };
4023
4024            let mut qualified_callable = qualified_owner;
4025            qualified_callable.push(member_name.to_string());
4026            let callable_name = qualified_callable.join("::");
4027            let free_function = self
4028                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4029                .into_iter()
4030                .find(|candidate| {
4031                    canonical_cpp_name_matches(candidate, &callable_name)
4032                        && type_owner_of(analyzer, candidate).is_none()
4033                })
4034                .cloned();
4035
4036            match (resolved_type, free_function) {
4037                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4038                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4039                (None, Some(function)) => {
4040                    return LexicalCallableValueResolution::FreeFunction(function);
4041                }
4042                (None, None) => {}
4043            }
4044        }
4045        LexicalCallableValueResolution::Missing
4046    }
4047
4048    fn resolve_type_for_declaration(
4049        &self,
4050        visible_from: &ProjectFile,
4051        declaration: &CodeUnit,
4052        raw_name: &str,
4053    ) -> Option<CodeUnit> {
4054        let normalized = normalize_reference_name(raw_name)?;
4055        if !normalized.contains("::")
4056            && let Some(namespace) = cpp_namespace_for(declaration)
4057        {
4058            for prefix in namespace_prefixes(&namespace) {
4059                let qualified = format!("{prefix}::{normalized}");
4060                if let Some(unit) = self
4061                    .type_candidates(visible_from, &qualified)
4062                    .into_iter()
4063                    .next()
4064                {
4065                    return Some(unit.clone());
4066                }
4067            }
4068        }
4069        self.resolve_type(visible_from, raw_name)
4070    }
4071
4072    fn resolve_unique_canonical_type_for_declaration(
4073        &self,
4074        analyzer: &CppGraphSource<'_>,
4075        visible_from: &ProjectFile,
4076        declaration: &CodeUnit,
4077        raw_name: &str,
4078    ) -> Option<CodeUnit> {
4079        let mut current =
4080            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4081        let mut seen_aliases = HashSet::default();
4082        loop {
4083            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4084                return current.is_class().then_some(current);
4085            };
4086            if matches!(target, StructuredAliasTarget::Builtin) {
4087                return current.is_class().then_some(current);
4088            }
4089            if !seen_aliases.insert(current.clone()) {
4090                return None;
4091            }
4092            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4093        }
4094    }
4095
4096    pub fn canonical_type_unit(
4097        &self,
4098        analyzer: &CppGraphSource<'_>,
4099        visible_from: &ProjectFile,
4100        unit: &CodeUnit,
4101    ) -> Option<CodeUnit> {
4102        self.canonical_type_resolution(analyzer, visible_from, unit)
4103            .ok()
4104    }
4105
4106    /// Follow `unit`'s alias chain to the class it names, or report why the
4107    /// chain does not end at one indexed class.
4108    ///
4109    /// A chain that leaves the index - an alias to a template parameter, to a
4110    /// standard-library type, or to any other declaration the workspace does
4111    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4112    /// there is still nothing to choose between.
4113    fn canonical_type_resolution(
4114        &self,
4115        analyzer: &CppGraphSource<'_>,
4116        visible_from: &ProjectFile,
4117        unit: &CodeUnit,
4118    ) -> Result<CodeUnit, TypeCandidateFailure> {
4119        let mut current = unit.clone();
4120        let mut seen_aliases = HashSet::default();
4121        loop {
4122            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4123                return current
4124                    .is_class()
4125                    .then_some(current)
4126                    .ok_or(TypeCandidateFailure::Unresolvable);
4127            };
4128            if matches!(target, StructuredAliasTarget::Builtin) {
4129                return current
4130                    .is_class()
4131                    .then_some(current)
4132                    .ok_or(TypeCandidateFailure::Unresolvable);
4133            }
4134            if !seen_aliases.insert(current.clone()) {
4135                return Err(TypeCandidateFailure::Unresolvable);
4136            }
4137            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4138        }
4139    }
4140
4141    pub fn canonical_visible_full_type_unit(
4142        &self,
4143        analyzer: &CppGraphSource<'_>,
4144        visible_from: &ProjectFile,
4145        unit: &CodeUnit,
4146    ) -> Option<CodeUnit> {
4147        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4148        if cpp_class_declaration_strength(analyzer, &canonical)
4149            != CppClassDeclarationStrength::Forward
4150        {
4151            return Some(canonical);
4152        }
4153        let mut full = Vec::new();
4154        for candidate in self
4155            .visible_identifier_candidates(visible_from, canonical.identifier())
4156            .filter(|candidate| {
4157                candidate.is_class()
4158                    && candidate.fq_name() == canonical.fq_name()
4159                    && cpp_class_declaration_strength(analyzer, candidate)
4160                        == CppClassDeclarationStrength::Full
4161            })
4162        {
4163            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4164                full.push(candidate.clone());
4165            }
4166        }
4167        match full.len() {
4168            0 => Some(canonical),
4169            1 => full.pop(),
4170            _ => None,
4171        }
4172    }
4173
4174    fn resolve_structured_alias_target(
4175        &self,
4176        visible_from: &ProjectFile,
4177        declaration: &CodeUnit,
4178        target: &StructuredAliasTarget,
4179    ) -> Option<CodeUnit> {
4180        self.structured_alias_target_resolution(visible_from, declaration, target)
4181            .ok()
4182    }
4183
4184    fn structured_alias_target_resolution(
4185        &self,
4186        visible_from: &ProjectFile,
4187        declaration: &CodeUnit,
4188        target: &StructuredAliasTarget,
4189    ) -> Result<CodeUnit, TypeCandidateFailure> {
4190        let primary =
4191            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4192        let StructuredAliasTarget::Named { arguments, .. } = target else {
4193            return Err(TypeCandidateFailure::Unresolvable);
4194        };
4195        match arguments {
4196            Some(arguments) => self
4197                .resolve_template_arguments(visible_from, primary, arguments)
4198                .map_err(|error| match error {
4199                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4200                        TypeCandidateFailure::Ambiguous
4201                    }
4202                    _ => TypeCandidateFailure::Unresolvable,
4203                }),
4204            None => Ok(primary),
4205        }
4206    }
4207
4208    fn resolve_structured_alias_primary(
4209        &self,
4210        visible_from: &ProjectFile,
4211        declaration: &CodeUnit,
4212        target: &StructuredAliasTarget,
4213    ) -> Option<CodeUnit> {
4214        self.structured_alias_primary_resolution(visible_from, declaration, target)
4215            .ok()
4216    }
4217
4218    fn structured_alias_primary_resolution(
4219        &self,
4220        visible_from: &ProjectFile,
4221        declaration: &CodeUnit,
4222        target: &StructuredAliasTarget,
4223    ) -> Result<CodeUnit, TypeCandidateFailure> {
4224        let StructuredAliasTarget::Named {
4225            components, global, ..
4226        } = target
4227        else {
4228            return Err(TypeCandidateFailure::Unresolvable);
4229        };
4230        let qualified = components.join("::");
4231        let candidates = if *global {
4232            self.type_candidates(visible_from, &qualified)
4233        } else {
4234            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4235        };
4236        logical_type_candidate(candidates)
4237    }
4238
4239    pub fn structured_alias_primary_preserves_target(
4240        &self,
4241        analyzer: &CppGraphSource<'_>,
4242        visible_from: &ProjectFile,
4243        candidate: &CodeUnit,
4244        target: &CodeUnit,
4245    ) -> bool {
4246        let mut current = candidate.clone();
4247        let mut seen = HashSet::default();
4248        let mut matched_target = false;
4249        loop {
4250            if same_visible_symbol(&current, target)
4251                || self.compatible_primary_template_redeclarations(&current, target)
4252            {
4253                matched_target = true;
4254            }
4255            if !seen.insert(current.clone()) {
4256                return false;
4257            }
4258            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4259                return matched_target;
4260            };
4261            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4262                return matched_target;
4263            };
4264            let Some(primary) =
4265                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4266            else {
4267                // A dependent member target such as `Detector<T>::type`
4268                // cannot be reduced to an indexed primary, but a preceding
4269                // structured alias hop may already have proven the requested
4270                // alias identity. Cycles still resolve a primary and are
4271                // rejected by `seen` above.
4272                return matched_target;
4273            };
4274            current = primary;
4275        }
4276    }
4277
4278    pub fn structured_class_alias_resolves_to_target(
4279        &self,
4280        analyzer: &CppGraphSource<'_>,
4281        visible_from: &ProjectFile,
4282        alias: &CodeUnit,
4283        target: &CodeUnit,
4284    ) -> bool {
4285        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4286            return false;
4287        };
4288        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4289            return false;
4290        };
4291        let StructuredAliasTarget::Named {
4292            components, global, ..
4293        } = &alias_target
4294        else {
4295            return false;
4296        };
4297        let lexical_scope = canonical_cpp_scope_components(&owner);
4298        match self.resolve_type_components_lexically_for_target(
4299            analyzer,
4300            visible_from,
4301            components,
4302            *global,
4303            &lexical_scope,
4304            target,
4305        ) {
4306            LexicalTypeResolution::Resolved {
4307                unit, candidates, ..
4308            } => {
4309                same_visible_symbol(&unit, target)
4310                    || self.same_template_member_identity(analyzer, &unit, target)
4311                    || candidates.iter().any(|candidate| {
4312                        same_visible_symbol(candidate, target)
4313                            || self.same_template_member_identity(analyzer, candidate, target)
4314                    })
4315            }
4316            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4317                self.structured_alias_primary_preserves_target(
4318                    analyzer,
4319                    visible_from,
4320                    alias,
4321                    target,
4322                ) || self.flattened_macro_namespace_alias_target_matches(
4323                    analyzer,
4324                    visible_from,
4325                    alias,
4326                    &alias_target,
4327                    target,
4328                )
4329            }
4330        }
4331    }
4332
4333    /// Return true when a class-owned alias names the requested type as one
4334    /// structured qualifier in its target path.
4335    ///
4336    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4337    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4338    /// canonical identity. Inverse lookup needs the same evidence when later
4339    /// references use only the alias spelling.
4340    pub fn structured_class_alias_path_preserves_target(
4341        &self,
4342        analyzer: &CppGraphSource<'_>,
4343        visible_from: &ProjectFile,
4344        alias: &CodeUnit,
4345        target: &CodeUnit,
4346    ) -> bool {
4347        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4348            return false;
4349        };
4350        let Some(StructuredAliasTarget::Named {
4351            components, global, ..
4352        }) = self.structured_alias_target(analyzer, alias)
4353        else {
4354            return false;
4355        };
4356        let lexical_scope = canonical_cpp_scope_components(&owner);
4357        (1..components.len()).rev().any(|component_count| {
4358            matches!(
4359                self.resolve_type_components_lexically_for_target(
4360                    analyzer,
4361                    visible_from,
4362                    &components[..component_count],
4363                    global,
4364                    &lexical_scope,
4365                    target,
4366                ),
4367                LexicalTypeResolution::Resolved {
4368                    ref unit,
4369                    ref candidates,
4370                    ..
4371                } if same_visible_symbol(unit, target)
4372                    || self.same_template_member_identity(analyzer, unit, target)
4373                    || candidates.iter().any(|candidate| {
4374                        same_visible_symbol(candidate, target)
4375                            || self.same_template_member_identity(analyzer, candidate, target)
4376                    })
4377            )
4378        })
4379    }
4380
4381    fn flattened_macro_namespace_alias_target_matches(
4382        &self,
4383        analyzer: &CppGraphSource<'_>,
4384        visible_from: &ProjectFile,
4385        alias: &CodeUnit,
4386        alias_target: &StructuredAliasTarget,
4387        target: &CodeUnit,
4388    ) -> bool {
4389        let StructuredAliasTarget::Named {
4390            components,
4391            global: false,
4392            arguments: None,
4393        } = alias_target
4394        else {
4395            return false;
4396        };
4397        let Some((target_name, namespace_components)) = components.split_last() else {
4398            return false;
4399        };
4400        if namespace_components.is_empty()
4401            || target_name != target.identifier()
4402            || alias.source() != target.source()
4403            || alias.source() != visible_from
4404            || !target.is_class()
4405            || declared_type_alias(analyzer, target)
4406        {
4407            return false;
4408        }
4409        if self
4410            .resolve_structured_alias_target(visible_from, alias, alias_target)
4411            .is_some()
4412        {
4413            return false;
4414        }
4415
4416        let alias_ranges = analyzer.ranges(alias);
4417        let target_ranges = analyzer.ranges(target);
4418        if alias_ranges.is_empty() || target_ranges.is_empty() {
4419            return false;
4420        }
4421        let alias_start = alias_ranges
4422            .iter()
4423            .map(|range| range.start_byte)
4424            .min()
4425            .expect("non-empty alias ranges have a minimum");
4426        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
4427            return false;
4428        };
4429        let root = prepared.tree().root_node();
4430        let has_matching_declaration = target_ranges
4431            .iter()
4432            .filter(|range| range.end_byte <= alias_start)
4433            .filter_map(|range| node_for_exact_range(root, range))
4434            .any(|node| {
4435                flattened_macro_namespace_components(node, prepared.source())
4436                    .is_some_and(|recovered| recovered == namespace_components)
4437            });
4438        if !has_matching_declaration {
4439            return false;
4440        }
4441
4442        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
4443        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
4444        guard_requirement_sets_match(&alias_guards, &target_guards)
4445    }
4446
4447    pub fn template_alias_arguments_preserve_target(
4448        &self,
4449        analyzer: &CppGraphSource<'_>,
4450        visible_from: &ProjectFile,
4451        alias: &CodeUnit,
4452        arguments: &[CppTemplateExpression],
4453        target: &CodeUnit,
4454    ) -> bool {
4455        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
4456            return false;
4457        };
4458        if metadata.alias_target.is_none()
4459            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
4460        {
4461            return false;
4462        }
4463        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
4464    }
4465
4466    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
4467        self.cpp_template_metadata
4468            .get(unit)
4469            .is_some_and(|metadata| metadata.specialization_arguments.is_empty())
4470    }
4471
4472    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
4473        self.cpp_template_metadata
4474            .get(unit)
4475            .is_some_and(|metadata| !metadata.specialization_arguments.is_empty())
4476    }
4477
4478    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
4479        same_visible_symbol(left, right)
4480            || self.compatible_primary_template_redeclarations(left, right)
4481    }
4482
4483    pub fn same_template_member_identity(
4484        &self,
4485        analyzer: &CppGraphSource<'_>,
4486        left: &CodeUnit,
4487        right: &CodeUnit,
4488    ) -> bool {
4489        if same_visible_symbol(left, right) {
4490            return true;
4491        }
4492        if left.kind() != right.kind()
4493            || left.identifier() != right.identifier()
4494            || left.signature() != right.signature()
4495        {
4496            return false;
4497        }
4498        let (Some(left_owner), Some(right_owner)) =
4499            (analyzer.parent_of(left), analyzer.parent_of(right))
4500        else {
4501            return false;
4502        };
4503        left_owner.is_class()
4504            && right_owner.is_class()
4505            && self.same_template_owner_identity(&left_owner, &right_owner)
4506    }
4507
4508    fn unique_canonical_type_candidate(
4509        &self,
4510        analyzer: &CppGraphSource<'_>,
4511        visible_from: &ProjectFile,
4512        candidates: &[&CodeUnit],
4513    ) -> Option<CodeUnit> {
4514        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
4515            .ok()
4516    }
4517
4518    fn canonical_type_candidate_resolution(
4519        &self,
4520        analyzer: &CppGraphSource<'_>,
4521        visible_from: &ProjectFile,
4522        candidates: &[&CodeUnit],
4523    ) -> Result<CodeUnit, TypeCandidateFailure> {
4524        let mut canonical = Vec::new();
4525        for candidate in candidates {
4526            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
4527            if canonical
4528                .iter()
4529                .any(|existing| same_visible_symbol(existing, &resolved))
4530            {
4531                continue;
4532            }
4533            canonical.push(resolved);
4534            if canonical.len() > 1 {
4535                return Err(TypeCandidateFailure::Ambiguous);
4536            }
4537        }
4538        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
4539    }
4540
4541    pub fn unique_type_candidate_preserving_target(
4542        &self,
4543        analyzer: &CppGraphSource<'_>,
4544        visible_from: &ProjectFile,
4545        candidates: &[&CodeUnit],
4546        target: &CodeUnit,
4547    ) -> Option<CodeUnit> {
4548        // C++ headers often expose one logical type through mutually exclusive
4549        // physical declarations, for example a class in the fallback branch
4550        // and a `using` alias to the standard-library type in the configured
4551        // branch. The index intentionally retains both declarations so forward
4552        // lookup can report each target. Preserve the requested target when
4553        // that is the only ambiguity: every candidate has the same type kind,
4554        // exact canonical FQN, and source file, and the requested declaration
4555        // itself is one of the physical candidates. Do not merge same-named
4556        // declarations from different files or namespaces; those remain
4557        // ambiguous and fail closed below.
4558        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
4559            return Some(target.clone());
4560        }
4561        let mut resolved_candidates = Vec::new();
4562        for candidate in candidates {
4563            let resolved =
4564                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)?;
4565            if resolved_candidates
4566                .iter()
4567                .any(|existing| same_visible_symbol(existing, &resolved))
4568            {
4569                continue;
4570            }
4571            resolved_candidates.push(resolved);
4572        }
4573        match resolved_candidates.as_slice() {
4574            [] => None,
4575            [single] => Some(single.clone()),
4576            // The branches disagree about what the name aliases. When they are
4577            // spellings of one entity (#1845) that disagreement is a build
4578            // configuration, not a choice between types, so it must not deny
4579            // the requested target its reference.
4580            _ => self
4581                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
4582                .map(|_| target.clone()),
4583        }
4584    }
4585
4586    /// The declaration a same-file same-FQN family stands for when a reference
4587    /// names `target`, or `None` when the candidates are not one family or the
4588    /// family does not name `target`.
4589    ///
4590    /// A translation unit cannot hold two different types under one qualified
4591    /// name, so several same-kind declarations of one FQN in one file are
4592    /// alternate spellings of one entity - the configuration branches of an
4593    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
4594    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
4595    /// targets differ; canonicalizing each branch on its own and then demanding
4596    /// agreement reports an ambiguity that denies every declaration in the
4597    /// family its usages (#1845). The family names `target` when it declares
4598    /// it, or when one branch's alias chain reaches it.
4599    ///
4600    /// Declarations in different files or namespaces are distinct entities and
4601    /// are deliberately excluded: their disagreement is a real ambiguity.
4602    pub fn same_fqn_type_spelling_for_target<'b>(
4603        &self,
4604        analyzer: &CppGraphSource<'_>,
4605        visible_from: &ProjectFile,
4606        candidates: &[&'b CodeUnit],
4607        target: &CodeUnit,
4608    ) -> Option<&'b CodeUnit> {
4609        let [first, rest @ ..] = candidates else {
4610            return None;
4611        };
4612        if rest.is_empty()
4613            || !rest.iter().all(|candidate| {
4614                candidate.kind() == first.kind()
4615                    && candidate.fq_name() == first.fq_name()
4616                    && candidate.source() == first.source()
4617            })
4618        {
4619            return None;
4620        }
4621        candidates
4622            .iter()
4623            .copied()
4624            .find(|candidate| same_symbol(candidate, target))
4625            .or_else(|| {
4626                candidates.iter().copied().find(|candidate| {
4627                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
4628                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
4629                })
4630            })
4631    }
4632
4633    pub fn alternate_same_fqn_type_declarations(
4634        &self,
4635        analyzer: &CppGraphSource<'_>,
4636        candidates: &[&CodeUnit],
4637        target: &CodeUnit,
4638    ) -> bool {
4639        let Some(first) = candidates.first() else {
4640            return false;
4641        };
4642        let same_api = first.kind() == target.kind()
4643            && first.fq_name() == target.fq_name()
4644            && first.source() == target.source()
4645            && candidates.iter().all(|candidate| {
4646                candidate.kind() == target.kind()
4647                    && candidate.fq_name() == target.fq_name()
4648                    && candidate.source() == target.source()
4649            })
4650            && candidates
4651                .iter()
4652                .any(|candidate| same_symbol(candidate, target))
4653            && candidates
4654                .iter()
4655                .any(|candidate| !same_logical_symbol(candidate, target));
4656        if !same_api {
4657            return false;
4658        }
4659
4660        let requirements = candidates
4661            .iter()
4662            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4663            .collect::<Vec<_>>();
4664        requirements.len() > 1
4665            && requirements
4666                .iter()
4667                .all(|requirement| !requirement.is_empty())
4668            && requirements.iter().enumerate().all(|(index, left)| {
4669                requirements[index + 1..].iter().all(|right| {
4670                    left.iter().all(|(_, left_guards)| {
4671                        right.iter().all(|(_, right_guards)| {
4672                            merge_preprocessor_guards(left_guards, right_guards).is_none()
4673                        })
4674                    })
4675                })
4676            })
4677    }
4678
4679    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
4680        let mut pending = vec![terms.to_vec()];
4681        while let Some(branch_terms) = pending.pop() {
4682            let mut normalized = Vec::new();
4683            let mut covers_branch = false;
4684            for term in branch_terms {
4685                if term.iter().any(|guard| term.contains(&guard.negated())) {
4686                    continue;
4687                }
4688                if term.is_empty() {
4689                    covers_branch = true;
4690                    break;
4691                }
4692                if !normalized.iter().any(|existing| existing == &term) {
4693                    normalized.push(term);
4694                }
4695            }
4696            if covers_branch {
4697                continue;
4698            }
4699            let Some(split_guard) = normalized
4700                .iter()
4701                .flat_map(|term| term.iter())
4702                .next()
4703                .cloned()
4704            else {
4705                return false;
4706            };
4707            let negated_guard = split_guard.negated();
4708            let mut when_defined = Vec::new();
4709            let mut when_undefined = Vec::new();
4710            for term in normalized {
4711                if term.contains(&negated_guard) {
4712                    // This term cannot hold when `split_guard` is true.
4713                } else if term.contains(&split_guard) {
4714                    let mut reduced = term.clone();
4715                    reduced.remove(&split_guard);
4716                    when_defined.push(reduced);
4717                } else {
4718                    when_defined.push(term.clone());
4719                }
4720                if term.contains(&split_guard) {
4721                    // This term cannot hold when `split_guard` is false.
4722                } else if term.contains(&negated_guard) {
4723                    let mut reduced = term;
4724                    reduced.remove(&negated_guard);
4725                    when_undefined.push(reduced);
4726                } else {
4727                    when_undefined.push(term);
4728                }
4729            }
4730            pending.push(when_defined);
4731            pending.push(when_undefined);
4732        }
4733        true
4734    }
4735
4736    /// The byte range of the one `#if` family with a terminal `#else` that holds
4737    /// every physical declaration of every candidate, or `None` when they do not
4738    /// share one such family.
4739    ///
4740    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
4741    /// whose macros changed between declarations. Require every physical range to
4742    /// belong to one syntax-tree family with a terminal `#else` before the terms
4743    /// can prove branch coverage.
4744    fn declarations_share_exhaustive_conditional_family(
4745        &self,
4746        analyzer: &CppGraphSource<'_>,
4747        candidates: &[&CodeUnit],
4748    ) -> Option<(usize, usize)> {
4749        let mut family_range = None;
4750        for candidate in candidates {
4751            let prepared = self.cpp.prepared_syntax(candidate.source())?;
4752            let root = prepared.tree().root_node();
4753            let mut candidate_family = None;
4754            for range in analyzer.ranges(candidate) {
4755                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
4756                let family = preprocessor_conditional_family_for_declaration(node)?;
4757                let key = (family.start_byte(), family.end_byte());
4758                if candidate_family.is_some_and(|existing| existing != key) {
4759                    return None;
4760                }
4761                candidate_family = Some(key);
4762            }
4763            let candidate_family = candidate_family?;
4764            if family_range.is_some_and(|existing| existing != candidate_family) {
4765                return None;
4766            }
4767            family_range = Some(candidate_family);
4768        }
4769        family_range
4770    }
4771
4772    pub fn complementary_same_fqn_type_declarations(
4773        &self,
4774        analyzer: &CppGraphSource<'_>,
4775        candidates: &[&CodeUnit],
4776        target: &CodeUnit,
4777    ) -> bool {
4778        if candidates.len() < 2
4779            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
4780            || self
4781                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
4782                .is_none()
4783        {
4784            return false;
4785        }
4786        Self::preprocessor_guard_terms_cover_all_paths(
4787            &self.declaration_family_guard_terms(analyzer, candidates),
4788        )
4789    }
4790
4791    fn declaration_family_guard_terms(
4792        &self,
4793        analyzer: &CppGraphSource<'_>,
4794        candidates: &[&CodeUnit],
4795    ) -> Vec<HashSet<PreprocessorGuard>> {
4796        candidates
4797            .iter()
4798            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4799            .map(|(_, guards)| guards)
4800            .collect()
4801    }
4802
4803    /// A callable name declared on every branch of one completed `#if`/`#else`
4804    /// family is declared on every configuration path, so a reference below the
4805    /// whole family sees one of the branches whatever the preprocessor decides.
4806    /// Answer the family's end byte: only past `#endif` is every branch's
4807    /// declaration behind the reference.
4808    ///
4809    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
4810    /// and shares both of its primitives. It does not require two distinct
4811    /// `CodeUnit`s: branches that declare the same signature can collapse into
4812    /// one unit carrying one physical range per branch.
4813    ///
4814    /// The branches are alternate spellings of one declaration, never competing
4815    /// declarations, so only the first branch stands for the family. Reporting
4816    /// every branch as visible would turn a name the source declares exactly
4817    /// once into an ambiguity between build configurations.
4818    fn exhaustive_guard_family_activation(
4819        &self,
4820        analyzer: &CppGraphSource<'_>,
4821        prepared: &PreparedSyntaxTree,
4822        candidate: &CodeUnit,
4823        reference: &CallableReferenceContext<'_>,
4824    ) -> Option<usize> {
4825        // Branch coverage says nothing about scope: a block-local declaration
4826        // stays invisible however many branches declare it.
4827        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
4828            return None;
4829        }
4830        let family = self
4831            .visible_identifier_candidates(candidate.source(), candidate.identifier())
4832            .filter(|peer| {
4833                peer.kind() == candidate.kind()
4834                    && peer.fq_name() == candidate.fq_name()
4835                    && peer.source() == candidate.source()
4836            })
4837            .collect::<Vec<_>>();
4838        let (_, family_end) =
4839            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
4840        if !Self::preprocessor_guard_terms_cover_all_paths(
4841            &self.declaration_family_guard_terms(analyzer, &family),
4842        ) {
4843            return None;
4844        }
4845        // A reference whose own guards pick one branch already reaches that
4846        // branch through the ordinary same-guard path; the family must not
4847        // resurrect the branch the reference contradicts.
4848        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
4849            .iter()
4850            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
4851        {
4852            return None;
4853        }
4854        (first_declaration_byte(analyzer, candidate)?
4855            == family
4856                .iter()
4857                .filter_map(|peer| first_declaration_byte(analyzer, peer))
4858                .min()?)
4859        .then_some(family_end)
4860    }
4861
4862    fn type_candidate_preserving_target(
4863        &self,
4864        analyzer: &CppGraphSource<'_>,
4865        visible_from: &ProjectFile,
4866        candidate: &CodeUnit,
4867        target: &CodeUnit,
4868    ) -> Option<CodeUnit> {
4869        let mut current = candidate.clone();
4870        let mut matched_target = same_visible_symbol(&current, target)
4871            || self.compatible_primary_template_redeclarations(&current, target);
4872        let mut seen = HashSet::default();
4873        loop {
4874            if !seen.insert(current.clone()) {
4875                return None;
4876            }
4877            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4878                return matched_target
4879                    .then(|| target.clone())
4880                    .or_else(|| current.is_class().then_some(current));
4881            };
4882            if self.flattened_macro_namespace_alias_target_matches(
4883                analyzer,
4884                visible_from,
4885                &current,
4886                &alias_target,
4887                target,
4888            ) {
4889                return Some(target.clone());
4890            }
4891            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4892                return matched_target
4893                    .then(|| target.clone())
4894                    .or_else(|| current.is_class().then_some(current));
4895            }
4896            // A non-template alias can name a template alias with explicit
4897            // arguments (for example, `using Result = Expected<int>`).  When
4898            // the requested target is that alias's primary declaration, keep
4899            // the primary identity before expanding the RHS arguments.  The
4900            // expansion would otherwise canonicalize through the underlying
4901            // implementation type and lose the target spelling used by the
4902            // forward resolver.
4903            if !self.cpp_template_metadata.contains_key(&current)
4904                && let Some(primary) =
4905                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4906                && (same_visible_symbol(&primary, target)
4907                    || self.compatible_primary_template_redeclarations(&primary, target))
4908            {
4909                return Some(target.clone());
4910            }
4911            if same_visible_symbol(&current, target) {
4912                return Some(target.clone());
4913            }
4914            if self.cpp_template_metadata.contains_key(&current) {
4915                return None;
4916            }
4917            let Some(next) =
4918                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
4919            else {
4920                return matched_target.then(|| target.clone());
4921            };
4922            current = next;
4923            matched_target |= same_visible_symbol(&current, target)
4924                || self.compatible_primary_template_redeclarations(&current, target);
4925        }
4926    }
4927
4928    fn compatible_primary_template_redeclarations(
4929        &self,
4930        left: &CodeUnit,
4931        right: &CodeUnit,
4932    ) -> bool {
4933        let (Some(left_metadata), Some(right_metadata)) = (
4934            self.cpp_template_metadata.get(left),
4935            self.cpp_template_metadata.get(right),
4936        ) else {
4937            return false;
4938        };
4939        left_metadata.primary_fq_name == right_metadata.primary_fq_name
4940            && left_metadata.specialization_arguments.is_empty()
4941            && right_metadata.specialization_arguments.is_empty()
4942            && cpp_reconcile_primary_template_parameters(
4943                &[(left, left_metadata), (right, right_metadata)],
4944                right,
4945            )
4946            .is_some()
4947    }
4948
4949    fn alias_candidate_may_preserve_target(
4950        &self,
4951        analyzer: &CppGraphSource<'_>,
4952        visible_from: &ProjectFile,
4953        candidate: &CodeUnit,
4954        target: &CodeUnit,
4955    ) -> bool {
4956        let mut current = candidate.clone();
4957        let mut seen = HashSet::default();
4958        loop {
4959            if same_visible_symbol(&current, target)
4960                || self.compatible_primary_template_redeclarations(&current, target)
4961            {
4962                return true;
4963            }
4964            if self.cpp_template_metadata.contains_key(&current) {
4965                return true;
4966            }
4967            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4968                return false;
4969            };
4970            let StructuredAliasTarget::Named {
4971                components,
4972                global,
4973                arguments,
4974            } = alias_target
4975            else {
4976                return false;
4977            };
4978            if arguments.is_some() || !seen.insert(current.clone()) {
4979                return true;
4980            }
4981            let qualified = components.join("::");
4982            let next = if global {
4983                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
4984            } else {
4985                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
4986            };
4987            let Some(next) = next else {
4988                return true;
4989            };
4990            current = next;
4991        }
4992    }
4993
4994    /// Every indexed type declaration `raw_name` names when it is written in
4995    /// `declaration`'s namespace: the innermost enclosing namespace that holds
4996    /// the name wins, otherwise the name is looked up unqualified.
4997    fn type_candidates_for_declaration<'b>(
4998        &'b self,
4999        visible_from: &ProjectFile,
5000        declaration: &CodeUnit,
5001        raw_name: &str,
5002    ) -> Vec<&'b CodeUnit> {
5003        let Some(normalized) = normalize_reference_name(raw_name) else {
5004            return Vec::new();
5005        };
5006        if let Some(namespace) = cpp_namespace_for(declaration) {
5007            for prefix in namespace_prefixes(&namespace) {
5008                let qualified = format!("{prefix}::{normalized}");
5009                let candidates = self.type_candidates(visible_from, &qualified);
5010                if !candidates.is_empty() {
5011                    return candidates;
5012                }
5013            }
5014        }
5015        self.type_candidates(visible_from, &normalized)
5016    }
5017
5018    fn resolve_unique_type_for_declaration(
5019        &self,
5020        visible_from: &ProjectFile,
5021        declaration: &CodeUnit,
5022        raw_name: &str,
5023    ) -> Option<CodeUnit> {
5024        unique_logical_type_candidate(self.type_candidates_for_declaration(
5025            visible_from,
5026            declaration,
5027            raw_name,
5028        ))
5029    }
5030
5031    pub fn resolves_to_type(
5032        &self,
5033        analyzer: &CppGraphSource<'_>,
5034        file: &ProjectFile,
5035        raw_name: &str,
5036        target: &CodeUnit,
5037    ) -> bool {
5038        let Some(normalized) = normalize_reference_name(raw_name) else {
5039            return false;
5040        };
5041        let candidates = self.type_candidates(file, &normalized);
5042        if candidates.is_empty() {
5043            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5044        }
5045        let Some(resolved) =
5046            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5047        else {
5048            return false;
5049        };
5050        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5051    }
5052
5053    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5054        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5055        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5056        match resolved.kind() {
5057            CodeUnitType::Class => Some(resolved),
5058            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5059            _ => None,
5060        }
5061    }
5062
5063    pub fn canonical_type_for_reference(
5064        &self,
5065        file: &ProjectFile,
5066        raw_name: &str,
5067    ) -> Option<CodeUnit> {
5068        let resolved = self.resolve_type(file, raw_name)?;
5069        self.alias_target(&resolved).or(Some(resolved))
5070    }
5071
5072    pub fn parser_alias_resolves_to_type(
5073        &self,
5074        analyzer: &CppGraphSource<'_>,
5075        file: &ProjectFile,
5076        raw_name: &str,
5077        target: &CodeUnit,
5078    ) -> bool {
5079        let Some(alias_name) = normalize_reference_name(raw_name) else {
5080            return false;
5081        };
5082        let Some(cpp) = analyzer.cpp else {
5083            return false;
5084        };
5085        let matches_file = |source_file: &ProjectFile| {
5086            self.file_alias_matches(cpp, source_file, &alias_name, target)
5087        };
5088        self.visible_source_files_by_root.get(file).map_or_else(
5089            || matches_file(file),
5090            |files| files.iter().any(matches_file),
5091        )
5092    }
5093
5094    fn file_alias_matches(
5095        &self,
5096        cpp: &dyn CppSource,
5097        file: &ProjectFile,
5098        alias_name: &str,
5099        target: &CodeUnit,
5100    ) -> bool {
5101        let cell = {
5102            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
5103            Arc::clone(
5104                cells
5105                    .entry(file.clone())
5106                    .or_insert_with(|| Arc::new(OnceLock::new())),
5107            )
5108        };
5109        cell.get_or_init(|| {
5110            #[cfg(any(test, feature = "test-support"))]
5111            {
5112                *self
5113                    .alias_source_parse_counts
5114                    .lock()
5115                    .expect("alias source parse count lock")
5116                    .entry(file.clone())
5117                    .or_default() += 1;
5118            }
5119            aliases_from_prepared_source(cpp, file).into_boxed_slice()
5120        })
5121        .iter()
5122        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
5123    }
5124
5125    #[cfg(any(test, feature = "test-support"))]
5126    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
5127        self.visible_source_files_by_root
5128            .get(file)
5129            .cloned()
5130            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
5131    }
5132
5133    #[cfg(any(test, feature = "test-support"))]
5134    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
5135        self.alias_source_parse_counts
5136            .lock()
5137            .expect("alias source parse count lock")
5138            .get(file)
5139            .copied()
5140            .unwrap_or(0)
5141    }
5142
5143    pub fn resolve_named(
5144        &self,
5145        file: &ProjectFile,
5146        raw_name: &str,
5147        kind: TargetKind,
5148    ) -> Option<CodeUnit> {
5149        let normalized = normalize_reference_name(raw_name)?;
5150        self.named_candidates_for_normalized(file, &normalized, kind)
5151            .into_iter()
5152            .next()
5153            .cloned()
5154    }
5155
5156    pub fn contains_named_symbol(
5157        &self,
5158        file: &ProjectFile,
5159        raw_name: &str,
5160        kind: TargetKind,
5161        target: &CodeUnit,
5162    ) -> bool {
5163        let Some(normalized) = normalize_reference_name(raw_name) else {
5164            return false;
5165        };
5166        self.named_candidates_for_normalized(file, &normalized, kind)
5167            .into_iter()
5168            .any(|unit| {
5169                matches_kind_for_lookup(unit, kind)
5170                    && reference_matches_unit(&normalized, unit)
5171                    && same_visible_symbol(unit, target)
5172            })
5173    }
5174
5175    pub fn named_candidates(
5176        &self,
5177        file: &ProjectFile,
5178        raw_name: &str,
5179        kind: TargetKind,
5180    ) -> Vec<CodeUnit> {
5181        let Some(normalized) = normalize_reference_name(raw_name) else {
5182            return Vec::new();
5183        };
5184        self.named_candidates_for_normalized(file, &normalized, kind)
5185            .into_iter()
5186            .cloned()
5187            .collect()
5188    }
5189
5190    pub fn resolve_known_non_target(
5191        &self,
5192        file: &ProjectFile,
5193        raw_name: &str,
5194        kind: TargetKind,
5195        target: &CodeUnit,
5196    ) -> bool {
5197        let Some(normalized) = normalize_reference_name(raw_name) else {
5198            return false;
5199        };
5200        normalized.contains("::")
5201            && self
5202                .named_candidates_for_normalized(file, &normalized, kind)
5203                .into_iter()
5204                .any(|unit| {
5205                    matches_kind_for_lookup(unit, kind)
5206                        && reference_matches_unit(&normalized, unit)
5207                        && !same_visible_symbol(unit, target)
5208                })
5209    }
5210
5211    pub fn resolve_call_return_binding(
5212        &self,
5213        analyzer: &CppGraphSource<'_>,
5214        file: &ProjectFile,
5215        raw_name: &str,
5216        arity: usize,
5217        lexical_namespace: Option<&str>,
5218        direct_type: Option<&CodeUnit>,
5219    ) -> Option<CppScanBinding> {
5220        let normalized = normalize_reference_name(raw_name)?;
5221        let mut candidates = Vec::new();
5222        for function in
5223            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5224        {
5225            if cpp_callable_arity(analyzer, function).accepts(arity)
5226                && !direct_type.is_some_and(|direct_type| {
5227                    self.callable_is_constructor_declaration(analyzer, function)
5228                        && type_owner_of(analyzer, function)
5229                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5230                })
5231            {
5232                candidates.push(function.clone());
5233            }
5234        }
5235        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5236        unanimous_return_binding(analyzer, self, file, &candidates)
5237    }
5238
5239    pub fn resolve_call_return_binding_without_arity(
5240        &self,
5241        analyzer: &CppGraphSource<'_>,
5242        file: &ProjectFile,
5243        raw_name: &str,
5244        lexical_namespace: Option<&str>,
5245        direct_type: Option<&CodeUnit>,
5246    ) -> (bool, Option<CppScanBinding>) {
5247        let Some(normalized) = normalize_reference_name(raw_name) else {
5248            return (false, None);
5249        };
5250        let mut candidates = self
5251            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5252            .into_iter()
5253            .filter(|function| {
5254                function.is_function()
5255                    && !direct_type.is_some_and(|direct_type| {
5256                        self.callable_is_constructor_declaration(analyzer, function)
5257                            && type_owner_of(analyzer, function)
5258                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5259                    })
5260            })
5261            .cloned()
5262            .collect::<Vec<_>>();
5263        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5264        let has_candidates = !candidates.is_empty();
5265        (
5266            has_candidates,
5267            unanimous_return_binding(analyzer, self, file, &candidates),
5268        )
5269    }
5270
5271    pub fn visible_identifier_candidates<'b>(
5272        &'b self,
5273        file: &ProjectFile,
5274        identifier: &str,
5275    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
5276        self.visible_by_identifier
5277            .get(file)
5278            .and_then(|by_name| by_name.get(identifier))
5279            .into_iter()
5280            .flatten()
5281    }
5282
5283    /// Return terminal reference names that can denote `target` from `file`.
5284    ///
5285    /// The indexed candidate table covers ordinary declarations and aliases;
5286    /// parser-only aliases are read through their per-file cells so this path
5287    /// never reparses a source that has already been inspected by the visibility
5288    /// index.
5289    pub fn visible_type_reference_component_names_for_target(
5290        &self,
5291        analyzer: &CppGraphSource<'_>,
5292        file: &ProjectFile,
5293        target: &CodeUnit,
5294    ) -> HashSet<String> {
5295        let mut names = HashSet::from_iter([target.identifier().to_string()]);
5296        if let Some(metadata) = self.cpp_template_metadata.get(target) {
5297            names.insert(metadata.primary_name.clone());
5298        }
5299
5300        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
5301            for (identifier, candidates) in by_identifier {
5302                if candidates.iter().any(|candidate| {
5303                    (candidate.is_class()
5304                        && (same_visible_symbol(candidate, target)
5305                            || self.compatible_primary_template_redeclarations(candidate, target)))
5306                        || (declared_type_alias(analyzer, candidate)
5307                            && self.alias_candidate_may_preserve_target(
5308                                analyzer, file, candidate, target,
5309                            ))
5310                }) {
5311                    names.insert(identifier.clone());
5312                }
5313            }
5314        }
5315
5316        names.extend(self.visible_parser_alias_names_for_target(file, target));
5317
5318        names
5319    }
5320
5321    pub fn indexed_structural_class_scope(
5322        &self,
5323        file: &ProjectFile,
5324        class: Node<'_>,
5325        source: &str,
5326    ) -> Option<Vec<String>> {
5327        let key = (file.clone(), class.start_byte(), class.end_byte());
5328        if let Some(cached) = self
5329            .indexed_structural_class_scopes
5330            .lock()
5331            .expect("C++ indexed structural-class scope cache poisoned")
5332            .get(&key)
5333            .cloned()
5334        {
5335            return cached;
5336        }
5337        let resolved = (|| {
5338            let name = class.child_by_field_name("name")?;
5339            let identifier = if name.kind() == "template_type" {
5340                node_text(name.child_by_field_name("name")?, source).to_string()
5341            } else {
5342                let mut components = Vec::new();
5343                append_cpp_name_components(name, source, &mut components)?;
5344                components.last()?.clone()
5345            };
5346            let visible = self
5347                .visible_identifier_candidates(file, &identifier)
5348                .cloned()
5349                .collect::<Vec<_>>();
5350            let mut visible = visible;
5351            for candidate in
5352                self.visible_by_file
5353                    .get(file)
5354                    .into_iter()
5355                    .flatten()
5356                    .filter(|candidate| {
5357                        self.cpp_template_metadata
5358                            .get(candidate)
5359                            .is_some_and(|metadata| metadata.primary_name == identifier)
5360                    })
5361            {
5362                if !visible
5363                    .iter()
5364                    .any(|existing| same_logical_symbol(existing, candidate))
5365                {
5366                    visible.push(candidate.clone());
5367                }
5368            }
5369            // Built once per call rather than per candidate; `cpp_source` rebuilds
5370            // the five-field source from the same `self.cpp` on every call.
5371            let cpp_source = self.cpp_source();
5372            let candidates = visible
5373                .iter()
5374                .filter(|candidate| {
5375                    candidate.source() == file
5376                        && candidate.is_class()
5377                        && !declared_type_alias(&cpp_source, candidate)
5378                        && self.cpp.ranges(candidate).iter().any(|range| {
5379                            range.start_byte <= class.start_byte()
5380                                && class.end_byte() <= range.end_byte
5381                        })
5382                })
5383                .collect::<Vec<_>>();
5384            let owner = if name.kind() == "template_type" {
5385                let expected = normalize_cpp_whitespace(node_text(name, source));
5386                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
5387                let exact = candidates
5388                    .iter()
5389                    .copied()
5390                    .filter(|candidate| {
5391                        candidate
5392                            .fq()
5393                            .segments()
5394                            .iter()
5395                            .rev()
5396                            .find_map(|&segment| {
5397                                let (text, kind) = interner.resolve(segment);
5398                                matches!(
5399                                    kind,
5400                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
5401                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
5402                                )
5403                                .then_some(text)
5404                            })
5405                            .is_some_and(|text| text == expected)
5406                    })
5407                    .collect::<Vec<_>>();
5408                unique_logical_type_candidate(exact)
5409                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
5410            } else {
5411                unique_logical_type_candidate(candidates)?
5412            };
5413            Some(canonical_cpp_scope_components(&owner))
5414        })();
5415        self.indexed_structural_class_scopes
5416            .lock()
5417            .expect("C++ indexed structural-class scope cache poisoned")
5418            .insert(key, resolved.clone());
5419        resolved
5420    }
5421
5422    pub fn indexed_enclosing_owner_scope(
5423        &self,
5424        analyzer: &CppGraphSource<'_>,
5425        file: &ProjectFile,
5426        node: Node<'_>,
5427    ) -> Option<Vec<String>> {
5428        let anchor = std::iter::successors(Some(node), |current| current.parent())
5429            .find(|current| {
5430                matches!(
5431                    current.kind(),
5432                    "function_definition"
5433                        | "class_specifier"
5434                        | "struct_specifier"
5435                        | "union_specifier"
5436                )
5437            })
5438            .unwrap_or(node);
5439        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
5440        if let Some(cached) = self
5441            .indexed_enclosing_owner_scopes
5442            .lock()
5443            .expect("C++ indexed enclosing-owner scope cache poisoned")
5444            .get(&key)
5445            .cloned()
5446        {
5447            return cached;
5448        }
5449        let resolved = (|| {
5450            let range = Range {
5451                start_byte: node.start_byte(),
5452                end_byte: node.end_byte(),
5453                start_line: node.start_position().row,
5454                end_line: node.end_position().row,
5455            };
5456            let start = analyzer.enclosing_code_unit(file, &range)?;
5457            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
5458                start,
5459                |unit| self.cached_precise_parent_of(analyzer, unit),
5460            )
5461            .find(|unit| {
5462                unit.is_class()
5463                    && !analyzer
5464                        .type_alias_provider()
5465                        .is_some_and(|provider| provider.is_type_alias(unit))
5466            })?;
5467            Some(canonical_cpp_scope_components(&owner))
5468        })();
5469        self.indexed_enclosing_owner_scopes
5470            .lock()
5471            .expect("C++ indexed enclosing-owner scope cache poisoned")
5472            .insert(key, resolved.clone());
5473        resolved
5474    }
5475
5476    fn cached_precise_parent_of(
5477        &self,
5478        analyzer: &CppGraphSource<'_>,
5479        code_unit: &CodeUnit,
5480    ) -> Option<CodeUnit> {
5481        if let Some(cached) = self
5482            .precise_parent_cache
5483            .lock()
5484            .expect("C++ precise-parent cache poisoned")
5485            .get(code_unit)
5486            .cloned()
5487        {
5488            return cached;
5489        }
5490        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
5491        self.precise_parent_cache
5492            .lock()
5493            .expect("C++ precise-parent cache poisoned")
5494            .insert(code_unit.clone(), resolved.clone());
5495        resolved
5496    }
5497
5498    pub fn callable_is_constructor_declaration(
5499        &self,
5500        analyzer: &CppGraphSource<'_>,
5501        candidate: &CodeUnit,
5502    ) -> bool {
5503        if !candidate.is_function() {
5504            return false;
5505        }
5506        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5507            return false;
5508        };
5509        let root = prepared.tree().root_node();
5510        let candidate_ranges = analyzer.ranges(candidate);
5511        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
5512            let mut current = root
5513                .descendant_for_byte_range(range.start_byte, range.end_byte)
5514                .and_then(|node| node.parent());
5515            while let Some(node) = current {
5516                if matches!(
5517                    node.kind(),
5518                    "class_specifier" | "struct_specifier" | "union_specifier"
5519                ) {
5520                    return node
5521                        .child_by_field_name("name")
5522                        .map(|name| terminal_name(node_text(name, prepared.source())))
5523                        .is_some_and(|name| name == candidate.identifier());
5524                }
5525                current = node.parent();
5526            }
5527            false
5528        });
5529        if enclosed_by_matching_type {
5530            return true;
5531        }
5532        let indexed_containment = analyzer
5533            .declarations(candidate.source())
5534            .into_iter()
5535            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
5536            .any(|owner| {
5537                analyzer.ranges(&owner).iter().any(|owner_range| {
5538                    candidate_ranges.iter().any(|candidate_range| {
5539                        owner_range.start_byte <= candidate_range.start_byte
5540                            && candidate_range.end_byte <= owner_range.end_byte
5541                    })
5542                })
5543            });
5544        if indexed_containment {
5545            return true;
5546        }
5547        let metadata = analyzer.signature_metadata(candidate);
5548        !metadata.is_empty()
5549            && metadata
5550                .iter()
5551                .all(|signature| signature.return_type_text().is_none())
5552    }
5553
5554    pub fn type_name_candidates<'b>(
5555        &'b self,
5556        file: &ProjectFile,
5557        normalized: &str,
5558    ) -> Vec<&'b CodeUnit> {
5559        self.candidate_units(file, normalized, TargetKind::Type)
5560    }
5561
5562    pub fn visible_members_for_owner_name<'b>(
5563        &'b self,
5564        file: &ProjectFile,
5565        owner: &CodeUnit,
5566        name: &str,
5567    ) -> Vec<&'b CodeUnit> {
5568        self.visible_identifier_candidates(file, name)
5569            .filter(|unit| {
5570                // Structured owner pop on the unit's own `fq()` (shared with
5571                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
5572                // string.
5573                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
5574                    .is_some_and(|parent| parent == owner.fq_name())
5575            })
5576            .collect()
5577    }
5578
5579    pub fn visible_member_for_owner_name(
5580        &self,
5581        file: &ProjectFile,
5582        owner: &CodeUnit,
5583        name: &str,
5584    ) -> VisibleMemberResolution {
5585        let candidates = self.visible_members_for_owner_name(file, owner, name);
5586        let mut callables = Vec::new();
5587        let mut non_callable = None;
5588        for candidate in candidates {
5589            if candidate.is_function() {
5590                callables.push(candidate.clone());
5591            } else if non_callable.is_none() {
5592                non_callable = Some(candidate.clone());
5593            }
5594        }
5595        match (callables.is_empty(), non_callable) {
5596            (false, None) => VisibleMemberResolution::Callable(callables),
5597            (true, Some(_)) => VisibleMemberResolution::NonCallable,
5598            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
5599            (true, None) => VisibleMemberResolution::Missing,
5600        }
5601    }
5602
5603    fn field_declared_type_fact(
5604        &self,
5605        analyzer: &CppGraphSource<'_>,
5606        field: &CodeUnit,
5607    ) -> Option<DeclaredFieldTypeFact> {
5608        if let Some(cached) = self
5609            .field_type_facts
5610            .lock()
5611            .expect("C++ field type fact cache poisoned")
5612            .get(field)
5613            .cloned()
5614        {
5615            return cached;
5616        }
5617        let decoded = decode_field_declared_type_fact(analyzer, field);
5618        self.field_type_facts
5619            .lock()
5620            .expect("C++ field type fact cache poisoned")
5621            .insert(field.clone(), decoded.clone());
5622        decoded
5623    }
5624
5625    fn structured_alias_target(
5626        &self,
5627        analyzer: &CppGraphSource<'_>,
5628        unit: &CodeUnit,
5629    ) -> Option<StructuredAliasTarget> {
5630        if let Some(cached) = self
5631            .structured_alias_targets
5632            .lock()
5633            .expect("C++ structured alias target cache poisoned")
5634            .get(unit)
5635            .cloned()
5636        {
5637            return cached;
5638        }
5639        let decoded = decode_structured_alias_target(analyzer, unit);
5640        self.structured_alias_targets
5641            .lock()
5642            .expect("C++ structured alias target cache poisoned")
5643            .insert(unit.clone(), decoded.clone());
5644        decoded
5645    }
5646
5647    pub fn type_candidates<'b>(
5648        &'b self,
5649        file: &ProjectFile,
5650        normalized: &str,
5651    ) -> Vec<&'b CodeUnit> {
5652        let mut candidates = self
5653            .candidate_units(file, normalized, TargetKind::Type)
5654            .into_iter()
5655            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
5656            .collect::<Vec<_>>();
5657        dedup_unit_refs(&mut candidates);
5658        candidates
5659    }
5660
5661    pub fn named_candidates_for_normalized<'b>(
5662        &'b self,
5663        file: &ProjectFile,
5664        normalized: &str,
5665        kind: TargetKind,
5666    ) -> Vec<&'b CodeUnit> {
5667        let mut candidates = self
5668            .candidate_units(file, normalized, kind)
5669            .into_iter()
5670            .filter(|unit| {
5671                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
5672            })
5673            .collect::<Vec<_>>();
5674        dedup_unit_refs(&mut candidates);
5675        candidates
5676    }
5677
5678    pub fn candidate_units<'b>(
5679        &'b self,
5680        file: &ProjectFile,
5681        normalized: &str,
5682        kind: TargetKind,
5683    ) -> Vec<&'b CodeUnit> {
5684        if normalized.contains("::") {
5685            // `normalized` comes from `normalize_cpp_reference_text`, which
5686            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
5687            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
5688            // kept intact by the shared splitter's operator merge — the same
5689            // domain `cpp_reference_fqn_candidates` below already parses with
5690            // the shared splitter. Re-tokenizing and taking the last segment
5691            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
5692            // scan exactly.
5693            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5694                brokk_bifrost_core::analyzer::Language::Cpp,
5695                normalized,
5696            )
5697            .pop() else {
5698                return Vec::new();
5699            };
5700            let fqns = cpp_reference_fqn_candidates(normalized, kind);
5701            return self
5702                .visible_identifier_candidates(file, &identifier)
5703                .filter(|unit| {
5704                    #[cfg(any(test, feature = "test-support"))]
5705                    self.qualified_candidate_inspections
5706                        .fetch_add(1, Ordering::Relaxed);
5707                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
5708                        || canonical_cpp_name_matches(unit, normalized)
5709                })
5710                .collect();
5711        }
5712        self.visible_identifier_candidates(file, normalized)
5713            .collect()
5714    }
5715
5716    #[cfg(any(test, feature = "test-support"))]
5717    pub fn reset_qualified_candidate_inspections(&self) {
5718        self.qualified_candidate_inspections
5719            .store(0, Ordering::Relaxed);
5720    }
5721
5722    #[cfg(any(test, feature = "test-support"))]
5723    pub fn qualified_candidate_inspections(&self) -> usize {
5724        self.qualified_candidate_inspections.load(Ordering::Relaxed)
5725    }
5726
5727    #[cfg(any(test, feature = "test-support"))]
5728    pub fn reset_target_preserving_type_resolution_count(&self) {
5729        self.target_preserving_type_resolution_count
5730            .store(0, Ordering::Relaxed);
5731    }
5732
5733    #[cfg(any(test, feature = "test-support"))]
5734    pub fn target_preserving_type_resolution_count(&self) -> usize {
5735        self.target_preserving_type_resolution_count
5736            .load(Ordering::Relaxed)
5737    }
5738
5739    #[cfg(any(test, feature = "test-support"))]
5740    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
5741        self.visible_parser_alias_name_set_build_count
5742            .load(Ordering::Relaxed)
5743    }
5744
5745    #[cfg(any(test, feature = "test-support"))]
5746    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
5747        self.visible_parser_alias_target_names_build_count
5748            .load(Ordering::Relaxed)
5749    }
5750}
5751
5752#[derive(Default)]
5753struct IncludeGraph {
5754    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
5755}
5756
5757impl IncludeGraph {
5758    fn extend_with<F>(
5759        &mut self,
5760        root: &ProjectFile,
5761        cancellation: Option<&CancellationToken>,
5762        targets_for: &mut F,
5763    ) where
5764        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5765    {
5766        let mut stack = vec![root.clone()];
5767        while let Some(file) = stack.pop() {
5768            if cancellation.is_some_and(CancellationToken::is_cancelled) {
5769                break;
5770            }
5771            if self.targets_by_file.contains_key(&file) {
5772                continue;
5773            }
5774            let targets = targets_for(&file);
5775            stack.extend(targets.iter().cloned());
5776            self.targets_by_file.insert(file, targets);
5777        }
5778    }
5779
5780    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
5781        self.targets_by_file.keys()
5782    }
5783
5784    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
5785        self.targets_by_file
5786            .get(file)
5787            .map(Vec::as_slice)
5788            .unwrap_or_default()
5789    }
5790}
5791
5792pub struct VisibilityData {
5793    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
5794    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
5795}
5796
5797pub fn build_visibility_data<F, D>(
5798    roots: &HashSet<ProjectFile>,
5799    cancellation: Option<&CancellationToken>,
5800    mut targets_for: F,
5801    mut declarations_for: D,
5802) -> VisibilityData
5803where
5804    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5805    D: FnMut(&ProjectFile) -> BTreeSet<CodeUnit>,
5806{
5807    let mut include_graph = IncludeGraph::default();
5808    for file in roots {
5809        if cancellation.is_some_and(CancellationToken::is_cancelled) {
5810            break;
5811        }
5812        include_graph.extend_with(file, cancellation, &mut targets_for);
5813    }
5814    let declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
5815        .files()
5816        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
5817        .map(|file| (file.clone(), declarations_for(file)))
5818        .collect();
5819    let mut visible_by_file = HashMap::default();
5820    let mut visible_source_files_by_root = HashMap::default();
5821    for file in roots {
5822        if cancellation.is_some_and(CancellationToken::is_cancelled) {
5823            break;
5824        }
5825        let mut visited = HashSet::default();
5826        let mut visible = HashSet::default();
5827        collect_visible_declarations(
5828            &include_graph,
5829            &declarations_by_file,
5830            file,
5831            &mut visited,
5832            &mut visible,
5833            cancellation,
5834        );
5835        visible_by_file.insert(file.clone(), visible);
5836        visible_source_files_by_root.insert(file.clone(), visited);
5837    }
5838    VisibilityData {
5839        visible_by_file,
5840        visible_source_files_by_root,
5841    }
5842}
5843
5844/// Admit the class that an out-of-line definition proves is in scope.
5845///
5846/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
5847/// names a class-like entity in that file's scope: a member declaration can
5848/// live in a file other than its class's only when it is written out of line.
5849/// A file a build concatenates rather than compiles carries no `#include` edge
5850/// to the header declaring `Owner` -- google/wuffs
5851/// `internal/cgen/auxiliary/image.cc` defines
5852/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
5853/// every unqualified member and constructor reference in it had no candidate at
5854/// all (#1832).
5855///
5856/// The evidence is the indexed declaration's own owner name, taken from its
5857/// `FqName`, so this stays a structured answer rather than a text fallback.
5858/// Only an owner the file cannot already see is admitted: that is what keeps a
5859/// header declaring its own class from additionally seeing every same-named
5860/// class in the workspace, and it makes the pass free for the ordinary file
5861/// whose owners are all visible.
5862fn extend_with_out_of_line_owner_bindings(
5863    cpp: &dyn CppSource,
5864    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
5865) {
5866    for (file, visible) in visible_by_file.iter_mut() {
5867        // The include-closure walk seeds every root with its own declarations,
5868        // so the file's members are already here; re-reading them from the
5869        // analyzer would pay for the same declaration set twice.
5870        let mut unseen_owners: HashSet<String> = visible
5871            .iter()
5872            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
5873            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
5874            .collect();
5875        if unseen_owners.is_empty() {
5876            continue;
5877        }
5878        for unit in visible.iter().filter(|unit| unit.is_class()) {
5879            unseen_owners.remove(&unit.fq_name());
5880        }
5881        let admitted = unseen_owners
5882            .iter()
5883            .flat_map(|owner| cpp.definitions(owner))
5884            .filter(CodeUnit::is_class)
5885            .collect::<Vec<_>>();
5886        visible.extend(admitted);
5887    }
5888}
5889
5890pub enum VisibleMemberResolution {
5891    Callable(Vec<CodeUnit>),
5892    NonCallable,
5893    AmbiguousKind,
5894    Missing,
5895}
5896
5897#[derive(Clone)]
5898pub enum EnclosingMemberOwnerResolution {
5899    Owner(CodeUnit),
5900    Ambiguous,
5901    Missing,
5902}
5903
5904pub fn resolve_declaring_member_owner(
5905    analyzer: &CppGraphSource<'_>,
5906    visibility: &VisibilityIndex<'_>,
5907    file: &ProjectFile,
5908    receiver_owner: &CodeUnit,
5909    member_name: &str,
5910) -> EnclosingMemberOwnerResolution {
5911    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5912        return EnclosingMemberOwnerResolution::Missing;
5913    };
5914    let Some(receiver_owner) =
5915        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
5916    else {
5917        return EnclosingMemberOwnerResolution::Ambiguous;
5918    };
5919    let resolve_level = |frontier: &[CodeUnit]| {
5920        let mut member_owners = Vec::new();
5921        for raw_owner in frontier {
5922            let Some(owner) =
5923                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
5924            else {
5925                return EnclosingMemberOwnerResolution::Ambiguous;
5926            };
5927            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
5928                let Some(member_owner) = type_owner_of(analyzer, member) else {
5929                    return EnclosingMemberOwnerResolution::Ambiguous;
5930                };
5931                if !member_owners
5932                    .iter()
5933                    .any(|existing| same_visible_symbol(existing, &member_owner))
5934                {
5935                    member_owners.push(member_owner);
5936                }
5937            }
5938        }
5939        match member_owners.len() {
5940            0 => EnclosingMemberOwnerResolution::Missing,
5941            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
5942            _ => EnclosingMemberOwnerResolution::Ambiguous,
5943        }
5944    };
5945    // The first declaration on each structured base path hides deeper names,
5946    // regardless of whether its callable overload is applicable at a particular
5947    // call site. Applicability is checked only after this owner is established.
5948    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
5949    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
5950        return direct;
5951    }
5952    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
5953    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5954    let mut path_matches = Vec::new();
5955    while let Some(raw_owner) = stack.pop() {
5956        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5957        else {
5958            return EnclosingMemberOwnerResolution::Ambiguous;
5959        };
5960        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
5961        // Propagate at most two occurrences of each owner: that preserves the distinction
5962        // between one and multiple resolving base paths without exponential diamond walks.
5963        let propagated = propagated_counts.entry(owner.clone()).or_default();
5964        if *propagated == 2 {
5965            continue;
5966        }
5967        *propagated += 1;
5968        match resolve_level(std::slice::from_ref(&owner)) {
5969            EnclosingMemberOwnerResolution::Owner(owner) => {
5970                path_matches.push(owner);
5971                if path_matches.len() == 2 {
5972                    return EnclosingMemberOwnerResolution::Ambiguous;
5973                }
5974            }
5975            EnclosingMemberOwnerResolution::Ambiguous => {
5976                return EnclosingMemberOwnerResolution::Ambiguous;
5977            }
5978            EnclosingMemberOwnerResolution::Missing => {
5979                stack.extend(hierarchy.get_direct_ancestors(&owner));
5980            }
5981        }
5982    }
5983    match path_matches.len() {
5984        0 => EnclosingMemberOwnerResolution::Missing,
5985        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
5986        _ => unreachable!("base-path matches are capped at one before returning"),
5987    }
5988}
5989
5990pub fn lexical_component_tiers<'a>(
5991    components: &'a [String],
5992    global: bool,
5993    lexical_scope: &'a [String],
5994) -> impl Iterator<Item = Vec<String>> + 'a {
5995    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
5996    (0..=first_prefix_len).rev().map(move |prefix_len| {
5997        let mut qualified = Vec::with_capacity(prefix_len + components.len());
5998        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
5999        qualified.extend_from_slice(components);
6000        qualified
6001    })
6002}
6003
6004pub fn build_visible_identifier_index(
6005    analyzer: &CppGraphSource<'_>,
6006    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
6007    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
6008    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
6009) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
6010    let mut out = HashMap::default();
6011    for (file, visible) in visible_by_file {
6012        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
6013        for unit in visible {
6014            if unit.is_field()
6015                && !visible_source_files_by_root
6016                    .get(file)
6017                    .is_some_and(|sources| sources.contains(unit.source()))
6018                && cpp_global_field_has_internal_linkage_cached(
6019                    analyzer,
6020                    global_field_internal_linkage,
6021                    unit,
6022                )
6023            {
6024                continue;
6025            }
6026            by_identifier
6027                .entry(unit.identifier().to_string())
6028                .or_default()
6029                .push(unit.clone());
6030        }
6031        for units in by_identifier.values_mut() {
6032            sort_lookup_units(units);
6033            units.dedup();
6034        }
6035        out.insert(file.clone(), by_identifier);
6036    }
6037    out
6038}
6039
6040fn sort_lookup_units(units: &mut [CodeUnit]) {
6041    units.sort_by(|left, right| {
6042        left.fq_name()
6043            .cmp(&right.fq_name())
6044            .then_with(|| left.signature().cmp(&right.signature()))
6045            .then_with(|| left.source().cmp(right.source()))
6046            .then_with(|| left.kind().cmp(&right.kind()))
6047            .then_with(|| {
6048                left.package_segment_count()
6049                    .cmp(&right.package_segment_count())
6050            })
6051            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
6052            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
6053    });
6054}
6055
6056fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
6057    let interner = segment_interner();
6058    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
6059        let (left_text, left_kind) = interner.resolve(left_id);
6060        let (right_text, right_kind) = interner.resolve(right_id);
6061        let order = left_text
6062            .cmp(right_text)
6063            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
6064        if order != CmpOrdering::Equal {
6065            return order;
6066        }
6067    }
6068    left.len().cmp(&right.len())
6069}
6070
6071const fn segment_kind_order(kind: SegmentKind) -> u8 {
6072    match kind {
6073        SegmentKind::Path => 0,
6074        SegmentKind::Package => 1,
6075        SegmentKind::Type => 2,
6076        SegmentKind::Companion => 3,
6077        SegmentKind::Nested => 4,
6078        SegmentKind::Member => 5,
6079        SegmentKind::Unknown => 6,
6080    }
6081}
6082
6083fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
6084    let mut deduped = Vec::with_capacity(units.len());
6085    for unit in units.drain(..) {
6086        if !deduped.contains(&unit) {
6087            deduped.push(unit);
6088        }
6089    }
6090    *units = deduped;
6091}
6092
6093pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
6094    // Same domain as `candidate_units` above: `reference` is a plain
6095    // `::`-joined qualified-id with operator tokens kept intact by the shared
6096    // splitter's operator merge.
6097    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6098        brokk_bifrost_core::analyzer::Language::Cpp,
6099        reference,
6100    );
6101    if parts.is_empty() {
6102        return Vec::new();
6103    }
6104
6105    let mut candidates = Vec::new();
6106    for package_len in 0..parts.len() {
6107        let package = parts[..package_len].join("::");
6108        let rest = &parts[package_len..];
6109        if rest.is_empty() {
6110            continue;
6111        }
6112        match kind {
6113            TargetKind::Type | TargetKind::Constructor => {
6114                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
6115                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6116            }
6117            TargetKind::FreeFunction
6118            | TargetKind::Method
6119            | TargetKind::GlobalField
6120            | TargetKind::MemberField
6121            | TargetKind::Macro => {
6122                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6123                if rest.len() > 1 {
6124                    let owner = rest[..rest.len() - 1].join("$");
6125                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
6126                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
6127                }
6128            }
6129        }
6130    }
6131    candidates
6132}
6133
6134fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
6135    let fqn = if package.is_empty() {
6136        short.to_string()
6137    } else {
6138        format!("{package}.{short}")
6139    };
6140    if !out.contains(&fqn) {
6141        out.push(fqn);
6142    }
6143}
6144
6145pub fn infer_cpp_initializer_type(
6146    analyzer: &CppGraphSource<'_>,
6147    visibility: &VisibilityIndex<'_>,
6148    file: &ProjectFile,
6149    source: &str,
6150    node: Node<'_>,
6151) -> Option<CodeUnit> {
6152    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
6153        .and_then(|binding| binding.unit)
6154}
6155
6156pub fn infer_cpp_initializer_binding(
6157    analyzer: &CppGraphSource<'_>,
6158    visibility: &VisibilityIndex<'_>,
6159    file: &ProjectFile,
6160    source: &str,
6161    node: Node<'_>,
6162    receiver_resolver: Option<&ReceiverResolver<'_>>,
6163) -> Option<CppScanBinding> {
6164    match node.kind() {
6165        "new_expression" => {
6166            let text = normalize_cpp_whitespace(node_text(node, source));
6167            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
6168            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
6169            let name = normalize_cpp_type_name(type_text);
6170            Some(CppScanBinding::from_type_name(
6171                name.clone(),
6172                visibility.resolve_type(file, &name),
6173                1,
6174            ))
6175        }
6176        "call_expression" => node.child_by_field_name("function").and_then(|function| {
6177            let function_text = node_text(function, source);
6178            let direct_type_binding = visibility
6179                .resolve_type(file, function_text)
6180                .map(|unit| CppScanBinding::from_unit(unit, 0));
6181            if function.kind() == "template_function" && direct_type_binding.is_some() {
6182                let lexical_namespace = enclosing_namespace_context(node, source);
6183                let arity = visibility.call_arity_evidence(file, node, source).exact();
6184                if let Some(arity) = arity
6185                    && let Some(binding) = visibility.resolve_call_return_binding(
6186                        analyzer,
6187                        file,
6188                        function_text,
6189                        arity,
6190                        lexical_namespace.as_deref(),
6191                        direct_type_binding
6192                            .as_ref()
6193                            .and_then(|binding| binding.unit.as_ref()),
6194                    )
6195                {
6196                    return Some(binding);
6197                }
6198                let (has_callable, callable_binding) = visibility
6199                    .resolve_call_return_binding_without_arity(
6200                        analyzer,
6201                        file,
6202                        function_text,
6203                        lexical_namespace.as_deref(),
6204                        direct_type_binding
6205                            .as_ref()
6206                            .and_then(|binding| binding.unit.as_ref()),
6207                    );
6208                if let Some(binding) = callable_binding {
6209                    return Some(binding);
6210                }
6211                if has_callable {
6212                    return None;
6213                }
6214                return direct_type_binding;
6215            }
6216            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
6217            let direct_type_binding_for_call = direct_type_binding.clone();
6218            resolve_static_method_call_return_binding(
6219                analyzer, visibility, file, source, function, arity,
6220            )
6221            .or(direct_type_binding)
6222            .or_else(|| {
6223                visibility.resolve_call_return_binding(
6224                    analyzer,
6225                    file,
6226                    function_text,
6227                    arity,
6228                    enclosing_namespace_context(node, source).as_deref(),
6229                    direct_type_binding_for_call
6230                        .as_ref()
6231                        .and_then(|binding| binding.unit.as_ref()),
6232                )
6233            })
6234            .or_else(|| {
6235                resolve_field_method_call_return_binding(
6236                    analyzer,
6237                    visibility,
6238                    file,
6239                    source,
6240                    function,
6241                    arity,
6242                    receiver_resolver,
6243                )
6244            })
6245        }),
6246        _ => None,
6247    }
6248}
6249
6250fn resolve_static_method_call_return_binding(
6251    analyzer: &CppGraphSource<'_>,
6252    visibility: &VisibilityIndex<'_>,
6253    file: &ProjectFile,
6254    source: &str,
6255    function: Node<'_>,
6256    arity: usize,
6257) -> Option<CppScanBinding> {
6258    if function.kind() != "qualified_identifier" {
6259        return None;
6260    }
6261    let qualified = normalize_cpp_reference_text(node_text(function, source));
6262    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
6263    // single component (the shared splitter's operator-token merge keeps
6264    // `operator+`-style names intact), so re-tokenizing with the shared
6265    // structured splitter and peeling the terminal segment reproduces
6266    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
6267    // `cpp_out_of_line_function_owner`'s `qualified` split above.
6268    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6269        brokk_bifrost_core::analyzer::Language::Cpp,
6270        &qualified,
6271    );
6272    let (owner_text, member_name) = match parts.split_last() {
6273        Some((member, owner_parts)) if !owner_parts.is_empty() => {
6274            (owner_parts.join("::"), member.clone())
6275        }
6276        _ => {
6277            let scope = function.child_by_field_name("scope")?;
6278            let name = function.child_by_field_name("name")?;
6279            (
6280                node_text(scope, source).to_string(),
6281                node_text(name, source).to_string(),
6282            )
6283        }
6284    };
6285    let owner = visibility.resolve_type(file, &owner_text)?;
6286    let candidates = visibility
6287        .visible_members_for_owner_name(file, &owner, &member_name)
6288        .into_iter()
6289        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
6290        .cloned()
6291        .collect::<Vec<_>>();
6292    unanimous_return_binding(analyzer, visibility, file, &candidates)
6293}
6294
6295fn resolve_field_method_call_return_binding(
6296    analyzer: &CppGraphSource<'_>,
6297    visibility: &VisibilityIndex<'_>,
6298    file: &ProjectFile,
6299    source: &str,
6300    function: Node<'_>,
6301    arity: usize,
6302    receiver_resolver: Option<&ReceiverResolver<'_>>,
6303) -> Option<CppScanBinding> {
6304    if function.kind() != "field_expression" {
6305        return None;
6306    }
6307    let receiver_resolver = receiver_resolver?;
6308    let field = function.child_by_field_name("field")?;
6309    let member_name = node_text(field, source);
6310    let receiver = function
6311        .child_by_field_name("argument")
6312        .or_else(|| function.named_child(0))?;
6313    let owners = receiver_resolver(receiver, source);
6314    let mut candidates = Vec::new();
6315    for owner in owners {
6316        candidates.extend(
6317            visibility
6318                .visible_members_for_owner_name(file, &owner, member_name)
6319                .into_iter()
6320                .filter(|unit| {
6321                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
6322                })
6323                .cloned(),
6324        );
6325    }
6326    unanimous_return_binding(analyzer, visibility, file, &candidates)
6327}
6328
6329fn unanimous_return_binding(
6330    analyzer: &CppGraphSource<'_>,
6331    visibility: &VisibilityIndex<'_>,
6332    file: &ProjectFile,
6333    candidates: &[CodeUnit],
6334) -> Option<CppScanBinding> {
6335    let mut resolved_return: Option<CppScanBinding> = None;
6336    for function in candidates {
6337        let metadata = analyzer.signature_metadata(function);
6338        let return_types = if metadata.is_empty() {
6339            vec![cpp_function_return_type_text(analyzer, function)?]
6340        } else {
6341            metadata
6342                .iter()
6343                .map(|metadata| metadata.return_type_text().map(str::to_string))
6344                .collect::<Option<Vec<_>>>()?
6345        };
6346        for return_text in return_types {
6347            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
6348            let name = normalize_cpp_type_name(&return_text);
6349            let binding = CppScanBinding::from_type_name(
6350                name.clone(),
6351                visibility
6352                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
6353                indirection,
6354            );
6355            if let Some(existing) = resolved_return.as_ref()
6356                && (existing.indirection != binding.indirection
6357                    || match (&existing.unit, &binding.unit) {
6358                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
6359                        (None, None) => existing.type_name != binding.type_name,
6360                        (Some(_), None) | (None, Some(_)) => true,
6361                    })
6362            {
6363                return None;
6364            }
6365            resolved_return = Some(binding);
6366        }
6367    }
6368    resolved_return
6369}
6370
6371fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
6372    let Some(prepared) = cpp.prepared_syntax(file) else {
6373        return Vec::new();
6374    };
6375    let mut aliases = Vec::new();
6376    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
6377    aliases
6378}
6379
6380fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6381    let mut stack = vec![root];
6382    while let Some(node) = stack.pop() {
6383        match node.kind() {
6384            "alias_declaration" if alias_has_visible_file_scope(node) => {
6385                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
6386                    out.push(alias);
6387                }
6388            }
6389            "type_definition" if alias_has_visible_file_scope(node) => {
6390                collect_typedef_aliases(node, source, out)
6391            }
6392            _ => {}
6393        }
6394
6395        for index in (0..node.named_child_count()).rev() {
6396            if let Some(child) = node.named_child(index) {
6397                stack.push(child);
6398            }
6399        }
6400    }
6401}
6402
6403fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
6404    let mut current = node.parent();
6405    while let Some(parent) = current {
6406        match parent.kind() {
6407            "translation_unit"
6408            | "namespace_definition"
6409            | "declaration_list"
6410            | "linkage_specification" => current = parent.parent(),
6411            "template_declaration" => current = parent.parent(),
6412            _ => return false,
6413        }
6414    }
6415    true
6416}
6417
6418fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
6419    let name = node
6420        .child_by_field_name("name")
6421        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6422    let target = node
6423        .child_by_field_name("type")
6424        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6425    Some(CppAlias {
6426        name,
6427        target,
6428        namespace: enclosing_namespace_context(node, source),
6429    })
6430}
6431
6432fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6433    let Some(type_node) = node.child_by_field_name("type") else {
6434        return;
6435    };
6436    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
6437        return;
6438    };
6439
6440    let mut cursor = node.walk();
6441    for child in node.named_children(&mut cursor) {
6442        if same_node(child, type_node) {
6443            continue;
6444        }
6445        if let Some(name) = extract_typedef_declarator_name(child, source) {
6446            out.push(CppAlias {
6447                name,
6448                target: target.clone(),
6449                namespace: enclosing_namespace_context(node, source),
6450            });
6451        }
6452    }
6453}
6454
6455fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
6456    match node.kind() {
6457        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
6458            normalize_reference_name(node_text(node, source))
6459        }
6460        _ => node
6461            .child_by_field_name("declarator")
6462            .or_else(|| node.child_by_field_name("name"))
6463            .or_else(|| last_named_child(node))
6464            .and_then(|child| extract_typedef_declarator_name(child, source)),
6465    }
6466}
6467
6468fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
6469    let count = node.named_child_count();
6470    if count == 0 {
6471        None
6472    } else {
6473        node.named_child(count - 1)
6474    }
6475}
6476
6477pub fn collect_include_closure(
6478    analyzer: &CppGraphSource<'_>,
6479    include_targets: &IncludeTargetIndex,
6480    file: &ProjectFile,
6481    out: &mut HashSet<ProjectFile>,
6482    cancellation: Option<&CancellationToken>,
6483) {
6484    let mut stack = vec![file.clone()];
6485    while let Some(file) = stack.pop() {
6486        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6487            break;
6488        }
6489        if !out.insert(file.clone()) {
6490            continue;
6491        }
6492        let imports = analyzer.import_statements(&file);
6493        for include in cpp_include_paths(&imports) {
6494            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
6495                stack.push(target);
6496            }
6497        }
6498    }
6499}
6500
6501fn collect_visible_declarations(
6502    include_graph: &IncludeGraph,
6503    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
6504    file: &ProjectFile,
6505    visited: &mut HashSet<ProjectFile>,
6506    out: &mut HashSet<CodeUnit>,
6507    cancellation: Option<&CancellationToken>,
6508) {
6509    let mut stack = vec![file.clone()];
6510    while let Some(file) = stack.pop() {
6511        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6512            break;
6513        }
6514        if !visited.insert(file.clone()) {
6515            continue;
6516        }
6517        if let Some(declarations) = declarations_by_file.get(&file) {
6518            out.extend(declarations.iter().cloned());
6519        }
6520        stack.extend(include_graph.targets(&file).iter().cloned());
6521    }
6522}
6523
6524pub fn signature_arity(signature: Option<&str>) -> usize {
6525    let Some(signature) = signature else {
6526        return 0;
6527    };
6528    let inner = signature
6529        .find('(')
6530        .and_then(|open| {
6531            signature[open + 1..]
6532                .find(')')
6533                .map(|close| &signature[open + 1..open + 1 + close])
6534        })
6535        .unwrap_or(signature)
6536        .trim();
6537    if inner.is_empty() || inner == "void" {
6538        return 0;
6539    }
6540    cpp_split_top_level_commas(inner).count()
6541}
6542
6543fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
6544    let source = format!("void __bifrost_macro_parameters({replacement});");
6545    let mut parser = Parser::new();
6546    parser
6547        .set_language(&tree_sitter_cpp::LANGUAGE.into())
6548        .ok()?;
6549    let tree = parser.parse(&source, None)?;
6550    let root = tree.root_node();
6551    if root.has_error() {
6552        return None;
6553    }
6554    let declaration = root.named_child(0)?;
6555    let declarator = declaration.child_by_field_name("declarator")?;
6556    let parameters = declarator.child_by_field_name("parameters")?;
6557    let mut required = 0;
6558    let mut total = 0;
6559    let mut repeated = false;
6560    let mut cursor = parameters.walk();
6561    for parameter in parameters.children(&mut cursor) {
6562        match parameter.kind() {
6563            "parameter_declaration" => {
6564                if parameter.child_by_field_name("declarator").is_none()
6565                    && parameter
6566                        .child_by_field_name("type")
6567                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
6568                {
6569                    continue;
6570                }
6571                required += 1;
6572                total += 1;
6573            }
6574            "optional_parameter_declaration" => total += 1,
6575            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
6576                repeated = true;
6577            }
6578            _ => {}
6579        }
6580    }
6581    Some(CallableArity::new(required, total, repeated))
6582}
6583
6584pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
6585    analyzer
6586        .signature_metadata(unit)
6587        .into_iter()
6588        .find_map(|metadata| metadata.callable_arity())
6589        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
6590}
6591
6592fn merge_compatible_callable_arities(
6593    left: CallableArity,
6594    right: CallableArity,
6595) -> Option<CallableArity> {
6596    let total = left.total();
6597    let left_repeated = left.accepts(total.saturating_add(1));
6598    let right_repeated = right.accepts(right.total().saturating_add(1));
6599    if total != right.total() || left_repeated != right_repeated {
6600        return None;
6601    }
6602    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
6603    Some(CallableArity::new(required, total, left_repeated))
6604}
6605
6606fn find_include_activation(
6607    cpp: &dyn CppSource,
6608    file: &ProjectFile,
6609    prepared: &PreparedSyntaxTree,
6610    donor_source: &ProjectFile,
6611) -> Option<usize> {
6612    let include_targets = cpp.include_target_index();
6613    let mut direct_includes = Vec::new();
6614    let mut nodes = vec![prepared.tree().root_node()];
6615    // An include activates for the whole file, so only an unconditional
6616    // directive counts here.
6617    let reference = CallableReferenceContext {
6618        file,
6619        position: None,
6620    };
6621    while let Some(node) = nodes.pop() {
6622        if node.kind() == "preproc_include" {
6623            if callable_preprocessor_context_is_visible_for_reference(
6624                node,
6625                prepared.source(),
6626                &reference,
6627            ) {
6628                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6629                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6630                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
6631                        file,
6632                        &include,
6633                        include_targets,
6634                    )) {
6635                        direct_includes.push((node.end_byte(), target));
6636                    }
6637                }
6638            }
6639            continue;
6640        }
6641        for index in (0..node.named_child_count()).rev() {
6642            if let Some(child) = node.named_child(index) {
6643                nodes.push(child);
6644            }
6645        }
6646    }
6647    direct_includes.sort_by_key(|(activation, _)| *activation);
6648    let mut known_missing = HashSet::default();
6649    direct_includes
6650        .into_iter()
6651        .find(|(_, direct)| {
6652            unconditional_include_reaches(
6653                cpp,
6654                include_targets,
6655                direct,
6656                donor_source,
6657                file,
6658                &mut known_missing,
6659            )
6660        })
6661        .map(|(activation, _)| activation)
6662}
6663
6664fn find_conditional_include_projection_index(
6665    cpp: &dyn CppSource,
6666    file: &ProjectFile,
6667    prepared: &PreparedSyntaxTree,
6668    on_state: &dyn Fn(),
6669) -> ConditionalIncludeProjectionIndex {
6670    let include_targets = cpp.include_target_index();
6671    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
6672        HashMap::default();
6673    let mut pending = Vec::new();
6674    let mut nodes = vec![prepared.tree().root_node()];
6675    while let Some(node) = nodes.pop() {
6676        if node.kind() == "preproc_include" {
6677            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
6678            else {
6679                continue;
6680            };
6681            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6682            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6683                let Some(target) = unique_include_target(resolve_include_targets_with_index(
6684                    file,
6685                    &include,
6686                    include_targets,
6687                )) else {
6688                    continue;
6689                };
6690                pending.push((target, node.end_byte(), required_guards.clone()));
6691            }
6692            continue;
6693        }
6694        for index in (0..node.named_child_count()).rev() {
6695            if let Some(child) = node.named_child(index) {
6696                nodes.push(child);
6697            }
6698        }
6699    }
6700
6701    // One reached file can have several distinct compatible guard paths. A
6702    // state is expanded once for each exact guard set and top-level activation
6703    // byte; this preserves those paths while terminating include cycles.
6704    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
6705        HashMap::default();
6706    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
6707        let guard_sets = expanded
6708            .entry((current_file.clone(), activation_byte))
6709            .or_default();
6710        if guard_sets.contains(&required_guards) {
6711            continue;
6712        }
6713        guard_sets.push(required_guards.clone());
6714        on_state();
6715
6716        let projections = projections_by_source
6717            .entry(current_file.clone())
6718            .or_default();
6719        if !projections.iter().any(|projection| {
6720            projection.activation_byte == activation_byte
6721                && projection.required_guards == required_guards
6722        }) {
6723            projections.push(ConditionalIncludeProjection {
6724                activation_byte,
6725                required_guards: required_guards.clone(),
6726            });
6727        }
6728
6729        let Some(current_prepared) = cpp.prepared_syntax(&current_file) else {
6730            continue;
6731        };
6732        let mut nodes = vec![current_prepared.tree().root_node()];
6733        while let Some(node) = nodes.pop() {
6734            if node.kind() == "preproc_include" {
6735                let Some(include_guards) =
6736                    preprocessor_guard_environment(node, current_prepared.source())
6737                else {
6738                    continue;
6739                };
6740                let Some(path_guards) =
6741                    merge_preprocessor_guards(&required_guards, &include_guards)
6742                else {
6743                    continue;
6744                };
6745                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
6746                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6747                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
6748                        &current_file,
6749                        &include,
6750                        include_targets,
6751                    )) else {
6752                        continue;
6753                    };
6754                    pending.push((target, activation_byte, path_guards.clone()));
6755                }
6756                continue;
6757            }
6758            for index in (0..node.named_child_count()).rev() {
6759                if let Some(child) = node.named_child(index) {
6760                    nodes.push(child);
6761                }
6762            }
6763        }
6764    }
6765
6766    projections_by_source
6767        .into_iter()
6768        .map(|(source, mut projections)| {
6769            projections.sort_by_key(|projection| projection.activation_byte);
6770            (source, Arc::from(projections))
6771        })
6772        .collect()
6773}
6774
6775fn unconditional_include_reaches(
6776    cpp: &dyn CppSource,
6777    include_targets: &IncludeTargetIndex,
6778    first: &ProjectFile,
6779    donor_source: &ProjectFile,
6780    reference_file: &ProjectFile,
6781    known_missing: &mut HashSet<ProjectFile>,
6782) -> bool {
6783    if first == donor_source {
6784        return true;
6785    }
6786    if known_missing.contains(first) {
6787        return false;
6788    }
6789    let reference_is_c = reference_file
6790        .rel_path()
6791        .extension()
6792        .and_then(|extension| extension.to_str())
6793        == Some("c");
6794    if let Some(reaches) =
6795        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
6796    {
6797        return reaches;
6798    }
6799    let mut visited = HashSet::default();
6800    let mut files = vec![first.clone()];
6801    // Only an unconditional directive extends the include reach, so the walk
6802    // asks the question without a reference position.
6803    let reference = CallableReferenceContext {
6804        file: reference_file,
6805        position: None,
6806    };
6807    while let Some(file) = files.pop() {
6808        if file == *donor_source {
6809            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
6810            return true;
6811        }
6812        if known_missing.contains(&file) || !visited.insert(file.clone()) {
6813            continue;
6814        }
6815        let Some(prepared) = cpp.prepared_syntax(&file) else {
6816            continue;
6817        };
6818        let mut nodes = vec![prepared.tree().root_node()];
6819        while let Some(node) = nodes.pop() {
6820            if node.kind() == "preproc_include" {
6821                if callable_preprocessor_context_is_visible_for_reference(
6822                    node,
6823                    prepared.source(),
6824                    &reference,
6825                ) {
6826                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6827                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6828                        if let Some(target) = unique_include_target(
6829                            resolve_include_targets_with_index(&file, &include, include_targets),
6830                        ) {
6831                            files.push(target);
6832                        }
6833                    }
6834                }
6835                continue;
6836            }
6837            for index in (0..node.named_child_count()).rev() {
6838                if let Some(child) = node.named_child(index) {
6839                    nodes.push(child);
6840                }
6841            }
6842        }
6843    }
6844    known_missing.extend(visited);
6845    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
6846    false
6847}
6848
6849fn declaration_guard_requirements(
6850    analyzer: &CppGraphSource<'_>,
6851    cpp: &dyn CppSource,
6852    candidate: &CodeUnit,
6853) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
6854    let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
6855        return Vec::new();
6856    };
6857    let root = prepared.tree().root_node();
6858    analyzer
6859        .ranges(candidate)
6860        .into_iter()
6861        .filter_map(|range| {
6862            root.descendant_for_byte_range(range.start_byte, range.end_byte)
6863                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
6864                // A class name is injected into its own body at the declaration's
6865                // introduction point, not after the complete class range. Using
6866                // the start also preserves normal before/after ordering for aliases.
6867                .map(|required| (range.start_byte, required))
6868        })
6869        .collect()
6870}
6871
6872fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
6873    analyzer
6874        .ranges(candidate)
6875        .into_iter()
6876        .map(|range| range.start_byte)
6877        .min()
6878}
6879
6880fn guard_requirements_hold_at_reference(
6881    required: &HashSet<PreprocessorGuard>,
6882    reference: Option<&HashSet<PreprocessorGuard>>,
6883) -> bool {
6884    reference.is_some_and(|active| required.is_subset(active))
6885}
6886
6887/// Cross-file guard rule: two guard sets are compatible when neither one
6888/// contradicts the other. Use this instead of the subset test whenever the
6889/// guards come from a foreign file, which resolves its own conditionals
6890/// independently of the reference.
6891fn guards_compatible_at_reference(
6892    declaration: &HashSet<PreprocessorGuard>,
6893    reference: Option<&HashSet<PreprocessorGuard>>,
6894) -> bool {
6895    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
6896}
6897
6898/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
6899/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
6900/// conditional.
6901///
6902/// Two declarations of one name that report the same chain stand in different
6903/// branches of it, so at most one of them is compiled in any configuration.
6904/// They are alternate spellings of a single declaration, not competing
6905/// declarations, and navigation must not present them as an ambiguity.
6906pub fn preprocessor_conditional_family_range(
6907    root: Node<'_>,
6908    start_byte: usize,
6909    end_byte: usize,
6910) -> Option<(usize, usize)> {
6911    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
6912    let mut ancestor = Some(node);
6913    while let Some(current) = ancestor {
6914        if is_preprocessor_conditional(current)
6915            && preprocessor_conditional_contains_descendant(current, node)
6916        {
6917            let family = preprocessor_conditional_family_root(current);
6918            return Some((family.start_byte(), family.end_byte()));
6919        }
6920        ancestor = current.parent();
6921    }
6922    None
6923}
6924
6925fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
6926    let mut ancestor = node.parent();
6927    while let Some(current) = ancestor {
6928        if is_preprocessor_conditional(current)
6929            && preprocessor_conditional_contains_descendant(current, node)
6930        {
6931            let family = preprocessor_conditional_family_root(current);
6932            if preprocessor_conditional_family_has_terminal_else(family) {
6933                return Some(family);
6934            }
6935        }
6936        ancestor = current.parent();
6937    }
6938    None
6939}
6940
6941fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
6942    while let Some(parent) = conditional.parent() {
6943        let is_alternative = parent
6944            .child_by_field_name("alternative")
6945            .is_some_and(|alternative| {
6946                alternative.start_byte() == conditional.start_byte()
6947                    && alternative.end_byte() == conditional.end_byte()
6948            });
6949        if !is_alternative {
6950            break;
6951        }
6952        conditional = parent;
6953    }
6954    conditional
6955}
6956
6957fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
6958    loop {
6959        let Some(alternative) = conditional.child_by_field_name("alternative") else {
6960            return false;
6961        };
6962        match alternative.kind() {
6963            "preproc_else" => return true,
6964            "preproc_elif" => conditional = alternative,
6965            _ => return false,
6966        }
6967    }
6968}
6969
6970pub fn preprocessor_guard_environment(
6971    node: Node<'_>,
6972    source: &str,
6973) -> Option<HashSet<PreprocessorGuard>> {
6974    let mut guards = HashSet::default();
6975    let mut ancestor = node.parent();
6976    while let Some(conditional) = ancestor {
6977        if matches!(
6978            conditional.kind(),
6979            "preproc_if" | "preproc_ifdef" | "preproc_elif"
6980        ) && !is_file_covering_include_guard(conditional, source)
6981            && preprocessor_conditional_contains_descendant(conditional, node)
6982        {
6983            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
6984            match guard {
6985                PreprocessorGuard::Constant(true) => {
6986                    ancestor = conditional.parent();
6987                    continue;
6988                }
6989                PreprocessorGuard::Constant(false) => return None,
6990                _ => {}
6991            }
6992            if guards.contains(&guard.negated()) {
6993                return None;
6994            }
6995            guards.insert(guard);
6996        }
6997        ancestor = conditional.parent();
6998    }
6999    Some(guards)
7000}
7001
7002fn preprocessor_guard_for_descendant(
7003    conditional: Node<'_>,
7004    descendant: Node<'_>,
7005    source: &str,
7006) -> Option<PreprocessorGuard> {
7007    let mut guard = simple_preprocessor_guard(conditional, source)?;
7008    if conditional
7009        .child_by_field_name("alternative")
7010        .is_some_and(|alternative| {
7011            alternative.start_byte() <= descendant.start_byte()
7012                && descendant.end_byte() <= alternative.end_byte()
7013        })
7014    {
7015        let alternative = conditional.child_by_field_name("alternative")?;
7016        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
7017        // descendant in any later branch must first exclude the parent branch,
7018        // then collect the nested `preproc_elif` guard from its own ancestor.
7019        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
7020            return None;
7021        }
7022        guard = guard.negated();
7023    }
7024    Some(guard)
7025}
7026
7027fn preprocessor_conditional_contains_descendant(
7028    conditional: Node<'_>,
7029    descendant: Node<'_>,
7030) -> bool {
7031    cpp_displaced_preprocessor_terminator(conditional)
7032        .is_none_or(|terminator| descendant.start_byte() < terminator.end_byte())
7033}
7034
7035pub fn merge_preprocessor_guards(
7036    left: &HashSet<PreprocessorGuard>,
7037    right: &HashSet<PreprocessorGuard>,
7038) -> Option<HashSet<PreprocessorGuard>> {
7039    let mut merged = left.clone();
7040    for guard in right {
7041        if merged.contains(&guard.negated()) {
7042            return None;
7043        }
7044        merged.insert(guard.clone());
7045    }
7046    Some(merged)
7047}
7048
7049fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
7050    if conditional.kind() == "preproc_ifdef" {
7051        let name = conditional.child_by_field_name("name")?;
7052        let name = node_text(name, source).to_string();
7053        return match conditional.child(0)?.kind() {
7054            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
7055            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
7056            _ => None,
7057        };
7058    }
7059    let condition = conditional.child_by_field_name("condition")?;
7060    simple_preprocessor_expression_guard(condition, source).or_else(|| {
7061        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
7062            node_text(condition, source),
7063        )))
7064    })
7065}
7066
7067fn simple_preprocessor_expression_guard(
7068    expression: Node<'_>,
7069    source: &str,
7070) -> Option<PreprocessorGuard> {
7071    match expression.kind() {
7072        "number_literal" => match node_text(expression, source).trim() {
7073            "0" => Some(PreprocessorGuard::Constant(false)),
7074            "1" => Some(PreprocessorGuard::Constant(true)),
7075            _ => None,
7076        },
7077        "preproc_defined" => {
7078            let identifier = (0..expression.named_child_count())
7079                .filter_map(|index| expression.named_child(index))
7080                .find(|child| child.kind() == "identifier")?;
7081            Some(PreprocessorGuard::Defined(
7082                node_text(identifier, source).to_string(),
7083            ))
7084        }
7085        "unary_expression"
7086            if expression
7087                .child_by_field_name("operator")
7088                .is_some_and(|operator| operator.kind() == "!") =>
7089        {
7090            simple_preprocessor_expression_guard(
7091                expression.child_by_field_name("argument")?,
7092                source,
7093            )
7094            .map(|guard| guard.negated())
7095        }
7096        "parenthesized_expression" => (0..expression.named_child_count())
7097            .filter_map(|index| expression.named_child(index))
7098            .next()
7099            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
7100        _ => None,
7101    }
7102}
7103
7104fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
7105    if targets.len() == 1 {
7106        targets.pop()
7107    } else {
7108        None
7109    }
7110}
7111
7112/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
7113/// later reference can name.
7114///
7115/// A declaration inside a real function body, lambda, or nested block is block
7116/// local and is dropped. A declaration inside a parser-recovery wrapper that
7117/// merely looks callable -- an export macro between `class` and its name, or a
7118/// namespace-opening macro token before `namespace x {` -- keeps class or
7119/// namespace scope and is kept.
7120fn nameable_callable_declaration_nodes<'tree>(
7121    analyzer: &CppGraphSource<'_>,
7122    prepared: &'tree PreparedSyntaxTree,
7123    candidate: &CodeUnit,
7124) -> Vec<Node<'tree>> {
7125    let root = prepared.tree().root_node();
7126    analyzer
7127        .ranges(candidate)
7128        .into_iter()
7129        .filter_map(|range| {
7130            let mut declaration =
7131                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7132            while !matches!(
7133                declaration.kind(),
7134                "declaration" | "field_declaration" | "function_definition"
7135            ) {
7136                declaration = declaration.parent()?;
7137            }
7138            let mut ancestor = declaration.parent();
7139            while let Some(node) = ancestor {
7140                if node.kind() == "function_definition"
7141                    && is_recovered_declaration_scope_container(node, prepared.source())
7142                {
7143                    ancestor = node.parent();
7144                    continue;
7145                }
7146                if node.kind() == "compound_statement"
7147                    && node.parent().is_some_and(|parent| {
7148                        is_recovered_declaration_scope_container(parent, prepared.source())
7149                    })
7150                {
7151                    ancestor = node.parent().and_then(|parent| parent.parent());
7152                    continue;
7153                }
7154                if matches!(
7155                    node.kind(),
7156                    "compound_statement" | "function_definition" | "lambda_expression"
7157                ) {
7158                    return None;
7159                }
7160                ancestor = node.parent();
7161            }
7162            Some(declaration)
7163        })
7164        .collect()
7165}
7166
7167fn callable_declaration_activation_in_file(
7168    analyzer: &CppGraphSource<'_>,
7169    prepared: &PreparedSyntaxTree,
7170    candidate: &CodeUnit,
7171    reference: &CallableReferenceContext<'_>,
7172) -> Option<usize> {
7173    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
7174        .into_iter()
7175        .filter(|declaration| {
7176            callable_preprocessor_context_is_visible_for_reference(
7177                *declaration,
7178                prepared.source(),
7179                reference,
7180            )
7181        })
7182        .map(callable_declaration_activation_byte)
7183        .min()
7184}
7185
7186/// C and C++ activate a declared name at the end of its declarator, not at the
7187/// end of the whole declaration. A function definition ends at the closing
7188/// brace of its body, so the declaration end byte would hide the function from
7189/// its own body and make self recursion unresolvable without a prototype.
7190fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
7191    if declaration.kind() != "function_definition" {
7192        return declaration.end_byte();
7193    }
7194    declaration
7195        .child_by_field_name("declarator")
7196        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
7197}
7198
7199/// The reference side of a callable visibility question.
7200///
7201/// An include-graph walk and a whole-file arity activation ask the question
7202/// without one reference position, so they carry no `position` and therefore no
7203/// guard environment.
7204struct CallableReferenceContext<'a> {
7205    file: &'a ProjectFile,
7206    position: Option<CallableReferencePosition<'a>>,
7207}
7208
7209/// One reference position plus its preprocessor guard environment. The
7210/// environment is computed on demand because most declarations carry no
7211/// non-trivial guard.
7212struct CallableReferencePosition<'a> {
7213    prepared: &'a PreparedSyntaxTree,
7214    byte: usize,
7215    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
7216}
7217
7218impl CallableReferenceContext<'_> {
7219    fn is_c(&self) -> bool {
7220        self.file
7221            .rel_path()
7222            .extension()
7223            .and_then(|extension| extension.to_str())
7224            == Some("c")
7225    }
7226
7227    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
7228        let position = self.position.as_ref()?;
7229        position
7230            .guards
7231            .get_or_init(|| {
7232                position
7233                    .prepared
7234                    .tree()
7235                    .root_node()
7236                    .descendant_for_byte_range(position.byte, position.byte)
7237                    .and_then(|node| {
7238                        preprocessor_guard_environment(node, position.prepared.source())
7239                    })
7240            })
7241            .as_ref()
7242    }
7243}
7244
7245fn callable_preprocessor_context_is_visible_for_reference(
7246    node: Node<'_>,
7247    source: &str,
7248    reference: &CallableReferenceContext<'_>,
7249) -> bool {
7250    let reference_is_c = reference.is_c();
7251    let mut ancestor = node.parent();
7252    while let Some(conditional) = ancestor {
7253        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
7254            && !is_file_covering_include_guard(conditional, source)
7255            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
7256            && preprocessor_conditional_contains_descendant(conditional, node)
7257        {
7258            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
7259                return false;
7260            };
7261            match guard {
7262                PreprocessorGuard::Constant(true) => {}
7263                PreprocessorGuard::Constant(false) => return false,
7264                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
7265                    if reference_is_c {
7266                        return false;
7267                    }
7268                }
7269                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
7270                    if !reference_is_c {
7271                        return false;
7272                    }
7273                }
7274                // The declaration stands under a guard whose value this
7275                // analyzer cannot decide. It is still co-active with a
7276                // reference that stands under the same guard, so accept the
7277                // guard when the reference already requires it. Collecting one
7278                // guard per ancestor makes the whole walk a subset test of the
7279                // declaration guards against the reference guards.
7280                guard => {
7281                    if !reference
7282                        .guards()
7283                        .is_some_and(|active| active.contains(&guard))
7284                    {
7285                        return false;
7286                    }
7287                }
7288            }
7289        }
7290        ancestor = conditional.parent();
7291    }
7292    true
7293}
7294
7295fn flattened_macro_namespace_declaration_matches(
7296    analyzer: &CppGraphSource<'_>,
7297    cpp: &dyn CppSource,
7298    reference_file: &ProjectFile,
7299    visible_declaration: &CodeUnit,
7300    qualified_candidate: &CodeUnit,
7301    reference_byte: usize,
7302) -> bool {
7303    // Namespace-opening macros can leave tree-sitter unable to retain the
7304    // namespace owner after a later recovery point. In that shape the forward
7305    // declaration is indexed at translation-unit scope, while the definition
7306    // still has its qualified owner. Require all surviving structural evidence
7307    // before treating the declaration as activation for that definition.
7308    if visible_declaration.kind() != qualified_candidate.kind()
7309        || visible_declaration.identifier() != qualified_candidate.identifier()
7310        || visible_declaration.signature() != qualified_candidate.signature()
7311        || !visible_declaration.package_name().is_empty()
7312        || qualified_candidate.package_name().is_empty()
7313    {
7314        return false;
7315    }
7316
7317    let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
7318        return false;
7319    };
7320    let root = prepared.tree().root_node();
7321    let closing_brace_limit = if visible_declaration.source() == reference_file {
7322        reference_byte
7323    } else {
7324        usize::MAX
7325    };
7326
7327    analyzer
7328        .ranges(visible_declaration)
7329        .into_iter()
7330        .any(|range| {
7331            let Some(mut declaration) =
7332                root.descendant_for_byte_range(range.start_byte, range.end_byte)
7333            else {
7334                return false;
7335            };
7336            while !matches!(
7337                declaration.kind(),
7338                "declaration" | "field_declaration" | "function_definition"
7339            ) {
7340                let Some(parent) = declaration.parent() else {
7341                    return false;
7342                };
7343                declaration = parent;
7344            }
7345            if declaration
7346                .parent()
7347                .is_none_or(|parent| parent.kind() != "translation_unit")
7348                || !macro_displaced_cpp_return_type(declaration, prepared.source())
7349            {
7350                return false;
7351            }
7352
7353            let mut cursor = root.walk();
7354            root.named_children(&mut cursor).any(|sibling| {
7355                sibling.start_byte() >= declaration.end_byte()
7356                    && sibling.start_byte() < closing_brace_limit
7357                    && direct_unmatched_closing_brace(sibling)
7358            })
7359        })
7360}
7361
7362fn flattened_macro_namespace_components(
7363    declaration: Node<'_>,
7364    source: &str,
7365) -> Option<Vec<String>> {
7366    flattened_macro_function_namespace_components(declaration, source)
7367        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
7368}
7369
7370fn flattened_macro_function_namespace_components(
7371    declaration: Node<'_>,
7372    source: &str,
7373) -> Option<Vec<String>> {
7374    let body = declaration
7375        .parent()
7376        .filter(|parent| parent.kind() == "compound_statement")?;
7377    let function = body.parent()?;
7378    if function.child_by_field_name("body") != Some(body) {
7379        return None;
7380    }
7381    let namespace_name = recovered_macro_namespace_name(function, source)?;
7382    let mut components = enclosing_namespace_components(declaration, source)?;
7383    components.push(namespace_name);
7384    Some(components)
7385}
7386
7387/// The namespace name a namespace-opening macro token displaced into a
7388/// synthetic `function_definition`, or `None` when `function` is not that
7389/// recovery shape.
7390///
7391/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
7392/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
7393/// the macro token, whose declarator is the namespace name behind an `ERROR`
7394/// holding the `namespace` keyword, and whose body spans the whole namespace
7395/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
7396/// artifact from a real function definition.
7397fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
7398    if function.kind() != "function_definition" || !function.has_error() {
7399        return None;
7400    }
7401    let body = function
7402        .child_by_field_name("body")
7403        .filter(|body| body.kind() == "compound_statement")?;
7404    let mut cursor = function.walk();
7405    let prefix = function
7406        .named_children(&mut cursor)
7407        .take_while(|child| child.start_byte() < body.start_byte())
7408        .filter(|child| child.kind() != "comment")
7409        .collect::<Vec<_>>();
7410    let begin_index = prefix.iter().rposition(|child| {
7411        flattened_macro_sentinel_name(*child, source)
7412            .is_some_and(|name| is_namespace_begin_sentinel(&name))
7413    })?;
7414    let mut identifiers = Vec::new();
7415    let mut stack = prefix[begin_index + 1..]
7416        .iter()
7417        .rev()
7418        .copied()
7419        .collect::<Vec<_>>();
7420    while let Some(current) = stack.pop() {
7421        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
7422            identifiers.push(identifier);
7423            continue;
7424        }
7425        let mut cursor = current.walk();
7426        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
7427        stack.extend(children.into_iter().rev());
7428    }
7429    let [keyword, namespace_name] = identifiers.as_slice() else {
7430        return None;
7431    };
7432    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
7433    {
7434        return None;
7435    }
7436    let mut next = function.next_named_sibling();
7437    let next = loop {
7438        let candidate = next?;
7439        next = candidate.next_named_sibling();
7440        if candidate.kind() != "comment" {
7441            break candidate;
7442        }
7443    };
7444    flattened_macro_sentinel_name(next, source)
7445        .is_some_and(|name| is_namespace_end_sentinel(&name))
7446        .then(|| namespace_name.clone())
7447}
7448
7449/// A `function_definition` that exists only because tree-sitter recovered a
7450/// macro-decorated class head or a namespace-opening macro token. A declaration
7451/// in such a body keeps class or namespace scope, so a scope walk must step over
7452/// the wrapper instead of treating the declaration as block local.
7453fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
7454    crate::declarations::is_recovered_exported_class_container(node, source)
7455        || recovered_macro_namespace_name(node, source).is_some()
7456}
7457
7458fn flattened_macro_error_namespace_components(
7459    declaration: Node<'_>,
7460    source: &str,
7461) -> Option<Vec<String>> {
7462    let parent = declaration
7463        .parent()
7464        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
7465    let mut cursor = parent.walk();
7466    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
7467    let declaration_index = siblings
7468        .iter()
7469        .position(|candidate| same_node(*candidate, declaration))?;
7470    let begin_index = (0..declaration_index).rev().find(|index| {
7471        flattened_macro_sentinel_name(siblings[*index], source)
7472            .is_some_and(|name| is_namespace_begin_sentinel(&name))
7473    })?;
7474
7475    let significant = siblings[begin_index + 1..declaration_index]
7476        .iter()
7477        .copied()
7478        .filter(|node| node.kind() != "comment")
7479        .collect::<Vec<_>>();
7480    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
7481        return None;
7482    };
7483    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
7484        return None;
7485    }
7486    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
7487    if significant[2..].iter().any(|node| {
7488        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
7489            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
7490        })
7491    }) {
7492        return None;
7493    }
7494
7495    let mut saw_namespace_close = false;
7496    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
7497        if sibling.kind() == "comment" {
7498            continue;
7499        }
7500        if !saw_namespace_close {
7501            if direct_unmatched_closing_brace(sibling) {
7502                saw_namespace_close = true;
7503                continue;
7504            }
7505            if flattened_macro_sentinel_name(sibling, source).is_some() {
7506                return None;
7507            }
7508            continue;
7509        }
7510        if !flattened_macro_sentinel_name(sibling, source)
7511            .is_some_and(|name| is_namespace_end_sentinel(&name))
7512        {
7513            return None;
7514        }
7515        let mut components = enclosing_namespace_components(declaration, source)?;
7516        components.push(namespace_name);
7517        return Some(components);
7518    }
7519    None
7520}
7521
7522fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
7523    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
7524    // an `expression_statement` with a missing semicolon; inside a namespace
7525    // body the same token stays a bare `type_identifier`.
7526    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
7527        node.named_child(0)?
7528    } else {
7529        node
7530    };
7531    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
7532        node.child_by_field_name("type")
7533            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
7534    })?;
7535    (cpp_export_macro_token(&candidate)
7536        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
7537    .then_some(candidate)
7538}
7539
7540/// Namespace-opening macros are spelled both ways in the wild:
7541/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
7542fn is_namespace_begin_sentinel(name: &str) -> bool {
7543    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
7544}
7545
7546fn is_namespace_end_sentinel(name: &str) -> bool {
7547    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
7548}
7549
7550fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
7551    if node.kind() != "ERROR" || node.named_child_count() != 1 {
7552        return None;
7553    }
7554    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
7555    (!cpp_export_macro_token(&name)).then_some(name)
7556}
7557
7558fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
7559    if !matches!(
7560        node.kind(),
7561        "identifier" | "namespace_identifier" | "type_identifier"
7562    ) {
7563        return None;
7564    }
7565    let name = normalize_cpp_whitespace(node_text(node, source));
7566    (!name.is_empty()).then_some(name)
7567}
7568
7569fn guard_requirement_sets_match(
7570    left: &[(usize, HashSet<PreprocessorGuard>)],
7571    right: &[(usize, HashSet<PreprocessorGuard>)],
7572) -> bool {
7573    left.len() == right.len()
7574        && left.iter().all(|(_, left_guards)| {
7575            right
7576                .iter()
7577                .any(|(_, right_guards)| left_guards == right_guards)
7578        })
7579        && right.iter().all(|(_, right_guards)| {
7580            left.iter()
7581                .any(|(_, left_guards)| right_guards == left_guards)
7582        })
7583}
7584
7585fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
7586    let Some(type_node) = declaration.child_by_field_name("type") else {
7587        return false;
7588    };
7589    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
7590    !type_name.is_empty()
7591        && type_name
7592            .chars()
7593            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
7594        && (0..declaration.named_child_count()).any(|index| {
7595            declaration
7596                .named_child(index)
7597                .is_some_and(|child| child.kind() == "ERROR")
7598        })
7599}
7600
7601fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
7602    node.kind() == "ERROR"
7603        && (0..node.child_count())
7604            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
7605}
7606
7607pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
7608    let mut ancestor = node.parent();
7609    while let Some(parent) = ancestor {
7610        if is_preprocessor_conditional(parent)
7611            && !is_file_covering_include_guard(parent, source)
7612            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
7613        {
7614            return false;
7615        }
7616        ancestor = parent.parent();
7617    }
7618    true
7619}
7620
7621fn is_split_cpp_language_linkage_wrapper(
7622    conditional: Node<'_>,
7623    descendant: Node<'_>,
7624    source: &str,
7625) -> bool {
7626    if conditional.kind() != "preproc_ifdef"
7627        || conditional.child_by_field_name("alternative").is_some()
7628        || conditional
7629            .child_by_field_name("name")
7630            .is_none_or(|name| node_text(name, source) != "__cplusplus")
7631    {
7632        return false;
7633    }
7634    let mut current = descendant.parent();
7635    let linkage = loop {
7636        let Some(node) = current else {
7637            return false;
7638        };
7639        if node == conditional {
7640            return false;
7641        }
7642        if node.kind() == "linkage_specification" {
7643            break node;
7644        }
7645        current = node.parent();
7646    };
7647    if linkage
7648        .child_by_field_name("value")
7649        .is_none_or(|value| node_text(value, source) != "\"C\"")
7650    {
7651        return false;
7652    }
7653    let Some(body) = linkage.child_by_field_name("body") else {
7654        return false;
7655    };
7656    let closes_opening_branch = (0..body.named_child_count())
7657        .filter_map(|index| body.named_child(index))
7658        .take_while(|child| child.end_byte() <= descendant.start_byte())
7659        .any(|child| {
7660            child.kind() == "preproc_call"
7661                && child
7662                    .child_by_field_name("directive")
7663                    .is_some_and(|directive| node_text(directive, source) == "#endif")
7664        });
7665    let reopens_for_closing_brace = (0..body.named_child_count())
7666        .filter_map(|index| body.named_child(index))
7667        .skip_while(|child| child.start_byte() < descendant.end_byte())
7668        .any(|child| {
7669            child.kind() == "preproc_ifdef"
7670                && child
7671                    .child_by_field_name("name")
7672                    .is_some_and(|name| node_text(name, source) == "__cplusplus")
7673                && (0..child.child_count()).any(|index| {
7674                    child
7675                        .child(index)
7676                        .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
7677                })
7678        });
7679    closes_opening_branch && reopens_for_closing_brace
7680}
7681
7682pub fn call_arity(node: Node<'_>) -> usize {
7683    node.child_by_field_name("arguments")
7684        .or_else(|| node.child_by_field_name("parameters"))
7685        .or_else(|| node.child_by_field_name("value"))
7686        .or_else(|| first_named_child_of_kind(node, "argument_list"))
7687        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
7688        .map(|args| argument_children(args).count())
7689        .unwrap_or(0)
7690}
7691
7692pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
7693    let recovered_block_arguments = recovered_block_literal_arguments(node);
7694    (0..node.child_count())
7695        .filter_map(move |index| node.child(index))
7696        .filter(|child| child.is_named() && !child.is_extra())
7697        .flat_map(move |child| {
7698            if let Some((raw, left, right)) = recovered_block_arguments
7699                && child == raw
7700            {
7701                [Some(left), Some(right)]
7702            } else {
7703                [Some(child), None]
7704            }
7705        })
7706        .flatten()
7707}
7708
7709fn recovered_block_literal_arguments<'tree>(
7710    arguments: Node<'tree>,
7711) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
7712    if arguments.kind() != "argument_list" {
7713        return None;
7714    }
7715    let mut raw_arguments = (0..arguments.child_count())
7716        .filter_map(|index| arguments.child(index))
7717        .filter(|child| child.is_named() && !child.is_extra());
7718    let raw = raw_arguments.next()?;
7719    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
7720        return None;
7721    }
7722
7723    let left = raw.child_by_field_name("left")?;
7724    if left.is_missing() || left.start_byte() == left.end_byte() {
7725        return None;
7726    }
7727    let right = raw.child_by_field_name("right")?;
7728    if right.kind() != "compound_literal_expression"
7729        || right.is_missing()
7730        || right
7731            .child_by_field_name("type")
7732            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
7733        || right
7734            .child_by_field_name("value")
7735            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
7736    {
7737        return None;
7738    }
7739    let has_intervening_error = (0..raw.child_count())
7740        .filter_map(|index| raw.child(index))
7741        .any(|child| {
7742            child.kind() == "ERROR"
7743                && !child.is_missing()
7744                && child.start_byte() >= left.end_byte()
7745                && child.end_byte() <= right.start_byte()
7746        });
7747    has_intervening_error.then_some((raw, left, right))
7748}
7749
7750pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
7751    match node.kind() {
7752        "new_expression" => node
7753            .child_by_field_name("type")
7754            .or_else(|| node.named_child(0)),
7755        "compound_literal_expression" => node.child_by_field_name("type"),
7756        "call_expression" => node.child_by_field_name("function"),
7757        _ => None,
7758    }
7759}
7760
7761pub fn field_initializer_constructs_target(
7762    node: Node<'_>,
7763    ctx: &ScanCtx<'_>,
7764    owner: &CodeUnit,
7765) -> bool {
7766    // A qualified name in a constructor initializer denotes a base
7767    // subobject constructor (`namespace::Base(args)`), not a member field.  The
7768    // field-initializer grammar exposes the qualified name as one structured
7769    // `qualified_identifier`; resolve its owner through the same lexical type
7770    // machinery used for ordinary C++ type references before considering the
7771    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
7772    // qualified non-constructor member, and an unresolved owner out of the
7773    // target constructor's inverse usage set.
7774    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
7775        return qualified_base_initializer_constructs_target(node, ctx, owner);
7776    }
7777    let Some(name) = node
7778        .child_by_field_name("name")
7779        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
7780        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
7781    else {
7782        return false;
7783    };
7784    let field_name = node_text(name, ctx.source);
7785    ctx.visibility
7786        .visible_identifier_candidates(ctx.file, field_name)
7787        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
7788        .any(|unit| field_declares_type(unit, ctx, owner))
7789}
7790
7791fn qualified_base_initializer_constructs_target(
7792    node: Node<'_>,
7793    ctx: &ScanCtx<'_>,
7794    owner: &CodeUnit,
7795) -> bool {
7796    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
7797        return false;
7798    };
7799    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
7800        return false;
7801    };
7802    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
7803        return false;
7804    };
7805    let resolves_target = |components: &[String]| {
7806        matches!(
7807            ctx.visibility.resolve_type_components_lexically_for_target(
7808                &ctx.analyzer,
7809                ctx.file,
7810                components,
7811                is_globally_qualified_cpp_name(qualified),
7812                &lexical_scope,
7813                owner,
7814            ),
7815            LexicalTypeResolution::Resolved { unit, .. }
7816                if same_visible_symbol(&unit, owner)
7817        )
7818    };
7819    if resolves_target(&components) {
7820        return true;
7821    }
7822
7823    // Some real-world code spells a base mem-initializer as
7824    // `Base::Base(args)`. In that structured path the final component repeats
7825    // the constructor name; resolve the preceding type path. The terminal
7826    // identity check prevents an arbitrary qualified member from taking this
7827    // route.
7828    components
7829        .last()
7830        .is_some_and(|terminal| terminal == owner.identifier())
7831        && resolves_target(&components[..components.len() - 1])
7832}
7833
7834fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
7835    unit.signature()
7836        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
7837        || ctx
7838            .analyzer
7839            .get_source(unit, false)
7840            .is_some_and(|declaration| {
7841                field_declaration_type_matches(&declaration, unit, ctx, owner)
7842            })
7843}
7844
7845pub fn field_declared_binding(
7846    analyzer: &CppGraphSource<'_>,
7847    visibility: &VisibilityIndex<'_>,
7848    visible_from: &ProjectFile,
7849    field: &CodeUnit,
7850) -> Option<CppScanBinding> {
7851    let fact = visibility.field_declared_type_fact(analyzer, field)?;
7852    let normalized = normalize_field_type_text(&fact.type_text);
7853    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
7854        analyzer,
7855        visible_from,
7856        field,
7857        &normalized,
7858    );
7859    let resolved = match (resolved, fact.template_arguments.as_deref()) {
7860        (Some(primary), Some(arguments)) => visibility
7861            .resolve_template_arguments(visible_from, primary, arguments)
7862            .ok(),
7863        (resolved, None) => resolved,
7864        (None, Some(_)) => None,
7865    };
7866    Some(CppScanBinding::from_type_name(
7867        normalized,
7868        resolved,
7869        fact.indirection,
7870    ))
7871}
7872
7873/// The one logical type the candidates name, or why they do not name one.
7874fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
7875    let Some(first) = candidates.first() else {
7876        return Err(TypeCandidateFailure::Unresolvable);
7877    };
7878    if candidates
7879        .iter()
7880        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
7881    {
7882        Ok((*first).clone())
7883    } else {
7884        Err(TypeCandidateFailure::Ambiguous)
7885    }
7886}
7887
7888fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
7889    logical_type_candidate(candidates).ok()
7890}
7891
7892fn unique_type_candidate_preserving_alias(
7893    analyzer: &CppGraphSource<'_>,
7894    candidates: &[&CodeUnit],
7895) -> Option<CodeUnit> {
7896    let first = *candidates.first()?;
7897    if declared_type_alias(analyzer, first) {
7898        return candidates
7899            .iter()
7900            .all(|candidate| {
7901                declared_type_alias(analyzer, candidate) && same_logical_symbol(first, candidate)
7902            })
7903            .then(|| first.clone());
7904    }
7905    candidates
7906        .iter()
7907        .all(|candidate| {
7908            !declared_type_alias(analyzer, candidate)
7909                && candidate.kind() == first.kind()
7910                && candidate.fq_name() == first.fq_name()
7911        })
7912        .then(|| first.clone())
7913}
7914
7915fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
7916    is_type_alias(unit)
7917        || analyzer
7918            .type_alias_provider()
7919            .is_some_and(|provider| provider.is_type_alias(unit))
7920}
7921
7922pub fn field_declared_type_binding(
7923    analyzer: &CppGraphSource<'_>,
7924    visibility: &VisibilityIndex<'_>,
7925    visible_from: &ProjectFile,
7926    field: &CodeUnit,
7927) -> Option<(String, Option<CodeUnit>, i32)> {
7928    let fact = visibility.field_declared_type_fact(analyzer, field)?;
7929    let normalized = normalize_field_type_text(&fact.type_text);
7930    let primary = visibility.resolve_unique_canonical_type_for_declaration(
7931        analyzer,
7932        visible_from,
7933        field,
7934        &normalized,
7935    );
7936    let resolved = match (primary, fact.template_arguments.as_deref()) {
7937        (Some(primary), Some(arguments)) => visibility
7938            .resolve_template_arguments(visible_from, primary, arguments)
7939            .ok(),
7940        (resolved, None) => resolved,
7941        (None, Some(_)) => None,
7942    };
7943    Some((normalized, resolved, fact.indirection))
7944}
7945
7946fn decode_field_declared_type_fact(
7947    analyzer: &CppGraphSource<'_>,
7948    field: &CodeUnit,
7949) -> Option<DeclaredFieldTypeFact> {
7950    let declaration = analyzer.get_source(field, false)?;
7951    let mut parser = Parser::new();
7952    parser
7953        .set_language(&tree_sitter_cpp::LANGUAGE.into())
7954        .ok()?;
7955    let tree = parser.parse(&declaration, None)?;
7956    let mut stack = vec![tree.root_node()];
7957    while let Some(node) = stack.pop() {
7958        if matches!(node.kind(), "declaration" | "field_declaration")
7959            && let Some(type_node) = node
7960                .child_by_field_name("type")
7961                .or_else(|| first_type_child(node))
7962            && let Some(indirection) =
7963                declared_name_indirection(node, type_node, field.identifier(), &declaration)
7964        {
7965            return Some(DeclaredFieldTypeFact {
7966                type_text: node_text(type_node, &declaration).to_string(),
7967                indirection,
7968                template_arguments: cpp_template_reference_arguments(type_node, &declaration),
7969            });
7970        }
7971        let mut cursor = node.walk();
7972        stack.extend(node.named_children(&mut cursor));
7973    }
7974    None
7975}
7976
7977/// Text of the type that a C or C++ alias declaration names, read from the
7978/// `type_definition` or `alias_declaration` node's `type` field.
7979///
7980/// The declaration text is never scanned. A function-pointer typedef
7981/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
7982/// so no prefix or suffix of the spelling isolates the target.
7983///
7984/// An alias whose declarator is a function declarator names a function type:
7985/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
7986/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
7987/// so such an alias has no canonical target. Its `type` field holds the return
7988/// type `R`, which is a different type from the alias, so this returns `None`
7989/// rather than that return type.
7990pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
7991    let mut parser = Parser::new();
7992    parser
7993        .set_language(&tree_sitter_cpp::LANGUAGE.into())
7994        .ok()?;
7995    let tree = parser.parse(declaration, None)?;
7996    let mut stack = vec![tree.root_node()];
7997    while let Some(node) = stack.pop() {
7998        let type_node = match node.kind() {
7999            "type_definition" => {
8000                let mut cursor = node.walk();
8001                if node
8002                    .children_by_field_name("declarator", &mut cursor)
8003                    .any(declarator_names_function_type)
8004                {
8005                    return None;
8006                }
8007                node.child_by_field_name("type")?
8008            }
8009            "alias_declaration" => {
8010                let type_node = node.child_by_field_name("type")?;
8011                if type_node
8012                    .child_by_field_name("declarator")
8013                    .is_some_and(declarator_names_function_type)
8014                {
8015                    return None;
8016                }
8017                type_node
8018            }
8019            _ => {
8020                let mut cursor = node.walk();
8021                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
8022                stack.extend(children.into_iter().rev());
8023                continue;
8024            }
8025        };
8026        return Some(node_text(type_node, declaration).to_string());
8027    }
8028    None
8029}
8030
8031/// True when an alias declarator names a function type.
8032///
8033/// The declarator chain is walked through the `declarator` field, so the
8034/// parameter list -- a sibling field -- is never entered and a parameter's own
8035/// function declarator cannot be mistaken for the alias's.
8036fn declarator_names_function_type(declarator: Node<'_>) -> bool {
8037    let mut current = Some(declarator);
8038    while let Some(node) = current {
8039        match node.kind() {
8040            "function_declarator" | "abstract_function_declarator" => return true,
8041            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
8042                current = node.named_child(0);
8043            }
8044            _ => current = node.child_by_field_name("declarator"),
8045        }
8046    }
8047    false
8048}
8049
8050fn decode_structured_alias_target(
8051    analyzer: &CppGraphSource<'_>,
8052    unit: &CodeUnit,
8053) -> Option<StructuredAliasTarget> {
8054    analyzer
8055        .get_source(unit, false)
8056        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
8057        .or_else(|| {
8058            let signature = unit.signature()?;
8059            decode_structured_alias_target_source(unit, signature, false)
8060        })
8061}
8062
8063fn decode_structured_alias_target_source(
8064    unit: &CodeUnit,
8065    declaration: &str,
8066    require_top_level: bool,
8067) -> Option<StructuredAliasTarget> {
8068    let mut parser = Parser::new();
8069    parser
8070        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8071        .ok()?;
8072    let tree = parser.parse(declaration, None)?;
8073    let mut stack = vec![tree.root_node()];
8074    while let Some(node) = stack.pop() {
8075        let type_node = match node.kind() {
8076            "type_definition" => {
8077                if require_top_level
8078                    && node
8079                        .parent()
8080                        .is_none_or(|parent| parent.kind() != "translation_unit")
8081                {
8082                    let mut cursor = node.walk();
8083                    stack.extend(node.named_children(&mut cursor));
8084                    continue;
8085                }
8086                let mut declarator_cursor = node.walk();
8087                let declarator = node
8088                    .children_by_field_name("declarator", &mut declarator_cursor)
8089                    .find(|declarator| {
8090                        extract_typedef_declarator_name(*declarator, declaration)
8091                            .is_some_and(|name| name == unit.identifier())
8092                    })?;
8093                if declarator_names_function_type(declarator) {
8094                    return None;
8095                }
8096                node.child_by_field_name("type")?
8097            }
8098            "alias_declaration" => {
8099                if require_top_level
8100                    && node
8101                        .parent()
8102                        .is_none_or(|parent| parent.kind() != "translation_unit")
8103                {
8104                    let mut cursor = node.walk();
8105                    stack.extend(node.named_children(&mut cursor));
8106                    continue;
8107                }
8108                let name = node.child_by_field_name("name")?;
8109                if node_text(name, declaration) != unit.identifier() {
8110                    return None;
8111                }
8112                let type_node = node.child_by_field_name("type")?;
8113                if type_node
8114                    .child_by_field_name("declarator")
8115                    .is_some_and(declarator_names_function_type)
8116                {
8117                    return None;
8118                }
8119                type_node
8120            }
8121            _ => {
8122                let mut cursor = node.walk();
8123                stack.extend(node.named_children(&mut cursor));
8124                continue;
8125            }
8126        };
8127        return structured_alias_type_target(type_node, declaration);
8128    }
8129    None
8130}
8131
8132fn structured_alias_type_target(
8133    mut type_node: Node<'_>,
8134    source: &str,
8135) -> Option<StructuredAliasTarget> {
8136    while type_node.kind() == "type_descriptor" {
8137        type_node = type_node.child_by_field_name("type")?;
8138    }
8139    if type_node.kind() == "primitive_type" {
8140        return Some(StructuredAliasTarget::Builtin);
8141    }
8142    if matches!(
8143        type_node.kind(),
8144        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
8145    ) {
8146        type_node = type_node.child_by_field_name("name")?;
8147    }
8148    let global = type_node.child_by_field_name("scope").is_none()
8149        && type_node.child(0).is_some_and(|child| child.kind() == "::");
8150    let mut components = Vec::new();
8151    append_structured_type_components(type_node, source, &mut components)?;
8152    let arguments = cpp_template_reference_arguments(type_node, source);
8153    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
8154        components,
8155        global,
8156        arguments,
8157    })
8158}
8159
8160fn append_structured_type_components(
8161    node: Node<'_>,
8162    source: &str,
8163    out: &mut Vec<String>,
8164) -> Option<()> {
8165    match node.kind() {
8166        "identifier" | "namespace_identifier" | "type_identifier" => {
8167            out.push(node_text(node, source).to_string());
8168            Some(())
8169        }
8170        "template_type" => {
8171            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8172        }
8173        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
8174            if let Some(scope) = node.child_by_field_name("scope") {
8175                append_structured_type_components(scope, source, out)?;
8176            }
8177            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8178        }
8179        _ => None,
8180    }
8181}
8182
8183fn declared_name_indirection(
8184    declaration: Node<'_>,
8185    type_node: Node<'_>,
8186    field_name: &str,
8187    source: &str,
8188) -> Option<i32> {
8189    let mut stack = Vec::new();
8190    let mut cursor = declaration.walk();
8191    stack.extend(
8192        declaration
8193            .named_children(&mut cursor)
8194            .filter(|child| !same_node(*child, type_node)),
8195    );
8196    while let Some(node) = stack.pop() {
8197        if matches!(node.kind(), "identifier" | "field_identifier")
8198            && node_text(node, source) == field_name
8199        {
8200            let mut indirection = 0;
8201            let mut current = node.parent();
8202            while let Some(parent) = current {
8203                if same_node(parent, declaration) {
8204                    return Some(indirection);
8205                }
8206                if parent.kind() == "pointer_declarator" {
8207                    indirection += 1;
8208                }
8209                current = parent.parent();
8210            }
8211            return None;
8212        }
8213        let mut cursor = node.walk();
8214        stack.extend(node.named_children(&mut cursor));
8215    }
8216    None
8217}
8218
8219fn field_declaration_type_matches(
8220    declaration: &str,
8221    unit: &CodeUnit,
8222    ctx: &ScanCtx<'_>,
8223    owner: &CodeUnit,
8224) -> bool {
8225    ctx.visibility
8226        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
8227        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
8228            let normalized = normalize_field_type_text(type_text);
8229            ctx.visibility
8230                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
8231                || ctx.visibility.resolves_to_type(
8232                    &ctx.analyzer,
8233                    ctx.file,
8234                    normalized.as_str(),
8235                    owner,
8236                )
8237        })
8238}
8239
8240fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
8241    let declaration = declaration
8242        .split(['=', ';'])
8243        .next()
8244        .unwrap_or(declaration)
8245        .trim();
8246    let index = declaration.rfind(field_name)?;
8247    let before = &declaration[..index];
8248    let after = &declaration[index + field_name.len()..];
8249    if before.chars().next_back().is_some_and(is_identifier_char)
8250        || after.chars().next().is_some_and(is_identifier_char)
8251    {
8252        return None;
8253    }
8254    Some(before.trim())
8255}
8256
8257fn normalize_field_type_text(type_text: &str) -> String {
8258    const FIELD_SPECIFIERS: [&str; 8] = [
8259        "extern ",
8260        "static ",
8261        "mutable ",
8262        "constexpr ",
8263        "constinit ",
8264        "inline ",
8265        "volatile ",
8266        "const ",
8267    ];
8268
8269    let mut normalized = normalize_type_text(type_text);
8270    loop {
8271        let Some(stripped) = FIELD_SPECIFIERS
8272            .iter()
8273            .find_map(|specifier| normalized.strip_prefix(specifier))
8274        else {
8275            return normalized;
8276        };
8277        normalized = normalize_type_text(stripped);
8278    }
8279}
8280
8281fn is_identifier_char(ch: char) -> bool {
8282    ch == '_' || ch.is_ascii_alphanumeric()
8283}
8284
8285pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
8286    let Some(type_node) = node.child_by_field_name("type") else {
8287        return false;
8288    };
8289    ctx.visibility.resolves_to_type(
8290        &ctx.analyzer,
8291        ctx.file,
8292        node_text(type_node, ctx.source),
8293        owner,
8294    )
8295}
8296
8297pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8298    !ctx.analyzer
8299        .declarations(ctx.file)
8300        .into_iter()
8301        .filter(|unit| unit.is_function())
8302        .any(|unit| {
8303            ctx.analyzer.ranges(&unit).iter().any(|range| {
8304                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
8305            })
8306        })
8307}
8308
8309pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
8310    let mut cursor = node.walk();
8311    for child in node.named_children(&mut cursor) {
8312        if child.kind() == "init_declarator" {
8313            return child
8314                .child_by_field_name("value")
8315                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
8316                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
8317                .map(declaration_init_value_arity)
8318                .unwrap_or(0);
8319        }
8320        if is_declarator_node(child) {
8321            return declaration_declarator_arity(child);
8322        }
8323    }
8324    0
8325}
8326
8327fn declaration_init_value_arity(value: Node<'_>) -> usize {
8328    match value.kind() {
8329        "argument_list" | "initializer_list" => argument_children(value).count(),
8330        "compound_literal_expression" => call_arity(value),
8331        _ => 1,
8332    }
8333}
8334
8335fn declaration_declarator_arity(node: Node<'_>) -> usize {
8336    if let Some(parameters) = node.child_by_field_name("parameters") {
8337        return argument_children(parameters).count();
8338    }
8339    node.child_by_field_name("declarator")
8340        .map(declaration_declarator_arity)
8341        .unwrap_or(0)
8342}
8343
8344fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8345    let mut cursor = node.walk();
8346    node.named_children(&mut cursor)
8347        .find(|child| child.kind() == kind)
8348}
8349
8350fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8351    let mut stack = vec![root];
8352    while let Some(node) = stack.pop() {
8353        if node.kind() == kind {
8354            return Some(node);
8355        }
8356        for index in (0..node.named_child_count()).rev() {
8357            if let Some(child) = node.named_child(index) {
8358                stack.push(child);
8359            }
8360        }
8361    }
8362    None
8363}
8364
8365fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
8366    if node.kind() == "identifier" {
8367        return true;
8368    }
8369    if node.kind() == "parenthesized_expression" {
8370        return false;
8371    }
8372    if node.kind() == "call_expression" {
8373        return node
8374            .child_by_field_name("function")
8375            .is_some_and(|function| function.kind() == "identifier");
8376    }
8377    let mut stack = vec![node];
8378    while let Some(descendant) = stack.pop() {
8379        if descendant != node && descendant.kind() == "parenthesized_expression" {
8380            continue;
8381        }
8382        if descendant.kind() == "identifier" {
8383            return true;
8384        }
8385        if descendant.kind() == "call_expression" {
8386            if descendant
8387                .child_by_field_name("function")
8388                .is_some_and(|function| function.kind() == "identifier")
8389            {
8390                return true;
8391            }
8392            continue;
8393        }
8394        for index in (0..descendant.named_child_count()).rev() {
8395            if let Some(child) = descendant.named_child(index) {
8396                stack.push(child);
8397            }
8398        }
8399    }
8400    false
8401}
8402
8403fn macro_expansion_shape_is_safe(
8404    node: Node<'_>,
8405    source: &str,
8406    parameters: &[String],
8407    environment: &MacroEnvironment,
8408) -> bool {
8409    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
8410        return true;
8411    }
8412    if node.kind() == "call_expression" {
8413        let Some(function) = node.child_by_field_name("function") else {
8414            return true;
8415        };
8416        if function.kind() != "identifier" {
8417            return true;
8418        }
8419        let function_name = node_text(function, source);
8420        if parameters
8421            .iter()
8422            .any(|parameter| parameter == function_name)
8423        {
8424            return false;
8425        }
8426        if !environment.may_bind(function_name) {
8427            return true;
8428        }
8429        let Some(arguments) = node.child_by_field_name("arguments") else {
8430            return false;
8431        };
8432        return argument_children(arguments).all(|argument| {
8433            if argument.kind() == "identifier"
8434                && parameters
8435                    .iter()
8436                    .any(|parameter| parameter == node_text(argument, source))
8437            {
8438                return false;
8439            }
8440            macro_expansion_shape_is_safe(argument, source, parameters, environment)
8441        });
8442    }
8443    let mut stack = vec![node];
8444    while let Some(descendant) = stack.pop() {
8445        if descendant != node {
8446            if descendant.kind() == "parenthesized_expression" {
8447                continue;
8448            }
8449            if descendant.kind() == "call_expression" {
8450                let expands = descendant
8451                    .child_by_field_name("function")
8452                    .filter(|function| function.kind() == "identifier")
8453                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
8454                if expands {
8455                    return false;
8456                }
8457                continue;
8458            }
8459        }
8460        if descendant.kind() == "identifier" {
8461            let identifier = node_text(descendant, source);
8462            if parameters.iter().any(|parameter| parameter == identifier)
8463                || environment.may_bind(identifier)
8464            {
8465                return false;
8466            }
8467        }
8468        for index in (0..descendant.named_child_count()).rev() {
8469            if let Some(child) = descendant.named_child(index) {
8470                stack.push(child);
8471            }
8472        }
8473    }
8474    true
8475}
8476
8477fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
8478    let text = node_text(path, source);
8479    match path.kind() {
8480        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
8481        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
8482        _ => None,
8483    }
8484}
8485
8486fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
8487    let descendant = node;
8488    while let Some(parent) = node.parent() {
8489        if is_preprocessor_conditional(parent)
8490            && !is_file_covering_include_guard(parent, source)
8491            && preprocessor_conditional_contains_descendant(parent, descendant)
8492        {
8493            return true;
8494        }
8495        node = parent;
8496    }
8497    false
8498}
8499
8500fn is_preprocessor_conditional(node: Node<'_>) -> bool {
8501    matches!(
8502        node.kind(),
8503        "preproc_if"
8504            | "preproc_ifdef"
8505            | "preproc_ifndef"
8506            | "preproc_elif"
8507            | "preproc_elifdef"
8508            | "preproc_else"
8509    )
8510}
8511
8512fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
8513    node.parent()
8514        .filter(|parent| parent.kind() == "translation_unit")
8515        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
8516        && is_canonical_include_guard(node, source)
8517}
8518
8519fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
8520    if node.kind() != "preproc_ifdef"
8521        || node
8522            .child(0)
8523            .is_none_or(|directive| directive.kind() != "#ifndef")
8524        || node.child_by_field_name("alternative").is_some()
8525    {
8526        return false;
8527    }
8528    let Some(guard_name) = node.child_by_field_name("name") else {
8529        return false;
8530    };
8531    let mut cursor = node.walk();
8532    node.named_children(&mut cursor)
8533        .find(|child| *child != guard_name && child.kind() != "comment")
8534        .filter(|child| child.kind() == "preproc_def")
8535        .and_then(|definition| definition.child_by_field_name("name"))
8536        .is_some_and(|defined_name| {
8537            node_text(defined_name, source) == node_text(guard_name, source)
8538        })
8539}
8540
8541fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
8542    let mut guard = None;
8543    for index in 0..root.named_child_count() {
8544        let Some(child) = root.named_child(index) else {
8545            continue;
8546        };
8547        if child.kind() == "comment" || is_pragma_once(child, source) {
8548            continue;
8549        }
8550        if guard.is_none() && is_canonical_include_guard(child, source) {
8551            guard = Some(child);
8552        } else {
8553            return None;
8554        }
8555    }
8556    guard
8557        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
8558        .map(|name| node_text(name, source).to_string())
8559}
8560
8561fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
8562    if (0..root.named_child_count())
8563        .filter_map(|index| root.named_child(index))
8564        .any(|child| is_pragma_once(child, source))
8565    {
8566        return MacroIncludeProtection::PragmaOnce;
8567    }
8568    top_level_canonical_include_guard_name(root, source)
8569        .map(MacroIncludeProtection::MacroGuard)
8570        .unwrap_or(MacroIncludeProtection::None)
8571}
8572
8573fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
8574    node.kind() == "preproc_call"
8575        && node
8576            .child_by_field_name("directive")
8577            .is_some_and(|directive| node_text(directive, source) == "#pragma")
8578        && node
8579            .child_by_field_name("argument")
8580            .is_some_and(|argument| node_text(argument, source).trim() == "once")
8581}
8582
8583fn parse_preproc_identifier(argument: &str) -> Option<String> {
8584    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
8585    let mut parser = Parser::new();
8586    parser
8587        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8588        .ok()?;
8589    let tree = parser.parse(&sentinel, None)?;
8590    if tree.root_node().has_error() {
8591        return None;
8592    }
8593    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
8594    let identifier = statement.named_child(0)?;
8595    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
8596        .then(|| node_text(identifier, &sentinel).to_string())
8597}
8598
8599pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
8600    match node.kind() {
8601        "identifier" | "field_identifier" => {
8602            let name = node_text(node, source).trim();
8603            (!name.is_empty()).then(|| name.to_string())
8604        }
8605        "abstract_array_declarator"
8606        | "abstract_function_declarator"
8607        | "abstract_parenthesized_declarator"
8608        | "abstract_pointer_declarator"
8609        | "abstract_reference_declarator" => None,
8610        "function_declarator" => node
8611            .child_by_field_name("declarator")
8612            .or_else(|| node.child_by_field_name("name"))
8613            .and_then(|child| extract_variable_name(child, source)),
8614        _ => node
8615            .child_by_field_name("declarator")
8616            .or_else(|| node.child_by_field_name("name"))
8617            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
8618            .and_then(|child| extract_variable_name(child, source)),
8619    }
8620}
8621
8622/// Whether `file` is proven to use plain-C source semantics.
8623///
8624/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
8625/// compilation dialect on their own, so only an exact `.c` source extension is
8626/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
8627/// identifiers.
8628pub fn is_c_source_file(file: &ProjectFile) -> bool {
8629    file.rel_path()
8630        .extension()
8631        .and_then(|extension| extension.to_str())
8632        == Some("c")
8633}
8634
8635pub fn is_declarator_node(node: Node<'_>) -> bool {
8636    matches!(
8637        node.kind(),
8638        "identifier"
8639            | "field_identifier"
8640            | "pointer_declarator"
8641            | "reference_declarator"
8642            | "array_declarator"
8643            | "parenthesized_declarator"
8644            | "function_declarator"
8645    )
8646}
8647
8648#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8649pub enum RecoveredDeclaratorTypeContext {
8650    Declaration,
8651    FunctionDefinition,
8652    Parameter,
8653}
8654
8655/// Recognize a real type displaced into a qualified declarator by parser
8656/// recovery.
8657///
8658/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
8659/// type and `Result` were the scope of a qualified declarator with a missing
8660/// `::`. The same recovery occurs for macro-prefixed definitions, extern
8661/// variables, and macro-decorated parameters (`f(MACRO T* p)`, where the
8662/// parameter's own `type` field takes the macro). Keep this intentionally
8663/// structural: the recovered scope must
8664/// have the grammar's missing separator, the qualified node must occupy the
8665/// declaration's declarator chain, a separate nonempty type must occupy the
8666/// normal type field, and the recovered name must unwrap to a real declarator
8667/// name.
8668pub fn recovered_macro_decorated_declarator_type(
8669    node: Node<'_>,
8670) -> Option<RecoveredDeclaratorTypeContext> {
8671    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
8672}
8673
8674/// Return the declaration/function `type` displaced by a macro-shaped
8675/// qualified declarator, together with the enclosing declaration context.
8676/// Callers use the macro scope only as structural admission evidence; the
8677/// returned node is the real type reference to resolve and record.
8678pub fn recovered_macro_decorated_type_node(
8679    node: Node<'_>,
8680) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
8681    if node.kind() != "namespace_identifier" || node.is_missing() {
8682        return None;
8683    }
8684    let qualified = node.parent()?;
8685    if qualified.kind() != "qualified_identifier"
8686        || qualified.child_by_field_name("scope") != Some(node)
8687        || !(0..qualified.child_count())
8688            .filter_map(|index| qualified.child(index))
8689            .any(|child| child.kind() == "::" && child.is_missing())
8690    {
8691        return None;
8692    }
8693    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
8694        return None;
8695    }
8696
8697    let (declaration, context) = recovered_declarator_container(qualified)?;
8698    let type_node = declaration
8699        .child_by_field_name("type")
8700        .filter(|type_node| {
8701            *type_node != qualified
8702                && !type_node.is_missing()
8703                && type_node.start_byte() != type_node.end_byte()
8704        })?;
8705    Some((type_node, context))
8706}
8707
8708fn recovered_declarator_container(
8709    mut declarator: Node<'_>,
8710) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
8711    loop {
8712        let parent = declarator.parent()?;
8713        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
8714            return Some((
8715                parent
8716                    .parent()
8717                    .filter(|declaration| declaration.kind() == "declaration")?,
8718                RecoveredDeclaratorTypeContext::Declaration,
8719            ));
8720        }
8721        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
8722            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
8723        }
8724        if parent.kind() == "function_definition"
8725            && has_field_child(parent, "declarator", declarator)
8726        {
8727            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
8728        }
8729        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
8730        // level down: the parameter's `type` field takes the macro token and
8731        // the real type `T` becomes the recovered scope of the declarator.
8732        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
8733        // candidate at all (#1830).
8734        if matches!(
8735            parent.kind(),
8736            "parameter_declaration" | "optional_parameter_declaration"
8737        ) && has_field_child(parent, "declarator", declarator)
8738        {
8739            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
8740        }
8741        if !matches!(
8742            parent.kind(),
8743            "array_declarator"
8744                | "function_declarator"
8745                | "parenthesized_declarator"
8746                | "pointer_declarator"
8747                | "pointer_type_declarator"
8748                | "reference_declarator"
8749        ) || !has_field_child(parent, "declarator", declarator)
8750        {
8751            return None;
8752        }
8753        declarator = parent;
8754    }
8755}
8756
8757fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
8758    let mut cursor = parent.walk();
8759    parent
8760        .children_by_field_name(field, &mut cursor)
8761        .any(|child| child == target)
8762}
8763
8764fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
8765    loop {
8766        if node.is_missing() || node.start_byte() == node.end_byte() {
8767            return false;
8768        }
8769        match node.kind() {
8770            "identifier" | "field_identifier" | "type_identifier" => return true,
8771            "array_declarator"
8772            | "function_declarator"
8773            | "parenthesized_declarator"
8774            | "pointer_declarator"
8775            | "pointer_type_declarator"
8776            | "reference_declarator" => {
8777                let Some(declarator) = node.child_by_field_name("declarator") else {
8778                    return false;
8779                };
8780                node = declarator;
8781            }
8782            _ => return false,
8783        }
8784    }
8785}
8786
8787/// Aggregate-owner proof for a structurally recognized designated initializer.
8788pub enum DesignatedInitializerOwner {
8789    Resolved(CodeUnit),
8790    Unresolved,
8791}
8792
8793/// Recognize a designated-initializer field and, when possible, resolve its
8794/// aggregate owner.
8795///
8796/// Covers both the grammar's ordinary `field_designator` shape and the exact
8797/// recovery used for `.field = value` after a preprocessor-split array
8798/// initializer. Nested aggregate levels are deliberately left unresolved unless
8799/// the single outer level is the containing array initializer: resolving those
8800/// would require following the enclosing field's declared type. `None` means the
8801/// node is not a designator at all; an unresolved designator remains classified so
8802/// callers cannot fall through to unrelated global/member heuristics.
8803pub fn designated_initializer_owner(
8804    visibility: &VisibilityIndex<'_>,
8805    file: &ProjectFile,
8806    source: &str,
8807    node: Node<'_>,
8808) -> Option<DesignatedInitializerOwner> {
8809    if let Some(designator) = node
8810        .parent()
8811        .filter(|parent| parent.kind() == "field_designator")
8812    {
8813        let pair = designator.parent()?;
8814        if pair.kind() != "initializer_pair"
8815            || pair.child_by_field_name("designator") != Some(designator)
8816        {
8817            return None;
8818        }
8819        let initializer = pair.parent()?;
8820        if initializer.kind() != "initializer_list" {
8821            return None;
8822        }
8823        return Some(classified_designated_owner(initializer_list_owner(
8824            visibility,
8825            file,
8826            source,
8827            initializer,
8828        )));
8829    }
8830
8831    let init_declarator = node.parent()?;
8832    if init_declarator.child_by_field_name("declarator") != Some(node)
8833        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
8834    {
8835        return None;
8836    }
8837    Some(classified_designated_owner(declaration_owner(
8838        visibility,
8839        file,
8840        source,
8841        init_declarator.parent()?,
8842    )))
8843}
8844
8845fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
8846    owner.map_or(
8847        DesignatedInitializerOwner::Unresolved,
8848        DesignatedInitializerOwner::Resolved,
8849    )
8850}
8851
8852fn initializer_list_owner(
8853    visibility: &VisibilityIndex<'_>,
8854    file: &ProjectFile,
8855    source: &str,
8856    initializer: Node<'_>,
8857) -> Option<CodeUnit> {
8858    let mut current = initializer;
8859    let mut outer_initializer_lists = 0usize;
8860    loop {
8861        let parent = current.parent()?;
8862        match parent.kind() {
8863            "initializer_pair" => return None,
8864            "initializer_list" => {
8865                outer_initializer_lists += 1;
8866                if outer_initializer_lists > 1 {
8867                    return None;
8868                }
8869                current = parent;
8870            }
8871            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
8872                let declaration = parent.parent()?;
8873                if outer_initializer_lists == 1
8874                    && !parent
8875                        .child_by_field_name("declarator")
8876                        .is_some_and(contains_array_declarator)
8877                {
8878                    return None;
8879                }
8880                return declaration_owner(visibility, file, source, declaration);
8881            }
8882            "compound_literal_expression"
8883                if parent.child_by_field_name("value") == Some(current)
8884                    && outer_initializer_lists == 0 =>
8885            {
8886                let type_node = parent.child_by_field_name("type")?;
8887                return resolve_designated_owner_type(visibility, file, source, type_node);
8888            }
8889            "ERROR" => current = parent,
8890            _ => return None,
8891        }
8892    }
8893}
8894
8895fn declaration_owner(
8896    visibility: &VisibilityIndex<'_>,
8897    file: &ProjectFile,
8898    source: &str,
8899    declaration: Node<'_>,
8900) -> Option<CodeUnit> {
8901    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
8902        return None;
8903    }
8904    let type_node = declaration
8905        .child_by_field_name("type")
8906        .or_else(|| first_type_child(declaration))?;
8907    resolve_designated_owner_type(visibility, file, source, type_node)
8908}
8909
8910fn resolve_designated_owner_type(
8911    visibility: &VisibilityIndex<'_>,
8912    file: &ProjectFile,
8913    source: &str,
8914    type_node: Node<'_>,
8915) -> Option<CodeUnit> {
8916    let type_name = normalize_type_text(node_text(type_node, source));
8917    visibility
8918        .resolve_type(file, &type_name)
8919        .filter(CodeUnit::is_class)
8920}
8921
8922fn contains_array_declarator(declarator: Node<'_>) -> bool {
8923    let mut stack = vec![declarator];
8924    while let Some(node) = stack.pop() {
8925        if node.kind() == "array_declarator" {
8926            return true;
8927        }
8928        if matches!(node.kind(), "initializer_list" | "compound_statement") {
8929            continue;
8930        }
8931        let mut cursor = node.walk();
8932        stack.extend(node.named_children(&mut cursor));
8933    }
8934    false
8935}
8936
8937pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
8938    let mut cursor = node.walk();
8939    node.named_children(&mut cursor).find(|child| {
8940        matches!(
8941            child.kind(),
8942            "type_identifier"
8943                | "primitive_type"
8944                | "qualified_identifier"
8945                | "scoped_type_identifier"
8946                | "struct_specifier"
8947                | "union_specifier"
8948                | "enum_specifier"
8949        )
8950    })
8951}
8952
8953pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
8954    visibility: &VisibilityIndex<'_>,
8955    file: &ProjectFile,
8956    source: &str,
8957    declarator: Node<'_>,
8958    type_text: Option<&str>,
8959    bindings: &LocalInferenceEngine<T>,
8960) -> bool {
8961    if !has_ancestor_kind(declarator, "compound_statement") {
8962        return false;
8963    }
8964    if declarator
8965        .child_by_field_name("declarator")
8966        .is_none_or(|declarator| declarator.kind() != "identifier")
8967    {
8968        return false;
8969    }
8970    if !type_text
8971        .and_then(|text| visibility.resolve_type(file, text))
8972        .is_some_and(|unit| unit.is_class())
8973    {
8974        return false;
8975    }
8976    declarator
8977        .child_by_field_name("parameters")
8978        .is_some_and(|parameters| {
8979            constructor_parameters_look_like_expressions(parameters, source, bindings)
8980        })
8981}
8982
8983fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
8984    parameters: Node<'_>,
8985    source: &str,
8986    bindings: &LocalInferenceEngine<T>,
8987) -> bool {
8988    let mut cursor = parameters.walk();
8989    parameters.named_children(&mut cursor).any(|parameter| {
8990        !matches!(
8991            parameter.kind(),
8992            "parameter_declaration" | "optional_parameter_declaration"
8993        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
8994    })
8995}
8996
8997fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
8998    parameter: Node<'_>,
8999    source: &str,
9000    bindings: &LocalInferenceEngine<T>,
9001) -> bool {
9002    let text = node_text(parameter, source).trim();
9003    text.chars()
9004        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
9005        && bindings.is_shadowed(text)
9006}
9007
9008pub fn is_declaration_name(node: Node<'_>) -> bool {
9009    let Some(parent) = node.parent() else {
9010        return false;
9011    };
9012    if parent
9013        .child_by_field_name("name")
9014        .is_some_and(|name| same_node(name, node))
9015    {
9016        if matches!(
9017            parent.kind(),
9018            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9019        ) {
9020            return cpp_tag_specifier_declares_name(parent);
9021        }
9022        if matches!(
9023            parent.kind(),
9024            "namespace_definition"
9025                | "namespace_alias_definition"
9026                | "alias_declaration"
9027                | "enumerator"
9028        ) {
9029            return true;
9030        }
9031    }
9032
9033    let mut current = Some(parent);
9034    while let Some(ancestor) = current {
9035        let type_definition = ancestor.kind() == "type_definition";
9036        let mut declarator_cursor = ancestor.walk();
9037        if ancestor
9038            .children_by_field_name("declarator", &mut declarator_cursor)
9039            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
9040        {
9041            return true;
9042        }
9043        if matches!(
9044            ancestor.kind(),
9045            "declaration"
9046                | "field_declaration"
9047                | "parameter_declaration"
9048                | "optional_parameter_declaration"
9049                | "function_definition"
9050                | "type_definition"
9051                | "alias_declaration"
9052                | "class_specifier"
9053                | "struct_specifier"
9054                | "union_specifier"
9055                | "enum_specifier"
9056        ) {
9057            return false;
9058        }
9059        current = ancestor.parent();
9060    }
9061    false
9062}
9063
9064pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
9065    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
9066        return false;
9067    }
9068    if let Some(parent) = node.parent() {
9069        if parent.kind() == "call_expression"
9070            && parent.child_by_field_name("function") == Some(node)
9071        {
9072            return false;
9073        }
9074        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
9075            && parent.child_by_field_name("label") == Some(node)
9076        {
9077            return false;
9078        }
9079    }
9080    let mut current = node.parent();
9081    while let Some(ancestor) = current {
9082        if ancestor.kind().starts_with("preproc_") {
9083            return false;
9084        }
9085        if matches!(
9086            ancestor.kind(),
9087            "translation_unit" | "function_definition" | "compound_statement"
9088        ) {
9089            break;
9090        }
9091        current = ancestor.parent();
9092    }
9093    true
9094}
9095
9096fn recovered_c_reference_node(
9097    visibility: &VisibilityIndex<'_>,
9098    file: &ProjectFile,
9099    node: Node<'_>,
9100    source: &str,
9101) -> bool {
9102    if node.start_byte() >= node.end_byte()
9103        || node.is_error()
9104        || node.is_missing()
9105        || !matches!(
9106            node.kind(),
9107            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
9108        )
9109        || recovered_c_macro_binding_role(node)
9110        || recovered_c_label_role(node)
9111    {
9112        return false;
9113    }
9114
9115    let name = node_text(node, source);
9116    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
9117        return true;
9118    }
9119    if is_declaration_name(node) {
9120        return false;
9121    }
9122    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
9123        return true;
9124    }
9125    recovered_c_reference_anchor(node)
9126}
9127
9128fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
9129    while let Some(parent) = node.parent() {
9130        if matches!(
9131            parent.kind(),
9132            "preproc_def" | "preproc_function_def" | "preproc_params"
9133        ) {
9134            return true;
9135        }
9136        if parent.is_error()
9137            || matches!(
9138                parent.kind(),
9139                "translation_unit" | "function_definition" | "compound_statement"
9140            )
9141        {
9142            return false;
9143        }
9144        node = parent;
9145    }
9146    false
9147}
9148
9149fn recovered_c_label_role(node: Node<'_>) -> bool {
9150    node.parent().is_some_and(|parent| {
9151        matches!(parent.kind(), "labeled_statement" | "goto_statement")
9152            && parent.child_by_field_name("label") == Some(node)
9153    })
9154}
9155
9156fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
9157    while let Some(parent) = node.parent() {
9158        if parent.is_error() {
9159            return false;
9160        }
9161        if parent.kind().ends_with("_expression")
9162            || matches!(
9163                parent.kind(),
9164                "argument_list"
9165                    | "return_statement"
9166                    | "expression_statement"
9167                    | "case_statement"
9168                    | "initializer_list"
9169                    | "init_declarator"
9170                    | "array_declarator"
9171                    | "field_designator"
9172                    | "enumerator"
9173            )
9174        {
9175            return true;
9176        }
9177        if matches!(
9178            parent.kind(),
9179            "translation_unit"
9180                | "function_definition"
9181                | "compound_statement"
9182                | "declaration"
9183                | "field_declaration"
9184                | "parameter_declaration"
9185        ) {
9186            return false;
9187        }
9188        node = parent;
9189    }
9190    false
9191}
9192
9193/// Whether a parameter declaration belongs to the callable scope whose body can
9194/// contain references to it.
9195///
9196/// Error recovery can wrap a macro-decorated class body in a synthetic outer
9197/// `function_definition`. Merely finding any callable ancestor would then leak
9198/// parameters from member prototypes into later member bodies. Require the
9199/// parameter to be inside that definition's own declarator instead.
9200pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
9201    let mut current = parameter.parent();
9202    while let Some(ancestor) = current {
9203        if ancestor.kind() == "lambda_expression" {
9204            return ancestor
9205                .child_by_field_name("declarator")
9206                .is_some_and(|declarator| {
9207                    declarator.start_byte() <= parameter.start_byte()
9208                        && parameter.end_byte() <= declarator.end_byte()
9209                });
9210        }
9211        if ancestor.kind() == "function_definition" {
9212            return ancestor
9213                .child_by_field_name("declarator")
9214                .is_some_and(|declarator| {
9215                    declarator.start_byte() <= parameter.start_byte()
9216                        && parameter.end_byte() <= declarator.end_byte()
9217                });
9218        }
9219        current = ancestor.parent();
9220    }
9221    false
9222}
9223
9224fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
9225    if specifier.child_by_field_name("body").is_some() {
9226        return true;
9227    }
9228    let mut current = specifier.parent();
9229    while let Some(ancestor) = current {
9230        match ancestor.kind() {
9231            "type_descriptor"
9232            | "parameter_declaration"
9233            | "optional_parameter_declaration"
9234            | "template_argument_list"
9235            | "cast_expression" => return false,
9236            "declaration" | "field_declaration" => {
9237                let mut cursor = ancestor.walk();
9238                return ancestor
9239                    .children_by_field_name("declarator", &mut cursor)
9240                    .next()
9241                    .is_none();
9242            }
9243            "translation_unit" => return true,
9244            _ => current = ancestor.parent(),
9245        }
9246    }
9247    false
9248}
9249
9250pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
9251    match node.kind() {
9252        "identifier"
9253        | "field_identifier"
9254        | "qualified_identifier"
9255        | "scoped_identifier"
9256        | "operator_name"
9257        | "destructor_name"
9258        | "literal_operator_name" => Some(node),
9259        _ => node
9260            .child_by_field_name("declarator")
9261            .or_else(|| node.child_by_field_name("name"))
9262            .or_else(|| node.child_by_field_name("field"))
9263            .and_then(declarator_name_node),
9264    }
9265}
9266
9267fn declarator_name_path_contains(
9268    declarator: Node<'_>,
9269    candidate: Node<'_>,
9270    allow_type_identifier: bool,
9271) -> bool {
9272    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
9273        return false;
9274    };
9275    let mut current = Some(declarator);
9276    while let Some(node) = current {
9277        if same_node(node, candidate) {
9278            return true;
9279        }
9280        if same_node(node, name) {
9281            return false;
9282        }
9283        current = node
9284            .child_by_field_name("declarator")
9285            .or_else(|| node.child_by_field_name("name"))
9286            .or_else(|| node.child_by_field_name("field"));
9287    }
9288    false
9289}
9290
9291fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
9292    match node.kind() {
9293        "identifier"
9294        | "field_identifier"
9295        | "operator_name"
9296        | "destructor_name"
9297        | "literal_operator_name" => Some(node),
9298        "type_identifier" if allow_type_identifier => Some(node),
9299        _ => node
9300            .child_by_field_name("declarator")
9301            .or_else(|| node.child_by_field_name("name"))
9302            .or_else(|| node.child_by_field_name("field"))
9303            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
9304    }
9305}
9306
9307/// True when `node` is a component of a larger structured type node whose outer
9308/// range is the single reference surfaced to callers.
9309pub fn is_nested_type_node(node: Node<'_>) -> bool {
9310    node.parent().is_some_and(|parent| {
9311        matches!(
9312            parent.kind(),
9313            "qualified_identifier" | "scoped_type_identifier" | "template_type"
9314        )
9315    })
9316}
9317
9318pub struct OutOfLineMemberDefinitionOwners<'tree> {
9319    pub owners: Vec<(Node<'tree>, CodeUnit)>,
9320    innermost: Option<(Node<'tree>, CodeUnit)>,
9321}
9322
9323impl OutOfLineMemberDefinitionOwners<'_> {
9324    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
9325        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
9326    }
9327}
9328
9329pub struct QualifiedOwnerComponents<'tree> {
9330    pub nodes: Vec<Node<'tree>>,
9331    pub names: Vec<String>,
9332    pub global: bool,
9333}
9334
9335/// True when each structured qualifier on the callable-name path has a real
9336/// `::` token. A macro-prefixed return type can make tree-sitter insert a
9337/// zero-width missing separator and parse `TYPE Result<T> method()` as the
9338/// false qualified declarator `Result<T>::method`.
9339pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
9340    let mut stack = vec![node];
9341    let mut found_separator = false;
9342    while let Some(current) = stack.pop() {
9343        if !matches!(
9344            current.kind(),
9345            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
9346        ) {
9347            continue;
9348        }
9349        let mut current_has_separator = false;
9350        for index in 0..current.child_count() {
9351            let Some(child) = current.child(index) else {
9352                continue;
9353            };
9354            if child.kind() == "::" {
9355                if child.is_missing() {
9356                    return false;
9357                }
9358                current_has_separator = true;
9359                found_separator = true;
9360            }
9361        }
9362        if !current_has_separator {
9363            return false;
9364        }
9365        for field in ["scope", "name"] {
9366            if let Some(child) = current.child_by_field_name(field)
9367                && matches!(
9368                    child.kind(),
9369                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
9370                )
9371            {
9372                stack.push(child);
9373            }
9374        }
9375    }
9376    found_separator
9377}
9378
9379pub fn qualified_owner_components<'tree>(
9380    node: Node<'tree>,
9381    source: &str,
9382) -> Option<QualifiedOwnerComponents<'tree>> {
9383    if !qualified_name_has_concrete_scope_separators(node) {
9384        return None;
9385    }
9386    let mut nodes = cpp_name_component_nodes(node)?;
9387    nodes.pop()?;
9388    if nodes.is_empty() {
9389        return None;
9390    }
9391    let names = nodes
9392        .iter()
9393        .map(|component| node_text(*component, source).to_string())
9394        .collect();
9395    Some(QualifiedOwnerComponents {
9396        nodes,
9397        names,
9398        global: is_globally_qualified_cpp_name(node),
9399    })
9400}
9401
9402/// Return the terminal type-name occurrence in an out-of-line destructor
9403/// declarator such as `endpoint::~endpoint`.  Unlike an ordinary terminal
9404/// method name, this identifier is a second reference to the owner type.
9405///
9406/// Every extra qualifier nests another `qualified_identifier` in the `name`
9407/// field, so `zmq::pair_t::~pair_t` reaches the destructor only two levels
9408/// down. Reading one level dropped the terminal occurrence for every
9409/// file-scope out-of-line member libzmq writes (#1831).
9410pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
9411    if node.kind() != "qualified_identifier" {
9412        return None;
9413    }
9414    let mut qualified = node;
9415    let destructor = loop {
9416        let name = qualified.child_by_field_name("name")?;
9417        match name.kind() {
9418            "qualified_identifier" => qualified = name,
9419            "destructor_name" => break name,
9420            _ => return None,
9421        }
9422    };
9423    (0..destructor.named_child_count())
9424        .filter_map(|index| destructor.named_child(index))
9425        .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
9426}
9427
9428pub fn out_of_line_member_definition_owner<'tree>(
9429    analyzer: &CppGraphSource<'_>,
9430    visibility: &VisibilityIndex<'_>,
9431    file: &ProjectFile,
9432    source: &str,
9433    node: Node<'tree>,
9434) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
9435    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9436        || !has_ancestor_kind(node, "function_definition")
9437        || !is_function_declarator_name_root(node)
9438    {
9439        return None;
9440    }
9441    let qualified = qualified_owner_components(node, source)?;
9442    let lexical_scope = enclosing_namespace_components(node, source)?;
9443    let mut owners = Vec::new();
9444    let mut innermost = None;
9445
9446    for component_count in 1..=qualified.names.len() {
9447        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
9448            .resolve_type_components_lexically(
9449                analyzer,
9450                file,
9451                &qualified.names[..component_count],
9452                qualified.global,
9453                &lexical_scope,
9454            )
9455            && !owners
9456                .iter()
9457                .any(|(_, existing)| same_visible_symbol(existing, &unit))
9458        {
9459            if component_count == qualified.names.len() {
9460                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
9461            }
9462            owners.push((qualified.nodes[component_count - 1], unit));
9463        }
9464    }
9465
9466    // The C++ analyzer has already reconciled an indexed out-of-line callable
9467    // against the include-visible class table. Consult that canonical owner
9468    // chain only when ordinary lexical lookup could not recover the innermost
9469    // owner.  A one-segment qualifier is safe here only when the enclosing
9470    // indexed callable has an authoritative class owner and the parser's
9471    // namespace path is a (possibly sparse) subsequence of that owner path.
9472    // The latter is what lets macro-wrapped namespace sentinels recover a
9473    // missing `time_internal`/`cord_internal` component without guessing an
9474    // unrelated short name.
9475    if innermost.is_none() {
9476        let indexed_owner_components = visibility
9477            .indexed_enclosing_owner_scope(analyzer, file, node)
9478            .or_else(|| {
9479                // Retain the legacy rendered-name fallback for the existing
9480                // multi-segment path when an enclosing owner chain is not
9481                // available (for example, cache-loaded units without parent
9482                // links).  One-segment recovery must stay canonical-only.
9483                if qualified.names.len() <= 1 {
9484                    return None;
9485                }
9486                let range = Range {
9487                    start_byte: node.start_byte(),
9488                    end_byte: node.end_byte(),
9489                    start_line: node.start_position().row,
9490                    end_line: node.end_position().row,
9491                };
9492                let start = analyzer.enclosing_code_unit(file, &range)?;
9493                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9494                    brokk_bifrost_core::analyzer::Language::Cpp,
9495                    &cpp_name_for(&start),
9496                );
9497                components.pop();
9498                Some(components)
9499            });
9500        if let Some(indexed_owner_components) = indexed_owner_components
9501            && indexed_owner_components.len() > qualified.names.len()
9502            && indexed_owner_components.ends_with(&qualified.names)
9503            && indexed_namespace_path_is_recoverable(
9504                &lexical_scope,
9505                &indexed_owner_components,
9506            )
9507            // A globally-qualified one-segment owner is an explicit request
9508            // for the top-level binding; do not reinterpret it as a missing
9509            // namespace component.  Existing multi-segment global lookups
9510            // retain their historical indexed recovery.
9511            && (qualified.names.len() > 1 || !qualified.global)
9512        {
9513            let namespace_count = indexed_owner_components.len() - qualified.names.len();
9514            for component_count in 1..=qualified.names.len() {
9515                let expected = &indexed_owner_components[..namespace_count + component_count];
9516                let owner_node = qualified.nodes[component_count - 1];
9517                for owner in visibility
9518                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
9519                    .filter(|candidate| candidate.is_class())
9520                    .filter(|candidate| {
9521                        canonical_cpp_scope_components(candidate) == expected
9522                            && visibility.external_type_candidate_visible_in_context(
9523                                analyzer, file, candidate, node,
9524                            )
9525                    })
9526                {
9527                    if component_count == qualified.names.len() && innermost.is_none() {
9528                        innermost = Some((owner_node, owner.clone()));
9529                    }
9530                    if !owners
9531                        .iter()
9532                        .any(|(_, existing)| same_symbol(existing, owner))
9533                    {
9534                        owners.push((owner_node, owner.clone()));
9535                    }
9536                }
9537            }
9538        }
9539    }
9540    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
9541}
9542
9543fn is_function_declarator_name_root(node: Node<'_>) -> bool {
9544    let mut current = node;
9545    while let Some(parent) = current.parent() {
9546        if parent.kind() == "function_declarator" {
9547            return parent.child_by_field_name("declarator") == Some(current);
9548        }
9549        if matches!(
9550            parent.kind(),
9551            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
9552        ) && parent.child_by_field_name("declarator") == Some(current)
9553        {
9554            current = parent;
9555            continue;
9556        }
9557        return false;
9558    }
9559    false
9560}
9561
9562pub fn append_cpp_name_components(
9563    node: Node<'_>,
9564    source: &str,
9565    out: &mut Vec<String>,
9566) -> Option<()> {
9567    out.extend(
9568        cpp_name_component_nodes(node)?
9569            .into_iter()
9570            .map(|component| node_text(component, source).to_string()),
9571    );
9572    Some(())
9573}
9574
9575pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
9576    let mut components = Vec::new();
9577    append_cpp_name_components(node, source, &mut components)?;
9578    Some(components)
9579}
9580
9581pub fn cpp_template_reference_arguments(
9582    mut node: Node<'_>,
9583    source: &str,
9584) -> Option<Vec<CppTemplateExpression>> {
9585    loop {
9586        match node.kind() {
9587            "template_type" | "template_function" => {
9588                let arguments = node.child_by_field_name("arguments")?;
9589                let mut cursor = arguments.walk();
9590                return Some(
9591                    arguments
9592                        .named_children(&mut cursor)
9593                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
9594                        .map(|argument| CppTemplateExpression {
9595                            text: normalize_cpp_whitespace(node_text(argument, source)),
9596                            term: cpp_template_term(argument, source, &[]),
9597                        })
9598                        .collect(),
9599                );
9600            }
9601            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
9602                node = node
9603                    .child_by_field_name("name")
9604                    .or_else(|| node.child_by_field_name("type"))?;
9605            }
9606            _ => return None,
9607        }
9608    }
9609}
9610
9611fn cpp_reconcile_primary_template_parameters(
9612    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
9613    preferred: &CodeUnit,
9614) -> Option<Vec<CppTemplateParameterMetadata>> {
9615    let canonical = candidates
9616        .iter()
9617        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
9618    let mut merged = canonical
9619        .parameters
9620        .iter()
9621        .map(|parameter| CppTemplateParameterMetadata {
9622            name: parameter.name.clone(),
9623            kind: parameter.kind,
9624            variadic: parameter.variadic,
9625            default: None,
9626        })
9627        .collect::<Vec<_>>();
9628
9629    for (_, metadata) in candidates {
9630        if metadata.parameters.len() != merged.len() {
9631            return None;
9632        }
9633        let rename_bindings = metadata
9634            .parameters
9635            .iter()
9636            .zip(&merged)
9637            .map(|(parameter, canonical)| {
9638                (
9639                    parameter.name.clone(),
9640                    CppTemplateTerm::Parameter(canonical.name.clone()),
9641                )
9642            })
9643            .collect::<HashMap<_, _>>();
9644        for ((parameter, canonical), merged_parameter) in metadata
9645            .parameters
9646            .iter()
9647            .zip(&canonical.parameters)
9648            .zip(&mut merged)
9649        {
9650            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
9651                return None;
9652            }
9653            let Some(default) = &parameter.default else {
9654                continue;
9655            };
9656            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
9657            if let Some(existing) = &merged_parameter.default {
9658                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
9659                    return None;
9660                }
9661            } else {
9662                merged_parameter.default = Some(CppTemplateExpression {
9663                    text: default.text.clone(),
9664                    term: normalized_term,
9665                });
9666            }
9667        }
9668    }
9669    Some(merged)
9670}
9671
9672pub fn cpp_bind_template_arguments(
9673    parameters: &[CppTemplateParameterMetadata],
9674    explicit_arguments: &[CppTemplateExpression],
9675) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
9676    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
9677    if variadic_index.is_some_and(|index| {
9678        index + 1 != parameters.len()
9679            || parameters[index + 1..]
9680                .iter()
9681                .any(|parameter| parameter.variadic)
9682    }) {
9683        return None;
9684    }
9685    let fixed_count = variadic_index.unwrap_or(parameters.len());
9686    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
9687        return None;
9688    }
9689    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
9690    let mut expanded = explicit_arguments[..explicit_fixed_count]
9691        .iter()
9692        .map(cpp_clone_template_expression_iterative)
9693        .collect::<Vec<_>>();
9694    let mut bindings = HashMap::default();
9695    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
9696        bindings.insert(
9697            parameter.name.clone(),
9698            cpp_clone_template_term_iterative(&argument.term),
9699        );
9700    }
9701    for parameter in &parameters[explicit_fixed_count..fixed_count] {
9702        let default = parameter.default.as_ref()?;
9703        let term = cpp_substitute_template_term(&default.term, &bindings)?;
9704        bindings.insert(parameter.name.clone(), term.clone());
9705        expanded.push(CppTemplateExpression {
9706            text: default.text.clone(),
9707            term,
9708        });
9709    }
9710    if let Some(index) = variadic_index {
9711        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
9712        expanded.extend(
9713            packed_arguments
9714                .iter()
9715                .map(cpp_clone_template_expression_iterative),
9716        );
9717        bindings.insert(
9718            parameters[index].name.clone(),
9719            CppTemplateTerm::Node {
9720                kind: "parameter_pack".to_string(),
9721                children: packed_arguments
9722                    .iter()
9723                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
9724                    .collect(),
9725            },
9726        );
9727    }
9728    Some((expanded, bindings))
9729}
9730
9731fn cpp_specialization_matches(
9732    metadata: &CppTemplateMetadata,
9733    arguments: &[CppTemplateExpression],
9734) -> bool {
9735    if metadata.specialization_arguments.len() != arguments.len() {
9736        return false;
9737    }
9738    let parameter_names = metadata
9739        .parameters
9740        .iter()
9741        .map(|parameter| parameter.name.as_str())
9742        .collect::<HashSet<_>>();
9743    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
9744    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
9745        if !cpp_unify_template_term(
9746            &pattern.term,
9747            &argument.term,
9748            &parameter_names,
9749            &mut bindings,
9750        ) {
9751            return false;
9752        }
9753    }
9754    true
9755}
9756
9757fn cpp_specialization_more_specialized(
9758    candidate: &CppTemplateMetadata,
9759    other: &CppTemplateMetadata,
9760) -> bool {
9761    cpp_specialization_pattern_accepts(other, candidate)
9762        && !cpp_specialization_pattern_accepts(candidate, other)
9763}
9764
9765fn cpp_specialization_pattern_accepts(
9766    broader: &CppTemplateMetadata,
9767    narrower: &CppTemplateMetadata,
9768) -> bool {
9769    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
9770        return false;
9771    }
9772    let parameter_names = broader
9773        .parameters
9774        .iter()
9775        .map(|parameter| parameter.name.as_str())
9776        .collect::<HashSet<_>>();
9777    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
9778    broader
9779        .specialization_arguments
9780        .iter()
9781        .zip(&narrower.specialization_arguments)
9782        .all(|(pattern, argument)| {
9783            cpp_unify_template_term(
9784                &pattern.term,
9785                &argument.term,
9786                &parameter_names,
9787                &mut bindings,
9788            )
9789        })
9790}
9791
9792pub fn cpp_substitute_template_term(
9793    term: &CppTemplateTerm,
9794    bindings: &HashMap<String, CppTemplateTerm>,
9795) -> Option<CppTemplateTerm> {
9796    enum Work<'a> {
9797        Visit(&'a CppTemplateTerm),
9798        Build { kind: String, child_count: usize },
9799    }
9800
9801    let mut work = vec![Work::Visit(term)];
9802    let mut substituted = Vec::new();
9803    while let Some(next) = work.pop() {
9804        match next {
9805            Work::Visit(CppTemplateTerm::Parameter(name)) => {
9806                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
9807            }
9808            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
9809                substituted.push(CppTemplateTerm::Atom {
9810                    kind: kind.clone(),
9811                    text: text.clone(),
9812                });
9813            }
9814            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
9815                work.push(Work::Build {
9816                    kind: kind.clone(),
9817                    child_count: children.len(),
9818                });
9819                work.extend(children.iter().rev().map(Work::Visit));
9820            }
9821            Work::Build { kind, child_count } => {
9822                let children = substituted.split_off(substituted.len() - child_count);
9823                substituted.push(CppTemplateTerm::Node { kind, children });
9824            }
9825        }
9826    }
9827    substituted.pop()
9828}
9829
9830pub fn cpp_substitute_template_arguments(
9831    arguments: &[CppTemplateExpression],
9832    bindings: &HashMap<String, CppTemplateTerm>,
9833) -> Option<Vec<CppTemplateExpression>> {
9834    let mut substituted = Vec::new();
9835    for argument in arguments {
9836        let CppTemplateTerm::Node { kind, children } = &argument.term else {
9837            substituted.push(CppTemplateExpression {
9838                text: argument.text.clone(),
9839                term: cpp_substitute_template_term(&argument.term, bindings)?,
9840            });
9841            continue;
9842        };
9843        if kind != "parameter_pack_expansion" {
9844            substituted.push(CppTemplateExpression {
9845                text: argument.text.clone(),
9846                term: cpp_substitute_template_term(&argument.term, bindings)?,
9847            });
9848            continue;
9849        }
9850        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
9851            return None;
9852        };
9853        if ellipsis != "..." {
9854            return None;
9855        }
9856
9857        let mut pack_names = Vec::new();
9858        let mut work = vec![pattern];
9859        while let Some(term) = work.pop() {
9860            match term {
9861                CppTemplateTerm::Parameter(name)
9862                    if matches!(
9863                        bindings.get(name),
9864                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
9865                    ) =>
9866                {
9867                    if !pack_names.contains(name) {
9868                        pack_names.push(name.clone());
9869                    }
9870                }
9871                CppTemplateTerm::Node { children, .. } => work.extend(children),
9872                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
9873            }
9874        }
9875        let first_pack = pack_names.first()?;
9876        let CppTemplateTerm::Node {
9877            children: first_elements,
9878            ..
9879        } = bindings.get(first_pack)?
9880        else {
9881            return None;
9882        };
9883        let pack_len = first_elements.len();
9884        for pack_name in &pack_names {
9885            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
9886                return None;
9887            };
9888            if children.len() != pack_len {
9889                return None;
9890            }
9891        }
9892        for index in 0..pack_len {
9893            let mut element_bindings = bindings.clone();
9894            for pack_name in &pack_names {
9895                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
9896                    return None;
9897                };
9898                element_bindings.insert(
9899                    pack_name.clone(),
9900                    cpp_clone_template_term_iterative(&children[index]),
9901                );
9902            }
9903            substituted.push(CppTemplateExpression {
9904                text: argument.text.clone(),
9905                term: cpp_substitute_template_term(pattern, &element_bindings)?,
9906            });
9907        }
9908    }
9909    Some(substituted)
9910}
9911
9912fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
9913    enum Work<'a> {
9914        Visit(&'a CppTemplateTerm),
9915        Build { kind: String, child_count: usize },
9916    }
9917
9918    let mut work = vec![Work::Visit(term)];
9919    let mut cloned = Vec::new();
9920    while let Some(next) = work.pop() {
9921        match next {
9922            Work::Visit(CppTemplateTerm::Parameter(name)) => {
9923                cloned.push(CppTemplateTerm::Parameter(name.clone()));
9924            }
9925            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
9926                cloned.push(CppTemplateTerm::Atom {
9927                    kind: kind.clone(),
9928                    text: text.clone(),
9929                });
9930            }
9931            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
9932                work.push(Work::Build {
9933                    kind: kind.clone(),
9934                    child_count: children.len(),
9935                });
9936                work.extend(children.iter().rev().map(Work::Visit));
9937            }
9938            Work::Build { kind, child_count } => {
9939                let children = cloned.split_off(cloned.len() - child_count);
9940                cloned.push(CppTemplateTerm::Node { kind, children });
9941            }
9942        }
9943    }
9944    cloned
9945        .pop()
9946        .expect("template term traversal emits one root")
9947}
9948
9949fn cpp_clone_template_expression_iterative(
9950    expression: &CppTemplateExpression,
9951) -> CppTemplateExpression {
9952    CppTemplateExpression {
9953        text: expression.text.clone(),
9954        term: cpp_clone_template_term_iterative(&expression.term),
9955    }
9956}
9957
9958pub fn cpp_unify_template_term(
9959    pattern: &CppTemplateTerm,
9960    argument: &CppTemplateTerm,
9961    parameters: &HashSet<&str>,
9962    bindings: &mut HashMap<String, CppTemplateTerm>,
9963) -> bool {
9964    let mut work = vec![(pattern, argument)];
9965    while let Some((pattern, argument)) = work.pop() {
9966        match pattern {
9967            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
9968                if let Some(bound) = bindings.get(name) {
9969                    if !cpp_template_terms_equal(bound, argument) {
9970                        return false;
9971                    }
9972                } else {
9973                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
9974                }
9975            }
9976            CppTemplateTerm::Atom {
9977                kind: pattern_kind,
9978                text: pattern_text,
9979            } => {
9980                if !matches!(
9981                    argument,
9982                    CppTemplateTerm::Atom { kind, text }
9983                        if kind == pattern_kind && text == pattern_text
9984                ) {
9985                    return false;
9986                }
9987            }
9988            CppTemplateTerm::Node {
9989                kind: pattern_kind,
9990                children: pattern_children,
9991            } => {
9992                let CppTemplateTerm::Node { kind, children } = argument else {
9993                    return false;
9994                };
9995                if kind != pattern_kind || children.len() != pattern_children.len() {
9996                    return false;
9997                }
9998                work.extend(pattern_children.iter().zip(children).rev());
9999            }
10000            CppTemplateTerm::Parameter(_) => return false,
10001        }
10002    }
10003    true
10004}
10005
10006fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
10007    let mut work = vec![(left, right)];
10008    while let Some((left, right)) = work.pop() {
10009        match (left, right) {
10010            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
10011                if left != right {
10012                    return false;
10013                }
10014            }
10015            (
10016                CppTemplateTerm::Atom {
10017                    kind: left_kind,
10018                    text: left_text,
10019                },
10020                CppTemplateTerm::Atom {
10021                    kind: right_kind,
10022                    text: right_text,
10023                },
10024            ) => {
10025                if left_kind != right_kind || left_text != right_text {
10026                    return false;
10027                }
10028            }
10029            (
10030                CppTemplateTerm::Node {
10031                    kind: left_kind,
10032                    children: left_children,
10033                },
10034                CppTemplateTerm::Node {
10035                    kind: right_kind,
10036                    children: right_children,
10037                },
10038            ) => {
10039                if left_kind != right_kind || left_children.len() != right_children.len() {
10040                    return false;
10041                }
10042                work.extend(left_children.iter().zip(right_children).rev());
10043            }
10044            _ => return false,
10045        }
10046    }
10047    true
10048}
10049
10050pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
10051    let mut components = Vec::new();
10052    let mut stack = vec![node];
10053    while let Some(current) = stack.pop() {
10054        match current.kind() {
10055            "identifier"
10056            | "field_identifier"
10057            | "namespace_identifier"
10058            | "type_identifier"
10059            | "operator_name"
10060            | "destructor_name" => components.push(current),
10061            "template_type" | "template_function" => {
10062                stack.push(current.child_by_field_name("name")?);
10063            }
10064            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
10065                stack.push(current.child_by_field_name("name")?);
10066                if let Some(scope) = current.child_by_field_name("scope") {
10067                    stack.push(scope);
10068                }
10069            }
10070            "nested_namespace_specifier" => {
10071                for index in (0..current.named_child_count()).rev() {
10072                    stack.push(current.named_child(index)?);
10073                }
10074            }
10075            _ => return None,
10076        }
10077    }
10078    Some(components)
10079}
10080
10081pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
10082    node.child_by_field_name("scope").is_none()
10083        && node.child(0).is_some_and(|child| child.kind() == "::")
10084}
10085
10086fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10087    let mut namespaces = Vec::new();
10088    let mut current = node.parent();
10089    while let Some(parent) = current {
10090        if parent.kind() == "namespace_definition"
10091            && let Some(name) = parent.child_by_field_name("name")
10092        {
10093            let mut components = Vec::new();
10094            append_cpp_name_components(name, source, &mut components)?;
10095            namespaces.push(components);
10096        }
10097        current = parent.parent();
10098    }
10099    namespaces.reverse();
10100    Some(namespaces.into_iter().flatten().collect())
10101}
10102
10103/// Whether a parser-derived namespace path can be reconciled with an indexed
10104/// owner scope without inventing an unrelated short-name binding.
10105///
10106/// Macro namespace sentinels can make tree-sitter omit one or more namespace
10107/// definitions from the ancestor chain.  Preserve the order of every
10108/// namespace that did survive parsing, but allow indexed components between
10109/// them.  An empty path is deliberately rejected: a one-segment owner at the
10110/// translation-unit root is not evidence of a malformed namespace.
10111fn indexed_namespace_path_is_recoverable(
10112    lexical_scope: &[String],
10113    indexed_owner_scope: &[String],
10114) -> bool {
10115    if lexical_scope.is_empty() || lexical_scope.len() >= indexed_owner_scope.len() {
10116        return false;
10117    }
10118    let mut indexed = indexed_owner_scope.iter();
10119    lexical_scope
10120        .iter()
10121        .all(|component| indexed.any(|candidate| candidate == component))
10122}
10123
10124pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
10125    let mut current = node.parent();
10126    while let Some(parent) = current {
10127        if parent.kind() == kind {
10128            return true;
10129        }
10130        current = parent.parent();
10131    }
10132    false
10133}
10134
10135/// Return the terminal identifier represented by a callable or type callee.
10136///
10137/// Qualified, scoped, template, and field wrappers are traversed through their
10138/// grammar fields so both function calls and type constructions emit the token
10139/// that names the referenced declaration.
10140pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
10141    loop {
10142        let next = match node.kind() {
10143            "qualified_identifier"
10144            | "scoped_identifier"
10145            | "template_function"
10146            | "template_type" => node.child_by_field_name("name"),
10147            "field_expression" => node.child_by_field_name("field"),
10148            _ => None,
10149        };
10150        let Some(next) = next else {
10151            return node;
10152        };
10153        node = next;
10154    }
10155}
10156
10157/// Whether `node` is part of a call's callee expression, walking only through
10158/// the grammar wrappers that can structurally contain that callee.
10159pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
10160    while let Some(parent) = node.parent() {
10161        match parent.kind() {
10162            "call_expression" => {
10163                return parent
10164                    .child_by_field_name("function")
10165                    .or_else(|| parent.named_child(0))
10166                    == Some(node);
10167            }
10168            "qualified_identifier"
10169            | "scoped_identifier"
10170            | "template_function"
10171            | "template_type"
10172            | "field_expression" => node = parent,
10173            _ => return false,
10174        }
10175    }
10176    false
10177}
10178
10179pub fn type_reference_hit_node<'tree, T: Clone + Eq + Hash>(
10180    node: Node<'tree>,
10181    file: &ProjectFile,
10182    source: &str,
10183    bindings: &LocalInferenceEngine<T>,
10184) -> Node<'tree> {
10185    if is_call_callee_node(node) {
10186        return function_terminal_node(node);
10187    }
10188    if file.rel_path().extension().is_some_and(|ext| ext == "c") {
10189        return node;
10190    }
10191    let mut current = node;
10192    let declaration = loop {
10193        let Some(parent) = current.parent() else {
10194            return node;
10195        };
10196        if parent.kind() == "declaration" {
10197            break parent;
10198        }
10199        if matches!(
10200            parent.kind(),
10201            "compound_statement" | "function_definition" | "lambda_expression"
10202        ) {
10203            return node;
10204        }
10205        current = parent;
10206    };
10207    let Some(_type_node) = declaration.child_by_field_name("type").filter(|type_node| {
10208        type_node.start_byte() <= node.start_byte() && node.end_byte() <= type_node.end_byte()
10209    }) else {
10210        return node;
10211    };
10212    let mut cursor = declaration.walk();
10213    let constructs_object = declaration.named_children(&mut cursor).any(|child| {
10214        if child.kind() == "init_declarator" {
10215            return child.child_by_field_name("value").is_some()
10216                || first_named_child_of_kind(child, "initializer_list").is_some()
10217                || first_named_child_of_kind(child, "compound_literal_expression").is_some();
10218        }
10219        let declarator = if is_declarator_node(child) {
10220            Some(child)
10221        } else {
10222            None
10223        };
10224        declarator.is_some_and(|declarator| {
10225            declarator.kind() == "function_declarator"
10226                && has_ancestor_kind(declarator, "compound_statement")
10227                && declarator
10228                    .child_by_field_name("declarator")
10229                    .is_some_and(|name| name.kind() == "identifier")
10230                && declarator
10231                    .child_by_field_name("parameters")
10232                    .is_some_and(|parameters| {
10233                        constructor_parameters_look_like_expressions(parameters, source, bindings)
10234                    })
10235        })
10236    });
10237    if constructs_object {
10238        function_terminal_node(node)
10239    } else {
10240        node
10241    }
10242}
10243
10244pub fn normalize_type_text(value: &str) -> String {
10245    strip_tag_type_prefix(
10246        normalize_cpp_whitespace(value)
10247            .trim_start_matches("const ")
10248            .trim_end_matches('*')
10249            .trim_end_matches('&')
10250            .trim(),
10251    )
10252    .to_string()
10253}
10254
10255fn strip_tag_type_prefix(value: &str) -> &str {
10256    let value = value.trim_start_matches("const ");
10257    value
10258        .strip_prefix("struct ")
10259        .or_else(|| value.strip_prefix("class "))
10260        .or_else(|| value.strip_prefix("enum "))
10261        .unwrap_or(value)
10262        .trim()
10263}
10264
10265pub fn normalize_reference_name(value: &str) -> Option<String> {
10266    let normalized = normalize_cpp_reference_text(value);
10267    (!normalized.is_empty()).then_some(normalized)
10268}
10269
10270pub fn normalize_cpp_reference_text(value: &str) -> String {
10271    let mut text = normalize_cpp_whitespace(value)
10272        .trim_start_matches("new ")
10273        .trim()
10274        .to_string();
10275    if let Some(index) = text.find(['(', '{']) {
10276        text.truncate(index);
10277    }
10278    if let Some(index) = text.find('<') {
10279        text.truncate(index);
10280    }
10281    let normalized = text
10282        .trim()
10283        .trim_start_matches("const ")
10284        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
10285        .trim_matches(':')
10286        .trim();
10287    strip_tag_type_prefix(normalized).to_string()
10288}
10289
10290pub fn cpp_name_for(unit: &CodeUnit) -> String {
10291    let short = unit.short_name().replace(['.', '$'], "::");
10292    if unit.package_name().is_empty() {
10293        short
10294    } else {
10295        format!("{}::{}", unit.package_name(), short)
10296    }
10297}
10298
10299/// Render an indexed C++ qualified name from its authoritative FqName
10300/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
10301/// that belong to a template argument (for example `Args...`).
10302fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
10303    let fq = unit.fq();
10304    if fq.is_empty() {
10305        return None;
10306    }
10307    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10308    Some(
10309        fq.segments()
10310            .iter()
10311            .map(|&segment| interner.resolve(segment).0)
10312            .collect::<Vec<_>>()
10313            .join("::"),
10314    )
10315}
10316
10317fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
10318    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
10319        || unit.fq().is_empty() && cpp_name_for(unit) == expected
10320}
10321
10322/// Return the indexed C++ owner scope without reparsing its rendered name.
10323///
10324/// Template spellings are opaque within an indexed `FqName` segment.  In
10325/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
10326/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
10327/// through `parse_symbol_path` would mistake those dots for component
10328/// separators.  Cache-loaded/legacy units may still have an empty structured
10329/// name, so retain the parser only as that explicit fallback.
10330pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
10331    let fq = unit.fq();
10332    if !fq.is_empty() {
10333        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10334        let scope = fq
10335            .segments()
10336            .iter()
10337            .filter_map(|&segment| {
10338                let (text, kind) = interner.resolve(segment);
10339                matches!(
10340                    kind,
10341                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
10342                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
10343                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
10344                )
10345                .then(|| text.to_string())
10346            })
10347            .collect();
10348        return scope;
10349    }
10350    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10351        brokk_bifrost_core::analyzer::Language::Cpp,
10352        &cpp_name_for(unit),
10353    )
10354}
10355
10356// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
10357// (not the substring "->"), which deliberately reduces an `operator->`-style
10358// terminal segment to an empty tail rather than keeping it intact; the shared
10359// structured splitter's cpp operator-token merge would keep `operator->`
10360// whole instead, changing this function's result — `name_matches_callable`'s
10361// `expected.starts_with("operator")` fallback exists specifically to
10362// compensate for that reduction, and a pinned regression test
10363// (`operator-> must not be reduced with terminal_name-style punctuation
10364// splitting`) asserts today's char-class behavior. Not equivalence-provable;
10365// revisit alongside that pinned test if it is ever relaxed.
10366pub fn terminal_name(value: &str) -> &str {
10367    value
10368        .rsplit("::")
10369        .next()
10370        .unwrap_or(value)
10371        .rsplit(['.', '-', '>'])
10372        .next()
10373        .unwrap_or(value)
10374        .trim()
10375}
10376
10377pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
10378    terminal_name(&normalize_cpp_reference_text(value)) == expected
10379}
10380
10381pub fn name_matches_callable(value: &str, expected: &str) -> bool {
10382    name_matches_terminal(value, expected)
10383        || expected.starts_with("operator")
10384            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
10385}
10386
10387pub fn name_mentions(value: &str, expected: &str) -> bool {
10388    normalize_cpp_reference_text(value)
10389        .split("::")
10390        .any(|part| part == expected)
10391}
10392
10393pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
10394    let cpp_name = cpp_name_for(unit);
10395    if reference.contains("::") {
10396        return reference == cpp_name;
10397    }
10398    reference == cpp_name
10399        || terminal_name(reference) == unit.identifier()
10400            && (unit.package_name().is_empty() || reference == unit.identifier())
10401}
10402
10403pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
10404    match kind {
10405        TargetKind::Type
10406        | TargetKind::Constructor
10407        | TargetKind::Method
10408        | TargetKind::MemberField => true,
10409        TargetKind::FreeFunction => unit.is_function(),
10410        TargetKind::GlobalField => unit.is_field(),
10411        TargetKind::Macro => unit.is_macro(),
10412    }
10413}
10414
10415pub fn is_type_alias(unit: &CodeUnit) -> bool {
10416    unit.kind() == CodeUnitType::Field
10417        && unit.signature().is_some_and(|signature| {
10418            signature.starts_with("typedef ") || signature.starts_with("using ")
10419        })
10420}
10421
10422fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
10423    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
10424    let target_name = cpp_name_for(target);
10425    if normalized.contains("::") {
10426        return normalized == target_name;
10427    }
10428    if let Some(namespace) = alias.namespace.as_deref() {
10429        return namespace_prefixes(namespace)
10430            .into_iter()
10431            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
10432    }
10433    target.package_name().is_empty() && normalized == target.identifier()
10434}
10435
10436fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
10437    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
10438    if normalized.contains("::") {
10439        return vec![normalized];
10440    }
10441    alias
10442        .namespace
10443        .as_deref()
10444        .map(namespace_prefixes)
10445        .map(|prefixes| {
10446            prefixes
10447                .into_iter()
10448                .map(|prefix| format!("{prefix}::{normalized}"))
10449                .collect()
10450        })
10451        .unwrap_or_else(|| vec![normalized])
10452}
10453
10454/// The declared return type text of a C++ function unit, with leading declaration specifiers
10455/// stripped, e.g. `T*` for `T* operator->()`.
10456pub fn cpp_function_return_type_text(
10457    analyzer: &CppGraphSource<'_>,
10458    function: &CodeUnit,
10459) -> Option<String> {
10460    let metadata = analyzer.signature_metadata(function);
10461    if !metadata.is_empty() {
10462        let first = metadata.first()?.return_type_text()?;
10463        return metadata
10464            .iter()
10465            .all(|metadata| metadata.return_type_text() == Some(first))
10466            .then(|| first.to_string());
10467    }
10468    let signature = cpp_function_signature_text(analyzer, function)?;
10469    cpp_function_return_type_text_from_signature(&signature)
10470}
10471
10472fn cpp_function_signature_text(
10473    analyzer: &CppGraphSource<'_>,
10474    function: &CodeUnit,
10475) -> Option<String> {
10476    function
10477        .signature()
10478        .filter(|signature| signature.contains(function.identifier()))
10479        .map(str::to_string)
10480        .or_else(|| analyzer.signatures(function).first().cloned())
10481        .or_else(|| analyzer.get_source(function, false))
10482}
10483
10484fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
10485    let open = signature.find('(')?;
10486    let name_at = cpp_function_name_start(signature, open)?;
10487    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
10488        return Some(return_type);
10489    }
10490    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
10491        .split_whitespace()
10492        .filter(|token| {
10493            !matches!(
10494                *token,
10495                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
10496            )
10497        })
10498        .collect::<Vec<_>>()
10499        .join(" ");
10500    let type_text = type_text.trim();
10501    (!type_text.is_empty()).then(|| type_text.to_string())
10502}
10503
10504fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
10505    let before_parameters = &signature[..open];
10506    if let Some(operator_at) = before_parameters.rfind("operator") {
10507        let boundary = operator_at == 0
10508            || before_parameters[..operator_at]
10509                .chars()
10510                .next_back()
10511                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
10512        if boundary {
10513            return Some(operator_at);
10514        }
10515    }
10516    before_parameters
10517        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
10518        .map(|index| index + 1)
10519}
10520
10521fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
10522    let open = signature_from_name.find('(')?;
10523    let mut depth = 0i32;
10524    for (offset, ch) in signature_from_name[open..].char_indices() {
10525        match ch {
10526            '(' => depth += 1,
10527            ')' => {
10528                depth -= 1;
10529                if depth == 0 {
10530                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
10531                    let arrow = rest.find("->")?;
10532                    let return_type = rest[arrow + 2..].trim_start();
10533                    let return_type = return_type
10534                        .split(['{', ';'])
10535                        .next()
10536                        .unwrap_or(return_type)
10537                        .trim();
10538                    return (!return_type.is_empty()).then(|| return_type.to_string());
10539                }
10540            }
10541            _ => {}
10542        }
10543    }
10544    None
10545}
10546
10547/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
10548/// Returns the input unchanged when there is no such clause.
10549fn cpp_strip_leading_template_clause(text: &str) -> &str {
10550    let trimmed = text.trim_start();
10551    let Some(rest) = trimmed.strip_prefix("template") else {
10552        return text;
10553    };
10554    let rest = rest.trim_start();
10555    if !rest.starts_with('<') {
10556        return text;
10557    }
10558    let mut depth = 0i32;
10559    for (offset, ch) in rest.char_indices() {
10560        match ch {
10561            '<' => depth += 1,
10562            '>' => {
10563                depth -= 1;
10564                if depth == 0 {
10565                    return rest[offset + ch.len_utf8()..].trim_start();
10566                }
10567            }
10568            _ => {}
10569        }
10570    }
10571    text
10572}
10573
10574pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
10575    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
10576    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
10577    // the same string `default_parent_fq_name`/`fq().parent()` would render:
10578    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
10579    // `::`) between a trailing `Package` segment and a following `Type`
10580    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
10581    // popping the unit's own `fq()` segment would NOT reproduce this
10582    // fully-`::`-joined string. Left as a split on the locally-built
10583    // all-colon string rather than the unit's structured name.
10584    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
10585        namespace
10586            .strip_prefix("anonymous_namespace::")
10587            .unwrap_or(namespace)
10588            .to_string()
10589    })
10590}
10591
10592fn namespace_prefixes(namespace: &str) -> Vec<String> {
10593    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
10594    // non-`::` separator already converted to `::`, so re-tokenizing it with
10595    // the shared structured splitter and progressively popping the last
10596    // component reproduces the `rsplit_once("::")` outward walk exactly (same
10597    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
10598    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10599        brokk_bifrost_core::analyzer::Language::Cpp,
10600        namespace,
10601    );
10602    let mut prefixes = Vec::new();
10603    while !parts.is_empty() {
10604        prefixes.push(parts.join("::"));
10605        parts.pop();
10606    }
10607    prefixes
10608}
10609
10610fn nearest_namespace_candidates(
10611    candidates: Vec<CodeUnit>,
10612    normalized: &str,
10613    lexical_namespace: Option<&str>,
10614) -> Vec<CodeUnit> {
10615    if normalized.contains("::") {
10616        return candidates;
10617    }
10618    if let Some(namespace) = lexical_namespace {
10619        for prefix in namespace_prefixes(namespace) {
10620            let scoped = candidates
10621                .iter()
10622                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
10623                .cloned()
10624                .collect::<Vec<_>>();
10625            if !scoped.is_empty() {
10626                return scoped;
10627            }
10628        }
10629    }
10630    candidates
10631        .into_iter()
10632        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
10633        .collect()
10634}
10635
10636pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
10637    let mut namespaces = Vec::new();
10638    let mut current = node.parent();
10639    while let Some(parent) = current {
10640        if parent.kind() == "namespace_definition"
10641            && let Some(name) = parent.child_by_field_name("name")
10642        {
10643            let namespace = normalize_cpp_reference_text(node_text(name, source));
10644            if !namespace.is_empty() {
10645                namespaces.push(namespace);
10646            }
10647        }
10648        current = parent.parent();
10649    }
10650    if namespaces.is_empty() {
10651        None
10652    } else {
10653        namespaces.reverse();
10654        Some(namespaces.join("::"))
10655    }
10656}
10657
10658/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
10659/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
10660/// globals rather than members.
10661pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
10662    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
10663}
10664
10665fn type_owner_resolution(
10666    analyzer: &CppGraphSource<'_>,
10667    code_unit: &CodeUnit,
10668) -> Option<ResolvedTypeOwner> {
10669    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
10670}
10671
10672fn target_type_owner_resolution(
10673    analyzer: &CppGraphSource<'_>,
10674    code_unit: &CodeUnit,
10675) -> Option<ResolvedTypeOwner> {
10676    match type_owner_resolution(analyzer, code_unit) {
10677        Some(owner) if !owner.is_forward_declaration => Some(owner),
10678        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
10679    }
10680}
10681
10682/// Recover method identity for an indexed out-of-line definition when the
10683/// analyzer has retained only its unique include-visible class forward
10684/// declaration. This is deliberately target-only: canonical declaration
10685/// resolution must continue to prefer the callable definition rather than
10686/// replacing it with the forward owner.
10687fn target_forward_owner_resolution(
10688    analyzer: &CppGraphSource<'_>,
10689    code_unit: &CodeUnit,
10690) -> Option<ResolvedTypeOwner> {
10691    if !code_unit.is_function() {
10692        return None;
10693    }
10694    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10695    let owner_fqn = code_unit.fq().parent()?.display(interner);
10696    let cpp = analyzer.cpp?;
10697    let mut visible_files = HashSet::default();
10698    collect_include_closure(
10699        analyzer,
10700        cpp.include_target_index(),
10701        code_unit.source(),
10702        &mut visible_files,
10703        None,
10704    );
10705    let mut forward = None;
10706    for candidate in analyzer
10707        .global_usage_definition_index()
10708        .fqn(&owner_fqn)
10709        .into_iter()
10710        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
10711    {
10712        match cpp_class_declaration_strength(analyzer, candidate) {
10713            CppClassDeclarationStrength::Forward if forward.is_none() => {
10714                forward = Some(candidate.clone());
10715            }
10716            CppClassDeclarationStrength::Forward
10717            | CppClassDeclarationStrength::Full
10718            | CppClassDeclarationStrength::Unknown => return None,
10719        }
10720    }
10721    forward.map(|unit| ResolvedTypeOwner {
10722        unit,
10723        is_forward_declaration: true,
10724    })
10725}
10726
10727pub fn precise_parent_of(
10728    analyzer: &CppGraphSource<'_>,
10729    visibility: &VisibilityIndex<'_>,
10730    code_unit: &CodeUnit,
10731) -> Option<CodeUnit> {
10732    visibility.cached_precise_parent_of(analyzer, code_unit)
10733}
10734
10735fn precise_parent_resolution(
10736    analyzer: &CppGraphSource<'_>,
10737    code_unit: &CodeUnit,
10738) -> Option<ResolvedTypeOwner> {
10739    #[cfg(any(test, feature = "test-support"))]
10740    if let Some(cpp) = analyzer.cpp {
10741        cpp.record_cpp_parent_resolution_for_test();
10742    }
10743    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
10744        return Some(ResolvedTypeOwner {
10745            unit,
10746            is_forward_declaration: false,
10747        });
10748    }
10749    let fallback = analyzer.parent_of(code_unit);
10750    // fqname-M4: `owner_name` is used both bare (passed standalone to the
10751    // owner-resolution calls below) and manually recombined with
10752    // `package_name()` a few lines down, so this needs the package-less
10753    // `short_name` owner specifically; `default_parent_fq_name`/`fq.parent()`
10754    // would render the package-qualified owner instead, changing both uses.
10755    let Some(owner_name) = code_unit
10756        .short_name()
10757        .rsplit_once('.')
10758        .map(|(owner, _)| owner)
10759    else {
10760        return fallback.map(|unit| ResolvedTypeOwner {
10761            unit,
10762            is_forward_declaration: false,
10763        });
10764    };
10765    let owner_fqn = if code_unit.package_name().is_empty() {
10766        owner_name.to_string()
10767    } else {
10768        format!("{}.{}", code_unit.package_name(), owner_name)
10769    };
10770    match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10771        DirectOwnerResolution::UniqueFull(owner) => {
10772            return Some(ResolvedTypeOwner {
10773                unit: owner,
10774                is_forward_declaration: false,
10775            });
10776        }
10777        DirectOwnerResolution::Ambiguous => return None,
10778        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
10779    }
10780    match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10781        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
10782            unit: owner,
10783            is_forward_declaration: false,
10784        }),
10785        DirectOwnerResolution::Ambiguous => None,
10786        DirectOwnerResolution::ForwardsOnly(forwards) => {
10787            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10788                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
10789                    unit: owner,
10790                    is_forward_declaration: false,
10791                }),
10792                FullOwnerResolution::None => {
10793                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
10794                        unit,
10795                        is_forward_declaration: true,
10796                    })
10797                }
10798                FullOwnerResolution::Ambiguous => None,
10799            }
10800        }
10801        DirectOwnerResolution::None => {
10802            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10803                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
10804                    unit: owner,
10805                    is_forward_declaration: false,
10806                }),
10807                FullOwnerResolution::Ambiguous => None,
10808                FullOwnerResolution::None => fallback
10809                    .filter(|parent| {
10810                        parent.source() == code_unit.source()
10811                            && parent.short_name() == owner_name
10812                            && parent.package_name() == code_unit.package_name()
10813                            && (!parent.is_class()
10814                                || cpp_class_declaration_strength(analyzer, parent)
10815                                    == CppClassDeclarationStrength::Full)
10816                    })
10817                    .map(|unit| ResolvedTypeOwner {
10818                        unit,
10819                        is_forward_declaration: false,
10820                    }),
10821            }
10822        }
10823    }
10824}
10825
10826fn exact_structural_type_parent(
10827    analyzer: &CppGraphSource<'_>,
10828    code_unit: &CodeUnit,
10829) -> Option<CodeUnit> {
10830    if !code_unit.is_function() && !code_unit.is_field() {
10831        return None;
10832    }
10833    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
10834    let cpp = analyzer.cpp?;
10835    let parent = cpp.structural_parent_of(code_unit)?;
10836    (!parent.is_module()
10837        && parent.source() == code_unit.source()
10838        && parent.package_name() == code_unit.package_name()
10839        && parent.short_name() == encoded_owner)
10840        .then_some(parent)
10841}
10842
10843fn same_source_owner(
10844    analyzer: &CppGraphSource<'_>,
10845    code_unit: &CodeUnit,
10846    owner_fqn: &str,
10847    owner_name: &str,
10848) -> DirectOwnerResolution {
10849    let candidates = analyzer
10850        .global_usage_definition_index()
10851        .fqn(owner_fqn)
10852        .into_iter()
10853        .filter(|candidate| {
10854            candidate.is_class()
10855                && candidate.source() == code_unit.source()
10856                && candidate.short_name() == owner_name
10857                && candidate.package_name() == code_unit.package_name()
10858        })
10859        .collect::<Vec<_>>();
10860    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10861    classify_direct_owner_candidates(analyzer, candidates.into_iter())
10862}
10863
10864fn visible_full_cpp_owner(
10865    analyzer: &CppGraphSource<'_>,
10866    code_unit: &CodeUnit,
10867    owner_fqn: &str,
10868    owner_name: &str,
10869) -> FullOwnerResolution {
10870    let Some(cpp) = analyzer.cpp else {
10871        return FullOwnerResolution::None;
10872    };
10873    let mut visible_files = HashSet::default();
10874    collect_include_closure(
10875        analyzer,
10876        cpp.include_target_index(),
10877        code_unit.source(),
10878        &mut visible_files,
10879        None,
10880    );
10881    let candidates = analyzer
10882        .global_usage_definition_index()
10883        .fqn(owner_fqn)
10884        .into_iter()
10885        .filter(|candidate| {
10886            candidate.is_class()
10887                && candidate.short_name() == owner_name
10888                && candidate.package_name() == code_unit.package_name()
10889                && visible_files.contains(candidate.source())
10890        })
10891        .collect::<Vec<_>>();
10892    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10893    let mut full_definition = None;
10894    for candidate in candidates {
10895        match cpp_class_declaration_strength(analyzer, candidate) {
10896            CppClassDeclarationStrength::Full if full_definition.is_some() => {
10897                return FullOwnerResolution::Ambiguous;
10898            }
10899            CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
10900            CppClassDeclarationStrength::Forward => {}
10901            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
10902        }
10903    }
10904    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
10905}
10906
10907pub enum DirectOwnerResolution {
10908    None,
10909    ForwardsOnly(Vec<CodeUnit>),
10910    UniqueFull(CodeUnit),
10911    Ambiguous,
10912}
10913
10914enum FullOwnerResolution {
10915    None,
10916    Unique(CodeUnit),
10917    Ambiguous,
10918}
10919
10920#[derive(Clone, Copy, PartialEq, Eq)]
10921pub enum CppClassDeclarationStrength {
10922    Full,
10923    Forward,
10924    Unknown,
10925}
10926
10927fn directly_included_owner(
10928    analyzer: &CppGraphSource<'_>,
10929    code_unit: &CodeUnit,
10930    owner_fqn: &str,
10931    owner_name: &str,
10932) -> DirectOwnerResolution {
10933    let Some(cpp) = analyzer.cpp else {
10934        return DirectOwnerResolution::None;
10935    };
10936    let imports = analyzer.import_statements(code_unit.source());
10937    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
10938        .into_iter()
10939        .flat_map(|include| {
10940            resolve_include_targets_with_index(
10941                code_unit.source(),
10942                &include,
10943                cpp.include_target_index(),
10944            )
10945        })
10946        .collect();
10947    let candidates = analyzer
10948        .global_usage_definition_index()
10949        .fqn(owner_fqn)
10950        .into_iter()
10951        .filter(|candidate| {
10952            candidate.is_class()
10953                && candidate.short_name() == owner_name
10954                && candidate.package_name() == code_unit.package_name()
10955                && direct_includes.contains(candidate.source())
10956        })
10957        .collect::<Vec<_>>();
10958    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10959    classify_direct_owner_candidates(analyzer, candidates.into_iter())
10960}
10961
10962fn prefer_member_declaring_owners<'a>(
10963    analyzer: &CppGraphSource<'_>,
10964    member: &CodeUnit,
10965    candidates: Vec<&'a CodeUnit>,
10966) -> Vec<&'a CodeUnit> {
10967    let matching = candidates
10968        .iter()
10969        .copied()
10970        .filter(|owner| owner_declares_member(analyzer, owner, member))
10971        .collect::<Vec<_>>();
10972    if matching.is_empty() {
10973        candidates
10974    } else {
10975        matching
10976    }
10977}
10978
10979fn owner_declares_member(
10980    analyzer: &CppGraphSource<'_>,
10981    owner: &CodeUnit,
10982    member: &CodeUnit,
10983) -> bool {
10984    analyzer.direct_children(owner).into_iter().any(|child| {
10985        child.kind() == member.kind()
10986            && child.identifier() == member.identifier()
10987            && child.signature() == member.signature()
10988    })
10989}
10990
10991fn classify_direct_owner_candidates<'a>(
10992    analyzer: &CppGraphSource<'_>,
10993    candidates: impl Iterator<Item = &'a CodeUnit>,
10994) -> DirectOwnerResolution {
10995    collapse_owner_candidates(candidates.map(|candidate| {
10996        (
10997            candidate.clone(),
10998            cpp_class_declaration_strength(analyzer, candidate),
10999        )
11000    }))
11001}
11002
11003pub fn collapse_owner_candidates(
11004    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
11005) -> DirectOwnerResolution {
11006    let mut full_definition = None;
11007    let mut forwards = Vec::new();
11008    for (candidate, strength) in candidates {
11009        match strength {
11010            CppClassDeclarationStrength::Full if full_definition.is_some() => {
11011                return DirectOwnerResolution::Ambiguous;
11012            }
11013            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
11014            CppClassDeclarationStrength::Forward => forwards.push(candidate),
11015            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
11016        }
11017    }
11018    if let Some(owner) = full_definition {
11019        DirectOwnerResolution::UniqueFull(owner)
11020    } else if !forwards.is_empty() {
11021        DirectOwnerResolution::ForwardsOnly(forwards)
11022    } else {
11023        DirectOwnerResolution::None
11024    }
11025}
11026
11027#[cfg(any(test, feature = "test-support"))]
11028pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
11029    unique_logical_forward_owner(forwards)
11030}
11031
11032fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
11033    let first = forwards.pop()?;
11034    forwards
11035        .iter()
11036        .all(|forward| same_logical_symbol(forward, &first))
11037        .then_some(first)
11038}
11039
11040pub fn cpp_class_declaration_strength(
11041    analyzer: &CppGraphSource<'_>,
11042    candidate: &CodeUnit,
11043) -> CppClassDeclarationStrength {
11044    if let Some(prepared) = analyzer
11045        .cpp
11046        .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
11047    {
11048        return cpp_class_declaration_strength_in_tree(
11049            analyzer,
11050            candidate,
11051            prepared.source(),
11052            prepared.tree().root_node(),
11053        );
11054    }
11055    let Some(source) = analyzer.indexed_source(candidate.source()) else {
11056        return CppClassDeclarationStrength::Unknown;
11057    };
11058    #[cfg(any(test, feature = "test-support"))]
11059    if let Some(cpp) = analyzer.cpp {
11060        cpp.record_cpp_class_strength_parse_for_test();
11061    }
11062    let mut parser = Parser::new();
11063    if parser
11064        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11065        .is_err()
11066    {
11067        return CppClassDeclarationStrength::Unknown;
11068    }
11069    let Some(tree) = parser.parse(&source, None) else {
11070        return CppClassDeclarationStrength::Unknown;
11071    };
11072    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
11073}
11074
11075fn cpp_class_declaration_strength_in_tree(
11076    analyzer: &CppGraphSource<'_>,
11077    candidate: &CodeUnit,
11078    source: &str,
11079    root: Node<'_>,
11080) -> CppClassDeclarationStrength {
11081    let ranges = analyzer.ranges(candidate);
11082    let mut saw_forward = false;
11083    for range in ranges {
11084        let mut stack = vec![root];
11085        while let Some(node) = stack.pop() {
11086            if node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
11087                continue;
11088            }
11089            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
11090                if matches!(
11091                    node.kind(),
11092                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11093                ) {
11094                    if cpp_class_node_has_body(node) {
11095                        return CppClassDeclarationStrength::Full;
11096                    }
11097                    saw_forward = true;
11098                } else if let Some(has_body) =
11099                    recovered_exported_class_has_body(node, source, candidate.identifier())
11100                {
11101                    if has_body {
11102                        return CppClassDeclarationStrength::Full;
11103                    }
11104                    saw_forward = true;
11105                }
11106            }
11107            let mut cursor = node.walk();
11108            stack.extend(node.named_children(&mut cursor));
11109        }
11110    }
11111    if saw_forward {
11112        CppClassDeclarationStrength::Forward
11113    } else {
11114        CppClassDeclarationStrength::Unknown
11115    }
11116}
11117
11118fn cpp_class_node_has_body(node: Node<'_>) -> bool {
11119    node.child_by_field_name("body").is_some() || {
11120        let mut cursor = node.walk();
11121        node.named_children(&mut cursor).any(|child| {
11122            matches!(
11123                child.kind(),
11124                "declaration_list" | "field_declaration_list" | "enumerator_list"
11125            )
11126        })
11127    }
11128}
11129
11130pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
11131    // fqname-M4: `owner_name` is used both bare and manually recombined with
11132    // `package_name()` below (same package-less short_name owner shape as
11133    // `precise_parent_resolution` above); `default_parent_fq_name` would
11134    // render the package-qualified owner instead, changing both uses.
11135    let owner_name = code_unit
11136        .short_name()
11137        .rsplit_once('.')
11138        .map(|(owner, _)| owner)?;
11139    let owner_fqn = if code_unit.package_name().is_empty() {
11140        owner_name.to_string()
11141    } else {
11142        format!("{}.{}", code_unit.package_name(), owner_name)
11143    };
11144    ctx.analyzer
11145        .global_usage_definition_index()
11146        .fqn(&owner_fqn)
11147        .into_iter()
11148        .find(|candidate| {
11149            candidate.is_class()
11150                && ctx.visibility.is_visible(ctx.file, candidate)
11151                && candidate.short_name() == owner_name
11152                && candidate.package_name() == code_unit.package_name()
11153        })
11154        .cloned()
11155}
11156
11157pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11158    left.kind() == right.kind()
11159        && left.fq_name() == right.fq_name()
11160        && left.signature() == right.signature()
11161        && left.source() == right.source()
11162}
11163
11164pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11165    same_symbol(left, right) || same_logical_symbol(left, right)
11166}
11167
11168pub fn same_visible_global_field_symbol(
11169    analyzer: &CppGraphSource<'_>,
11170    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
11171    left: &CodeUnit,
11172    right: &CodeUnit,
11173) -> bool {
11174    if same_symbol(left, right) {
11175        return true;
11176    }
11177    if !same_logical_symbol(left, right) {
11178        return false;
11179    }
11180    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
11181        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
11182    {
11183        left.source() == right.source()
11184    } else {
11185        true
11186    }
11187}
11188
11189fn cpp_global_field_has_internal_linkage_cached(
11190    analyzer: &CppGraphSource<'_>,
11191    cache: &mut HashMap<CodeUnit, bool>,
11192    candidate: &CodeUnit,
11193) -> bool {
11194    if let Some(internal) = cache.get(candidate) {
11195        return *internal;
11196    }
11197    #[cfg(any(test, feature = "test-support"))]
11198    note_cpp_global_field_internal_linkage_classification_for_test();
11199    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
11200    cache.insert(candidate.clone(), internal);
11201    internal
11202}
11203
11204#[cfg(any(test, feature = "test-support"))]
11205thread_local! {
11206    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
11207}
11208
11209#[cfg(any(test, feature = "test-support"))]
11210fn note_cpp_global_field_internal_linkage_classification_for_test() {
11211    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
11212        count.set(count.get() + 1);
11213    });
11214}
11215
11216#[cfg(any(test, feature = "test-support"))]
11217pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
11218    body: impl FnOnce() -> T,
11219) -> (T, usize) {
11220    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
11221        count.set(0);
11222        let result = body();
11223        let observed = count.get();
11224        count.set(0);
11225        (result, observed)
11226    })
11227}
11228
11229pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11230    left.kind() == right.kind()
11231        && left.fq_name() == right.fq_name()
11232        && left.signature() == right.signature()
11233}
11234
11235pub fn cpp_global_field_has_internal_linkage(
11236    analyzer: &CppGraphSource<'_>,
11237    candidate: &CodeUnit,
11238) -> bool {
11239    if !candidate.is_field() || candidate.short_name().contains('.') {
11240        return false;
11241    }
11242    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
11243        return false;
11244    };
11245    match local_linkage {
11246        CppFieldLinkage::Internal => true,
11247        CppFieldLinkage::External => false,
11248        CppFieldLinkage::InternalUnlessExternalPeer => {
11249            !cpp_global_field_linkage_peers(analyzer, candidate)
11250                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
11251                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
11252        }
11253    }
11254}
11255
11256fn cpp_global_field_linkage_peers<'a>(
11257    analyzer: &CppGraphSource<'a>,
11258    candidate: &'a CodeUnit,
11259) -> impl Iterator<Item = &'a CodeUnit> + 'a {
11260    // These peers are returned to the caller, so they must borrow the analyzer
11261    // for `'a` rather than a handle that dies with this call. `fqn` reads the
11262    // shards directly for exactly that reason.
11263    let fq_name = candidate.fq_name();
11264    analyzer
11265        .global_usage_definition_index()
11266        .fqn(&fq_name)
11267        .into_iter()
11268        .filter(move |peer| {
11269            if *peer == candidate {
11270                return false;
11271            }
11272            #[cfg(any(test, feature = "test-support"))]
11273            note_cpp_global_field_linkage_peer_inspection_for_test();
11274            same_logical_symbol(peer, candidate)
11275        })
11276}
11277
11278#[cfg(any(test, feature = "test-support"))]
11279thread_local! {
11280    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
11281}
11282
11283#[cfg(any(test, feature = "test-support"))]
11284fn note_cpp_global_field_linkage_peer_inspection_for_test() {
11285    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
11286        count.set(count.get() + 1);
11287    });
11288}
11289
11290#[cfg(any(test, feature = "test-support"))]
11291pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
11292    body: impl FnOnce() -> T,
11293) -> (T, usize) {
11294    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
11295        count.set(0);
11296        let result = body();
11297        let observed = count.get();
11298        count.set(0);
11299        (result, observed)
11300    })
11301}
11302
11303fn cpp_global_field_declaration_linkage(
11304    analyzer: &CppGraphSource<'_>,
11305    candidate: &CodeUnit,
11306) -> Option<CppFieldLinkage> {
11307    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
11308        return Some(linkage);
11309    }
11310    let cpp = analyzer.cpp?;
11311    if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
11312        return cpp_global_field_declaration_linkage_in_tree(
11313            analyzer,
11314            candidate,
11315            prepared.source(),
11316            prepared.tree().root_node(),
11317        );
11318    }
11319    let source = analyzer.indexed_source(candidate.source())?;
11320    let mut parser = Parser::new();
11321    if parser
11322        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11323        .is_err()
11324    {
11325        return None;
11326    }
11327    let tree = parser.parse(&source, None)?;
11328    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
11329}
11330
11331fn cpp_global_field_declaration_linkage_in_tree(
11332    analyzer: &CppGraphSource<'_>,
11333    candidate: &CodeUnit,
11334    source: &str,
11335    root: Node<'_>,
11336) -> Option<CppFieldLinkage> {
11337    analyzer.ranges(candidate).iter().find_map(|range| {
11338        node_for_exact_range(root, range)
11339            .and_then(enclosing_cpp_field_declaration)
11340            .map(|declaration| cpp_field_declaration_linkage(declaration, source))
11341    })
11342}
11343
11344fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
11345    loop {
11346        if matches!(node.kind(), "declaration" | "field_declaration") {
11347            return Some(node);
11348        }
11349        node = node.parent()?;
11350    }
11351}
11352
11353#[cfg(test)]
11354mod tests {
11355    use super::*;
11356
11357    #[test]
11358    fn sort_lookup_units_totally_orders_every_identity_field() {
11359        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
11360        let base = CodeUnit::with_signature(
11361            file.clone(),
11362            CodeUnitType::Function,
11363            "scope",
11364            "value",
11365            Some("()".to_string()),
11366            false,
11367        );
11368        let different_kind = CodeUnit::with_signature(
11369            file.clone(),
11370            CodeUnitType::Field,
11371            "scope",
11372            "value",
11373            Some("()".to_string()),
11374            false,
11375        );
11376        let synthetic = base.with_synthetic(true);
11377
11378        let interner = segment_interner();
11379        let mut member_fq = FqName::new();
11380        member_fq.push(interner.intern("scope", SegmentKind::Package));
11381        member_fq.push(interner.intern("value", SegmentKind::Member));
11382        let different_package_boundary = CodeUnit::from_fq(
11383            file.clone(),
11384            CodeUnitType::Function,
11385            member_fq,
11386            0,
11387            Some("()".to_string()),
11388            false,
11389        );
11390
11391        let mut unknown_fq = FqName::new();
11392        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
11393        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
11394        let different_segment_kind = CodeUnit::from_fq(
11395            file,
11396            CodeUnitType::Function,
11397            unknown_fq,
11398            1,
11399            Some("()".to_string()),
11400            false,
11401        );
11402
11403        let input = vec![
11404            base,
11405            different_kind,
11406            synthetic,
11407            different_package_boundary,
11408            different_segment_kind,
11409        ];
11410        let mut expected = input.clone();
11411        sort_lookup_units(&mut expected);
11412        assert!(expected.windows(2).all(|pair| {
11413            let mut ordered = pair.to_vec();
11414            sort_lookup_units(&mut ordered);
11415            ordered == pair && pair[0] != pair[1]
11416        }));
11417
11418        let mut reversed = input.clone();
11419        reversed.reverse();
11420        sort_lookup_units(&mut reversed);
11421        assert_eq!(reversed, expected);
11422
11423        let mut rotated = input;
11424        rotated.rotate_left(2);
11425        sort_lookup_units(&mut rotated);
11426        assert_eq!(rotated, expected);
11427    }
11428
11429    #[test]
11430    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
11431        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";
11432        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
11433        let parse = |source: &str| {
11434            let mut parser = Parser::new();
11435            parser
11436                .set_language(&tree_sitter_cpp::LANGUAGE.into())
11437                .expect("C++ grammar");
11438            parser.parse(source, None).expect("fixture tree")
11439        };
11440
11441        let tree = parse(damaged);
11442        let root = tree.root_node();
11443        let target = damaged.find("target").expect("target byte");
11444        let declaration = root
11445            .descendant_for_byte_range(target, target + "target".len())
11446            .and_then(|mut node| {
11447                loop {
11448                    if node.kind() == "declaration" {
11449                        break Some(node);
11450                    }
11451                    node = node.parent()?;
11452                }
11453            })
11454            .expect("declaration after the displaced terminator");
11455        let conditional = declaration
11456            .parent()
11457            .filter(|node| node.kind() == "preproc_ifdef")
11458            .expect("damaged inner conditional");
11459        let outer = conditional
11460            .parent()
11461            .filter(|node| node.kind() == "preproc_ifdef")
11462            .expect("ordinary outer include guard");
11463        let terminator = cpp_displaced_preprocessor_terminator(conditional)
11464            .expect("structured displaced #endif");
11465        assert_eq!(node_text(terminator, damaged), "#endif");
11466        assert!(terminator.end_byte() <= declaration.start_byte());
11467        assert!(!preprocessor_conditional_contains_descendant(
11468            conditional,
11469            declaration
11470        ));
11471        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
11472        assert!(preprocessor_conditional_contains_descendant(
11473            outer,
11474            declaration
11475        ));
11476
11477        let tree = parse(guarded);
11478        let conditional = tree
11479            .root_node()
11480            .named_child(0)
11481            .filter(|node| node.kind() == "preproc_ifdef")
11482            .expect("ordinary conditional");
11483        let declaration = conditional
11484            .named_children(&mut conditional.walk())
11485            .find(|node| node.kind() == "declaration")
11486            .expect("guarded declaration");
11487        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
11488        assert!(preprocessor_conditional_contains_descendant(
11489            conditional,
11490            declaration
11491        ));
11492
11493        let damaged_alternative = format!(
11494            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
11495            "UNUSED(value)\n".repeat(64)
11496        );
11497        let tree = parse(&damaged_alternative);
11498        let conditional = tree
11499            .root_node()
11500            .named_child(0)
11501            .filter(|node| node.kind() == "preproc_ifdef")
11502            .expect("outer conditional with an alternative");
11503        assert!(conditional.has_error());
11504        assert!(conditional.child_by_field_name("alternative").is_some());
11505        assert!(
11506            conditional
11507                .child(conditional.child_count() - 1)
11508                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
11509        );
11510        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
11511    }
11512
11513    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
11514        let mut parser = Parser::new();
11515        parser
11516            .set_language(&tree_sitter_cpp::LANGUAGE.into())
11517            .expect("C++ grammar");
11518        let tree = parser.parse(source, None).expect("C++ fixture tree");
11519        let mut stack = vec![tree.root_node()];
11520        while let Some(node) = stack.pop() {
11521            if node.kind() == "enum_specifier" {
11522                return flattened_macro_namespace_components(node, source);
11523            }
11524            let mut cursor = node.walk();
11525            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
11526            stack.extend(children.into_iter().rev());
11527        }
11528        None
11529    }
11530
11531    #[test]
11532    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
11533        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
11534namespace detail
11535{
11536enum class value_t { null };
11537}
11538NLOHMANN_JSON_NAMESPACE_END
11539NLOHMANN_JSON_NAMESPACE_BEGIN
11540namespace next
11541{
11542struct next_type {};
11543}
11544NLOHMANN_JSON_NAMESPACE_END
11545"#;
11546        assert_eq!(
11547            first_enum_flattened_namespace(complete),
11548            Some(vec!["detail".to_string()])
11549        );
11550
11551        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
11552        assert_eq!(
11553            first_enum_flattened_namespace(&stale_end),
11554            Some(vec!["detail".to_string()]),
11555            "a stale end marker before the begin marker must not replace the intended namespace"
11556        );
11557
11558        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
11559namespace detail
11560{
11561enum class value_t { null };
11562}
11563struct next_type {};
11564"#;
11565        assert_eq!(first_enum_flattened_namespace(incomplete), None);
11566    }
11567}