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