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