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