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::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8    CppComparableNode, CppComparableParameter, CppComparableSlot, cpp_callable_identity_suffix,
9    cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11    cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12    recovered_embedded_function_like_export_class_has_body, recovered_exported_class_has_body,
13    recovered_fragmented_plain_class_has_body, recovered_function_like_export_class_pair_has_body,
14};
15use crate::graph::CppGraphSource;
16use crate::graph::extractor::ScanCtx;
17use crate::graph::syntax::object_macro_replacement_type_references;
18use crate::graph_support::CppSource;
19use crate::imports::{
20    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
21};
22use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
23use brokk_bifrost_core::analyzer::model::{
24    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
25    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
26};
27use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
28use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
29use brokk_bifrost_core::analyzer::query_token::QueryToken;
30use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
31use brokk_bifrost_core::analyzer::usages::common::same_node;
32use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
33use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
34use brokk_bifrost_core::cancellation::CancellationToken;
35use brokk_bifrost_core::hash::{HashMap, HashSet};
36use std::borrow::Cow;
37#[cfg(any(test, feature = "test-support"))]
38use std::cell::Cell;
39use std::cell::OnceCell;
40use std::cmp::Ordering as CmpOrdering;
41use std::collections::BTreeSet;
42use std::hash::Hash;
43use std::sync::atomic::{AtomicUsize, Ordering};
44use std::sync::{Arc, Mutex, OnceLock, RwLock};
45use std::thread::ThreadId;
46use std::time::{Duration, Instant};
47use tree_sitter::{Node, Parser, Tree};
48
49#[cfg(any(test, feature = "test-support"))]
50thread_local! {
51    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
52}
53
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub enum TargetKind {
56    Type,
57    Constructor,
58    FreeFunction,
59    Method,
60    GlobalField,
61    MemberField,
62    Macro,
63}
64
65pub enum LexicalTypeResolution {
66    Resolved {
67        unit: CodeUnit,
68        components: Vec<String>,
69        candidates: Vec<CodeUnit>,
70    },
71    Ambiguous,
72    Missing,
73}
74
75#[derive(Clone, Copy)]
76enum TypeCandidateResolution<'a> {
77    Canonical,
78    PreserveAlias,
79    PreserveTarget(&'a CodeUnit),
80}
81
82/// Why a name did not reduce to one indexed type declaration.
83///
84/// The two answers are not interchangeable. `Ambiguous` means the index holds
85/// several declarations and the caller must choose; `Unresolvable` means the
86/// index holds none, which is a boundary the workspace cannot see past. A
87/// `using`/`typedef` alias to a template parameter or to a standard-library
88/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
89/// answer with an empty candidate list (#1828).
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91enum TypeCandidateFailure {
92    Ambiguous,
93    Unresolvable,
94}
95
96impl TypeCandidateFailure {
97    fn lexical_resolution(self) -> LexicalTypeResolution {
98        match self {
99            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
100            Self::Unresolvable => LexicalTypeResolution::Missing,
101        }
102    }
103}
104
105pub enum LexicalCallableValueResolution {
106    Type(CodeUnit),
107    FreeFunction(CodeUnit),
108    Ambiguous,
109    Missing,
110}
111
112pub enum UsingEnumMemberResolution {
113    Resolved { owner: CodeUnit, member: CodeUnit },
114    Ambiguous,
115    Missing,
116}
117
118pub enum NamespaceValueResolution {
119    Resolved,
120    Ambiguous,
121    Missing,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub enum OrdinaryMacroReferenceResolution {
126    Resolved(CodeUnit),
127    Ambiguous,
128    Missing,
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum RecoveredCReferenceRanges {
133    Complete(Vec<Range>),
134    LimitExceeded,
135}
136
137pub fn resolve_namespace_value(
138    analyzer: &CppGraphSource<'_>,
139    visibility: &VisibilityIndex<'_>,
140    file: &ProjectFile,
141    namespace: &str,
142    name: &str,
143    before_byte: usize,
144) -> NamespaceValueResolution {
145    let mut matches = Vec::new();
146    for candidate in visibility.visible_identifier_candidates(file, name) {
147        if type_owner_of(analyzer, candidate).is_some()
148            || candidate.package_name() != namespace
149            || (candidate.source() == file
150                && !analyzer
151                    .ranges(candidate)
152                    .iter()
153                    .any(|range| range.start_byte < before_byte))
154            || matches
155                .iter()
156                .any(|existing| same_visible_symbol(existing, candidate))
157        {
158            continue;
159        }
160        matches.push(candidate.clone());
161        if matches.len() > 1 {
162            return NamespaceValueResolution::Ambiguous;
163        }
164    }
165    matches
166        .pop()
167        .map(|_| NamespaceValueResolution::Resolved)
168        .unwrap_or(NamespaceValueResolution::Missing)
169}
170
171pub(crate) struct ScopedUsingEnumOwners {
172    scopes: Vec<Vec<CodeUnit>>,
173}
174
175/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
176/// Cross-file and inherited class imports are deliberately not inferred without persisted
177/// evidence; a missing imported enumerator therefore remains unproven rather than being
178/// misresolved.
179pub(crate) struct SemanticUsingEnumOwners {
180    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
181    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
182}
183
184pub(crate) enum SemanticUsingEnumMemberResolution {
185    Class(UsingEnumMemberResolution),
186    Namespace(UsingEnumMemberResolution),
187    Missing,
188}
189
190impl SemanticUsingEnumOwners {
191    pub(crate) fn new() -> Self {
192        Self {
193            class_imports: HashMap::default(),
194            namespace_imports: HashMap::default(),
195        }
196    }
197
198    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
199        let imports = self.class_imports.entry(class).or_default();
200        if !imports
201            .iter()
202            .any(|existing| same_visible_symbol(existing, &enum_owner))
203        {
204            imports.push(enum_owner);
205        }
206    }
207
208    pub fn import_namespace(
209        &mut self,
210        namespace: Vec<String>,
211        declaration_byte: usize,
212        enum_owner: CodeUnit,
213    ) {
214        let imports = self.namespace_imports.entry(namespace).or_default();
215        if !imports
216            .iter()
217            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
218        {
219            imports.push((declaration_byte, enum_owner));
220        }
221    }
222
223    pub fn resolve_member(
224        &self,
225        visibility: &VisibilityIndex<'_>,
226        file: &ProjectFile,
227        class: Option<&CodeUnit>,
228        namespace: &[String],
229        before_byte: usize,
230        name: &str,
231    ) -> SemanticUsingEnumMemberResolution {
232        if let Some(class) = class
233            && let Some((_, imports)) = self
234                .class_imports
235                .iter()
236                .find(|(owner, _)| same_visible_symbol(owner, class))
237        {
238            let resolution =
239                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
240            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
241                return SemanticUsingEnumMemberResolution::Class(resolution);
242            }
243        }
244        for prefix_len in (0..=namespace.len()).rev() {
245            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
246                continue;
247            };
248            let owners = imports
249                .iter()
250                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
251                .map(|(_, owner)| owner);
252            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
253            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
254                return SemanticUsingEnumMemberResolution::Namespace(resolution);
255            }
256        }
257        SemanticUsingEnumMemberResolution::Missing
258    }
259}
260
261fn resolve_using_enum_member_for_owners<'a>(
262    visibility: &VisibilityIndex<'_>,
263    file: &ProjectFile,
264    owners: impl IntoIterator<Item = &'a CodeUnit>,
265    name: &str,
266) -> UsingEnumMemberResolution {
267    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
268    for owner in owners {
269        for member in visibility.visible_members_for_owner_name(file, owner, name) {
270            if !member.is_field()
271                || matches.iter().any(|(existing_owner, existing_member)| {
272                    same_visible_symbol(existing_owner, owner)
273                        && same_visible_symbol(existing_member, member)
274                })
275            {
276                continue;
277            }
278            matches.push((owner.clone(), member.clone()));
279        }
280    }
281    match matches.len() {
282        0 => UsingEnumMemberResolution::Missing,
283        1 => {
284            let (owner, member) = matches.pop().expect("one using-enum match");
285            UsingEnumMemberResolution::Resolved { owner, member }
286        }
287        _ => UsingEnumMemberResolution::Ambiguous,
288    }
289}
290
291impl ScopedUsingEnumOwners {
292    pub(crate) fn new() -> Self {
293        Self {
294            scopes: vec![Vec::new()],
295        }
296    }
297
298    pub fn enter_scope(&mut self) {
299        self.scopes.push(Vec::new());
300    }
301
302    pub fn exit_scope(&mut self) {
303        if self.scopes.len() > 1 {
304            self.scopes.pop();
305        }
306    }
307
308    pub fn import(&mut self, owner: CodeUnit) {
309        let scope = self
310            .scopes
311            .last_mut()
312            .expect("using-enum scope stack is never empty");
313        if !scope
314            .iter()
315            .any(|existing| same_visible_symbol(existing, &owner))
316        {
317            scope.push(owner);
318        }
319    }
320
321    pub fn resolve_member(
322        &self,
323        visibility: &VisibilityIndex<'_>,
324        file: &ProjectFile,
325        name: &str,
326    ) -> UsingEnumMemberResolution {
327        for scope in self.scopes.iter().rev() {
328            let resolution =
329                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
330            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
331                return resolution;
332            }
333        }
334        UsingEnumMemberResolution::Missing
335    }
336}
337
338#[derive(Clone)]
339pub struct TargetSpec {
340    pub target: CodeUnit,
341    pub kind: TargetKind,
342    pub owner: Option<CodeUnit>,
343    pub member_name: String,
344    pub callable_arity: Option<CallableArity>,
345    pub activated_callable_arities: Vec<ActivatedCallableArity>,
346    pub param_types: Option<Vec<String>>,
347    pub enum_owner_kind: EnumOwnerKind,
348    pub owner_is_forward_declaration: bool,
349    pub callable_has_definition_body: bool,
350}
351
352#[derive(Clone, Copy)]
353pub struct ActivatedCallableArity {
354    pub activation_byte: usize,
355    pub arity: CallableArity,
356}
357
358#[derive(Debug, PartialEq, Eq, Hash)]
359pub struct TypeScanKey {
360    target: LogicalSymbolKey,
361    member_name: String,
362}
363
364#[derive(Clone, Debug, PartialEq, Eq, Hash)]
365struct LogicalSymbolKey {
366    kind: CodeUnitType,
367    fq_name: String,
368    signature: Option<String>,
369}
370
371struct ResolvedTypeOwner {
372    unit: CodeUnit,
373    is_forward_declaration: bool,
374}
375
376#[derive(Clone, Copy, PartialEq, Eq)]
377pub enum EnumOwnerKind {
378    Scoped,
379    Unscoped,
380    NonEnum,
381}
382
383impl TargetSpec {
384    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
385        (self.kind == TargetKind::Type).then(|| TypeScanKey {
386            target: logical_symbol_key(&self.target),
387            member_name: self.member_name.clone(),
388        })
389    }
390
391    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
392        if target.is_class() {
393            return Some(Self::new(
394                target.clone(),
395                TargetKind::Type,
396                Some(target.clone()),
397                target.identifier().to_string(),
398                None,
399                None,
400            ));
401        }
402
403        if target.is_field() {
404            // A namespace (module) is not a receiver: a namespace-scoped constant such as
405            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
406            // qualified from outside, exactly like a global. Treating a module owner as a
407            // member-field owner makes the receiver/owner-context match reject every valid
408            // reference, so resolve it as a global field instead.
409            let owner = type_owner_of(analyzer, target);
410            let kind = if owner.is_some() {
411                TargetKind::MemberField
412            } else {
413                TargetKind::GlobalField
414            };
415            let enum_owner_kind = owner
416                .as_ref()
417                .map(|owner| classify_enum_owner(analyzer, owner))
418                .unwrap_or(EnumOwnerKind::NonEnum);
419            let mut spec = Self::new(
420                target.clone(),
421                kind,
422                owner,
423                target.identifier().to_string(),
424                None,
425                None,
426            );
427            spec.enum_owner_kind = enum_owner_kind;
428            return Some(spec);
429        }
430
431        if target.is_function() {
432            // Free functions declared inside a namespace have a module owner; that namespace is
433            // not a call receiver, so resolve them as free functions rather than methods.
434            let owner_resolution = target_type_owner_resolution(analyzer, target);
435            let owner_is_forward_declaration = owner_resolution
436                .as_ref()
437                .is_some_and(|owner| owner.is_forward_declaration);
438            let owner = owner_resolution.map(|owner| owner.unit);
439            let kind = if owner.as_ref().is_some_and(|owner| {
440                target.identifier() == owner.identifier()
441                    || analyzer
442                        .cpp
443                        .and_then(|cpp| cpp.template_metadata(owner))
444                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
445            }) {
446                TargetKind::Constructor
447            } else if owner.is_some() {
448                TargetKind::Method
449            } else {
450                TargetKind::FreeFunction
451            };
452            let mut spec = Self::new(
453                target.clone(),
454                kind,
455                owner,
456                target.identifier().to_string(),
457                Some(cpp_callable_arity(analyzer, target)),
458                cpp_callable_parameter_types(analyzer, target),
459            );
460            spec.owner_is_forward_declaration = owner_is_forward_declaration;
461            spec.callable_has_definition_body =
462                callable_target_has_definition_body(analyzer, target);
463            return Some(spec);
464        }
465
466        if target.is_macro() {
467            return Some(Self::new(
468                target.clone(),
469                TargetKind::Macro,
470                None,
471                target.identifier().to_string(),
472                None,
473                None,
474            ));
475        }
476
477        None
478    }
479
480    pub fn with_visible_callable_arities<'a>(
481        &'a self,
482        analyzer: &CppGraphSource<'_>,
483        cpp: &dyn CppSource,
484        visibility: &VisibilityIndex<'_>,
485        file: &ProjectFile,
486        prepared: &PreparedSyntaxTree,
487    ) -> Cow<'a, Self> {
488        let macro_parameter_arity =
489            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
490        let activated_callable_arities =
491            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
492        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
493            return Cow::Borrowed(self);
494        }
495        let mut effective = self.clone();
496        if let Some(macro_parameter_arity) = macro_parameter_arity {
497            effective.callable_arity = Some(macro_parameter_arity);
498        }
499        effective.activated_callable_arities = activated_callable_arities;
500        Cow::Owned(effective)
501    }
502
503    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
504        let base = self.callable_arity?;
505        Some(
506            self.activated_callable_arities
507                .iter()
508                .filter(|candidate| candidate.activation_byte <= byte)
509                .fold(base, |arity, candidate| {
510                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
511                }),
512        )
513    }
514
515    pub fn new(
516        target: CodeUnit,
517        kind: TargetKind,
518        owner: Option<CodeUnit>,
519        member_name: String,
520        callable_arity: Option<CallableArity>,
521        param_types: Option<Vec<String>>,
522    ) -> Self {
523        Self {
524            target,
525            kind,
526            owner,
527            member_name,
528            callable_arity,
529            activated_callable_arities: Vec::new(),
530            param_types,
531            enum_owner_kind: EnumOwnerKind::NonEnum,
532            owner_is_forward_declaration: false,
533            callable_has_definition_body: false,
534        }
535    }
536}
537
538fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
539    let Some(cpp) = analyzer.cpp else {
540        return false;
541    };
542    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
543        return false;
544    };
545    analyzer.ranges(target).into_iter().any(|range| {
546        let end = range
547            .start_byte
548            .saturating_add(1)
549            .min(prepared.source().len());
550        let mut current = prepared
551            .tree()
552            .root_node()
553            .descendant_for_byte_range(range.start_byte, end);
554        while let Some(node) = current {
555            match node.kind() {
556                "function_definition" => return true,
557                "declaration" => return false,
558                _ => current = node.parent(),
559            }
560        }
561        false
562    })
563}
564
565fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
566    LogicalSymbolKey {
567        kind: unit.kind(),
568        fq_name: unit.fq_name(),
569        signature: unit.signature().map(str::to_string),
570    }
571}
572
573fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
574    let classify = |source: &str| {
575        let source = source.trim_start();
576        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
577            Some(EnumOwnerKind::Scoped)
578        } else if source.starts_with("enum ") {
579            Some(EnumOwnerKind::Unscoped)
580        } else {
581            None
582        }
583    };
584    owner
585        .signature()
586        .and_then(classify)
587        .or_else(|| {
588            analyzer
589                .get_source(owner, false)
590                .as_deref()
591                .and_then(classify)
592        })
593        .unwrap_or(EnumOwnerKind::NonEnum)
594}
595
596#[derive(Clone, PartialEq, Eq, Hash)]
597pub struct CppScanBinding {
598    pub unit: Option<CodeUnit>,
599    pub type_name: Option<String>,
600    pub indirection: i32,
601}
602
603impl CppScanBinding {
604    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
605        Self {
606            type_name: Some(cpp_name_for(&unit)),
607            unit: Some(unit),
608            indirection,
609        }
610    }
611
612    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
613        Self {
614            type_name: Some(type_name),
615            unit,
616            indirection,
617        }
618    }
619
620    pub fn as_arg_type(&self) -> Option<CppArgType> {
621        let name = self
622            .type_name
623            .clone()
624            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
625        Some(CppArgType {
626            name,
627            unit: self.unit.clone(),
628            indirection: self.indirection,
629            pointee_const: false,
630        })
631    }
632}
633
634type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
635pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
636pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
637type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
638pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
639type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
640type MacroLocalBindingTemplateCache =
641    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
642
643#[derive(Clone, Default)]
644pub struct MacroEnvironment {
645    bindings: HashMap<String, MacroBinding>,
646    known_undefined_names: HashSet<String>,
647    /// Names the translation unit's compile command proves defined (#2011):
648    /// the `-D`s that survive command ordering, intersected across every
649    /// configuration naming the TU. Seeded once at TU start. An explicit
650    /// `#undef` seen later lands in `known_undefined_names` and wins.
651    build_proven_defines: HashSet<String>,
652    unknown_names: bool,
653    applied_pragma_once_files: HashSet<ProjectFile>,
654    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
655}
656
657#[derive(Default)]
658pub struct MacroEnvironmentCursor {
659    frontier: usize,
660    environment: Arc<MacroEnvironment>,
661}
662
663impl MacroEnvironment {
664    fn binding(&self, name: &str) -> Option<&MacroBinding> {
665        self.bindings.get(name)
666    }
667
668    fn may_bind(&self, name: &str) -> bool {
669        self.bindings.contains_key(name) || self.unknown_names
670    }
671
672    fn insert(&mut self, name: String, binding: MacroBinding) {
673        self.known_undefined_names.remove(&name);
674        self.bindings.insert(name, binding);
675    }
676
677    fn remove(&mut self, name: &str) {
678        self.bindings.remove(name);
679        self.known_undefined_names.insert(name.to_string());
680    }
681
682    fn remove_known_undefined(&mut self, name: &str) {
683        self.known_undefined_names.remove(name);
684    }
685
686    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
687        for binding in self.bindings.values_mut() {
688            *binding = MacroBinding::uncertain_from(binding, source, byte);
689        }
690        self.known_undefined_names.clear();
691        // An untracked include could `#undef` a command-line define, so the
692        // may-hold filter must stop treating the build facts as decisive from
693        // here on. The additive proof path keeps its facts: they still hold at
694        // the include chain's activation point.
695        self.build_proven_defines.clear();
696        self.unknown_names = true;
697    }
698
699    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
700        guards.iter().all(|guard| self.guard_may_hold(guard))
701    }
702
703    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
704        let Some(expression) = guard.as_boolean_expression() else {
705            return true;
706        };
707        self.boolean_guard_may_hold(&expression)
708    }
709
710    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
711        match expression {
712            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
713            BooleanGuardExpression::Undefined(name) => {
714                self.bindings
715                    .get(name)
716                    .is_none_or(|binding| !binding.is_exact())
717                    && (!self.build_proven_defines.contains(name)
718                        || self.known_undefined_names.contains(name))
719            }
720            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
721            BooleanGuardExpression::Opaque(_)
722            | BooleanGuardExpression::NegatedOpaque(_)
723            | BooleanGuardExpression::Constant(true) => true,
724            BooleanGuardExpression::Constant(false) => false,
725            BooleanGuardExpression::All(expressions) => expressions
726                .iter()
727                .all(|expression| self.boolean_guard_may_hold(expression)),
728            BooleanGuardExpression::Any(expressions) => expressions
729                .iter()
730                .any(|expression| self.boolean_guard_may_hold(expression)),
731        }
732    }
733}
734
735#[derive(Clone)]
736pub enum EffectiveUsingTarget {
737    Ordinary {
738        name: String,
739        target_components: Vec<String>,
740        global: bool,
741    },
742    Namespace {
743        namespace_components: Vec<String>,
744        global: bool,
745    },
746}
747
748#[derive(Clone)]
749pub struct OrdinaryTypeImport {
750    pub target: EffectiveUsingTarget,
751    pub source: ProjectFile,
752    pub declaration_byte: usize,
753    pub scope_start: usize,
754    pub scope_end: usize,
755    pub scope_depth: usize,
756    pub block_scope: bool,
757    pub lexical_depth: usize,
758    pub declaration_namespace: Vec<String>,
759    pub namespace_scope: Option<Vec<String>>,
760    pub resolved_target_components: Option<Vec<String>>,
761    pub required_guards: HashSet<PreprocessorGuard>,
762}
763
764#[derive(Clone)]
765pub struct ConditionalIncludeProjection {
766    pub activation_byte: usize,
767    pub required_guards: HashSet<PreprocessorGuard>,
768}
769
770#[derive(Default)]
771pub struct SourceUsingIndex {
772    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
773    pub directives: Vec<OrdinaryTypeImport>,
774}
775
776#[derive(Default)]
777pub struct ProjectUsingIndex {
778    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
779    pub directives: Vec<OrdinaryTypeImport>,
780}
781
782type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
783
784pub struct EffectiveUsingIndex {
785    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
786}
787
788impl EffectiveUsingIndex {
789    fn new(_root: ProjectFile) -> Self {
790        Self {
791            projected_by_name: Mutex::new(HashMap::default()),
792        }
793    }
794
795    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
796        self.projected_by_name
797            .lock()
798            .expect("C++ effective-using projection cache poisoned")
799            .entry(name.to_string())
800            .or_default()
801            .clone()
802    }
803}
804
805pub enum OrdinaryTypeImportResolution {
806    Resolved {
807        target: CodeUnit,
808        target_components: Vec<String>,
809        lexical_depth: usize,
810        is_direct: bool,
811    },
812    Ambiguous {
813        lexical_depth: usize,
814    },
815    Missing,
816}
817
818type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
819type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
820type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
821type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
822type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
823type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
824type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
825
826/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
827/// read from its declaration syntax rather than from its persisted signature
828/// string: the comparable shape of each parameter, and the trailing identity
829/// suffix that shape does not carry.
830struct ExtractedComparable {
831    shapes: Vec<CppComparableSlot>,
832    suffix: String,
833}
834
835/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
836/// before giving up on a written type name. A visited set already stops a
837/// cycle; this stops an adversarially long chain from costing a lookup per hop.
838const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
839
840/// Per-query C++ visibility facts.
841///
842/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
843/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
844/// generations and overlays, where another generation's hydrated states would
845/// be wrong). An index that owned a clone would therefore see an inactive read
846/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
847/// the same source from the store once per candidate instead of once per query
848/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
849/// tens of thousands of times.
850pub struct VisibilityIndex<'a> {
851    cpp: &'a dyn CppSource,
852    /// Proof that the request scope the index was built under is still open.
853    /// The index is a per-query object whose lifetime is inside the scope's,
854    /// so carrying the token here instead of on ninety method signatures is
855    /// the same guarantee for far less plumbing (issue #2414 step 3).
856    token: QueryToken<'a>,
857    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
858    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
859    global_field_internal_linkage: HashMap<CodeUnit, bool>,
860    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
861    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
862    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
863    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
864    project_using_index: OnceLock<ProjectUsingIndex>,
865    callable_reference_specs:
866        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
867    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
868    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
869    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
870    #[cfg(any(test, feature = "test-support"))]
871    conditional_include_projection_index_build_count: AtomicUsize,
872    #[cfg(any(test, feature = "test-support"))]
873    conditional_include_projection_state_count: AtomicUsize,
874    #[cfg(any(test, feature = "test-support"))]
875    conditional_include_target_state_count: AtomicUsize,
876    #[cfg(any(test, feature = "test-support"))]
877    include_activation_build_count: AtomicUsize,
878    #[cfg(any(test, feature = "test-support"))]
879    using_donor_activation_count: AtomicUsize,
880    #[cfg(any(test, feature = "test-support"))]
881    using_namespace_lookup_count: AtomicUsize,
882    #[cfg(any(test, feature = "test-support"))]
883    using_name_candidate_inspection_count: AtomicUsize,
884    #[cfg(any(test, feature = "test-support"))]
885    callable_reference_spec_build_count: AtomicUsize,
886    #[cfg(any(test, feature = "test-support"))]
887    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
888    #[cfg(any(test, feature = "test-support"))]
889    visible_parser_alias_name_set_build_count: AtomicUsize,
890    parser_alias_fallback_calls: AtomicUsize,
891    parser_alias_fallback_files: AtomicUsize,
892    parser_alias_source_parses: AtomicUsize,
893    parser_alias_fallback_elapsed_micros: AtomicUsize,
894    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
895    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
896    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
897    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
898    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
899    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
900    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
901    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
902    // A forward cursor is useful only while its caller visits one source in byte order. The
903    // authoritative differential shares this index across target workers, whose frontiers can
904    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
905    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
906    // immutable event and parse caches above remain shared.
907    pub macro_environment_cursors:
908        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
909    macro_replacements: Mutex<MacroReplacementCache>,
910    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
911    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
912    #[cfg(any(test, feature = "test-support"))]
913    pub macro_replacement_parse_count: AtomicUsize,
914    #[cfg(any(test, feature = "test-support"))]
915    pub macro_event_application_count: AtomicUsize,
916    #[cfg(any(test, feature = "test-support"))]
917    pub macro_environment_copy_count: AtomicUsize,
918    #[cfg(any(test, feature = "test-support"))]
919    pub macro_environment_request_count: AtomicUsize,
920    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
921    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
922    #[cfg(any(test, feature = "test-support"))]
923    qualified_candidate_inspections: AtomicUsize,
924    #[cfg(any(test, feature = "test-support"))]
925    target_preserving_type_resolution_count: AtomicUsize,
926}
927
928impl Drop for VisibilityIndex<'_> {
929    fn drop(&mut self) {
930        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
931            return;
932        }
933        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
934        if calls == 0 {
935            return;
936        }
937        eprintln!(
938            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
939            calls,
940            self.parser_alias_fallback_files.load(Ordering::Relaxed),
941            self.parser_alias_source_parses.load(Ordering::Relaxed),
942            self.parser_alias_fallback_elapsed_micros
943                .load(Ordering::Relaxed)
944                / 1_000,
945        );
946    }
947}
948
949#[derive(Clone, Debug, PartialEq, Eq, Hash)]
950pub enum PreprocessorGuard {
951    Defined(String),
952    Undefined(String),
953    Boolean(BooleanGuardExpression),
954    Expression(String),
955    NegatedExpression(String),
956    Constant(bool),
957}
958
959#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
960pub enum BooleanGuardExpression {
961    Defined(String),
962    Undefined(String),
963    Truthy(String),
964    Falsy(String),
965    Opaque(String),
966    NegatedOpaque(String),
967    All(Vec<BooleanGuardExpression>),
968    Any(Vec<BooleanGuardExpression>),
969    Constant(bool),
970}
971
972impl BooleanGuardExpression {
973    fn negated(&self) -> Self {
974        match self {
975            Self::Defined(name) => Self::Undefined(name.clone()),
976            Self::Undefined(name) => Self::Defined(name.clone()),
977            Self::Truthy(name) => Self::Falsy(name.clone()),
978            Self::Falsy(name) => Self::Truthy(name.clone()),
979            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
980            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
981            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
982            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
983            Self::Constant(value) => Self::Constant(!value),
984        }
985    }
986
987    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
988        Self::normalized(expressions, true)
989    }
990
991    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
992        Self::normalized(expressions, false)
993    }
994
995    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
996        let mut normalized = Vec::new();
997        for expression in expressions {
998            match expression {
999                Self::All(nested) if conjunction => normalized.extend(nested),
1000                Self::Any(nested) if !conjunction => normalized.extend(nested),
1001                Self::Constant(value) if value == conjunction => {}
1002                Self::Constant(value) => return Self::Constant(value),
1003                expression => normalized.push(expression),
1004            }
1005        }
1006        normalized.sort_unstable();
1007        normalized.dedup();
1008        match normalized.len() {
1009            0 => Self::Constant(conjunction),
1010            1 => normalized.pop().expect("one Boolean guard expression"),
1011            _ if conjunction => Self::All(normalized),
1012            _ => Self::Any(normalized),
1013        }
1014    }
1015
1016    fn implies(&self, required: &Self) -> bool {
1017        if self == required
1018            || matches!(self, Self::Constant(false))
1019            || matches!(required, Self::Constant(true))
1020        {
1021            return true;
1022        }
1023        if matches!(
1024            (self, required),
1025            (Self::Truthy(active), Self::Defined(required))
1026                | (Self::Undefined(active), Self::Falsy(required))
1027                if active == required
1028        ) {
1029            return true;
1030        }
1031        match self {
1032            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1033            Self::All(active) => match required {
1034                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1035                _ => active.iter().any(|expression| expression.implies(required)),
1036            },
1037            _ => match required {
1038                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1039                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1040                _ => false,
1041            },
1042        }
1043    }
1044
1045    pub fn heap_size(&self) -> usize {
1046        match self {
1047            Self::Defined(value)
1048            | Self::Undefined(value)
1049            | Self::Truthy(value)
1050            | Self::Falsy(value)
1051            | Self::Opaque(value)
1052            | Self::NegatedOpaque(value) => value.len(),
1053            Self::All(expressions) | Self::Any(expressions) => {
1054                expressions
1055                    .iter()
1056                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1057                        size.saturating_add(std::mem::size_of::<Self>())
1058                            .saturating_add(expression.heap_size())
1059                    })
1060            }
1061            Self::Constant(_) => 0,
1062        }
1063    }
1064}
1065
1066impl PreprocessorGuard {
1067    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1068        match self {
1069            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1070            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1071            Self::Boolean(expression) => Some(expression.clone()),
1072            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1073            Self::Expression(_) | Self::NegatedExpression(_) => None,
1074        }
1075    }
1076
1077    fn negated(&self) -> Self {
1078        match self {
1079            Self::Defined(name) => Self::Undefined(name.clone()),
1080            Self::Undefined(name) => Self::Defined(name.clone()),
1081            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1082            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1083            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1084            Self::Constant(value) => Self::Constant(!value),
1085        }
1086    }
1087
1088    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1089        match self {
1090            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1091            // The expression has already been isolated structurally by
1092            // tree-sitter, but its full preprocessor semantics are outside the
1093            // analyzer's guard model. Any macro mutation can therefore change
1094            // its truth value.
1095            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1096            Self::Constant(_) => false,
1097        }
1098    }
1099}
1100
1101#[derive(Clone, PartialEq, Eq)]
1102pub enum MacroDefinition {
1103    Object {
1104        replacement: String,
1105    },
1106    Function {
1107        parameters: Vec<String>,
1108        replacement: String,
1109    },
1110    Unsupported,
1111}
1112
1113#[derive(Clone, Debug, PartialEq, Eq)]
1114pub enum MacroIncludeProtection {
1115    MacroGuard(String),
1116    PragmaOnce,
1117    None,
1118}
1119
1120enum ParsedMacroReplacement {
1121    Parsed { source: String, tree: Tree },
1122    Unsupported,
1123}
1124
1125fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1126    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1127    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1128        if compact.starts_with("0x") || compact.starts_with("0X") {
1129            (16, 2, |ch| ch.is_ascii_hexdigit())
1130        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1131            (2, 2, |ch| matches!(ch, '0' | '1'))
1132        } else if compact.starts_with('0') && compact.len() > 1 {
1133            (8, 0, |ch| matches!(ch, '0'..='7'))
1134        } else {
1135            (10, 0, |ch| ch.is_ascii_digit())
1136        };
1137    let digit_len = compact[digits_start..]
1138        .chars()
1139        .take_while(|ch| digit_matches(*ch))
1140        .map(char::len_utf8)
1141        .sum::<usize>();
1142    if digit_len == 0 {
1143        return None;
1144    }
1145    let digits_end = digits_start + digit_len;
1146    if !compact[digits_end..]
1147        .chars()
1148        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1149    {
1150        return None;
1151    }
1152    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1153}
1154
1155#[derive(Clone)]
1156enum MacroLocalBindingTypeTemplate {
1157    Parameter(usize),
1158    Fixed(String),
1159}
1160
1161#[derive(Clone)]
1162struct MacroLocalBindingTemplate {
1163    name: String,
1164    declared_type: MacroLocalBindingTypeTemplate,
1165    pointer_depth: i32,
1166}
1167
1168/// A local declaration contributed by one structurally known function-like macro.
1169///
1170/// `type_node` points into the invocation syntax when the replacement's type
1171/// is one of the macro parameters. Consumers can therefore use their normal
1172/// lexical type resolver without parsing replacement text themselves.
1173pub struct MacroLocalBinding<'tree> {
1174    pub name: String,
1175    pub type_name: String,
1176    pub type_node: Option<Node<'tree>>,
1177    pub pointer_depth: i32,
1178}
1179
1180/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1181/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1182/// invocation as the assignment's left operand and the declared name as one
1183/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1184fn recognized_c_macro_declarator_binding<'tree>(
1185    statement: Node<'tree>,
1186    source: &str,
1187) -> Option<MacroLocalBinding<'tree>> {
1188    let assignment = match statement.kind() {
1189        "assignment_expression" => statement,
1190        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1191        _ => return None,
1192    };
1193    if assignment.kind() != "assignment_expression" {
1194        return None;
1195    }
1196    let call = assignment.child_by_field_name("left")?;
1197    if call.kind() != "call_expression" {
1198        return None;
1199    }
1200    let function = call.child_by_field_name("function")?;
1201    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1202        return None;
1203    }
1204    let arguments = call.child_by_field_name("arguments")?;
1205    let mut actuals = argument_children(arguments);
1206    let type_node = actuals.next()?;
1207    if actuals.next().is_some()
1208        || !matches!(
1209            type_node.kind(),
1210            "identifier"
1211                | "type_identifier"
1212                | "qualified_identifier"
1213                | "scoped_type_identifier"
1214                | "template_type"
1215        )
1216    {
1217        return None;
1218    }
1219    let name_node = (0..assignment.named_child_count())
1220        .filter_map(|index| assignment.named_child(index))
1221        .filter(|child| child.kind() == "ERROR")
1222        .filter_map(|error| {
1223            (error.named_child_count() == 1)
1224                .then(|| error.named_child(0))
1225                .flatten()
1226        })
1227        .find(|node| node.kind() == "identifier")?;
1228    let name = node_text(name_node, source).trim();
1229    let type_name = node_text(type_node, source).trim();
1230    if name.is_empty() || type_name.is_empty() {
1231        return None;
1232    }
1233    Some(MacroLocalBinding {
1234        name: name.to_string(),
1235        type_name: type_name.to_string(),
1236        type_node: Some(type_node),
1237        pointer_depth: 1,
1238    })
1239}
1240
1241#[derive(Clone, PartialEq, Eq)]
1242pub struct MacroBinding {
1243    source: ProjectFile,
1244    declaration_byte: usize,
1245    definition: MacroDefinition,
1246    exact: bool,
1247}
1248
1249impl MacroBinding {
1250    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1251        Self {
1252            source: source.clone(),
1253            declaration_byte,
1254            definition: MacroDefinition::Unsupported,
1255            exact: false,
1256        }
1257    }
1258
1259    fn is_exact(&self) -> bool {
1260        self.exact
1261    }
1262
1263    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1264        Self {
1265            source: source.clone(),
1266            declaration_byte,
1267            definition: current.definition.clone(),
1268            exact: false,
1269        }
1270    }
1271}
1272
1273#[derive(Clone)]
1274pub enum MacroEvent {
1275    Define {
1276        name: String,
1277        binding: MacroBinding,
1278        byte: usize,
1279        conditional: bool,
1280    },
1281    Undef {
1282        name: String,
1283        byte: usize,
1284        conditional: bool,
1285    },
1286    Include {
1287        targets: Vec<ProjectFile>,
1288        byte: usize,
1289        conditional: bool,
1290    },
1291    Invalidate {
1292        byte: usize,
1293    },
1294}
1295
1296impl MacroEvent {
1297    pub fn byte(&self) -> usize {
1298        match self {
1299            Self::Define { byte, .. }
1300            | Self::Undef { byte, .. }
1301            | Self::Include { byte, .. }
1302            | Self::Invalidate { byte } => *byte,
1303        }
1304    }
1305}
1306
1307#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1308pub enum CallArityEvidence {
1309    Exact(usize),
1310    Unknown,
1311}
1312
1313impl CallArityEvidence {
1314    pub fn exact(self) -> Option<usize> {
1315        match self {
1316            Self::Exact(arity) => Some(arity),
1317            Self::Unknown => None,
1318        }
1319    }
1320
1321    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1322        self.exact().map(|arity| expected.accepts(arity))
1323    }
1324}
1325
1326#[derive(Clone)]
1327struct DeclaredFieldTypeFact {
1328    type_text: String,
1329    indirection: i32,
1330    template_arguments: Option<Vec<CppTemplateExpression>>,
1331}
1332
1333#[derive(Clone, PartialEq, Eq)]
1334enum StructuredAliasTarget {
1335    Builtin,
1336    Named {
1337        components: Vec<String>,
1338        global: bool,
1339        arguments: Option<Vec<CppTemplateExpression>>,
1340    },
1341}
1342
1343struct CppAlias {
1344    name: String,
1345    target: String,
1346    namespace: Option<String>,
1347}
1348
1349type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1350
1351/// Why template-argument resolution failed. Definition diagnostics render
1352/// each mode differently; graph scans only care that the resolution is
1353/// unproven and match `Err(_)`.
1354#[derive(Debug, Clone, PartialEq, Eq)]
1355pub enum CppTemplateResolutionError {
1356    /// A template alias expansion revisited `alias`.
1357    AliasCycle { alias: CodeUnit },
1358    /// The explicit arguments do not bind to the declared template parameters.
1359    ArgumentBinding,
1360    /// Bound arguments do not substitute into the alias target's arguments.
1361    Substitution,
1362    /// No visible primary template declaration could be selected and
1363    /// reconciled for the specialization family.
1364    PrimarySelection,
1365    /// More than one applicable specialization remains and none is strictly
1366    /// more specialized than every other candidate.
1367    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1368}
1369
1370/// The ambiguity candidates, deduplicated to one representative per visible
1371/// symbol so a diagnostic lists each contender once.
1372fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1373    let mut distinct: Vec<CodeUnit> = Vec::new();
1374    for unit in units {
1375        if !distinct
1376            .iter()
1377            .any(|existing| same_visible_symbol(existing, unit))
1378        {
1379            distinct.push(unit.clone());
1380        }
1381    }
1382    distinct
1383}
1384
1385impl<'a> VisibilityIndex<'a> {
1386    pub fn cpp(&self) -> &'a dyn CppSource {
1387        self.cpp
1388    }
1389
1390    /// The request-scope proof this index was built with (issue #2414 step 3).
1391    pub fn token(&self) -> QueryToken<'a> {
1392        self.token
1393    }
1394
1395    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1396    /// bypassing the include-closure walk [`Self::build`] performs.
1397    ///
1398    /// The resolver's own unit tests drive the type-resolution paths against a
1399    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1400    /// because they need a real `CppAnalyzer`, so the struct literal they used
1401    /// to write inline is here instead of thirty-three public fields.
1402    #[cfg(any(test, feature = "test-support"))]
1403    pub fn from_visible_files_for_test(
1404        cpp: &'a dyn CppSource,
1405        token: QueryToken<'a>,
1406        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1407    ) -> Self {
1408        let visible_source_files_by_root = visible_by_file
1409            .iter()
1410            .map(|(file, visible)| {
1411                (
1412                    file.clone(),
1413                    visible
1414                        .iter()
1415                        .map(|unit| unit.source().clone())
1416                        .chain(std::iter::once(file.clone()))
1417                        .collect(),
1418                )
1419            })
1420            .collect();
1421        let mut global_field_internal_linkage = HashMap::default();
1422        Self {
1423            cpp,
1424            token,
1425            visible_by_identifier: build_visible_identifier_index(
1426                &CppGraphSource::from_source(cpp, token),
1427                &visible_by_file,
1428                &visible_source_files_by_root,
1429                &mut global_field_internal_linkage,
1430            ),
1431            global_field_internal_linkage,
1432            visible_by_file,
1433            visible_source_files_by_root,
1434            alias_cells: Mutex::new(HashMap::default()),
1435            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1436            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1437            project_using_index: OnceLock::new(),
1438            callable_reference_specs: Mutex::new(HashMap::default()),
1439            include_activation_cells: Mutex::new(HashMap::default()),
1440            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1441            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1442            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1443            conditional_include_projection_state_count: AtomicUsize::new(0),
1444            conditional_include_target_state_count: AtomicUsize::new(0),
1445            include_activation_build_count: AtomicUsize::new(0),
1446            using_donor_activation_count: AtomicUsize::new(0),
1447            using_namespace_lookup_count: AtomicUsize::new(0),
1448            using_name_candidate_inspection_count: AtomicUsize::new(0),
1449            callable_reference_spec_build_count: AtomicUsize::new(0),
1450            alias_source_parse_counts: Mutex::new(HashMap::default()),
1451            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1452            parser_alias_fallback_calls: AtomicUsize::new(0),
1453            parser_alias_fallback_files: AtomicUsize::new(0),
1454            parser_alias_source_parses: AtomicUsize::new(0),
1455            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1456            field_type_facts: Mutex::new(HashMap::default()),
1457            structured_alias_targets: Mutex::new(HashMap::default()),
1458            callable_comparables: Mutex::new(HashMap::default()),
1459            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1460            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1461            precise_parent_cache: Mutex::new(HashMap::default()),
1462            macro_event_cells: Mutex::new(HashMap::default()),
1463            macro_include_protection_cells: Mutex::new(HashMap::default()),
1464            macro_environment_cursors: Mutex::new(HashMap::default()),
1465            macro_replacements: Mutex::new(HashMap::default()),
1466            macro_local_binding_templates: Mutex::new(HashMap::default()),
1467            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1468            macro_replacement_parse_count: AtomicUsize::new(0),
1469            macro_event_application_count: AtomicUsize::new(0),
1470            macro_environment_copy_count: AtomicUsize::new(0),
1471            macro_environment_request_count: AtomicUsize::new(0),
1472            cpp_template_metadata: HashMap::default(),
1473            cpp_template_families: HashMap::default(),
1474            qualified_candidate_inspections: AtomicUsize::new(0),
1475            target_preserving_type_resolution_count: AtomicUsize::new(0),
1476        }
1477    }
1478
1479    /// The index's own C++ source, in the dispatching-analyzer shape.
1480    ///
1481    /// Four resolution paths reach the workspace through the C++ analyzer they
1482    /// already hold rather than through the analyzer the query was issued
1483    /// against; before the move they passed `&CppAnalyzer` straight into a
1484    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1485    fn cpp_source(&self) -> CppGraphSource<'a> {
1486        CppGraphSource::from_source(self.cpp, self.token)
1487    }
1488
1489    pub fn build(
1490        cpp: &'a dyn CppSource,
1491        token: QueryToken<'a>,
1492        analyzer: &CppGraphSource<'_>,
1493        roots: &HashSet<ProjectFile>,
1494    ) -> Self {
1495        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1496    }
1497
1498    pub fn build_with_cancellation(
1499        cpp: &'a dyn CppSource,
1500        token: QueryToken<'a>,
1501        analyzer: &CppGraphSource<'_>,
1502        roots: &HashSet<ProjectFile>,
1503        cancellation: Option<&CancellationToken>,
1504    ) -> Self {
1505        let visibility_started = Instant::now();
1506        let include_targets = cpp.include_target_index();
1507        let includes_started = Instant::now();
1508        let mut include_graph = IncludeGraph::default();
1509        for root in roots {
1510            include_graph.extend_with(root, cancellation, &mut |file| {
1511                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1512                    .into_iter()
1513                    .flat_map(|include| {
1514                        resolve_include_targets_with_index(file, &include, include_targets)
1515                    })
1516                    .collect()
1517            });
1518        }
1519        let include_elapsed = includes_started.elapsed();
1520        let include_file_count = include_graph.files().count();
1521        let visible_source_files_by_root = roots
1522            .iter()
1523            .map(|root| {
1524                (
1525                    root.clone(),
1526                    include_graph.reachable_files(root, cancellation),
1527                )
1528            })
1529            .collect::<HashMap<_, _>>();
1530        let mut visibility_stats = BoundedVisibilityStats::default();
1531        let mut visible_by_file = build_bounded_visible_declarations(
1532            cpp,
1533            token,
1534            analyzer,
1535            roots,
1536            &visible_source_files_by_root,
1537            cancellation,
1538            &mut visibility_stats,
1539        );
1540        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1541            eprintln!(
1542                "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
1543                visibility_started.elapsed().as_millis(),
1544                include_elapsed.as_millis(),
1545                include_file_count,
1546                visibility_stats.rounds,
1547                visibility_stats.root_names,
1548                visibility_stats.identifier_lookups,
1549                visibility_stats.candidate_units,
1550                visibility_stats.candidate_sources,
1551                visibility_stats.declaration_reads,
1552                visibility_stats.declaration_units,
1553                visibility_stats.selected_units,
1554                visibility_stats.dependency_ast_nodes,
1555                visibility_stats.dependency_names,
1556                visibility_stats.lookup_elapsed.as_millis(),
1557                visibility_stats.declaration_elapsed.as_millis(),
1558                visibility_stats.dependency_ast_elapsed.as_millis(),
1559            );
1560        }
1561        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1562        let finalize_started = Instant::now();
1563        if report_stats {
1564            eprintln!(
1565                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1566                visible_by_file.len(),
1567                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1568            );
1569        }
1570        let owner_started = Instant::now();
1571        if report_stats {
1572            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1573        }
1574        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1575        if report_stats {
1576            eprintln!(
1577                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1578                owner_stats.unseen_owners,
1579                owner_stats.definition_lookups,
1580                owner_stats.admitted,
1581                owner_started.elapsed().as_millis(),
1582            );
1583        }
1584        let mut global_field_internal_linkage = HashMap::default();
1585        let identifier_started = Instant::now();
1586        if report_stats {
1587            eprintln!(
1588                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1589            );
1590        }
1591        let visible_by_identifier = build_visible_identifier_index(
1592            analyzer,
1593            &visible_by_file,
1594            &visible_source_files_by_root,
1595            &mut global_field_internal_linkage,
1596        );
1597        if report_stats {
1598            eprintln!(
1599                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1600                visible_by_identifier.len(),
1601                visible_by_identifier
1602                    .values()
1603                    .map(HashMap::len)
1604                    .sum::<usize>(),
1605                visible_by_identifier
1606                    .values()
1607                    .flat_map(HashMap::values)
1608                    .map(Vec::len)
1609                    .sum::<usize>(),
1610                identifier_started.elapsed().as_millis(),
1611            );
1612        }
1613        let mut cpp_template_metadata = HashMap::default();
1614        let metadata_started = Instant::now();
1615        let mut template_classes = 0usize;
1616        if report_stats {
1617            eprintln!(
1618                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1619            );
1620        }
1621        for unit in visible_by_file
1622            .values()
1623            .flatten()
1624            .filter(|unit| unit.is_class())
1625        {
1626            template_classes += 1;
1627            if cpp_template_metadata.contains_key(unit) {
1628                continue;
1629            }
1630            if let Some(metadata) = cpp.template_metadata(unit) {
1631                cpp_template_metadata.insert(unit.clone(), metadata);
1632            }
1633        }
1634        if report_stats {
1635            eprintln!(
1636                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1637                template_classes,
1638                cpp_template_metadata.len(),
1639                metadata_started.elapsed().as_millis(),
1640            );
1641        }
1642        let families_started = Instant::now();
1643        if report_stats {
1644            eprintln!(
1645                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1646            );
1647        }
1648        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1649        for (unit, metadata) in &cpp_template_metadata {
1650            cpp_template_families
1651                .entry(metadata.primary_fq_name.clone())
1652                .or_default()
1653                .push(unit.clone());
1654        }
1655        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1656        // order above is a function of those hashes. Two mirrored headers can
1657        // declare one specialization; `select_template_specialization` treats
1658        // them as interchangeable and returns the family's first entry, so an
1659        // unsorted family made the reported declaration depend on the
1660        // workspace's absolute path and on unrelated files (#1836). Order the
1661        // family exactly as `build_visible_identifier_index` orders its
1662        // per-identifier candidate lists.
1663        for family in cpp_template_families.values_mut() {
1664            sort_lookup_units(family);
1665        }
1666        if report_stats {
1667            eprintln!(
1668                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1669                cpp_template_families.len(),
1670                cpp_template_families.values().map(Vec::len).sum::<usize>(),
1671                families_started.elapsed().as_millis(),
1672            );
1673            eprintln!(
1674                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1675                visible_by_file.len(),
1676                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1677                finalize_started.elapsed().as_millis(),
1678                visibility_started.elapsed().as_millis(),
1679            );
1680        }
1681        Self {
1682            cpp,
1683            token,
1684            visible_by_file,
1685            visible_by_identifier,
1686            global_field_internal_linkage,
1687            visible_source_files_by_root,
1688            alias_cells: Mutex::new(HashMap::default()),
1689            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1690            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1691            project_using_index: OnceLock::new(),
1692            callable_reference_specs: Mutex::new(HashMap::default()),
1693            include_activation_cells: Mutex::new(HashMap::default()),
1694            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1695            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1696            #[cfg(any(test, feature = "test-support"))]
1697            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1698            #[cfg(any(test, feature = "test-support"))]
1699            conditional_include_projection_state_count: AtomicUsize::new(0),
1700            #[cfg(any(test, feature = "test-support"))]
1701            conditional_include_target_state_count: AtomicUsize::new(0),
1702            #[cfg(any(test, feature = "test-support"))]
1703            include_activation_build_count: AtomicUsize::new(0),
1704            #[cfg(any(test, feature = "test-support"))]
1705            using_donor_activation_count: AtomicUsize::new(0),
1706            #[cfg(any(test, feature = "test-support"))]
1707            using_namespace_lookup_count: AtomicUsize::new(0),
1708            #[cfg(any(test, feature = "test-support"))]
1709            using_name_candidate_inspection_count: AtomicUsize::new(0),
1710            #[cfg(any(test, feature = "test-support"))]
1711            callable_reference_spec_build_count: AtomicUsize::new(0),
1712            #[cfg(any(test, feature = "test-support"))]
1713            alias_source_parse_counts: Mutex::new(HashMap::default()),
1714            #[cfg(any(test, feature = "test-support"))]
1715            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1716            parser_alias_fallback_calls: AtomicUsize::new(0),
1717            parser_alias_fallback_files: AtomicUsize::new(0),
1718            parser_alias_source_parses: AtomicUsize::new(0),
1719            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1720            field_type_facts: Mutex::new(HashMap::default()),
1721            structured_alias_targets: Mutex::new(HashMap::default()),
1722            callable_comparables: Mutex::new(HashMap::default()),
1723            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1724            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1725            precise_parent_cache: Mutex::new(HashMap::default()),
1726            macro_event_cells: Mutex::new(HashMap::default()),
1727            macro_include_protection_cells: Mutex::new(HashMap::default()),
1728            macro_environment_cursors: Mutex::new(HashMap::default()),
1729            macro_replacements: Mutex::new(HashMap::default()),
1730            macro_local_binding_templates: Mutex::new(HashMap::default()),
1731            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1732            #[cfg(any(test, feature = "test-support"))]
1733            macro_replacement_parse_count: AtomicUsize::new(0),
1734            #[cfg(any(test, feature = "test-support"))]
1735            macro_event_application_count: AtomicUsize::new(0),
1736            #[cfg(any(test, feature = "test-support"))]
1737            macro_environment_copy_count: AtomicUsize::new(0),
1738            #[cfg(any(test, feature = "test-support"))]
1739            macro_environment_request_count: AtomicUsize::new(0),
1740            cpp_template_metadata,
1741            cpp_template_families,
1742            #[cfg(any(test, feature = "test-support"))]
1743            qualified_candidate_inspections: AtomicUsize::new(0),
1744            #[cfg(any(test, feature = "test-support"))]
1745            target_preserving_type_resolution_count: AtomicUsize::new(0),
1746        }
1747    }
1748
1749    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1750        if file == target.source() {
1751            return true;
1752        }
1753        if self.global_field_has_internal_linkage(target) {
1754            return self
1755                .visible_source_files_by_root
1756                .get(file)
1757                .is_some_and(|sources| sources.contains(target.source()));
1758        }
1759        self.visible_by_file
1760            .get(file)
1761            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1762    }
1763
1764    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1765        self.global_field_internal_linkage
1766            .get(unit)
1767            .copied()
1768            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1769    }
1770
1771    pub fn call_arity_evidence(
1772        &self,
1773        file: &ProjectFile,
1774        call: Node<'_>,
1775        source: &str,
1776    ) -> CallArityEvidence {
1777        let Some(arguments) = call
1778            .child_by_field_name("arguments")
1779            .or_else(|| call.child_by_field_name("parameters"))
1780            .or_else(|| call.child_by_field_name("value"))
1781            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1782            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1783        else {
1784            return CallArityEvidence::Exact(0);
1785        };
1786        let recovered_c_keyword_arguments =
1787            recovered_c_keyword_argument_count(file, call, arguments, source);
1788        let arguments = argument_children(arguments).collect::<Vec<_>>();
1789        if arguments
1790            .iter()
1791            .all(|argument| !argument_shape_may_change_arity(*argument))
1792        {
1793            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1794        }
1795        let environment = self.macro_environment(file, call.start_byte());
1796        let mut stack = Vec::new();
1797        let mut total = recovered_c_keyword_arguments;
1798        for argument in arguments {
1799            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1800                return CallArityEvidence::Unknown;
1801            }
1802            let CallArityEvidence::Exact(spread) =
1803                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1804            else {
1805                return CallArityEvidence::Unknown;
1806            };
1807            total += spread;
1808        }
1809        CallArityEvidence::Exact(total)
1810    }
1811
1812    fn argument_arity_evidence(
1813        &self,
1814        argument: Node<'_>,
1815        source: &str,
1816        environment: &MacroEnvironment,
1817        stack: &mut Vec<(ProjectFile, usize)>,
1818    ) -> CallArityEvidence {
1819        let (name, invocation_arguments, function_like) = match argument.kind() {
1820            "identifier" => (node_text(argument, source), None, false),
1821            "call_expression" => {
1822                let Some(function) = argument.child_by_field_name("function") else {
1823                    return CallArityEvidence::Exact(1);
1824                };
1825                if function.kind() != "identifier" {
1826                    return CallArityEvidence::Exact(1);
1827                }
1828                let Some(arguments) = argument.child_by_field_name("arguments") else {
1829                    return CallArityEvidence::Exact(1);
1830                };
1831                (node_text(function, source), Some(arguments), true)
1832            }
1833            _ => return CallArityEvidence::Exact(1),
1834        };
1835        let Some(binding) = environment.binding(name) else {
1836            return if environment.unknown_names {
1837                CallArityEvidence::Unknown
1838            } else {
1839                CallArityEvidence::Exact(1)
1840            };
1841        };
1842        if !binding.is_exact() {
1843            return CallArityEvidence::Unknown;
1844        }
1845        match (&binding.definition, invocation_arguments, function_like) {
1846            (MacroDefinition::Object { replacement }, None, false) => self
1847                .replacement_arity_evidence(
1848                    replacement,
1849                    &[],
1850                    &[],
1851                    source,
1852                    environment,
1853                    stack,
1854                    binding,
1855                ),
1856            (
1857                MacroDefinition::Function {
1858                    parameters,
1859                    replacement,
1860                },
1861                Some(arguments),
1862                true,
1863            ) => {
1864                let actuals = argument_children(arguments).collect::<Vec<_>>();
1865                if actuals.len() != parameters.len() {
1866                    CallArityEvidence::Unknown
1867                } else {
1868                    self.replacement_arity_evidence(
1869                        replacement,
1870                        parameters,
1871                        &actuals,
1872                        source,
1873                        environment,
1874                        stack,
1875                        binding,
1876                    )
1877                }
1878            }
1879            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1880            _ => CallArityEvidence::Unknown,
1881        }
1882    }
1883
1884    #[allow(clippy::too_many_arguments)]
1885    fn replacement_arity_evidence(
1886        &self,
1887        replacement: &str,
1888        parameters: &[String],
1889        actuals: &[Node<'_>],
1890        actual_source: &str,
1891        environment: &MacroEnvironment,
1892        stack: &mut Vec<(ProjectFile, usize)>,
1893        binding: &MacroBinding,
1894    ) -> CallArityEvidence {
1895        let identity = (binding.source.clone(), binding.declaration_byte);
1896        if stack.contains(&identity) || replacement.trim().is_empty() {
1897            return CallArityEvidence::Unknown;
1898        }
1899        stack.push(identity);
1900        let parsed = self.parsed_macro_replacement(binding, replacement);
1901        let evidence = (|| {
1902            let ParsedMacroReplacement::Parsed {
1903                source: sentinel,
1904                tree,
1905            } = parsed.as_ref()
1906            else {
1907                return None;
1908            };
1909            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1910            let arguments = call.child_by_field_name("arguments")?;
1911            let mut total = 0usize;
1912            for argument in argument_children(arguments) {
1913                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1914                    return None;
1915                }
1916                if argument.kind() == "identifier"
1917                    && let Some(parameter_index) = parameters
1918                        .iter()
1919                        .position(|parameter| parameter == node_text(argument, sentinel))
1920                {
1921                    if !macro_expansion_shape_is_safe(
1922                        actuals[parameter_index],
1923                        actual_source,
1924                        &[],
1925                        environment,
1926                    ) {
1927                        return None;
1928                    }
1929                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1930                        actuals[parameter_index],
1931                        actual_source,
1932                        environment,
1933                        stack,
1934                    ) else {
1935                        return None;
1936                    };
1937                    total += spread;
1938                    continue;
1939                }
1940                let CallArityEvidence::Exact(spread) =
1941                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1942                else {
1943                    return None;
1944                };
1945                total += spread;
1946            }
1947            Some(CallArityEvidence::Exact(total))
1948        })()
1949        .unwrap_or(CallArityEvidence::Unknown);
1950        stack.pop();
1951        evidence
1952    }
1953
1954    fn parsed_macro_replacement(
1955        &self,
1956        binding: &MacroBinding,
1957        replacement: &str,
1958    ) -> Arc<ParsedMacroReplacement> {
1959        let key = (binding.source.clone(), binding.declaration_byte);
1960        let mut cache = self
1961            .macro_replacements
1962            .lock()
1963            .expect("C++ macro replacement cache poisoned");
1964        if let Some(parsed) = cache.get(&key) {
1965            return Arc::clone(parsed);
1966        }
1967        #[cfg(any(test, feature = "test-support"))]
1968        self.macro_replacement_parse_count
1969            .fetch_add(1, Ordering::Relaxed);
1970        let source =
1971            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1972        let mut parser = Parser::new();
1973        let parsed = parser
1974            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1975            .ok()
1976            .and_then(|()| parser.parse(&source, None))
1977            .filter(|tree| !tree.root_node().has_error())
1978            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1979                ParsedMacroReplacement::Parsed { source, tree }
1980            });
1981        let parsed = Arc::new(parsed);
1982        cache.insert(key, Arc::clone(&parsed));
1983        parsed
1984    }
1985
1986    /// Recover a typed local declared by an active C function-like macro.
1987    ///
1988    /// This is intentionally narrower than macro expansion. The replacement
1989    /// must parse as one declaration, and the invocation must bind every
1990    /// formal parameter to one structured argument. That is sufficient for
1991    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1992    /// can make the binding provisional without erasing its last known
1993    /// definition; an explicit conflicting definition still replaces it with
1994    /// Unsupported. Malformed and statement-producing macros also fail closed.
1995    pub fn function_macro_local_binding<'tree>(
1996        &self,
1997        file: &ProjectFile,
1998        statement: Node<'tree>,
1999        source: &str,
2000    ) -> Option<MacroLocalBinding<'tree>> {
2001        if !is_c_source_file(file) {
2002            return None;
2003        }
2004        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2005            return Some(binding);
2006        }
2007        let call = match statement.kind() {
2008            "call_expression" => statement,
2009            "expression_statement" if statement.named_child_count() == 1 => {
2010                statement.named_child(0)?
2011            }
2012            _ => return None,
2013        };
2014        if call.kind() != "call_expression" {
2015            return None;
2016        }
2017        let function = call.child_by_field_name("function")?;
2018        if function.kind() != "identifier" {
2019            return None;
2020        }
2021        let arguments = call.child_by_field_name("arguments")?;
2022        let actuals = argument_children(arguments).collect::<Vec<_>>();
2023        let environment = self.macro_environment(file, call.start_byte());
2024        let function_name = node_text(function, source);
2025        let binding = environment.binding(function_name)?;
2026        let MacroDefinition::Function {
2027            parameters,
2028            replacement,
2029        } = &binding.definition
2030        else {
2031            return None;
2032        };
2033        if actuals.len() != parameters.len() {
2034            return None;
2035        }
2036        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2037        let (type_name, type_node) = match &template.declared_type {
2038            MacroLocalBindingTypeTemplate::Parameter(index) => {
2039                let actual = *actuals.get(*index)?;
2040                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2041                    return None;
2042                }
2043                (node_text(actual, source).trim().to_string(), Some(actual))
2044            }
2045            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2046        };
2047        if type_name.is_empty() {
2048            return None;
2049        }
2050        Some(MacroLocalBinding {
2051            name: template.name.clone(),
2052            type_name,
2053            type_node,
2054            pointer_depth: template.pointer_depth,
2055        })
2056    }
2057
2058    fn macro_local_binding_template(
2059        &self,
2060        binding: &MacroBinding,
2061        parameters: &[String],
2062        replacement: &str,
2063    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2064        let key = (binding.source.clone(), binding.declaration_byte);
2065        let mut cache = self
2066            .macro_local_binding_templates
2067            .lock()
2068            .expect("C++ macro local-binding cache poisoned");
2069        if let Some(template) = cache.get(&key) {
2070            return template.clone();
2071        }
2072        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
2073        let template = (|| {
2074            let mut parser = Parser::new();
2075            parser
2076                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2077                .ok()?;
2078            let tree = parser.parse(&sentinel, None)?;
2079            if tree.root_node().has_error() {
2080                return None;
2081            }
2082            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
2083            let body = function.child_by_field_name("body")?;
2084            if body.named_child_count() != 1 {
2085                return None;
2086            }
2087            let declaration = body.named_child(0)?;
2088            if declaration.kind() != "declaration" {
2089                return None;
2090            }
2091            let type_node = declaration
2092                .child_by_field_name("type")
2093                .or_else(|| first_type_child(declaration))?;
2094            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2095                let mut cursor = declaration.walk();
2096                declaration.named_children(&mut cursor).find_map(|child| {
2097                    if child.kind() == "init_declarator" {
2098                        child.child_by_field_name("declarator")
2099                    } else {
2100                        is_declarator_node(child).then_some(child)
2101                    }
2102                })
2103            })?;
2104            let name = extract_variable_name(declarator, &sentinel)?;
2105            let pointer_depth =
2106                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
2107            let type_text = node_text(type_node, &sentinel).trim();
2108            let declared_type = parameters
2109                .iter()
2110                .position(|parameter| parameter == type_text)
2111                .map(MacroLocalBindingTypeTemplate::Parameter)
2112                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2113            Some(Arc::new(MacroLocalBindingTemplate {
2114                name,
2115                declared_type,
2116                pointer_depth,
2117            }))
2118        })();
2119        cache.insert(key, template.clone());
2120        template
2121    }
2122
2123    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2124        let replacement = node
2125            .child_by_field_name("value")
2126            .map(|value| node_text(value, source).to_string())
2127            .unwrap_or_default();
2128        if node.kind() == "preproc_def" {
2129            return MacroDefinition::Object { replacement };
2130        }
2131        let Some(parameters) = node.child_by_field_name("parameters") else {
2132            return MacroDefinition::Unsupported;
2133        };
2134        if (0..parameters.child_count()).any(|index| {
2135            parameters
2136                .child(index)
2137                .is_some_and(|child| child.kind() == "...")
2138        }) {
2139            return MacroDefinition::Unsupported;
2140        }
2141        let parameters = (0..parameters.named_child_count())
2142            .filter_map(|index| parameters.named_child(index))
2143            .map(|parameter| node_text(parameter, source).to_string())
2144            .collect();
2145        MacroDefinition::Function {
2146            parameters,
2147            replacement,
2148        }
2149    }
2150
2151    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2152        self.macro_event_cells
2153            .lock()
2154            .expect("C++ macro event cache poisoned")
2155            .entry(file.clone())
2156            .or_default()
2157            .clone()
2158    }
2159
2160    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
2161        let key = (file.clone(), std::thread::current().id());
2162        self.macro_environment_cursors
2163            .lock()
2164            .expect("C++ macro environment cursor cache poisoned")
2165            .entry(key)
2166            .or_default()
2167            .clone()
2168    }
2169
2170    pub fn macro_environment(
2171        &self,
2172        file: &ProjectFile,
2173        before_byte: usize,
2174    ) -> Arc<MacroEnvironment> {
2175        #[cfg(any(test, feature = "test-support"))]
2176        self.macro_environment_request_count
2177            .fetch_add(1, Ordering::Relaxed);
2178        let cell = self.macro_event_cell(file);
2179        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2180        let frontier = events.partition_point(|event| event.byte() < before_byte);
2181        let cursor_cell = self.macro_environment_cursor_cell(file);
2182        let mut cursor = cursor_cell
2183            .lock()
2184            .expect("C++ macro environment cursor poisoned");
2185        if frontier < cursor.frontier {
2186            *cursor = MacroEnvironmentCursor::default();
2187        }
2188        // Seed the TU's build-proven defines once, before any event applies
2189        // (#2011). They are facts of the whole compile, so they hold from the
2190        // first byte; a later explicit #undef event still overrides them
2191        // through `known_undefined_names`.
2192        if cursor.frontier == 0 {
2193            let proven = self.compile_proven_guards(file);
2194            if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
2195                Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
2196                    .iter()
2197                    .filter_map(|guard| match guard {
2198                        PreprocessorGuard::Defined(name) => Some(name.clone()),
2199                        _ => None,
2200                    })
2201                    .collect();
2202            }
2203        }
2204        if frontier > cursor.frontier {
2205            #[cfg(any(test, feature = "test-support"))]
2206            if Arc::strong_count(&cursor.environment) > 1 {
2207                self.macro_environment_copy_count
2208                    .fetch_add(1, Ordering::Relaxed);
2209            }
2210            let start = cursor.frontier;
2211            let environment = Arc::make_mut(&mut cursor.environment);
2212            let mut include_stack = HashSet::from_iter([file.clone()]);
2213            for event in &events[start..frontier] {
2214                self.apply_macro_event(file, event, environment, &mut include_stack);
2215            }
2216            cursor.frontier = frontier;
2217        }
2218        Arc::clone(&cursor.environment)
2219    }
2220
2221    /// Whether `name` is bound as a macro at `before_byte` in `file`,
2222    /// including a binding this environment cannot pin to one replacement
2223    /// (a conditional `#define`, or a function-like macro).
2224    ///
2225    /// [`Self::object_macro_replacement_at`] collapses every such binding to
2226    /// `None`, which is indistinguishable from "not a macro at all". A caller
2227    /// that must not read a macro token as an ordinary type name needs the two
2228    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
2229    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2230        self.macro_environment(file, before_byte)
2231            .binding(name)
2232            .is_some()
2233    }
2234
2235    pub fn macro_name_may_be_bound_at(
2236        &self,
2237        file: &ProjectFile,
2238        name: &str,
2239        before_byte: usize,
2240    ) -> bool {
2241        self.macro_environment(file, before_byte).may_bind(name)
2242    }
2243
2244    /// Whether the active macro binding at this reference is the requested
2245    /// indexed definition. Name equality alone is not enough because two
2246    /// headers can define the same macro for different translation units.
2247    pub fn macro_binding_matches_target_at(
2248        &self,
2249        analyzer: &CppGraphSource<'_>,
2250        file: &ProjectFile,
2251        name: &str,
2252        before_byte: usize,
2253        target: &CodeUnit,
2254    ) -> bool {
2255        let environment = self.macro_environment(file, before_byte);
2256        let Some(binding) = environment.binding(name) else {
2257            return false;
2258        };
2259        if binding.definition == MacroDefinition::Unsupported {
2260            return false;
2261        }
2262        // A normal header guard makes the replacement text conditional, but
2263        // it does not erase the definition site's source and byte identity.
2264        // Keep that identity even when expansion details are not exact.
2265        if binding.source != *target.source() {
2266            return false;
2267        }
2268        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2269            return false;
2270        };
2271        analyzer.ranges(target).iter().any(|range| {
2272            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2273                return false;
2274            };
2275            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2276                let Some(parent) = node.parent() else {
2277                    return false;
2278                };
2279                node = parent;
2280            }
2281            node.start_byte() == binding.declaration_byte
2282        })
2283    }
2284
2285    /// Resolve an ordinary expression-position macro token at its exact byte.
2286    ///
2287    /// Calls and preprocessor-condition tokens have separate resolution
2288    /// surfaces. Declaration names, macro parameters, and labels are not
2289    /// references. Keeping that role policy here makes forward and both
2290    /// inverse graph builders consume the same activation verdict (#2093).
2291    pub fn resolve_ordinary_macro_reference(
2292        &self,
2293        analyzer: &CppGraphSource<'_>,
2294        file: &ProjectFile,
2295        node: Node<'_>,
2296        source: &str,
2297    ) -> OrdinaryMacroReferenceResolution {
2298        if !is_ordinary_macro_reference_node(node) {
2299            return OrdinaryMacroReferenceResolution::Missing;
2300        }
2301        let name = node_text(node, source);
2302        if name.is_empty() {
2303            return OrdinaryMacroReferenceResolution::Missing;
2304        }
2305        let visible = self
2306            .visible_identifier_candidates(file, name)
2307            .filter(|candidate| candidate.is_macro())
2308            .cloned()
2309            .collect::<Vec<_>>();
2310        let mut exact = Vec::new();
2311        for candidate in &visible {
2312            if self.macro_binding_matches_target_at(
2313                analyzer,
2314                file,
2315                name,
2316                node.start_byte(),
2317                candidate,
2318            ) && !exact
2319                .iter()
2320                .any(|existing| same_visible_symbol(existing, candidate))
2321            {
2322                exact.push(candidate.clone());
2323            }
2324        }
2325        match exact.len() {
2326            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2327            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2328            0 if !visible.is_empty()
2329                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2330            {
2331                OrdinaryMacroReferenceResolution::Ambiguous
2332            }
2333            0 => OrdinaryMacroReferenceResolution::Missing,
2334        }
2335    }
2336
2337    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2338    ///
2339    /// The ordinary census deliberately skips every `ERROR` subtree. This
2340    /// separate, precision-only frontier admits only roles that retain enough
2341    /// structure for the C usage graph to interpret independently (#2089).
2342    /// Macro evidence comes from this visibility index at the exact byte; no
2343    /// source-text parsing or terminal-name fallback is used.
2344    pub fn recovered_c_reference_ranges(
2345        &self,
2346        file: &ProjectFile,
2347        root: Node<'_>,
2348        source: &str,
2349        limit: usize,
2350    ) -> RecoveredCReferenceRanges {
2351        if !is_c_source_file(file) {
2352            return RecoveredCReferenceRanges::Complete(Vec::new());
2353        }
2354        let mut ranges = Vec::new();
2355        let mut seen = HashSet::default();
2356        let mut stack = vec![(root, root.is_error())];
2357        while let Some((node, inside_error)) = stack.pop() {
2358            let inside_error = inside_error || node.is_error();
2359            if inside_error
2360                && recovered_c_reference_node(self, file, node, source)
2361                && seen.insert((node.start_byte(), node.end_byte()))
2362            {
2363                if ranges.len() == limit {
2364                    return RecoveredCReferenceRanges::LimitExceeded;
2365                }
2366                ranges.push(Range {
2367                    start_byte: node.start_byte(),
2368                    end_byte: node.end_byte(),
2369                    start_line: node.start_position().row,
2370                    end_line: node.end_position().row,
2371                });
2372            }
2373            let mut cursor = node.walk();
2374            for child in node.named_children(&mut cursor) {
2375                stack.push((child, inside_error));
2376            }
2377        }
2378        ranges.sort_unstable();
2379        RecoveredCReferenceRanges::Complete(ranges)
2380    }
2381
2382    /// Whether this target is an indexed macro visible from this file.
2383    ///
2384    /// An unresolved conditional can make more than one same-name macro a
2385    /// possible active binding. Each possible target can keep the site as an
2386    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2387    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2388        self.visible_identifier_candidates(file, target.identifier())
2389            .filter(|candidate| candidate.is_macro())
2390            .any(|candidate| {
2391                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2392            })
2393    }
2394
2395    pub fn object_macro_replacement_at(
2396        &self,
2397        file: &ProjectFile,
2398        name: &str,
2399        before_byte: usize,
2400    ) -> Option<String> {
2401        let environment = self.macro_environment(file, before_byte);
2402        let binding = environment.binding(name)?;
2403        if !binding.exact {
2404            return None;
2405        }
2406        match &binding.definition {
2407            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2408            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2409        }
2410    }
2411
2412    fn apply_macro_events(
2413        &self,
2414        file: &ProjectFile,
2415        before_byte: Option<usize>,
2416        environment: &mut MacroEnvironment,
2417        include_stack: &mut HashSet<ProjectFile>,
2418    ) {
2419        if !include_stack.insert(file.clone()) {
2420            return;
2421        }
2422        if self.cpp.prepared_syntax(self.token, file).is_none() {
2423            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2424            include_stack.remove(file);
2425            return;
2426        }
2427        match self.macro_include_protection(file) {
2428            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2429                Some(binding) if binding.is_exact() => {
2430                    include_stack.remove(file);
2431                    return;
2432                }
2433                Some(_) | None if environment.unknown_names => {
2434                    let mut ambiguous_seen = HashSet::default();
2435                    self.mark_macro_events_ambiguous(
2436                        file,
2437                        environment,
2438                        &mut ambiguous_seen,
2439                        file,
2440                        before_byte.unwrap_or_default(),
2441                    );
2442                    include_stack.remove(file);
2443                    return;
2444                }
2445                Some(_) => {
2446                    let mut ambiguous_seen = HashSet::default();
2447                    self.mark_macro_events_ambiguous(
2448                        file,
2449                        environment,
2450                        &mut ambiguous_seen,
2451                        file,
2452                        before_byte.unwrap_or_default(),
2453                    );
2454                    include_stack.remove(file);
2455                    return;
2456                }
2457                None => {}
2458            },
2459            MacroIncludeProtection::PragmaOnce => {
2460                if !environment.applied_pragma_once_files.insert(file.clone()) {
2461                    include_stack.remove(file);
2462                    return;
2463                }
2464                if environment.maybe_applied_pragma_once_files.remove(file) {
2465                    // A prior conditional include may already have consumed the pragma-once
2466                    // header. This unconditional include guarantees it is consumed now, but
2467                    // cannot prove whether its events occur before or after intervening local
2468                    // macro changes, so preserve the union as ambiguous.
2469                    let mut ambiguous_seen = HashSet::default();
2470                    environment.applied_pragma_once_files.remove(file);
2471                    self.mark_macro_events_ambiguous(
2472                        file,
2473                        environment,
2474                        &mut ambiguous_seen,
2475                        file,
2476                        before_byte.unwrap_or_default(),
2477                    );
2478                    environment.maybe_applied_pragma_once_files.remove(file);
2479                    environment.applied_pragma_once_files.insert(file.clone());
2480                    include_stack.remove(file);
2481                    return;
2482                }
2483            }
2484            MacroIncludeProtection::None => {}
2485        }
2486        let cell = self.macro_event_cell(file);
2487        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2488        for event in events {
2489            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2490                break;
2491            }
2492            self.apply_macro_event(file, event, environment, include_stack);
2493        }
2494        include_stack.remove(file);
2495    }
2496
2497    fn apply_macro_event(
2498        &self,
2499        file: &ProjectFile,
2500        event: &MacroEvent,
2501        environment: &mut MacroEnvironment,
2502        include_stack: &mut HashSet<ProjectFile>,
2503    ) {
2504        #[cfg(any(test, feature = "test-support"))]
2505        self.macro_event_application_count
2506            .fetch_add(1, Ordering::Relaxed);
2507        match event {
2508            MacroEvent::Define {
2509                name,
2510                binding,
2511                conditional,
2512                byte,
2513            } => {
2514                match conditional
2515                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2516                    .unwrap_or(Some(true))
2517                {
2518                    Some(true) => environment.insert(name.clone(), binding.clone()),
2519                    Some(false) => {}
2520                    None => Self::merge_conditional_macro_definition(
2521                        environment,
2522                        name,
2523                        binding,
2524                        file,
2525                        *byte,
2526                    ),
2527                }
2528            }
2529            MacroEvent::Undef {
2530                name,
2531                conditional,
2532                byte,
2533            } => {
2534                match conditional
2535                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2536                    .unwrap_or(Some(true))
2537                {
2538                    Some(true) => environment.remove(name),
2539                    Some(false) => {}
2540                    None => {
2541                        if environment.binding(name).is_some() {
2542                            environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2543                        }
2544                    }
2545                }
2546            }
2547            MacroEvent::Include {
2548                targets,
2549                conditional,
2550                byte,
2551            } => {
2552                let condition = conditional
2553                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2554                    .unwrap_or(Some(true));
2555                if condition == Some(false) {
2556                    return;
2557                }
2558                if targets.is_empty() {
2559                    environment.mark_unknown_names(file, *byte);
2560                    return;
2561                }
2562                if condition.is_none() || targets.len() > 1 {
2563                    let mut ambiguous_seen = HashSet::default();
2564                    for target in targets {
2565                        self.mark_macro_events_ambiguous(
2566                            target,
2567                            environment,
2568                            &mut ambiguous_seen,
2569                            file,
2570                            *byte,
2571                        );
2572                    }
2573                } else if let Some(target) = targets.first() {
2574                    self.apply_macro_events(target, None, environment, include_stack);
2575                }
2576            }
2577            MacroEvent::Invalidate { byte } => {
2578                for binding in environment.bindings.values_mut() {
2579                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2580                }
2581            }
2582        }
2583    }
2584
2585    /// Evaluate the structured conditional path that owns one macro event.
2586    ///
2587    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
2588    /// this source byte. `None` preserves the old conditional merge when a
2589    /// build/configuration input or an unsupported expression is involved.
2590    fn macro_event_condition_value(
2591        &self,
2592        file: &ProjectFile,
2593        event_byte: usize,
2594        environment: &MacroEnvironment,
2595    ) -> Option<bool> {
2596        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2597        let source = prepared.source();
2598        let root = prepared.tree().root_node();
2599        let descendant = root.descendant_for_byte_range(
2600            event_byte,
2601            event_byte.saturating_add(1).min(source.len()),
2602        )?;
2603        let mut unknown = false;
2604        let mut current = descendant.parent();
2605        while let Some(conditional) = current {
2606            if matches!(
2607                conditional.kind(),
2608                "preproc_if" | "preproc_ifdef" | "preproc_elif"
2609            ) && !is_file_covering_include_guard(conditional, source)
2610                && preprocessor_conditional_contains_descendant(conditional, descendant)
2611            {
2612                let mut value = match conditional.kind() {
2613                    "preproc_ifdef" => {
2614                        let name = conditional.child_by_field_name("name")?;
2615                        let defined =
2616                            self.macro_name_defined_value(environment, node_text(name, source));
2617                        match conditional.child(0)?.kind() {
2618                            "#ifdef" => defined,
2619                            "#ifndef" => defined.map(|defined| !defined),
2620                            _ => None,
2621                        }
2622                    }
2623                    "preproc_if" | "preproc_elif" => conditional
2624                        .child_by_field_name("condition")
2625                        .and_then(|condition| {
2626                            self.preprocessor_integer_value(
2627                                condition,
2628                                source,
2629                                environment,
2630                                &mut Vec::new(),
2631                                0,
2632                            )
2633                        })
2634                        .map(|value| value != 0),
2635                    _ => unreachable!(),
2636                };
2637                if conditional
2638                    .child_by_field_name("alternative")
2639                    .is_some_and(|alternative| {
2640                        alternative.start_byte() <= descendant.start_byte()
2641                            && descendant.end_byte() <= alternative.end_byte()
2642                    })
2643                {
2644                    value = value.map(|value| !value);
2645                }
2646                match value {
2647                    Some(true) => {}
2648                    Some(false) => return Some(false),
2649                    None => unknown = true,
2650                }
2651            }
2652            current = conditional.parent();
2653        }
2654        (!unknown).then_some(true)
2655    }
2656
2657    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
2658        if environment.known_undefined_names.contains(name) {
2659            return Some(false);
2660        }
2661        if let Some(binding) = environment.binding(name) {
2662            return binding.is_exact().then_some(true);
2663        }
2664        environment
2665            .build_proven_defines
2666            .contains(name)
2667            .then_some(true)
2668    }
2669
2670    fn preprocessor_integer_value(
2671        &self,
2672        expression: Node<'_>,
2673        source: &str,
2674        environment: &MacroEnvironment,
2675        expansion_stack: &mut Vec<(ProjectFile, usize)>,
2676        depth: usize,
2677    ) -> Option<i128> {
2678        // Macro replacement graphs can cycle. This explicit bound makes the
2679        // otherwise recursive AST evaluation stack-safe for hostile input.
2680        if depth >= 64 {
2681            return None;
2682        }
2683        match expression.kind() {
2684            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
2685            "identifier" | "type_identifier" => {
2686                let binding = environment.binding(node_text(expression, source))?;
2687                if !binding.is_exact() {
2688                    return None;
2689                }
2690                let MacroDefinition::Object { replacement } = &binding.definition else {
2691                    return None;
2692                };
2693                let identity = (binding.source.clone(), binding.declaration_byte);
2694                if expansion_stack.contains(&identity) {
2695                    return None;
2696                }
2697                expansion_stack.push(identity);
2698                let parsed = self.parsed_macro_replacement(binding, replacement);
2699                let value = match parsed.as_ref() {
2700                    ParsedMacroReplacement::Parsed {
2701                        source: replacement_source,
2702                        tree,
2703                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
2704                        .and_then(|call| call.child_by_field_name("arguments"))
2705                        .and_then(|arguments| argument_children(arguments).next())
2706                        .and_then(|argument| {
2707                            self.preprocessor_integer_value(
2708                                argument,
2709                                replacement_source,
2710                                environment,
2711                                expansion_stack,
2712                                depth + 1,
2713                            )
2714                        }),
2715                    ParsedMacroReplacement::Unsupported => None,
2716                };
2717                expansion_stack.pop();
2718                value
2719            }
2720            "preproc_defined" => {
2721                let mut cursor = expression.walk();
2722                let name = expression
2723                    .named_children(&mut cursor)
2724                    .find(|child| child.kind() == "identifier")?;
2725                self.macro_name_defined_value(environment, node_text(name, source))
2726                    .map(i128::from)
2727            }
2728            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
2729                self.preprocessor_integer_value(
2730                    child,
2731                    source,
2732                    environment,
2733                    expansion_stack,
2734                    depth + 1,
2735                )
2736            }),
2737            "unary_expression" => {
2738                let operator = expression.child_by_field_name("operator")?.kind();
2739                let argument = expression.child_by_field_name("argument")?;
2740                let value = self.preprocessor_integer_value(
2741                    argument,
2742                    source,
2743                    environment,
2744                    expansion_stack,
2745                    depth + 1,
2746                )?;
2747                match operator {
2748                    "+" => Some(value),
2749                    "-" => value.checked_neg(),
2750                    "!" => Some(i128::from(value == 0)),
2751                    "~" => Some(!value),
2752                    _ => None,
2753                }
2754            }
2755            "binary_expression" => {
2756                let left = self.preprocessor_integer_value(
2757                    expression.child_by_field_name("left")?,
2758                    source,
2759                    environment,
2760                    expansion_stack,
2761                    depth + 1,
2762                )?;
2763                let right = self.preprocessor_integer_value(
2764                    expression.child_by_field_name("right")?,
2765                    source,
2766                    environment,
2767                    expansion_stack,
2768                    depth + 1,
2769                )?;
2770                match expression.child_by_field_name("operator")?.kind() {
2771                    "+" => left.checked_add(right),
2772                    "-" => left.checked_sub(right),
2773                    "*" => left.checked_mul(right),
2774                    "/" => left.checked_div(right),
2775                    "%" => left.checked_rem(right),
2776                    "<<" => u32::try_from(right)
2777                        .ok()
2778                        .and_then(|shift| left.checked_shl(shift)),
2779                    ">>" => u32::try_from(right)
2780                        .ok()
2781                        .and_then(|shift| left.checked_shr(shift)),
2782                    "<" => Some(i128::from(left < right)),
2783                    "<=" => Some(i128::from(left <= right)),
2784                    ">" => Some(i128::from(left > right)),
2785                    ">=" => Some(i128::from(left >= right)),
2786                    "==" => Some(i128::from(left == right)),
2787                    "!=" => Some(i128::from(left != right)),
2788                    "&" => Some(left & right),
2789                    "|" => Some(left | right),
2790                    "^" => Some(left ^ right),
2791                    "&&" => Some(i128::from(left != 0 && right != 0)),
2792                    "||" => Some(i128::from(left != 0 || right != 0)),
2793                    _ => None,
2794                }
2795            }
2796            _ => None,
2797        }
2798    }
2799
2800    fn mark_macro_events_ambiguous(
2801        &self,
2802        file: &ProjectFile,
2803        environment: &mut MacroEnvironment,
2804        include_stack: &mut HashSet<ProjectFile>,
2805        conditional_file: &ProjectFile,
2806        conditional_byte: usize,
2807    ) {
2808        if !include_stack.insert(file.clone()) {
2809            return;
2810        }
2811        if self.cpp.prepared_syntax(self.token, file).is_none() {
2812            environment.mark_unknown_names(conditional_file, conditional_byte);
2813            return;
2814        }
2815        match self.macro_include_protection(file) {
2816            MacroIncludeProtection::MacroGuard(guard) => {
2817                if environment
2818                    .binding(&guard)
2819                    .is_some_and(MacroBinding::is_exact)
2820                {
2821                    return;
2822                }
2823            }
2824            MacroIncludeProtection::PragmaOnce => {
2825                if environment.applied_pragma_once_files.contains(file) {
2826                    return;
2827                }
2828                environment
2829                    .maybe_applied_pragma_once_files
2830                    .insert(file.clone());
2831            }
2832            MacroIncludeProtection::None => {}
2833        }
2834        let cell = self.macro_event_cell(file);
2835        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2836        for event in events {
2837            #[cfg(any(test, feature = "test-support"))]
2838            self.macro_event_application_count
2839                .fetch_add(1, Ordering::Relaxed);
2840            match event {
2841                MacroEvent::Define { name, binding, .. } => {
2842                    Self::merge_conditional_macro_definition(
2843                        environment,
2844                        name,
2845                        binding,
2846                        conditional_file,
2847                        conditional_byte,
2848                    );
2849                }
2850                MacroEvent::Undef { name, .. } => {
2851                    if environment.binding(name).is_some() {
2852                        environment.insert(
2853                            name.clone(),
2854                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2855                        );
2856                    } else {
2857                        environment.remove_known_undefined(name);
2858                    }
2859                }
2860                MacroEvent::Include { targets, .. } => {
2861                    if targets.is_empty() {
2862                        environment.mark_unknown_names(conditional_file, conditional_byte);
2863                        continue;
2864                    }
2865                    for target in targets {
2866                        self.mark_macro_events_ambiguous(
2867                            target,
2868                            environment,
2869                            include_stack,
2870                            conditional_file,
2871                            conditional_byte,
2872                        );
2873                    }
2874                }
2875                MacroEvent::Invalidate { .. } => {
2876                    for binding in environment.bindings.values_mut() {
2877                        *binding = MacroBinding::uncertain_from(
2878                            binding,
2879                            conditional_file,
2880                            conditional_byte,
2881                        );
2882                    }
2883                }
2884            }
2885        }
2886    }
2887
2888    fn merge_conditional_macro_definition(
2889        environment: &mut MacroEnvironment,
2890        name: &str,
2891        possible_binding: &MacroBinding,
2892        conditional_file: &ProjectFile,
2893        conditional_byte: usize,
2894    ) {
2895        // A conditional include can revisit an already-active guarded header.
2896        // If the possible branch defines the exact same macro, both outcomes
2897        // leave the binding unchanged; degrading it to Unknown would discard
2898        // proof because of an unrelated unresolved macro name (#2092).
2899        if environment.binding(name).is_some_and(|current| {
2900            current.definition != MacroDefinition::Unsupported
2901                && current.definition == possible_binding.definition
2902        }) {
2903            return;
2904        }
2905        environment.insert(
2906            name.to_string(),
2907            MacroBinding::ambiguous(conditional_file, conditional_byte),
2908        );
2909    }
2910
2911    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2912        let cell = self
2913            .macro_include_protection_cells
2914            .lock()
2915            .expect("C++ include protection cache poisoned")
2916            .entry(file.clone())
2917            .or_default()
2918            .clone();
2919        cell.get_or_init(|| {
2920            self.cpp.prepared_syntax(self.token, file).map_or(
2921                MacroIncludeProtection::None,
2922                |prepared| {
2923                    top_level_macro_include_protection(
2924                        prepared.tree().root_node(),
2925                        prepared.source(),
2926                    )
2927                },
2928            )
2929        })
2930        .clone()
2931    }
2932
2933    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2934        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2935            return Vec::new();
2936        };
2937        let source = prepared.source();
2938        let mut events = Vec::new();
2939        let mut stack = vec![prepared.tree().root_node()];
2940        while let Some(node) = stack.pop() {
2941            let conditional = has_preprocessor_conditional_ancestor(node, source);
2942            match node.kind() {
2943                "preproc_def" | "preproc_function_def" => {
2944                    let Some(name) = node.child_by_field_name("name") else {
2945                        continue;
2946                    };
2947                    let name = node_text(name, source).to_string();
2948                    events.push(MacroEvent::Define {
2949                        name,
2950                        binding: MacroBinding {
2951                            source: file.clone(),
2952                            declaration_byte: node.start_byte(),
2953                            definition: Self::decode_macro_definition(node, source),
2954                            exact: true,
2955                        },
2956                        byte: node.start_byte(),
2957                        conditional,
2958                    });
2959                    continue;
2960                }
2961                "preproc_include" => {
2962                    let Some(path) = node.child_by_field_name("path") else {
2963                        events.push(MacroEvent::Include {
2964                            targets: Vec::new(),
2965                            byte: node.start_byte(),
2966                            conditional,
2967                        });
2968                        continue;
2969                    };
2970                    let targets =
2971                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2972                            resolve_include_targets_with_index(
2973                                file,
2974                                path,
2975                                self.cpp.include_target_index(),
2976                            )
2977                        });
2978                    // An unresolved angle-bracket include crosses into an external system
2979                    // boundary that is absent from the source index. It must not poison all
2980                    // later local macro evidence. Quoted/project-local and computed includes,
2981                    // by contrast, may hide indexed macro state and therefore fail closed.
2982                    if targets.is_empty() && path.kind() == "system_lib_string" {
2983                        continue;
2984                    }
2985                    events.push(MacroEvent::Include {
2986                        targets,
2987                        byte: node.start_byte(),
2988                        conditional,
2989                    });
2990                    continue;
2991                }
2992                "preproc_call" => {
2993                    let Some(directive) = node.child_by_field_name("directive") else {
2994                        continue;
2995                    };
2996                    if node_text(directive, source) != "#undef" {
2997                        continue;
2998                    }
2999                    let name = node
3000                        .child_by_field_name("argument")
3001                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3002                    if let Some(name) = name {
3003                        events.push(MacroEvent::Undef {
3004                            name,
3005                            byte: node.start_byte(),
3006                            conditional,
3007                        });
3008                    } else {
3009                        events.push(MacroEvent::Invalidate {
3010                            byte: node.start_byte(),
3011                        });
3012                    }
3013                    continue;
3014                }
3015                _ => {}
3016            }
3017            for index in (0..node.named_child_count()).rev() {
3018                if let Some(child) = node.named_child(index) {
3019                    stack.push(child);
3020                }
3021            }
3022        }
3023        events.sort_by_key(MacroEvent::byte);
3024        events
3025    }
3026
3027    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3028        self.ordinary_type_import_cells
3029            .lock()
3030            .expect("C++ ordinary type import cache poisoned")
3031            .entry(file.clone())
3032            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3033            .clone()
3034    }
3035
3036    pub fn project_using_index(
3037        &self,
3038        build: impl FnOnce() -> ProjectUsingIndex,
3039    ) -> &ProjectUsingIndex {
3040        self.project_using_index.get_or_init(build)
3041    }
3042
3043    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3044        let mut files = self
3045            .visible_source_files_by_root
3046            .values()
3047            .flatten()
3048            .cloned()
3049            .collect::<HashSet<_>>()
3050            .into_iter()
3051            .collect::<Vec<_>>();
3052        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3053        files
3054    }
3055
3056    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3057        self.visible_source_files_by_root
3058            .get(root)
3059            .is_some_and(|files| files.contains(source))
3060    }
3061
3062    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3063        let cached = self
3064            .visible_parser_alias_name_sets
3065            .read()
3066            .expect("visible parser alias-name cache poisoned")
3067            .get(file)
3068            .cloned();
3069        let cell = if let Some(cached) = cached {
3070            cached
3071        } else {
3072            let mut cells = self
3073                .visible_parser_alias_name_sets
3074                .write()
3075                .expect("visible parser alias-name cache poisoned");
3076            Arc::clone(
3077                cells
3078                    .entry(file.clone())
3079                    .or_insert_with(|| Arc::new(OnceLock::new())),
3080            )
3081        };
3082        cell.get_or_init(|| {
3083            #[cfg(any(test, feature = "test-support"))]
3084            self.visible_parser_alias_name_set_build_count
3085                .fetch_add(1, Ordering::Relaxed);
3086            let mut names = HashSet::default();
3087            let visible_files = self
3088                .visible_source_files_by_root
3089                .get(file)
3090                .cloned()
3091                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3092            for visible_file in visible_files {
3093                let aliases = {
3094                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3095                    Arc::clone(
3096                        cells
3097                            .entry(visible_file.clone())
3098                            .or_insert_with(|| Arc::new(OnceLock::new())),
3099                    )
3100                };
3101                for alias in aliases
3102                    .get_or_init(|| {
3103                        self.parser_alias_source_parses
3104                            .fetch_add(1, Ordering::Relaxed);
3105                        #[cfg(any(test, feature = "test-support"))]
3106                        {
3107                            *self
3108                                .alias_source_parse_counts
3109                                .lock()
3110                                .expect("alias source parse count lock")
3111                                .entry(visible_file.clone())
3112                                .or_default() += 1;
3113                        }
3114                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3115                            .into_boxed_slice()
3116                    })
3117                    .iter()
3118                {
3119                    names.insert(alias.name.clone());
3120                }
3121            }
3122            names
3123        })
3124        .contains(name)
3125    }
3126
3127    pub fn parser_alias_name_may_resolve_to_target(
3128        &self,
3129        file: &ProjectFile,
3130        alias_name: &str,
3131        target: &CodeUnit,
3132    ) -> bool {
3133        let started = std::time::Instant::now();
3134        self.parser_alias_fallback_calls
3135            .fetch_add(1, Ordering::Relaxed);
3136        let mut files = 0usize;
3137        let matched = match self.visible_source_files_by_root.get(file) {
3138            None => {
3139                files = 1;
3140                self.file_alias_matches(self.cpp, file, alias_name, target)
3141            }
3142            Some(visible_files) => visible_files.iter().any(|visible_file| {
3143                files += 1;
3144                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3145            }),
3146        };
3147        self.parser_alias_fallback_files
3148            .fetch_add(files, Ordering::Relaxed);
3149        self.parser_alias_fallback_elapsed_micros.fetch_add(
3150            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3151            Ordering::Relaxed,
3152        );
3153        matched
3154    }
3155
3156    fn callable_arities_for_target(
3157        &self,
3158        analyzer: &CppGraphSource<'_>,
3159        cpp: &dyn CppSource,
3160        file: &ProjectFile,
3161        prepared: &PreparedSyntaxTree,
3162        spec: &TargetSpec,
3163    ) -> Vec<ActivatedCallableArity> {
3164        let Some(signature) = spec.target.signature() else {
3165            return Vec::new();
3166        };
3167        let Some(candidates) = self
3168            .visible_by_identifier
3169            .get(file)
3170            .and_then(|by_name| by_name.get(&spec.member_name))
3171        else {
3172            return Vec::new();
3173        };
3174        let differing_candidates = candidates
3175            .iter()
3176            .filter(|candidate| {
3177                candidate.is_function()
3178                    && candidate.fq_name() == spec.target.fq_name()
3179                    && candidate.signature() == Some(signature)
3180            })
3181            .filter_map(|candidate| {
3182                analyzer
3183                    .signature_metadata(candidate)
3184                    .into_iter()
3185                    .find_map(|metadata| metadata.callable_arity())
3186                    .filter(|arity| Some(*arity) != spec.callable_arity)
3187                    .map(|arity| (candidate, arity))
3188            })
3189            .collect::<Vec<_>>();
3190        if differing_candidates.is_empty() {
3191            return Vec::new();
3192        }
3193        let mut arities = Vec::with_capacity(differing_candidates.len());
3194        // The activation ranges here describe the whole file rather than one
3195        // reference, so there is no reference guard environment to consult.
3196        let reference = CallableReferenceContext {
3197            file,
3198            position: None,
3199        };
3200        for (candidate, candidate_arity) in differing_candidates {
3201            let declaration_activation = if candidate.source() == file {
3202                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3203            } else {
3204                cpp.prepared_syntax(self.token, candidate.source())
3205                    .and_then(|syntax| {
3206                        callable_declaration_activation_in_file(
3207                            analyzer,
3208                            syntax.as_ref(),
3209                            candidate,
3210                            &reference,
3211                        )
3212                    })
3213            };
3214            let Some(declaration_activation) = declaration_activation else {
3215                continue;
3216            };
3217            let activation_byte = if candidate.source() == file {
3218                Some(declaration_activation)
3219            } else {
3220                self.include_activation_for_source(cpp, file, prepared, candidate.source())
3221            };
3222            if let Some(activation_byte) = activation_byte {
3223                arities.push(ActivatedCallableArity {
3224                    activation_byte,
3225                    arity: candidate_arity,
3226                });
3227            }
3228        }
3229        arities
3230    }
3231
3232    fn callable_parameter_macro_arity(
3233        &self,
3234        target: &CodeUnit,
3235        signature: Option<&str>,
3236    ) -> Option<CallableArity> {
3237        let parameter_types = cpp_signature_param_types(signature?)?;
3238        let [macro_name] = parameter_types.as_slice() else {
3239            return None;
3240        };
3241        if macro_name.is_empty()
3242            || !macro_name
3243                .chars()
3244                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3245        {
3246            return None;
3247        }
3248        let cache_key = (target.source().clone(), macro_name.clone());
3249        if let Some(cached) = self
3250            .callable_parameter_macro_arities
3251            .lock()
3252            .expect("C++ callable parameter-macro arity cache poisoned")
3253            .get(&cache_key)
3254            .copied()
3255        {
3256            return cached;
3257        }
3258        let mut visible_files = HashSet::default();
3259        collect_include_closure(
3260            &self.cpp_source(),
3261            self.cpp.include_target_index(),
3262            target.source(),
3263            &mut visible_files,
3264            None,
3265        );
3266        let mut arities = Vec::new();
3267        for visible_file in visible_files {
3268            let cell = self.macro_event_cell(&visible_file);
3269            for event in
3270                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3271            {
3272                let MacroEvent::Define { name, binding, .. } = event else {
3273                    continue;
3274                };
3275                if name != macro_name {
3276                    continue;
3277                }
3278                let MacroDefinition::Object { replacement } = &binding.definition else {
3279                    continue;
3280                };
3281                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3282                    continue;
3283                };
3284                if !arities.contains(&arity) {
3285                    arities.push(arity);
3286                }
3287            }
3288        }
3289        let resolved = (|| {
3290            let required = arities
3291                .iter()
3292                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3293                .min()?;
3294            let total = arities.iter().map(|arity| arity.total()).max()?;
3295            let repeated = arities
3296                .iter()
3297                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3298            // Preprocessor conditions can leave more than one object-like parameter
3299            // bundle active in the target header's include closure. Preserve their
3300            // conservative callable envelope instead of choosing whichever definition
3301            // happened to be visited first.
3302            Some(CallableArity::new(required, total, repeated))
3303        })();
3304        self.callable_parameter_macro_arities
3305            .lock()
3306            .expect("C++ callable parameter-macro arity cache poisoned")
3307            .insert(cache_key, resolved);
3308        resolved
3309    }
3310
3311    pub fn include_activation_for_source(
3312        &self,
3313        cpp: &dyn CppSource,
3314        file: &ProjectFile,
3315        prepared: &PreparedSyntaxTree,
3316        donor_source: &ProjectFile,
3317    ) -> Option<usize> {
3318        let key = (file.clone(), donor_source.clone());
3319        if let Some(cached) = self
3320            .include_activation_cells
3321            .lock()
3322            .expect("C++ include activation cache poisoned")
3323            .get(&key)
3324            .copied()
3325        {
3326            return cached;
3327        }
3328        #[cfg(any(test, feature = "test-support"))]
3329        self.include_activation_build_count
3330            .fetch_add(1, Ordering::Relaxed);
3331        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3332        let mut cells = self
3333            .include_activation_cells
3334            .lock()
3335            .expect("C++ include activation cache poisoned");
3336        *cells.entry(key).or_insert(activation)
3337    }
3338
3339    pub fn conditional_include_projections_for_source(
3340        &self,
3341        file: &ProjectFile,
3342        prepared: &PreparedSyntaxTree,
3343        donor_source: &ProjectFile,
3344    ) -> Arc<[ConditionalIncludeProjection]> {
3345        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3346        let cell = self
3347            .conditional_include_projection_cells
3348            .lock()
3349            .expect("C++ conditional include projection cache poisoned")
3350            .entry(file.clone())
3351            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3352            .clone();
3353        let index = cell.get_or_build_pool_independent(|| {
3354            #[cfg(any(test, feature = "test-support"))]
3355            self.conditional_include_projection_index_build_count
3356                .fetch_add(1, Ordering::Relaxed);
3357            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3358                #[cfg(any(test, feature = "test-support"))]
3359                self.conditional_include_projection_state_count
3360                    .fetch_add(1, Ordering::Relaxed);
3361            })
3362        });
3363        index
3364            .get(donor_source)
3365            .cloned()
3366            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3367    }
3368
3369    #[cfg(any(test, feature = "test-support"))]
3370    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3371        (
3372            self.conditional_include_projection_index_build_count
3373                .load(Ordering::Relaxed),
3374            self.conditional_include_projection_state_count
3375                .load(Ordering::Relaxed),
3376        )
3377    }
3378
3379    #[cfg(any(test, feature = "test-support"))]
3380    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3381        self.conditional_include_target_state_count
3382            .load(Ordering::Relaxed)
3383    }
3384
3385    #[cfg(any(test, feature = "test-support"))]
3386    pub fn include_activation_build_count_for_test(&self) -> usize {
3387        self.include_activation_build_count.load(Ordering::Relaxed)
3388    }
3389
3390    #[cfg(any(test, feature = "test-support"))]
3391    pub fn note_using_donor_activation_for_test(&self) {
3392        self.using_donor_activation_count
3393            .fetch_add(1, Ordering::Relaxed);
3394    }
3395
3396    #[cfg(not(any(test, feature = "test-support")))]
3397    pub fn note_using_donor_activation_for_test(&self) {}
3398
3399    #[cfg(any(test, feature = "test-support"))]
3400    pub fn note_using_namespace_lookup_for_test(&self) {
3401        self.using_namespace_lookup_count
3402            .fetch_add(1, Ordering::Relaxed);
3403    }
3404
3405    #[cfg(not(any(test, feature = "test-support")))]
3406    pub fn note_using_namespace_lookup_for_test(&self) {}
3407
3408    #[cfg(any(test, feature = "test-support"))]
3409    pub fn note_using_name_candidate_inspection_for_test(&self) {
3410        self.using_name_candidate_inspection_count
3411            .fetch_add(1, Ordering::Relaxed);
3412    }
3413
3414    #[cfg(not(any(test, feature = "test-support")))]
3415    pub fn note_using_name_candidate_inspection_for_test(&self) {}
3416
3417    #[cfg(any(test, feature = "test-support"))]
3418    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3419        (
3420            self.using_donor_activation_count.load(Ordering::Relaxed),
3421            self.using_namespace_lookup_count.load(Ordering::Relaxed),
3422            self.callable_reference_spec_build_count
3423                .load(Ordering::Relaxed),
3424            self.using_name_candidate_inspection_count
3425                .load(Ordering::Relaxed),
3426        )
3427    }
3428
3429    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3430        file == target.source()
3431            || self
3432                .visible_by_file
3433                .get(file)
3434                .is_some_and(|visible| visible.contains(target))
3435    }
3436
3437    /// Whether some declaration of `declaration`'s logical symbol is visible at
3438    /// `reference_byte` in `file`.
3439    ///
3440    /// The question is asked of the *logical* symbol, not of the physical unit:
3441    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
3442    /// and it does not have to be - what makes the call legal is the header
3443    /// declaration that the reference file does include. Reading that relation
3444    /// through `same_logical_callable` rather than through signature strings is
3445    /// the same #2010 correction the gates make, and it matters here because
3446    /// the body and the declaration are exactly the pair that spells one
3447    /// parameter type two ways.
3448    pub fn declaration_visible_at(
3449        &self,
3450        analyzer: &CppGraphSource<'_>,
3451        file: &ProjectFile,
3452        declaration: &CodeUnit,
3453        reference_byte: usize,
3454    ) -> bool {
3455        let reference_guards = OnceCell::new();
3456        self.visible_identifier_candidates(file, declaration.identifier())
3457            .filter(|candidate| {
3458                self.same_logical_callable(analyzer, candidate, declaration)
3459                    || flattened_macro_namespace_declaration_matches(
3460                        analyzer,
3461                        self.cpp,
3462                        file,
3463                        candidate,
3464                        declaration,
3465                        reference_byte,
3466                    )
3467            })
3468            .any(|candidate| {
3469                self.physical_declaration_visible_at(
3470                    analyzer,
3471                    file,
3472                    candidate,
3473                    reference_byte,
3474                    &reference_guards,
3475                )
3476            })
3477    }
3478
3479    /// C forward navigation may bind a call to a later same-file definition.
3480    /// There is no earlier source declaration to activate in that legacy C
3481    /// shape, but the call's preprocessor environment must still imply the
3482    /// definition's requirements. Ordinary C++ and inverse visibility retain
3483    /// the declaration-order rule in [`Self::declaration_visible_at`].
3484    pub fn declaration_visible_for_c_forward_call(
3485        &self,
3486        analyzer: &CppGraphSource<'_>,
3487        file: &ProjectFile,
3488        declaration: &CodeUnit,
3489        reference_byte: usize,
3490    ) -> bool {
3491        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
3492            return true;
3493        }
3494        if declaration.source() != file {
3495            return false;
3496        }
3497        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3498            return false;
3499        };
3500        let reference_guards = prepared
3501            .tree()
3502            .root_node()
3503            .descendant_for_byte_range(reference_byte, reference_byte)
3504            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
3505        declaration_guard_requirements(analyzer, self.cpp, declaration)
3506            .into_iter()
3507            .any(|(_, required)| {
3508                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
3509            })
3510    }
3511
3512    pub fn callable_arity_at_reference(
3513        &self,
3514        analyzer: &CppGraphSource<'_>,
3515        file: &ProjectFile,
3516        candidate: &CodeUnit,
3517        reference_byte: usize,
3518    ) -> Option<CallableArity> {
3519        let key = (file.clone(), logical_symbol_key(candidate));
3520        let cell = self
3521            .callable_reference_specs
3522            .lock()
3523            .expect("C++ callable reference-spec cache poisoned")
3524            .entry(key)
3525            .or_default()
3526            .clone();
3527        let spec = cell.get_or_init(|| {
3528            let prepared = self.cpp.prepared_syntax(self.token, file)?;
3529            let spec = TargetSpec::from_target(analyzer, candidate)?;
3530            let spec = spec
3531                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3532                .into_owned();
3533            #[cfg(any(test, feature = "test-support"))]
3534            self.callable_reference_spec_build_count
3535                .fetch_add(1, Ordering::Relaxed);
3536            Some(spec)
3537        });
3538        spec.as_ref()?.callable_arity_at(reference_byte)
3539    }
3540
3541    fn physical_declaration_visible_at(
3542        &self,
3543        analyzer: &CppGraphSource<'_>,
3544        file: &ProjectFile,
3545        declaration: &CodeUnit,
3546        reference_byte: usize,
3547        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3548    ) -> bool {
3549        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3550            return false;
3551        };
3552        let reference = CallableReferenceContext {
3553            file,
3554            position: Some(CallableReferencePosition {
3555                prepared: prepared.as_ref(),
3556                byte: reference_byte,
3557                guards: reference_guards,
3558            }),
3559        };
3560        if declaration.source() == file {
3561            return callable_declaration_activation_in_file(
3562                analyzer,
3563                prepared.as_ref(),
3564                declaration,
3565                &reference,
3566            )
3567            .or_else(|| {
3568                self.exhaustive_guard_family_activation(
3569                    analyzer,
3570                    prepared.as_ref(),
3571                    declaration,
3572                    &reference,
3573                )
3574            })
3575            .is_some_and(|activation| activation < reference_byte);
3576        }
3577        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3578            return false;
3579        };
3580        if callable_declaration_activation_in_file(
3581            analyzer,
3582            donor_syntax.as_ref(),
3583            declaration,
3584            &reference,
3585        )
3586        .or_else(|| {
3587            self.exhaustive_guard_family_activation(
3588                analyzer,
3589                donor_syntax.as_ref(),
3590                declaration,
3591                &reference,
3592            )
3593        })
3594        .is_none()
3595        {
3596            return false;
3597        }
3598        declaration_guard_requirements(analyzer, self.cpp, declaration)
3599            .into_iter()
3600            .any(|(_, declaration_guards)| {
3601                self.foreign_declaration_reachable_at_reference(
3602                    file,
3603                    prepared.as_ref(),
3604                    declaration.source(),
3605                    &declaration_guards,
3606                    reference.guards(),
3607                    reference_byte,
3608                )
3609            })
3610    }
3611
3612    pub fn external_type_candidate_visible_at(
3613        &self,
3614        file: &ProjectFile,
3615        candidate: &CodeUnit,
3616        reference_byte: usize,
3617    ) -> bool {
3618        if candidate.source() == file {
3619            return true;
3620        }
3621        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3622            return false;
3623        };
3624        self.visible_identifier_candidates(file, candidate.identifier())
3625            .filter(|peer| same_logical_symbol(candidate, peer))
3626            .any(|peer| {
3627                peer.source() == file
3628                    || self
3629                        .include_activation_for_source(
3630                            self.cpp,
3631                            file,
3632                            prepared.as_ref(),
3633                            peer.source(),
3634                        )
3635                        .is_some_and(|activation| activation <= reference_byte)
3636            })
3637    }
3638
3639    pub fn external_type_declaration_visible_at(
3640        &self,
3641        file: &ProjectFile,
3642        candidate: &CodeUnit,
3643        reference_byte: usize,
3644    ) -> bool {
3645        if candidate.source() == file {
3646            return true;
3647        }
3648        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3649            return false;
3650        };
3651        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3652            .is_some_and(|activation| activation <= reference_byte)
3653    }
3654
3655    /// The preprocessor facts the build proves for a reference sited in
3656    /// `file` (#2011).
3657    ///
3658    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
3659    /// `Defined` fact, and a fact holds only when every compile configuration
3660    /// that governs the file agrees on it (intersection). The facts are
3661    /// strictly additive to the reference's active guard set: they can prove a
3662    /// required guard, but the guard check itself is never weakened and no
3663    /// implication is ever inferred from source text.
3664    ///
3665    /// A file with its own database entry answers from that entry alone
3666    /// (phase 1). A header takes its context from the translation units whose
3667    /// include closure reaches it, intersected across all of them (phase 2):
3668    /// the header is compiled once per including TU, so a fact holds for a
3669    /// header-sited reference only when every one of those compilations
3670    /// proves it. A reaching TU the database does not cover proves nothing,
3671    /// which empties the intersection. A file nothing covers or reaches has
3672    /// no facts and every check runs on source structure alone.
3673    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3674        if let Some(cached) = self
3675            .compile_proven_guard_cells
3676            .lock()
3677            .expect("C++ compile-proven guard cache poisoned")
3678            .get(file)
3679        {
3680            return Arc::clone(cached);
3681        }
3682        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3683            Some(names) => names,
3684            None => {
3685                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3686                let seed = translation_units.next().and_then(|translation_unit| {
3687                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3688                });
3689                match seed {
3690                    None => HashSet::default(),
3691                    Some(mut names) => {
3692                        for translation_unit in translation_units {
3693                            let Some(reached) = context_fact_names(
3694                                self.cpp.compile_contexts_for(&translation_unit),
3695                            ) else {
3696                                names.clear();
3697                                break;
3698                            };
3699                            names.retain(|name| reached.contains(name));
3700                            if names.is_empty() {
3701                                break;
3702                            }
3703                        }
3704                        names
3705                    }
3706                }
3707            }
3708        };
3709        let proven = Arc::new(
3710            names
3711                .into_iter()
3712                .map(PreprocessorGuard::Defined)
3713                .collect::<HashSet<_>>(),
3714        );
3715        self.compile_proven_guard_cells
3716            .lock()
3717            .expect("C++ compile-proven guard cache poisoned")
3718            .insert(file.clone(), Arc::clone(&proven));
3719        proven
3720    }
3721
3722    /// Whether no compile data covers the compilations of `file`: it has no
3723    /// database entry of its own, and either nothing reaches it or some
3724    /// translation unit that reaches it has no entry. This is the state a
3725    /// regenerated `compile_commands.json` could decide; data that is present
3726    /// for every governing compilation but does not prove a guard is a
3727    /// decided conservative miss, not this state.
3728    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3729        if !self.cpp.compile_contexts_for(file).is_empty() {
3730            return false;
3731        }
3732        let translation_units = self.cpp.reaching_translation_units(file);
3733        translation_units.is_empty()
3734            || translation_units
3735                .iter()
3736                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3737    }
3738
3739    /// Whether a lookup miss for `identifier` in `file` is explainable by
3740    /// missing compile context (#2011): some same-name declaration is
3741    /// reachable through a conditional include whose required guards neither
3742    /// contradict the reference's active guards nor follow from them, and the
3743    /// translation unit has no compile-commands entry that could decide the
3744    /// question. Callers surface this as an explicit "requires compile
3745    /// context" incompleteness instead of an indistinguishable miss.
3746    ///
3747    /// A structurally disproven declaration (contradicting guards) and a TU
3748    /// whose compile context exists but does not prove the guard both answer
3749    /// `false`: those misses are decided, not incomplete.
3750    pub fn miss_requires_compile_context(
3751        &self,
3752        file: &ProjectFile,
3753        identifier: &str,
3754        reference: Node<'_>,
3755    ) -> bool {
3756        if !self.compile_context_is_absent(file) {
3757            return false;
3758        }
3759        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3760            return false;
3761        };
3762        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3763        let reference_byte = reference.start_byte();
3764        let mut sources = self
3765            .visible_identifier_candidates(file, identifier)
3766            .map(CodeUnit::source)
3767            .filter(|source| *source != file)
3768            .collect::<Vec<_>>();
3769        sources.sort();
3770        sources.dedup();
3771        sources.into_iter().any(|declaration_source| {
3772            self.conditional_include_projections_for_source(
3773                file,
3774                prepared.as_ref(),
3775                declaration_source,
3776            )
3777            .iter()
3778            .any(|projection| {
3779                projection.activation_byte <= reference_byte
3780                    && !guard_requirements_hold_at_reference(
3781                        &projection.required_guards,
3782                        reference_guards.as_ref(),
3783                    )
3784                    && guards_compatible_at_reference(
3785                        &projection.required_guards,
3786                        reference_guards.as_ref(),
3787                    )
3788            })
3789        })
3790    }
3791
3792    /// Decide whether a declaration that lives in another file reaches a
3793    /// reference in `file`.
3794    ///
3795    /// An external header selects its declaration branch before the reference
3796    /// file is parsed. Require compatible reference guards, but do not test
3797    /// the header's guard expression for stability in the reference file: a
3798    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3799    /// wraps every declaration of a portable C header, and demanding it would
3800    /// hide the whole header. Guards that the reference file imposes on its
3801    /// own `#include` still have to hold, and still have to be stable.
3802    fn foreign_declaration_reachable_at_reference(
3803        &self,
3804        file: &ProjectFile,
3805        prepared: &PreparedSyntaxTree,
3806        declaration_source: &ProjectFile,
3807        declaration_guards: &HashSet<PreprocessorGuard>,
3808        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3809        reference_byte: usize,
3810    ) -> bool {
3811        // The translation unit's build-proven defines join the reference's
3812        // active guard set (#2011): a conditional include like the nng
3813        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
3814        // A reference whose own environment is unknown stays unknown -- the
3815        // facts extend an environment, they never invent one.
3816        let proven = self.compile_proven_guards(file);
3817        let augmented;
3818        let reference_guards = match reference_guards {
3819            Some(active) if !proven.is_empty() => {
3820                augmented = active.union(&proven).cloned().collect();
3821                Some(&augmented)
3822            }
3823            other => other,
3824        };
3825        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3826            return false;
3827        }
3828        if self
3829            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3830            .is_some_and(|activation| activation <= reference_byte)
3831        {
3832            return true;
3833        }
3834        let projections =
3835            self.conditional_include_projections_for_source(file, prepared, declaration_source);
3836        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3837            eprintln!(
3838                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
3839                declaration_source.rel_path().display(),
3840                declaration_guards.len(),
3841                proven.len(),
3842                projections.len(),
3843            );
3844        }
3845        projections.iter().any(|projection| {
3846            projection.activation_byte <= reference_byte
3847                && guard_requirements_hold_at_reference(
3848                    &projection.required_guards,
3849                    reference_guards,
3850                )
3851                && self.preprocessor_guards_stable_between(
3852                    file,
3853                    projection.activation_byte,
3854                    reference_byte,
3855                    &projection.required_guards,
3856                )
3857        })
3858    }
3859
3860    fn foreign_declaration_may_be_reachable_from_raw_guards(
3861        &self,
3862        file: &ProjectFile,
3863        prepared: &PreparedSyntaxTree,
3864        declaration_source: &ProjectFile,
3865        declaration_guards: &HashSet<PreprocessorGuard>,
3866        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3867        reference_byte: usize,
3868    ) -> bool {
3869        let proven = self.compile_proven_guards(file);
3870        let augmented;
3871        let reference_guards = match reference_guards {
3872            Some(active) if !proven.is_empty() => {
3873                augmented = active.union(&proven).cloned().collect();
3874                Some(&augmented)
3875            }
3876            other => other,
3877        };
3878        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3879            return false;
3880        }
3881        if self
3882            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3883            .is_some_and(|activation| activation <= reference_byte)
3884        {
3885            return true;
3886        }
3887        let reachable = find_conditional_include_projection_for_source(
3888            self.cpp,
3889            self.token,
3890            file,
3891            prepared,
3892            declaration_source,
3893            reference_guards,
3894            reference_byte,
3895            &|| {
3896                #[cfg(any(test, feature = "test-support"))]
3897                self.conditional_include_target_state_count
3898                    .fetch_add(1, Ordering::Relaxed);
3899            },
3900        );
3901        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3902            eprintln!(
3903                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
3904                declaration_source.rel_path().display(),
3905                declaration_guards.len(),
3906                proven.len(),
3907                reference_guards.map_or(0, HashSet::len),
3908            );
3909        }
3910        reachable
3911    }
3912
3913    fn foreign_declaration_reachable_from_compile_proven_guards(
3914        &self,
3915        file: &ProjectFile,
3916        prepared: &PreparedSyntaxTree,
3917        declaration_source: &ProjectFile,
3918        declaration_guards: &HashSet<PreprocessorGuard>,
3919        reference_byte: usize,
3920    ) -> bool {
3921        let proven = self.compile_proven_guards(file);
3922        if proven.is_empty()
3923            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
3924        {
3925            return false;
3926        }
3927        let projections =
3928            self.conditional_include_projections_for_source(file, prepared, declaration_source);
3929        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3930            eprintln!(
3931                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
3932                declaration_source.rel_path().display(),
3933                declaration_guards.len(),
3934                proven.len(),
3935                projections.len(),
3936            );
3937        }
3938        projections.iter().any(|projection| {
3939            projection.activation_byte <= reference_byte
3940                    && guard_requirements_hold_at_reference(
3941                        &projection.required_guards,
3942                        Some(proven.as_ref()),
3943                    )
3944                    // Build facts hold at translation-unit entry. A source
3945                    // `#undef` or an earlier include may invalidate one before
3946                    // this conditional include is reached; mutations after the
3947                    // include cannot revoke declarations it already supplied.
3948                    && self.preprocessor_guards_stable_between(
3949                        file,
3950                        0,
3951                        projection.activation_byte,
3952                        &projection.required_guards,
3953                    )
3954        })
3955    }
3956
3957    pub fn external_type_candidate_visible_in_context(
3958        &self,
3959        analyzer: &CppGraphSource<'_>,
3960        file: &ProjectFile,
3961        candidate: &CodeUnit,
3962        reference: Node<'_>,
3963    ) -> bool {
3964        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
3965        if report_stats {
3966            eprintln!(
3967                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
3968                candidate.fq_name(),
3969                candidate.source().rel_path().display(),
3970                file.rel_path().display(),
3971                reference.start_byte(),
3972            );
3973        }
3974        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3975            return false;
3976        };
3977        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
3978        let reference_guards = OnceCell::new();
3979        let reference_guards_at_site = || {
3980            reference_guards.get_or_init(|| {
3981                let started = Instant::now();
3982                if report_stats {
3983                    eprintln!(
3984                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
3985                        file.rel_path().display(),
3986                        reference.start_byte(),
3987                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
3988                    );
3989                }
3990                let macro_environment = self.macro_environment(file, reference.start_byte());
3991                let filtered = raw_reference_guards
3992                    .clone()
3993                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3994                if report_stats {
3995                    eprintln!(
3996                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
3997                        filtered.is_some(),
3998                        started.elapsed().as_millis(),
3999                    );
4000                }
4001                filtered
4002            })
4003        };
4004
4005        let peers = self
4006            .visible_identifier_candidates(file, candidate.identifier())
4007            .filter(|peer| same_logical_symbol(candidate, peer))
4008            .collect::<Vec<_>>();
4009        if report_stats {
4010            let peer_sources = peers
4011                .iter()
4012                .map(|peer| peer.source().rel_path().display().to_string())
4013                .collect::<Vec<_>>();
4014            eprintln!(
4015                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4016                candidate.fq_name(),
4017            );
4018        }
4019        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4020            declaration_guard_requirements(analyzer, self.cpp, peer)
4021                .into_iter()
4022                .any(|(declaration_byte, declaration_guards)| {
4023                    if peer.source() == file {
4024                        let visible = declaration_byte < reference.start_byte()
4025                            && declaration_guards.is_empty();
4026                        if report_stats {
4027                            eprintln!(
4028                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4029                                peer.source().rel_path().display(),
4030                                declaration_guards.len(),
4031                            );
4032                        }
4033                        return visible;
4034                    }
4035                    let direct = declaration_guards.is_empty()
4036                        && self
4037                            .include_activation_for_source(
4038                                self.cpp,
4039                                file,
4040                                prepared.as_ref(),
4041                                peer.source(),
4042                            )
4043                            .is_some_and(|activation| activation <= reference.start_byte());
4044                    let compile_proven = !direct
4045                        && self.foreign_declaration_reachable_from_compile_proven_guards(
4046                            file,
4047                            prepared.as_ref(),
4048                            peer.source(),
4049                            &declaration_guards,
4050                            reference.start_byte(),
4051                        );
4052                    if report_stats {
4053                        eprintln!(
4054                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4055                            peer.source().rel_path().display(),
4056                            declaration_guards.len(),
4057                        );
4058                    }
4059                    direct || compile_proven
4060                })
4061        });
4062        if directly_visible_without_reference_environment {
4063            if report_stats {
4064                eprintln!(
4065                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4066                    candidate.fq_name(),
4067                );
4068            }
4069            return true;
4070        }
4071        let directly_visible = peers.iter().any(|peer| {
4072            declaration_guard_requirements(analyzer, self.cpp, peer)
4073                .into_iter()
4074                .any(|(declaration_byte, declaration_guards)| {
4075                    if peer.source() == file {
4076                        if declaration_byte >= reference.start_byte() {
4077                            return false;
4078                        }
4079                        if !guard_requirements_hold_at_reference(
4080                            &declaration_guards,
4081                            raw_reference_guards.as_ref(),
4082                        ) {
4083                            return false;
4084                        }
4085                        return guard_requirements_hold_at_reference(
4086                            &declaration_guards,
4087                            reference_guards_at_site().as_ref(),
4088                        ) && self.preprocessor_guards_stable_between(
4089                            file,
4090                            declaration_byte,
4091                            reference.start_byte(),
4092                            &declaration_guards,
4093                        );
4094                    }
4095                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4096                        file,
4097                        prepared.as_ref(),
4098                        peer.source(),
4099                        &declaration_guards,
4100                        raw_reference_guards.as_ref(),
4101                        reference.start_byte(),
4102                    );
4103                    if report_stats {
4104                        eprintln!(
4105                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4106                            peer.source().rel_path().display(),
4107                            declaration_guards.len(),
4108                        );
4109                    }
4110                    if !raw_feasible {
4111                        return false;
4112                    }
4113                    self.foreign_declaration_reachable_at_reference(
4114                        file,
4115                        prepared.as_ref(),
4116                        peer.source(),
4117                        &declaration_guards,
4118                        reference_guards_at_site().as_ref(),
4119                        reference.start_byte(),
4120                    )
4121                })
4122        });
4123        if directly_visible {
4124            if report_stats {
4125                eprintln!(
4126                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4127                    candidate.fq_name(),
4128                );
4129            }
4130            return true;
4131        }
4132        let complementary = self
4133            .visible_identifier_candidates(file, candidate.identifier())
4134            .filter(|peer| {
4135                peer.kind() == candidate.kind()
4136                    && peer.fq_name() == candidate.fq_name()
4137                    && peer.source() == candidate.source()
4138            })
4139            .collect::<Vec<_>>();
4140        // A completed #if/#else family declares the shared source-level name
4141        // before this reference. A later macro mutation cannot revoke that
4142        // declaration. The family gate below rejects declarations split across
4143        // separate conditional blocks, where mutation can change coverage.
4144        let complementary_family =
4145            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4146        let raw_candidate_branch_compatible = complementary_family
4147            && raw_reference_guards.as_ref().is_some_and(|active| {
4148                declaration_guard_requirements(analyzer, self.cpp, candidate)
4149                    .iter()
4150                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4151            });
4152        if report_stats {
4153            eprintln!(
4154                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4155                candidate.fq_name(),
4156                complementary.len(),
4157                complementary_family,
4158                raw_candidate_branch_compatible,
4159            );
4160        }
4161        let candidate_branch_compatible = raw_candidate_branch_compatible
4162            && reference_guards_at_site().as_ref().is_some_and(|active| {
4163                declaration_guard_requirements(analyzer, self.cpp, candidate)
4164                    .iter()
4165                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4166            });
4167        let complementary_visible = candidate_branch_compatible
4168            && if candidate.source() == file {
4169                declaration_guard_requirements(analyzer, self.cpp, candidate)
4170                    .iter()
4171                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4172            } else {
4173                self.include_activation_for_source(
4174                    self.cpp,
4175                    file,
4176                    prepared.as_ref(),
4177                    candidate.source(),
4178                )
4179                .is_some_and(|activation| activation <= reference.start_byte())
4180            };
4181        if report_stats {
4182            eprintln!(
4183                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4184                if complementary_visible {
4185                    "complementary"
4186                } else {
4187                    "missing"
4188                },
4189                candidate.fq_name(),
4190            );
4191        }
4192        complementary_visible
4193    }
4194
4195    pub fn is_exhaustive_same_fqn_type_declaration_family(
4196        &self,
4197        analyzer: &CppGraphSource<'_>,
4198        file: &ProjectFile,
4199        candidate: &CodeUnit,
4200    ) -> bool {
4201        let candidates = self
4202            .visible_identifier_candidates(file, candidate.identifier())
4203            .filter(|peer| {
4204                peer.kind() == candidate.kind()
4205                    && peer.fq_name() == candidate.fq_name()
4206                    && peer.source() == candidate.source()
4207            })
4208            .collect::<Vec<_>>();
4209        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4210    }
4211
4212    /// Prove a nested type alias used as a dependent member-pointer owner when
4213    /// its owning class has mutually-exclusive declarations.  A common C++11
4214    /// compatibility shape provides the owning class in one preprocessor
4215    /// branch and aliases it to a standard-library type in the other branch;
4216    /// the nested fallback alias is therefore not itself active in every
4217    /// branch even though the qualified owner API is.
4218    ///
4219    /// This is deliberately narrower than ordinary type visibility.  The
4220    /// caller has already recovered a member-pointer owner path from the CST;
4221    /// this helper additionally requires the target's structured parent to
4222    /// match that path, physical source visibility, and exact preprocessor
4223    /// guard agreement with the parent declaration.  Only then may the
4224    /// parent's direct/complementary same-FQN visibility stand in for the
4225    /// nested terminal's active-branch check.
4226    pub fn dependent_member_pointer_alias_visible_in_context(
4227        &self,
4228        analyzer: &CppGraphSource<'_>,
4229        file: &ProjectFile,
4230        candidate: &CodeUnit,
4231        owner_components: &[String],
4232        reference: Node<'_>,
4233    ) -> bool {
4234        if !analyzer
4235            .type_alias_provider()
4236            .is_some_and(|provider| provider.is_type_alias(candidate))
4237        {
4238            return false;
4239        }
4240        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4241            return false;
4242        };
4243        if terminal != candidate.identifier()
4244            || canonical_cpp_scope_components(candidate) != owner_components
4245        {
4246            return false;
4247        }
4248        let Some(expected_parent_fq_name) =
4249            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4250        else {
4251            return false;
4252        };
4253        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4254            return false;
4255        };
4256        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4257            || parent_anchor.source() != candidate.source()
4258            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4259        {
4260            return false;
4261        }
4262
4263        // The ordinary path already handles unguarded aliases (and preserves
4264        // same-file declaration ordering).  This fallback is only for a
4265        // physically visible declaration whose guard is the owning branch's
4266        // guard, so reject a same-file declaration that appears after the
4267        // reference before considering guard compatibility.
4268        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4269            || candidate.source() == file
4270                && !analyzer
4271                    .ranges(candidate)
4272                    .iter()
4273                    .any(|range| range.start_byte < reference.start_byte())
4274        {
4275            return false;
4276        }
4277
4278        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4279        if candidate_guards.is_empty() {
4280            return false;
4281        }
4282        let same_guard_sets =
4283            |left: &[(usize, HashSet<PreprocessorGuard>)],
4284             right: &[(usize, HashSet<PreprocessorGuard>)]| {
4285                left.iter().all(|(_, left_guards)| {
4286                    right
4287                        .iter()
4288                        .any(|(_, right_guards)| left_guards == right_guards)
4289                })
4290            };
4291        let parent_candidates = self
4292            .visible_identifier_candidates(file, parent_anchor.identifier())
4293            .filter(|peer| {
4294                peer.kind() == parent_anchor.kind()
4295                    && peer.fq_name() == expected_parent_fq_name.as_str()
4296                    && peer.source() == parent_anchor.source()
4297                    && canonical_cpp_scope_components(peer) == owner_prefix
4298            })
4299            .filter_map(|peer| {
4300                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4301                (candidate_guards.len() == parent_guards.len()
4302                    && same_guard_sets(&candidate_guards, &parent_guards)
4303                    && same_guard_sets(&parent_guards, &candidate_guards))
4304                .then(|| (peer.clone(), parent_guards))
4305            })
4306            .collect::<Vec<_>>();
4307        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4308            return false;
4309        };
4310
4311        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4312            return false;
4313        };
4314        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4315        else {
4316            return false;
4317        };
4318        // An external header selects its declaration branch before the
4319        // reference file is parsed. Require compatible reference guards, but
4320        // do not test the header's guard expression for stability in the
4321        // reference file. Same-file aliases still require that stability.
4322        if !candidate_guards.iter().any(|(_, target_guards)| {
4323            guards_compatible_at_reference(target_guards, Some(&reference_guards))
4324                && (candidate.source() != file
4325                    || self.preprocessor_guards_stable_between(
4326                        file,
4327                        0,
4328                        reference.start_byte(),
4329                        target_guards,
4330                    ))
4331        }) {
4332            return false;
4333        }
4334
4335        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4336    }
4337
4338    /// Check a type candidate's preprocessor/import context without imposing
4339    /// ordinary declaration-before-reference ordering for same-file peers.
4340    ///
4341    /// C++ class scope makes member names visible throughout the complete
4342    /// class, including a trailing return type that appears before the member
4343    /// alias declaration in source order. Callers must first prove that the
4344    /// reference is inside the candidate's indexed class owner; this helper
4345    /// only relaxes the byte-order predicate while retaining guard and include
4346    /// activation checks.
4347    pub fn external_type_candidate_guard_compatible_in_context(
4348        &self,
4349        analyzer: &CppGraphSource<'_>,
4350        file: &ProjectFile,
4351        candidate: &CodeUnit,
4352        reference: Node<'_>,
4353    ) -> bool {
4354        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4355            return false;
4356        };
4357        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4358
4359        self.visible_identifier_candidates(file, candidate.identifier())
4360            .filter(|peer| same_logical_symbol(candidate, peer))
4361            .any(|peer| {
4362                declaration_guard_requirements(analyzer, self.cpp, peer)
4363                    .into_iter()
4364                    .any(|(declaration_byte, declaration_guards)| {
4365                        if peer.source() == file {
4366                            let (start, end) = if declaration_byte <= reference.start_byte() {
4367                                (declaration_byte, reference.start_byte())
4368                            } else {
4369                                (reference.start_byte(), declaration_byte)
4370                            };
4371                            return guard_requirements_hold_at_reference(
4372                                &declaration_guards,
4373                                reference_guards.as_ref(),
4374                            ) && self.preprocessor_guards_stable_between(
4375                                file,
4376                                start,
4377                                end,
4378                                &declaration_guards,
4379                            );
4380                        }
4381                        self.foreign_declaration_reachable_at_reference(
4382                            file,
4383                            prepared.as_ref(),
4384                            peer.source(),
4385                            &declaration_guards,
4386                            reference_guards.as_ref(),
4387                            reference.start_byte(),
4388                        )
4389                    })
4390            })
4391    }
4392
4393    /// Whether a same-file callable declaration is nameable from `reference`
4394    /// after deliberately relaxing declaration-before-reference ordering.
4395    ///
4396    /// Ordinary lookup still requires an earlier declaration. Definition
4397    /// navigation for incomplete C translation units may recover a later
4398    /// definition, but only when it is at file scope and its preprocessor
4399    /// requirements hold at the call (#2404).
4400    pub fn same_file_callable_guard_compatible_ignoring_order(
4401        &self,
4402        analyzer: &CppGraphSource<'_>,
4403        file: &ProjectFile,
4404        candidate: &CodeUnit,
4405        reference: Node<'_>,
4406    ) -> bool {
4407        if candidate.source() != file || !candidate.is_callable() {
4408            return false;
4409        }
4410        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4411            return false;
4412        };
4413        let guards = OnceCell::new();
4414        let context = CallableReferenceContext {
4415            file,
4416            position: Some(CallableReferencePosition {
4417                prepared: prepared.as_ref(),
4418                byte: reference.start_byte(),
4419                guards: &guards,
4420            }),
4421        };
4422        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
4423            .into_iter()
4424            .any(|declaration| {
4425                callable_preprocessor_context_is_visible_for_reference(
4426                    declaration,
4427                    prepared.source(),
4428                    &context,
4429                )
4430            })
4431    }
4432
4433    pub fn type_candidate_may_be_visible_before_reference(
4434        &self,
4435        analyzer: &CppGraphSource<'_>,
4436        file: &ProjectFile,
4437        candidate: &CodeUnit,
4438        reference_byte: usize,
4439    ) -> bool {
4440        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4441            return false;
4442        };
4443        let root = prepared.tree().root_node();
4444        let end_byte = reference_byte
4445            .saturating_add(1)
4446            .min(prepared.source().len());
4447        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
4448            return false;
4449        };
4450        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
4451    }
4452
4453    pub fn preprocessor_guards_stable_between(
4454        &self,
4455        file: &ProjectFile,
4456        start_byte: usize,
4457        end_byte: usize,
4458        guards: &HashSet<PreprocessorGuard>,
4459    ) -> bool {
4460        if guards.is_empty() || start_byte >= end_byte {
4461            return true;
4462        }
4463        let cell = self.macro_event_cell(file);
4464        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4465        let mut visited = HashSet::from_iter([file.clone()]);
4466        !events.iter().any(|event| {
4467            event.byte() >= start_byte
4468                && event.byte() < end_byte
4469                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
4470        })
4471    }
4472
4473    fn macro_event_may_mutate_guards(
4474        &self,
4475        event: &MacroEvent,
4476        guards: &HashSet<PreprocessorGuard>,
4477        visited: &mut HashSet<ProjectFile>,
4478    ) -> bool {
4479        match event {
4480            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
4481                guards.iter().any(|guard| guard.may_depend_on_macro(name))
4482            }
4483            MacroEvent::Include { targets, .. } => {
4484                targets.is_empty()
4485                    || targets
4486                        .iter()
4487                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
4488            }
4489            MacroEvent::Invalidate { .. } => true,
4490        }
4491    }
4492
4493    fn source_may_mutate_guards(
4494        &self,
4495        file: &ProjectFile,
4496        guards: &HashSet<PreprocessorGuard>,
4497        visited: &mut HashSet<ProjectFile>,
4498    ) -> bool {
4499        if !visited.insert(file.clone()) {
4500            return false;
4501        }
4502        let cell = self.macro_event_cell(file);
4503        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4504        events
4505            .iter()
4506            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
4507    }
4508
4509    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
4510        let normalized = normalize_reference_name(raw_name)?;
4511        self.type_candidates(file, &normalized)
4512            .into_iter()
4513            .next()
4514            .cloned()
4515    }
4516
4517    /// Mirror forward navigation's visible-name fallback for a bare parameter
4518    /// type after lexical owner and inheritance lookup is exhausted.
4519    ///
4520    /// Generated or otherwise unindexed base classes can hide the alias that
4521    /// makes a parameter type valid C++. Accept the fallback only when every
4522    /// include-visible class or alias with that spelling canonicalizes to one
4523    /// logical type. A shadowing local type resolves lexically before this
4524    /// path, while distinct visible types keep the result ambiguous.
4525    pub fn unique_visible_parameter_type_fallback(
4526        &self,
4527        analyzer: &CppGraphSource<'_>,
4528        file: &ProjectFile,
4529        node: Node<'_>,
4530        source: &str,
4531    ) -> Option<CodeUnit> {
4532        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
4533            return None;
4534        }
4535        let name = node_text(node, source);
4536        let candidates = self
4537            .visible_identifier_candidates(file, name)
4538            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
4539            .filter(|candidate| {
4540                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
4541            })
4542            .collect::<Vec<_>>();
4543        self.unique_canonical_type_candidate(analyzer, file, &candidates)
4544    }
4545
4546    pub fn resolve_type_node_result(
4547        &self,
4548        file: &ProjectFile,
4549        node: Node<'_>,
4550        source: &str,
4551    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
4552        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
4553            return Ok(None);
4554        };
4555        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4556            return Ok(Some(primary));
4557        };
4558        self.resolve_template_arguments(file, primary, &arguments)
4559            .map(Some)
4560    }
4561
4562    pub fn resolve_type_node_primary(
4563        &self,
4564        file: &ProjectFile,
4565        node: Node<'_>,
4566        source: &str,
4567    ) -> Option<CodeUnit> {
4568        let components = cpp_type_name_components(node, source)?;
4569        self.resolve_type(file, &components.join("::"))
4570    }
4571
4572    pub fn resolve_template_arguments(
4573        &self,
4574        file: &ProjectFile,
4575        primary: CodeUnit,
4576        arguments: &[CppTemplateExpression],
4577    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4578        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
4579    }
4580
4581    fn resolve_template_arguments_inner(
4582        &self,
4583        file: &ProjectFile,
4584        primary: CodeUnit,
4585        arguments: &[CppTemplateExpression],
4586        seen_aliases: &mut HashSet<CodeUnit>,
4587    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4588        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
4589            && let Some(alias_target) = &metadata.alias_target
4590        {
4591            if !seen_aliases.insert(primary.clone()) {
4592                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
4593            }
4594            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
4595                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4596            let target_name = alias_target.components.join("::");
4597            let target_primary = if alias_target.global {
4598                unique_logical_type_candidate(self.type_candidates(file, &target_name))
4599            } else {
4600                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
4601            };
4602            let Some(target_primary) = target_primary else {
4603                // A dependent or external RHS cannot be canonicalized from the
4604                // indexed graph. Preserve the alias's direct identity instead
4605                // of inventing a target from its source spelling.
4606                return Ok(primary);
4607            };
4608            let Some(target_arguments) = &alias_target.arguments else {
4609                return Ok(target_primary);
4610            };
4611            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
4612                .ok_or(CppTemplateResolutionError::Substitution)?;
4613            return self.resolve_template_arguments_inner(
4614                file,
4615                target_primary,
4616                &target_arguments,
4617                seen_aliases,
4618            );
4619        }
4620
4621        let primary_fq_name = self
4622            .cpp_template_metadata
4623            .get(&primary)
4624            .map(|metadata| metadata.primary_fq_name.clone())
4625            .unwrap_or_else(|| primary.fq_name());
4626        let has_specialization_metadata = self
4627            .cpp_template_families
4628            .get(&primary_fq_name)
4629            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
4630        if !has_specialization_metadata {
4631            return Ok(primary);
4632        }
4633        self.select_template_specialization(file, &primary, arguments)
4634    }
4635
4636    fn select_template_specialization(
4637        &self,
4638        file: &ProjectFile,
4639        resolved: &CodeUnit,
4640        explicit_arguments: &[CppTemplateExpression],
4641    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4642        let primary_fq_name = self
4643            .cpp_template_metadata
4644            .get(resolved)
4645            .map(|metadata| metadata.primary_fq_name.clone())
4646            .unwrap_or_else(|| resolved.fq_name());
4647        let family = self
4648            .cpp_template_families
4649            .get(&primary_fq_name)
4650            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4651        let primary_candidates = family
4652            .iter()
4653            .filter_map(|unit| {
4654                let metadata = self.cpp_template_metadata.get(unit)?;
4655                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
4656            })
4657            .collect::<Vec<_>>();
4658        let primary_unit = primary_candidates
4659            .iter()
4660            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
4661            .or_else(|| {
4662                primary_candidates
4663                    .iter()
4664                    .map(|(unit, _)| *unit)
4665                    .min_by_key(|unit| {
4666                        (
4667                            unit.source().to_string(),
4668                            unit.signature().unwrap_or_default(),
4669                        )
4670                    })
4671            })
4672            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4673        let primary_parameters =
4674            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
4675                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4676        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
4677            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4678
4679        let mut applicable = Vec::new();
4680        for unit in family {
4681            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
4682                continue;
4683            };
4684            if metadata.is_primary() || !self.is_visible(file, unit) {
4685                continue;
4686            }
4687            if !cpp_specialization_matches(metadata, &expanded) {
4688                continue;
4689            }
4690            applicable.push((unit, metadata));
4691        }
4692        if applicable.is_empty() {
4693            return Ok(primary_unit.clone());
4694        }
4695
4696        // A scalar constraint count cannot represent C++ partial ordering:
4697        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
4698        // Select only a logical candidate whose structural pattern is strictly
4699        // more specialized than every other distinct applicable candidate.
4700        let winners = applicable
4701            .iter()
4702            .filter(|(candidate, candidate_metadata)| {
4703                applicable.iter().all(|(other, other_metadata)| {
4704                    same_visible_symbol(candidate, other)
4705                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
4706                })
4707            })
4708            .copied()
4709            .collect::<Vec<_>>();
4710        let Some((selected, _)) = winners.first() else {
4711            // Mutually incomparable applicable candidates: every one of them
4712            // is a live contender.
4713            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4714                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
4715            });
4716        };
4717        if winners
4718            .iter()
4719            .any(|(unit, _)| !same_visible_symbol(unit, selected))
4720        {
4721            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4722                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
4723            });
4724        }
4725        Ok((*selected).clone())
4726    }
4727
4728    pub fn resolve_type_components_lexically(
4729        &self,
4730        analyzer: &CppGraphSource<'_>,
4731        file: &ProjectFile,
4732        components: &[String],
4733        global: bool,
4734        lexical_scope: &[String],
4735    ) -> LexicalTypeResolution {
4736        self.resolve_type_components_lexically_inner(
4737            analyzer,
4738            file,
4739            components,
4740            global,
4741            lexical_scope,
4742            TypeCandidateResolution::Canonical,
4743        )
4744    }
4745
4746    pub fn resolve_type_components_lexically_for_forward(
4747        &self,
4748        analyzer: &CppGraphSource<'_>,
4749        file: &ProjectFile,
4750        components: &[String],
4751        global: bool,
4752        lexical_scope: &[String],
4753    ) -> LexicalTypeResolution {
4754        self.resolve_type_components_lexically_inner(
4755            analyzer,
4756            file,
4757            components,
4758            global,
4759            lexical_scope,
4760            TypeCandidateResolution::PreserveAlias,
4761        )
4762    }
4763
4764    pub fn resolve_type_components_lexically_for_target(
4765        &self,
4766        analyzer: &CppGraphSource<'_>,
4767        file: &ProjectFile,
4768        components: &[String],
4769        global: bool,
4770        lexical_scope: &[String],
4771        target: &CodeUnit,
4772    ) -> LexicalTypeResolution {
4773        #[cfg(any(test, feature = "test-support"))]
4774        self.target_preserving_type_resolution_count
4775            .fetch_add(1, Ordering::Relaxed);
4776        self.resolve_type_components_lexically_inner(
4777            analyzer,
4778            file,
4779            components,
4780            global,
4781            lexical_scope,
4782            TypeCandidateResolution::PreserveTarget(target),
4783        )
4784    }
4785
4786    pub fn coarse_unqualified_type_reference_may_resolve(
4787        &self,
4788        file: &ProjectFile,
4789        name: &str,
4790    ) -> bool {
4791        if name.is_empty() {
4792            return true;
4793        }
4794        self.visible_identifier_candidates(file, name)
4795            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4796            || self.visible_parser_alias_name_is_visible(file, name)
4797    }
4798
4799    #[allow(clippy::too_many_arguments)]
4800    pub fn structured_type_reference_may_resolve_to_target(
4801        &self,
4802        analyzer: &CppGraphSource<'_>,
4803        file: &ProjectFile,
4804        components: &[String],
4805        global: bool,
4806        lexical_scope: &[String],
4807        target: &CodeUnit,
4808    ) -> bool {
4809        if components.is_empty() {
4810            return true;
4811        }
4812        let Some(terminal) = components.last() else {
4813            return true;
4814        };
4815        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4816            .map(|qualified| qualified.join("::"))
4817            .collect::<Vec<_>>();
4818        let target_name = cpp_name_for(target);
4819        if qualified_tiers
4820            .iter()
4821            .any(|qualified| qualified == &target_name)
4822        {
4823            return true;
4824        }
4825
4826        let mut saw_shape_candidate = false;
4827        for candidate in self.visible_identifier_candidates(file, terminal) {
4828            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4829            {
4830                continue;
4831            }
4832            let candidate_name = cpp_name_for(candidate);
4833            let shape_matches = if global || components.len() > 1 {
4834                qualified_tiers
4835                    .iter()
4836                    .any(|qualified| qualified == &candidate_name)
4837            } else {
4838                true
4839            };
4840            if !shape_matches {
4841                continue;
4842            }
4843            saw_shape_candidate = true;
4844            if same_visible_symbol(candidate, target)
4845                || self.compatible_primary_template_redeclarations(candidate, target)
4846                || (declared_type_alias(analyzer, candidate)
4847                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4848            {
4849                return true;
4850            }
4851        }
4852
4853        !saw_shape_candidate
4854    }
4855
4856    pub fn target_preserving_reference_namespace(
4857        &self,
4858        analyzer: &CppGraphSource<'_>,
4859        file: &ProjectFile,
4860        identifier: &str,
4861        target: &CodeUnit,
4862    ) -> Option<Vec<String>> {
4863        let mut namespace = None;
4864        for candidate in self.visible_identifier_candidates(file, identifier) {
4865            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4866            {
4867                continue;
4868            }
4869            if !(same_visible_symbol(candidate, target)
4870                || self.compatible_primary_template_redeclarations(candidate, target)
4871                || declared_type_alias(analyzer, candidate)
4872                    && self.structured_alias_primary_preserves_target(
4873                        analyzer, file, candidate, target,
4874                    ))
4875            {
4876                continue;
4877            }
4878            if namespace
4879                .as_ref()
4880                .is_some_and(|existing| existing != candidate.package_name())
4881            {
4882                return None;
4883            }
4884            namespace = Some(candidate.package_name().to_string());
4885        }
4886        let namespace = namespace?;
4887        Some(
4888            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4889                brokk_bifrost_core::analyzer::Language::Cpp,
4890                &namespace,
4891            ),
4892        )
4893    }
4894
4895    pub fn resolve_imported_type_candidate(
4896        &self,
4897        analyzer: &CppGraphSource<'_>,
4898        file: &ProjectFile,
4899        target: &CodeUnit,
4900        target_components: &[String],
4901        direct_target: Option<&CodeUnit>,
4902        preserve_alias: bool,
4903    ) -> LexicalTypeResolution {
4904        let candidates = [target];
4905        let resolution = if preserve_alias {
4906            TypeCandidateResolution::PreserveAlias
4907        } else {
4908            direct_target.map_or(
4909                TypeCandidateResolution::Canonical,
4910                TypeCandidateResolution::PreserveTarget,
4911            )
4912        };
4913        // One candidate goes in, so a failure here is never "choose one of
4914        // these": it is the alias chain leaving the index, which must answer
4915        // missing rather than ambiguous (#1828).
4916        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4917            Ok(unit) => LexicalTypeResolution::Resolved {
4918                unit,
4919                components: target_components.to_vec(),
4920                candidates: vec![target.clone()],
4921            },
4922            Err(failure) => failure.lexical_resolution(),
4923        }
4924    }
4925
4926    fn resolve_type_components_lexically_inner(
4927        &self,
4928        analyzer: &CppGraphSource<'_>,
4929        file: &ProjectFile,
4930        components: &[String],
4931        global: bool,
4932        lexical_scope: &[String],
4933        resolution: TypeCandidateResolution<'_>,
4934    ) -> LexicalTypeResolution {
4935        if components.is_empty() {
4936            return LexicalTypeResolution::Missing;
4937        }
4938        // A C++ class injects its own name into the class scope.  The indexed
4939        // FqName for that declaration is the class path itself (for example,
4940        // `n::raw_hash_set`), not a synthetic child named
4941        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
4942        // requested identifier to every scope component, so they cannot
4943        // represent that injected binding when the enclosing class is the
4944        // closest scope.  Recover the binding from the structured class path
4945        // before allowing lookup to fall through to an outer same-spelled
4946        // declaration.
4947        let mut injected = self.resolve_injected_class_name(
4948            analyzer,
4949            file,
4950            components,
4951            global,
4952            lexical_scope,
4953            resolution,
4954        );
4955        for qualified in lexical_component_tiers(components, global, lexical_scope) {
4956            let prefix_len = qualified.len().saturating_sub(components.len());
4957            if injected
4958                .as_ref()
4959                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4960            {
4961                return injected
4962                    .take()
4963                    .expect("injected class resolution was just present")
4964                    .1;
4965            }
4966            let qualified_name = qualified.join("::");
4967            let candidates = self
4968                .type_candidates(file, &qualified_name)
4969                .into_iter()
4970                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4971                .collect::<Vec<_>>();
4972            if candidates.is_empty() {
4973                if !global && components.len() == 1 {
4974                    match self.resolve_inherited_type_for_lexical_scope(
4975                        analyzer,
4976                        file,
4977                        &qualified[..prefix_len],
4978                        &components[0],
4979                        resolution,
4980                    ) {
4981                        LexicalTypeResolution::Missing => {}
4982                        inherited => return inherited,
4983                    }
4984                }
4985                continue;
4986            }
4987            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4988                Ok(unit) => unit,
4989                Err(failure) => return failure.lexical_resolution(),
4990            };
4991            return LexicalTypeResolution::Resolved {
4992                unit,
4993                components: qualified,
4994                candidates: candidates.into_iter().cloned().collect(),
4995            };
4996        }
4997        LexicalTypeResolution::Missing
4998    }
4999
5000    fn resolve_injected_class_name(
5001        &self,
5002        analyzer: &CppGraphSource<'_>,
5003        file: &ProjectFile,
5004        components: &[String],
5005        global: bool,
5006        lexical_scope: &[String],
5007        resolution: TypeCandidateResolution<'_>,
5008    ) -> Option<(usize, LexicalTypeResolution)> {
5009        if global
5010            || components.len() != 1
5011            || file.rel_path().extension().is_some_and(|ext| ext == "c")
5012            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5013        {
5014            return None;
5015        }
5016        let name = components.first()?;
5017        let mut matches: Vec<&CodeUnit> = Vec::new();
5018        let mut owner_len = 0;
5019        for candidate in self.visible_identifier_candidates(file, name) {
5020            if !candidate.is_class()
5021                || declared_type_alias(analyzer, candidate)
5022                || candidate.identifier() != name
5023            {
5024                continue;
5025            }
5026            let candidate_scope = canonical_cpp_scope_components(candidate);
5027            if candidate_scope.len() > lexical_scope.len()
5028                || !lexical_scope.starts_with(&candidate_scope)
5029                || candidate_scope.last().is_none_or(|last| last != name)
5030            {
5031                continue;
5032            }
5033            if candidate_scope.len() > owner_len {
5034                owner_len = candidate_scope.len();
5035                matches.clear();
5036            }
5037            if candidate_scope.len() == owner_len
5038                && !matches
5039                    .iter()
5040                    .any(|existing| same_logical_symbol(existing, candidate))
5041            {
5042                matches.push(candidate);
5043            }
5044        }
5045        if matches.is_empty() {
5046            return None;
5047        }
5048        // A same-named class at the current lexical boundary is already
5049        // represented by the ordinary namespace/class tier.  The injected
5050        // recovery is only needed when lookup is occurring inside a nested
5051        // class, where the enclosing class name is injected across that
5052        // additional class boundary.  Keeping this boundary strict avoids
5053        // treating qualified receiver/static-qualifier context as an
5054        // injected-name reference.
5055        if owner_len >= lexical_scope.len() {
5056            return None;
5057        }
5058        let owner_components = lexical_scope[..owner_len].to_vec();
5059        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5060            Ok(unit) => LexicalTypeResolution::Resolved {
5061                unit,
5062                components: owner_components,
5063                candidates: matches.into_iter().cloned().collect(),
5064            },
5065            Err(failure) => failure.lexical_resolution(),
5066        };
5067        Some((owner_len, resolution))
5068    }
5069
5070    fn resolve_inherited_type_for_lexical_scope(
5071        &self,
5072        analyzer: &CppGraphSource<'_>,
5073        file: &ProjectFile,
5074        lexical_scope: &[String],
5075        name: &str,
5076        resolution: TypeCandidateResolution<'_>,
5077    ) -> LexicalTypeResolution {
5078        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5079            return LexicalTypeResolution::Missing;
5080        };
5081        let lexical_owner_name = lexical_scope.join("::");
5082        if lexical_owner_name.is_empty() {
5083            return LexicalTypeResolution::Missing;
5084        }
5085        let owner_candidates = self
5086            .type_candidates(file, &lexical_owner_name)
5087            .into_iter()
5088            .filter(|candidate| {
5089                canonical_cpp_name_matches(candidate, &lexical_owner_name)
5090                    && !declared_type_alias(analyzer, candidate)
5091            })
5092            .collect::<Vec<_>>();
5093        if owner_candidates.is_empty() {
5094            return LexicalTypeResolution::Missing;
5095        }
5096        // A visible forward declaration and the physical class definition share
5097        // one FQN, but only the definition owns hierarchy facts. When lookup is
5098        // physically inside that definition, do not let an earlier header
5099        // forward declaration erase its base edges (#2240).
5100        let physical_owner_candidates = owner_candidates
5101            .iter()
5102            .copied()
5103            .filter(|candidate| candidate.source() == file)
5104            .collect::<Vec<_>>();
5105        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5106            owner_candidates
5107        } else {
5108            physical_owner_candidates
5109        };
5110        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5111            return LexicalTypeResolution::Ambiguous;
5112        };
5113
5114        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5115        let mut visited_owners = HashSet::default();
5116        while !frontier.is_empty() {
5117            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5118            let mut next_frontier = Vec::new();
5119            for owner in frontier {
5120                if !visited_owners.insert(owner.fq_name()) {
5121                    continue;
5122                }
5123                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5124                let candidates = self
5125                    .type_candidates(file, &qualified_name)
5126                    .into_iter()
5127                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5128                    .collect::<Vec<_>>();
5129                if candidates.is_empty() {
5130                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
5131                        if !next_frontier
5132                            .iter()
5133                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5134                        {
5135                            next_frontier.push(ancestor);
5136                        }
5137                    }
5138                    continue;
5139                }
5140                let unit =
5141                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5142                        Ok(unit) => unit,
5143                        Err(failure) => return failure.lexical_resolution(),
5144                    };
5145                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5146            }
5147            if let Some((unit, candidates)) = level_matches.first().cloned() {
5148                let Some(first_declaration) = candidates.first() else {
5149                    return LexicalTypeResolution::Ambiguous;
5150                };
5151                if !level_matches.iter().all(|(_, declarations)| {
5152                    declarations
5153                        .iter()
5154                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
5155                }) {
5156                    return LexicalTypeResolution::Ambiguous;
5157                }
5158                let mut components = lexical_scope.to_vec();
5159                components.push(name.to_string());
5160                return LexicalTypeResolution::Resolved {
5161                    unit,
5162                    components,
5163                    candidates,
5164                };
5165            }
5166            frontier = next_frontier;
5167        }
5168        LexicalTypeResolution::Missing
5169    }
5170
5171    /// Resolve a base class through its injected class name at the nearest
5172    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
5173    pub fn inherited_injected_class_owner(
5174        &self,
5175        analyzer: &CppGraphSource<'_>,
5176        file: &ProjectFile,
5177        enclosing_owner: &CodeUnit,
5178        injected_name: &str,
5179    ) -> Option<CodeUnit> {
5180        let hierarchy = analyzer.type_hierarchy_provider()?;
5181        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5182        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5183        while !frontier.is_empty() {
5184            let mut level_matches = Vec::new();
5185            let mut next_frontier = Vec::new();
5186            for raw_owner in frontier {
5187                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
5188                let propagated = propagated_counts.entry(owner.clone()).or_default();
5189                if *propagated == 2 {
5190                    continue;
5191                }
5192                *propagated += 1;
5193                if owner.identifier() == injected_name {
5194                    level_matches.push(owner.clone());
5195                }
5196                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5197            }
5198            match level_matches.as_slice() {
5199                [owner] => return Some(owner.clone()),
5200                [_, ..] => return None,
5201                [] => {}
5202            }
5203            frontier = next_frontier;
5204        }
5205        None
5206    }
5207
5208    /// The one type the candidates name under `resolution`, or why they do not
5209    /// name one. The two preserving modes only ever reject candidates that
5210    /// disagree with each other, which is ambiguity; canonicalization can also
5211    /// fail because the alias chain leaves the index (#1828).
5212    fn resolve_type_candidates(
5213        &self,
5214        analyzer: &CppGraphSource<'_>,
5215        file: &ProjectFile,
5216        candidates: &[&CodeUnit],
5217        resolution: TypeCandidateResolution<'_>,
5218    ) -> Result<CodeUnit, TypeCandidateFailure> {
5219        match resolution {
5220            TypeCandidateResolution::Canonical => {
5221                self.canonical_type_candidate_resolution(analyzer, file, candidates)
5222            }
5223            TypeCandidateResolution::PreserveAlias => {
5224                // A generated index can retain identical alias spellings from
5225                // mutually exclusive headers. When the reference file
5226                // physically reaches exactly one of those source declarations,
5227                // include closure is the structured evidence that selects it;
5228                // treating the two source spellings as an overload set makes a
5229                // reachable alias appear ambiguous (#1844).
5230                let same_fqn_alias_family = candidates.len() > 1
5231                    && candidates.iter().all(|candidate| {
5232                        declared_type_alias(analyzer, candidate)
5233                            && same_logical_symbol(candidates[0], candidate)
5234                    })
5235                    && candidates
5236                        .iter()
5237                        .any(|candidate| candidate.source() != candidates[0].source());
5238                if same_fqn_alias_family {
5239                    let physically_visible = candidates
5240                        .iter()
5241                        .copied()
5242                        .filter(|candidate| self.is_physically_visible(file, candidate))
5243                        .collect::<Vec<_>>();
5244                    // The family is one logical declaration only when the
5245                    // reachable spellings agree. Two same-FQN aliases whose
5246                    // written targets differ (`using Choice = Canonical;` in
5247                    // one header, `using Choice = ::Canonical;` in another)
5248                    // are a genuine conflict, and choosing the first indexed
5249                    // one silently binds the reference to an arbitrary owner
5250                    // (#2398). Collapse only a single reachable declaration
5251                    // or reachable declarations with one structured target;
5252                    // everything else stays ambiguous below.
5253                    let one_structured_target = physically_visible.len() > 1
5254                        && physically_visible.iter().skip(1).all(|candidate| {
5255                            let target = self.structured_alias_target(analyzer, candidate);
5256                            target.is_some()
5257                                && target
5258                                    == self.structured_alias_target(analyzer, physically_visible[0])
5259                        });
5260                    if physically_visible.len() == 1 || one_structured_target {
5261                        return Ok(physically_visible[0].clone());
5262                    }
5263                }
5264                unique_type_candidate_preserving_alias(analyzer, candidates)
5265                    .ok_or(TypeCandidateFailure::Ambiguous)
5266            }
5267            TypeCandidateResolution::PreserveTarget(target) => self
5268                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
5269                .ok_or(TypeCandidateFailure::Ambiguous),
5270        }
5271    }
5272
5273    pub fn resolve_callable_value_components_lexically(
5274        &self,
5275        analyzer: &CppGraphSource<'_>,
5276        file: &ProjectFile,
5277        owner_components: &[String],
5278        member_name: &str,
5279        global: bool,
5280        lexical_scope: &[String],
5281    ) -> LexicalCallableValueResolution {
5282        if owner_components.is_empty() || member_name.is_empty() {
5283            return LexicalCallableValueResolution::Missing;
5284        }
5285        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
5286            let owner_name = qualified_owner.join("::");
5287            let type_candidates = self
5288                .type_candidates(file, &owner_name)
5289                .into_iter()
5290                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
5291                .collect::<Vec<_>>();
5292            let resolved_type = if type_candidates.is_empty() {
5293                None
5294            } else {
5295                let Some(unit) =
5296                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
5297                else {
5298                    return LexicalCallableValueResolution::Ambiguous;
5299                };
5300                Some(unit)
5301            };
5302
5303            let mut qualified_callable = qualified_owner;
5304            qualified_callable.push(member_name.to_string());
5305            let callable_name = qualified_callable.join("::");
5306            let free_function = self
5307                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
5308                .into_iter()
5309                .find(|candidate| {
5310                    canonical_cpp_name_matches(candidate, &callable_name)
5311                        && type_owner_of(analyzer, candidate).is_none()
5312                })
5313                .cloned();
5314
5315            match (resolved_type, free_function) {
5316                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
5317                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
5318                (None, Some(function)) => {
5319                    return LexicalCallableValueResolution::FreeFunction(function);
5320                }
5321                (None, None) => {}
5322            }
5323        }
5324        LexicalCallableValueResolution::Missing
5325    }
5326
5327    fn resolve_type_for_declaration(
5328        &self,
5329        visible_from: &ProjectFile,
5330        declaration: &CodeUnit,
5331        raw_name: &str,
5332    ) -> Option<CodeUnit> {
5333        let normalized = normalize_reference_name(raw_name)?;
5334        if !normalized.contains("::")
5335            && let Some(namespace) = cpp_namespace_for(declaration)
5336        {
5337            for prefix in namespace_prefixes(&namespace) {
5338                let qualified = format!("{prefix}::{normalized}");
5339                if let Some(unit) = self
5340                    .type_candidates(visible_from, &qualified)
5341                    .into_iter()
5342                    .next()
5343                {
5344                    return Some(unit.clone());
5345                }
5346            }
5347        }
5348        self.resolve_type(visible_from, raw_name)
5349    }
5350
5351    fn resolve_unique_canonical_type_for_declaration(
5352        &self,
5353        analyzer: &CppGraphSource<'_>,
5354        visible_from: &ProjectFile,
5355        declaration: &CodeUnit,
5356        raw_name: &str,
5357    ) -> Option<CodeUnit> {
5358        let mut current =
5359            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
5360        let mut seen_aliases = HashSet::default();
5361        loop {
5362            let Some(target) = self.structured_alias_target(analyzer, &current) else {
5363                return current.is_class().then_some(current);
5364            };
5365            if matches!(target, StructuredAliasTarget::Builtin) {
5366                return current.is_class().then_some(current);
5367            }
5368            if !seen_aliases.insert(current.clone()) {
5369                return None;
5370            }
5371            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
5372        }
5373    }
5374
5375    pub fn canonical_type_unit(
5376        &self,
5377        analyzer: &CppGraphSource<'_>,
5378        visible_from: &ProjectFile,
5379        unit: &CodeUnit,
5380    ) -> Option<CodeUnit> {
5381        self.canonical_type_resolution(analyzer, visible_from, unit)
5382            .ok()
5383    }
5384
5385    /// Follow `unit`'s alias chain to the class it names, or report why the
5386    /// chain does not end at one indexed class.
5387    ///
5388    /// A chain that leaves the index - an alias to a template parameter, to a
5389    /// standard-library type, or to any other declaration the workspace does
5390    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
5391    /// there is still nothing to choose between.
5392    fn canonical_type_resolution(
5393        &self,
5394        analyzer: &CppGraphSource<'_>,
5395        visible_from: &ProjectFile,
5396        unit: &CodeUnit,
5397    ) -> Result<CodeUnit, TypeCandidateFailure> {
5398        let mut current = unit.clone();
5399        let mut seen_aliases = HashSet::default();
5400        loop {
5401            let Some(target) = self.structured_alias_target(analyzer, &current) else {
5402                return current
5403                    .is_class()
5404                    .then_some(current)
5405                    .ok_or(TypeCandidateFailure::Unresolvable);
5406            };
5407            if matches!(target, StructuredAliasTarget::Builtin) {
5408                return current
5409                    .is_class()
5410                    .then_some(current)
5411                    .ok_or(TypeCandidateFailure::Unresolvable);
5412            }
5413            if !seen_aliases.insert(current.clone()) {
5414                return Err(TypeCandidateFailure::Unresolvable);
5415            }
5416            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
5417        }
5418    }
5419
5420    pub fn canonical_visible_full_type_unit(
5421        &self,
5422        analyzer: &CppGraphSource<'_>,
5423        visible_from: &ProjectFile,
5424        unit: &CodeUnit,
5425    ) -> Option<CodeUnit> {
5426        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
5427        if cpp_class_declaration_strength(analyzer, &canonical)
5428            != CppClassDeclarationStrength::Forward
5429        {
5430            return Some(canonical);
5431        }
5432        let mut full = Vec::new();
5433        for candidate in self
5434            .visible_identifier_candidates(visible_from, canonical.identifier())
5435            .filter(|candidate| {
5436                candidate.is_class()
5437                    && candidate.fq_name() == canonical.fq_name()
5438                    && cpp_class_declaration_strength(analyzer, candidate)
5439                        == CppClassDeclarationStrength::Full
5440            })
5441        {
5442            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
5443                full.push(candidate.clone());
5444            }
5445        }
5446        match full.len() {
5447            0 => Some(canonical),
5448            1 => full.pop(),
5449            _ => None,
5450        }
5451    }
5452
5453    fn resolve_structured_alias_target(
5454        &self,
5455        visible_from: &ProjectFile,
5456        declaration: &CodeUnit,
5457        target: &StructuredAliasTarget,
5458    ) -> Option<CodeUnit> {
5459        self.structured_alias_target_resolution(visible_from, declaration, target)
5460            .ok()
5461    }
5462
5463    fn structured_alias_target_resolution(
5464        &self,
5465        visible_from: &ProjectFile,
5466        declaration: &CodeUnit,
5467        target: &StructuredAliasTarget,
5468    ) -> Result<CodeUnit, TypeCandidateFailure> {
5469        let primary =
5470            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
5471        let StructuredAliasTarget::Named { arguments, .. } = target else {
5472            return Err(TypeCandidateFailure::Unresolvable);
5473        };
5474        match arguments {
5475            Some(arguments) => self
5476                .resolve_template_arguments(visible_from, primary, arguments)
5477                .map_err(|error| match error {
5478                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
5479                        TypeCandidateFailure::Ambiguous
5480                    }
5481                    _ => TypeCandidateFailure::Unresolvable,
5482                }),
5483            None => Ok(primary),
5484        }
5485    }
5486
5487    fn resolve_structured_alias_primary(
5488        &self,
5489        visible_from: &ProjectFile,
5490        declaration: &CodeUnit,
5491        target: &StructuredAliasTarget,
5492    ) -> Option<CodeUnit> {
5493        self.structured_alias_primary_resolution(visible_from, declaration, target)
5494            .ok()
5495    }
5496
5497    fn structured_alias_primary_resolution(
5498        &self,
5499        visible_from: &ProjectFile,
5500        declaration: &CodeUnit,
5501        target: &StructuredAliasTarget,
5502    ) -> Result<CodeUnit, TypeCandidateFailure> {
5503        let StructuredAliasTarget::Named {
5504            components, global, ..
5505        } = target
5506        else {
5507            return Err(TypeCandidateFailure::Unresolvable);
5508        };
5509        let qualified = components.join("::");
5510        let candidates = if *global {
5511            // `::A::B` anchors at the root scope, so a candidate whose
5512            // canonical path merely ends with the spelled components does not
5513            // qualify. Without this filter a global `::Canonical` target also
5514            // collects `alpha::Canonical`, the lookup reports a false
5515            // ambiguity, and the alias arm silently drops out of its
5516            // conflicting family instead of proving the conflict (#2398).
5517            let mut candidates = self.type_candidates(visible_from, &qualified);
5518            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
5519            candidates
5520        } else {
5521            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
5522        };
5523        logical_type_candidate(candidates)
5524    }
5525
5526    pub fn structured_alias_primary_preserves_target(
5527        &self,
5528        analyzer: &CppGraphSource<'_>,
5529        visible_from: &ProjectFile,
5530        candidate: &CodeUnit,
5531        target: &CodeUnit,
5532    ) -> bool {
5533        let mut current = candidate.clone();
5534        let mut seen = HashSet::default();
5535        let mut matched_target = false;
5536        loop {
5537            if same_visible_symbol(&current, target)
5538                || self.compatible_primary_template_redeclarations(&current, target)
5539            {
5540                matched_target = true;
5541            }
5542            if !seen.insert(current.clone()) {
5543                return false;
5544            }
5545            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5546                return matched_target;
5547            };
5548            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5549                return matched_target;
5550            };
5551            let Some(primary) =
5552                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5553            else {
5554                // A dependent member target such as `Detector<T>::type`
5555                // cannot be reduced to an indexed primary, but a preceding
5556                // structured alias hop may already have proven the requested
5557                // alias identity. Cycles still resolve a primary and are
5558                // rejected by `seen` above.
5559                return matched_target;
5560            };
5561            current = primary;
5562        }
5563    }
5564
5565    pub fn structured_class_alias_resolves_to_target(
5566        &self,
5567        analyzer: &CppGraphSource<'_>,
5568        visible_from: &ProjectFile,
5569        alias: &CodeUnit,
5570        target: &CodeUnit,
5571    ) -> bool {
5572        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5573            return false;
5574        };
5575        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
5576            return false;
5577        };
5578        let StructuredAliasTarget::Named {
5579            components, global, ..
5580        } = &alias_target
5581        else {
5582            return false;
5583        };
5584        let lexical_scope = canonical_cpp_scope_components(&owner);
5585        match self.resolve_type_components_lexically_for_target(
5586            analyzer,
5587            visible_from,
5588            components,
5589            *global,
5590            &lexical_scope,
5591            target,
5592        ) {
5593            LexicalTypeResolution::Resolved {
5594                unit, candidates, ..
5595            } => {
5596                same_visible_symbol(&unit, target)
5597                    || self.same_template_member_identity(analyzer, &unit, target)
5598                    || candidates.iter().any(|candidate| {
5599                        same_visible_symbol(candidate, target)
5600                            || self.same_template_member_identity(analyzer, candidate, target)
5601                    })
5602            }
5603            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5604                self.structured_alias_primary_preserves_target(
5605                    analyzer,
5606                    visible_from,
5607                    alias,
5608                    target,
5609                ) || self.flattened_macro_namespace_alias_target_matches(
5610                    analyzer,
5611                    visible_from,
5612                    alias,
5613                    &alias_target,
5614                    target,
5615                )
5616            }
5617        }
5618    }
5619
5620    /// Return true when a class-owned alias names the requested type as one
5621    /// structured qualifier in its target path.
5622    ///
5623    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
5624    /// indexed class. Forward lookup can still retain `Primary` as its bounded
5625    /// canonical identity. Inverse lookup needs the same evidence when later
5626    /// references use only the alias spelling.
5627    pub fn structured_class_alias_path_preserves_target(
5628        &self,
5629        analyzer: &CppGraphSource<'_>,
5630        visible_from: &ProjectFile,
5631        alias: &CodeUnit,
5632        target: &CodeUnit,
5633    ) -> bool {
5634        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5635            return false;
5636        };
5637        let Some(StructuredAliasTarget::Named {
5638            components, global, ..
5639        }) = self.structured_alias_target(analyzer, alias)
5640        else {
5641            return false;
5642        };
5643        let lexical_scope = canonical_cpp_scope_components(&owner);
5644        (1..components.len()).rev().any(|component_count| {
5645            matches!(
5646                self.resolve_type_components_lexically_for_target(
5647                    analyzer,
5648                    visible_from,
5649                    &components[..component_count],
5650                    global,
5651                    &lexical_scope,
5652                    target,
5653                ),
5654                LexicalTypeResolution::Resolved {
5655                    ref unit,
5656                    ref candidates,
5657                    ..
5658                } if same_visible_symbol(unit, target)
5659                    || self.same_template_member_identity(analyzer, unit, target)
5660                    || candidates.iter().any(|candidate| {
5661                        same_visible_symbol(candidate, target)
5662                            || self.same_template_member_identity(analyzer, candidate, target)
5663                    })
5664            )
5665        })
5666    }
5667
5668    fn flattened_macro_namespace_alias_target_matches(
5669        &self,
5670        analyzer: &CppGraphSource<'_>,
5671        visible_from: &ProjectFile,
5672        alias: &CodeUnit,
5673        alias_target: &StructuredAliasTarget,
5674        target: &CodeUnit,
5675    ) -> bool {
5676        let StructuredAliasTarget::Named {
5677            components,
5678            global: false,
5679            arguments: None,
5680        } = alias_target
5681        else {
5682            return false;
5683        };
5684        let Some((target_name, namespace_components)) = components.split_last() else {
5685            return false;
5686        };
5687        if namespace_components.is_empty()
5688            || target_name != target.identifier()
5689            || alias.source() != target.source()
5690            || alias.source() != visible_from
5691            || !target.is_class()
5692            || declared_type_alias(analyzer, target)
5693        {
5694            return false;
5695        }
5696        if self
5697            .resolve_structured_alias_target(visible_from, alias, alias_target)
5698            .is_some()
5699        {
5700            return false;
5701        }
5702
5703        let alias_ranges = analyzer.ranges(alias);
5704        let target_ranges = analyzer.ranges(target);
5705        if alias_ranges.is_empty() || target_ranges.is_empty() {
5706            return false;
5707        }
5708        let alias_start = alias_ranges
5709            .iter()
5710            .map(|range| range.start_byte)
5711            .min()
5712            .expect("non-empty alias ranges have a minimum");
5713        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
5714            return false;
5715        };
5716        let root = prepared.tree().root_node();
5717        let has_matching_declaration = target_ranges
5718            .iter()
5719            .filter(|range| range.end_byte <= alias_start)
5720            .filter_map(|range| node_for_exact_range(root, range))
5721            .any(|node| {
5722                flattened_macro_namespace_components(node, prepared.source())
5723                    .is_some_and(|recovered| recovered == namespace_components)
5724            });
5725        if !has_matching_declaration {
5726            return false;
5727        }
5728
5729        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5730        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5731        guard_requirement_sets_match(&alias_guards, &target_guards)
5732    }
5733
5734    pub fn template_alias_arguments_preserve_target(
5735        &self,
5736        analyzer: &CppGraphSource<'_>,
5737        visible_from: &ProjectFile,
5738        alias: &CodeUnit,
5739        arguments: &[CppTemplateExpression],
5740        target: &CodeUnit,
5741    ) -> bool {
5742        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5743            return false;
5744        };
5745        if metadata.alias_target.is_none()
5746            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5747        {
5748            return false;
5749        }
5750        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5751    }
5752
5753    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5754        self.cpp_template_metadata
5755            .get(unit)
5756            .is_some_and(CppTemplateMetadata::is_primary)
5757    }
5758
5759    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5760        self.cpp_template_metadata
5761            .get(unit)
5762            .is_some_and(CppTemplateMetadata::is_specialization)
5763    }
5764
5765    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5766        same_visible_symbol(left, right)
5767            || self.compatible_primary_template_redeclarations(left, right)
5768    }
5769
5770    pub fn same_template_member_identity(
5771        &self,
5772        analyzer: &CppGraphSource<'_>,
5773        left: &CodeUnit,
5774        right: &CodeUnit,
5775    ) -> bool {
5776        if same_visible_symbol(left, right) {
5777            return true;
5778        }
5779        if left.kind() != right.kind()
5780            || left.identifier() != right.identifier()
5781            || left.signature() != right.signature()
5782        {
5783            return false;
5784        }
5785        let (Some(left_owner), Some(right_owner)) =
5786            (analyzer.parent_of(left), analyzer.parent_of(right))
5787        else {
5788            return false;
5789        };
5790        left_owner.is_class()
5791            && right_owner.is_class()
5792            && self.same_template_owner_identity(&left_owner, &right_owner)
5793    }
5794
5795    fn unique_canonical_type_candidate(
5796        &self,
5797        analyzer: &CppGraphSource<'_>,
5798        visible_from: &ProjectFile,
5799        candidates: &[&CodeUnit],
5800    ) -> Option<CodeUnit> {
5801        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5802            .ok()
5803    }
5804
5805    fn canonical_type_candidate_resolution(
5806        &self,
5807        analyzer: &CppGraphSource<'_>,
5808        visible_from: &ProjectFile,
5809        candidates: &[&CodeUnit],
5810    ) -> Result<CodeUnit, TypeCandidateFailure> {
5811        let mut canonical = Vec::new();
5812        for candidate in candidates {
5813            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
5814            if canonical
5815                .iter()
5816                .any(|existing| same_visible_symbol(existing, &resolved))
5817            {
5818                continue;
5819            }
5820            if let Some(existing) = canonical.iter_mut().find(|existing| {
5821                self.compatible_primary_template_redeclarations(existing, &resolved)
5822            }) {
5823                // A forward declaration and its full primary-template
5824                // definition are one C++ type even when they live in
5825                // different headers and alpha-rename their parameters. The
5826                // target-preserving path already reconciles this family; do
5827                // the same for ordinary canonical lookup so an out-of-line
5828                // member's lexical owner is not made ambiguous by its own
5829                // forward declaration. Retain the strongest physical
5830                // declaration for later owner/range queries.
5831                if matches!(
5832                    (
5833                        cpp_class_declaration_strength(analyzer, existing),
5834                        cpp_class_declaration_strength(analyzer, &resolved),
5835                    ),
5836                    (
5837                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
5838                        CppClassDeclarationStrength::Full,
5839                    ) | (
5840                        CppClassDeclarationStrength::Unknown,
5841                        CppClassDeclarationStrength::Forward,
5842                    )
5843                ) {
5844                    *existing = resolved;
5845                }
5846                continue;
5847            }
5848            canonical.push(resolved);
5849            if canonical.len() > 1 {
5850                return Err(TypeCandidateFailure::Ambiguous);
5851            }
5852        }
5853        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5854    }
5855
5856    pub fn unique_type_candidate_preserving_target(
5857        &self,
5858        analyzer: &CppGraphSource<'_>,
5859        visible_from: &ProjectFile,
5860        candidates: &[&CodeUnit],
5861        target: &CodeUnit,
5862    ) -> Option<CodeUnit> {
5863        // C++ headers often expose one logical type through mutually exclusive
5864        // physical declarations, for example a class in the fallback branch
5865        // and a `using` alias to the standard-library type in the configured
5866        // branch. The index intentionally retains both declarations so forward
5867        // lookup can report each target. Preserve the requested target when
5868        // that is the only ambiguity: every candidate has the same type kind,
5869        // exact canonical FQN, and source file, and the requested declaration
5870        // itself is one of the physical candidates. Do not merge same-named
5871        // declarations from different files or namespaces; those remain
5872        // ambiguous and fail closed below.
5873        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5874            return Some(target.clone());
5875        }
5876        let mut resolved_candidates = Vec::new();
5877        for candidate in candidates {
5878            // An ifdef branch that aliases an unindexed system type (for
5879            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
5880            // canonicalized. That branch does not name `target`. Dropping it
5881            // keeps the branch that does. Failing the whole family here would
5882            // deny every usage of the reachable spelling (#2368).
5883            let Some(resolved) =
5884                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5885            else {
5886                continue;
5887            };
5888            if resolved_candidates
5889                .iter()
5890                .any(|existing| same_visible_symbol(existing, &resolved))
5891            {
5892                continue;
5893            }
5894            resolved_candidates.push(resolved);
5895        }
5896        match resolved_candidates.as_slice() {
5897            [] => None,
5898            [single] => Some(single.clone()),
5899            // The branches disagree about what the name aliases. When they are
5900            // spellings of one entity (#1845) that disagreement is a build
5901            // configuration, not a choice between types, so it must not deny
5902            // the requested target its reference.
5903            _ => self
5904                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5905                .map(|_| target.clone()),
5906        }
5907    }
5908
5909    /// The declaration a same-file same-FQN family stands for when a reference
5910    /// names `target`, or `None` when the candidates are not one family or the
5911    /// family does not name `target`.
5912    ///
5913    /// A translation unit cannot hold two different types under one qualified
5914    /// name, so several same-kind declarations of one FQN in one file are
5915    /// alternate spellings of one entity - the configuration branches of an
5916    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
5917    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
5918    /// targets differ; canonicalizing each branch on its own and then demanding
5919    /// agreement reports an ambiguity that denies every declaration in the
5920    /// family its usages (#1845). The family names `target` when it declares
5921    /// it, or when one branch's alias chain reaches it.
5922    ///
5923    /// Declarations in different files or namespaces are distinct entities and
5924    /// are deliberately excluded: their disagreement is a real ambiguity.
5925    pub fn same_fqn_type_spelling_for_target<'b>(
5926        &self,
5927        analyzer: &CppGraphSource<'_>,
5928        visible_from: &ProjectFile,
5929        candidates: &[&'b CodeUnit],
5930        target: &CodeUnit,
5931    ) -> Option<&'b CodeUnit> {
5932        let [first, rest @ ..] = candidates else {
5933            return None;
5934        };
5935        if rest.is_empty()
5936            || !rest.iter().all(|candidate| {
5937                candidate.kind() == first.kind()
5938                    && candidate.fq_name() == first.fq_name()
5939                    && candidate.source() == first.source()
5940            })
5941        {
5942            return None;
5943        }
5944        candidates
5945            .iter()
5946            .copied()
5947            .find(|candidate| same_symbol(candidate, target))
5948            .or_else(|| {
5949                candidates.iter().copied().find(|candidate| {
5950                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5951                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5952                })
5953            })
5954    }
5955
5956    pub fn alternate_same_fqn_type_declarations(
5957        &self,
5958        analyzer: &CppGraphSource<'_>,
5959        candidates: &[&CodeUnit],
5960        target: &CodeUnit,
5961    ) -> bool {
5962        let Some(first) = candidates.first() else {
5963            return false;
5964        };
5965        let same_api = first.kind() == target.kind()
5966            && first.fq_name() == target.fq_name()
5967            && first.source() == target.source()
5968            && candidates.iter().all(|candidate| {
5969                candidate.kind() == target.kind()
5970                    && candidate.fq_name() == target.fq_name()
5971                    && candidate.source() == target.source()
5972            })
5973            && candidates
5974                .iter()
5975                .any(|candidate| same_symbol(candidate, target))
5976            && candidates
5977                .iter()
5978                .any(|candidate| !same_logical_symbol(candidate, target));
5979        if !same_api {
5980            return false;
5981        }
5982
5983        let requirements = candidates
5984            .iter()
5985            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5986            .collect::<Vec<_>>();
5987        requirements.len() > 1
5988            && requirements
5989                .iter()
5990                .all(|requirement| !requirement.is_empty())
5991            && requirements.iter().enumerate().all(|(index, left)| {
5992                requirements[index + 1..].iter().all(|right| {
5993                    left.iter().all(|(_, left_guards)| {
5994                        right.iter().all(|(_, right_guards)| {
5995                            merge_preprocessor_guards(left_guards, right_guards).is_none()
5996                        })
5997                    })
5998                })
5999            })
6000    }
6001
6002    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6003        let mut pending = vec![terms.to_vec()];
6004        while let Some(branch_terms) = pending.pop() {
6005            let mut normalized = Vec::new();
6006            let mut covers_branch = false;
6007            for term in branch_terms {
6008                if term.iter().any(|guard| term.contains(&guard.negated())) {
6009                    continue;
6010                }
6011                if term.is_empty() {
6012                    covers_branch = true;
6013                    break;
6014                }
6015                if !normalized.iter().any(|existing| existing == &term) {
6016                    normalized.push(term);
6017                }
6018            }
6019            if covers_branch {
6020                continue;
6021            }
6022            let Some(split_guard) = normalized
6023                .iter()
6024                .flat_map(|term| term.iter())
6025                .next()
6026                .cloned()
6027            else {
6028                return false;
6029            };
6030            let negated_guard = split_guard.negated();
6031            let mut when_defined = Vec::new();
6032            let mut when_undefined = Vec::new();
6033            for term in normalized {
6034                if term.contains(&negated_guard) {
6035                    // This term cannot hold when `split_guard` is true.
6036                } else if term.contains(&split_guard) {
6037                    let mut reduced = term.clone();
6038                    reduced.remove(&split_guard);
6039                    when_defined.push(reduced);
6040                } else {
6041                    when_defined.push(term.clone());
6042                }
6043                if term.contains(&split_guard) {
6044                    // This term cannot hold when `split_guard` is false.
6045                } else if term.contains(&negated_guard) {
6046                    let mut reduced = term;
6047                    reduced.remove(&negated_guard);
6048                    when_undefined.push(reduced);
6049                } else {
6050                    when_undefined.push(term);
6051                }
6052            }
6053            pending.push(when_defined);
6054            pending.push(when_undefined);
6055        }
6056        true
6057    }
6058
6059    /// The byte range of the one `#if` family with a terminal `#else` that holds
6060    /// every physical declaration of every candidate, or `None` when they do not
6061    /// share one such family.
6062    ///
6063    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
6064    /// whose macros changed between declarations. Require every physical range to
6065    /// belong to one syntax-tree family with a terminal `#else` before the terms
6066    /// can prove branch coverage.
6067    fn declarations_share_exhaustive_conditional_family(
6068        &self,
6069        analyzer: &CppGraphSource<'_>,
6070        candidates: &[&CodeUnit],
6071    ) -> Option<(usize, usize)> {
6072        let mut family_range = None;
6073        for candidate in candidates {
6074            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6075            let root = prepared.tree().root_node();
6076            let mut candidate_family = None;
6077            for range in analyzer.ranges(candidate) {
6078                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6079                let family = preprocessor_conditional_family_for_declaration(node)?;
6080                let key = (family.start_byte(), family.end_byte());
6081                if candidate_family.is_some_and(|existing| existing != key) {
6082                    return None;
6083                }
6084                candidate_family = Some(key);
6085            }
6086            let candidate_family = candidate_family?;
6087            if family_range.is_some_and(|existing| existing != candidate_family) {
6088                return None;
6089            }
6090            family_range = Some(candidate_family);
6091        }
6092        family_range
6093    }
6094
6095    pub fn complementary_same_fqn_type_declarations(
6096        &self,
6097        analyzer: &CppGraphSource<'_>,
6098        candidates: &[&CodeUnit],
6099        target: &CodeUnit,
6100    ) -> bool {
6101        if candidates.len() < 2
6102            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6103            || self
6104                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6105                .is_none()
6106        {
6107            return false;
6108        }
6109        Self::preprocessor_guard_terms_cover_all_paths(
6110            &self.declaration_family_guard_terms(analyzer, candidates),
6111        )
6112    }
6113
6114    fn declaration_family_guard_terms(
6115        &self,
6116        analyzer: &CppGraphSource<'_>,
6117        candidates: &[&CodeUnit],
6118    ) -> Vec<HashSet<PreprocessorGuard>> {
6119        candidates
6120            .iter()
6121            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6122            .map(|(_, guards)| guards)
6123            .collect()
6124    }
6125
6126    /// A callable name declared on every branch of one completed `#if`/`#else`
6127    /// family is declared on every configuration path, so a reference below the
6128    /// whole family sees one of the branches whatever the preprocessor decides.
6129    /// Answer the family's end byte: only past `#endif` is every branch's
6130    /// declaration behind the reference.
6131    ///
6132    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
6133    /// and shares both of its primitives. It does not require two distinct
6134    /// `CodeUnit`s: branches that declare the same signature can collapse into
6135    /// one unit carrying one physical range per branch.
6136    ///
6137    /// The branches are alternate spellings of one declaration, never competing
6138    /// declarations, so only the first branch stands for the family. Reporting
6139    /// every branch as visible would turn a name the source declares exactly
6140    /// once into an ambiguity between build configurations.
6141    fn exhaustive_guard_family_activation(
6142        &self,
6143        analyzer: &CppGraphSource<'_>,
6144        prepared: &PreparedSyntaxTree,
6145        candidate: &CodeUnit,
6146        reference: &CallableReferenceContext<'_>,
6147    ) -> Option<usize> {
6148        // Branch coverage says nothing about scope: a block-local declaration
6149        // stays invisible however many branches declare it.
6150        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
6151            return None;
6152        }
6153        let family = self
6154            .visible_identifier_candidates(candidate.source(), candidate.identifier())
6155            .filter(|peer| {
6156                peer.kind() == candidate.kind()
6157                    && peer.fq_name() == candidate.fq_name()
6158                    && peer.source() == candidate.source()
6159            })
6160            .collect::<Vec<_>>();
6161        let (_, family_end) =
6162            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
6163        if !Self::preprocessor_guard_terms_cover_all_paths(
6164            &self.declaration_family_guard_terms(analyzer, &family),
6165        ) {
6166            return None;
6167        }
6168        // A reference whose own guards pick one branch already reaches that
6169        // branch through the ordinary same-guard path; the family must not
6170        // resurrect the branch the reference contradicts.
6171        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
6172            .iter()
6173            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
6174        {
6175            return None;
6176        }
6177        (first_declaration_byte(analyzer, candidate)?
6178            == family
6179                .iter()
6180                .filter_map(|peer| first_declaration_byte(analyzer, peer))
6181                .min()?)
6182        .then_some(family_end)
6183    }
6184
6185    fn type_candidate_preserving_target(
6186        &self,
6187        analyzer: &CppGraphSource<'_>,
6188        visible_from: &ProjectFile,
6189        candidate: &CodeUnit,
6190        target: &CodeUnit,
6191    ) -> Option<CodeUnit> {
6192        let mut current = candidate.clone();
6193        let mut matched_target = same_visible_symbol(&current, target)
6194            || self.compatible_primary_template_redeclarations(&current, target);
6195        let mut seen = HashSet::default();
6196        loop {
6197            if !seen.insert(current.clone()) {
6198                return None;
6199            }
6200            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
6201                return matched_target
6202                    .then(|| target.clone())
6203                    .or_else(|| current.is_class().then_some(current));
6204            };
6205            if self.flattened_macro_namespace_alias_target_matches(
6206                analyzer,
6207                visible_from,
6208                &current,
6209                &alias_target,
6210                target,
6211            ) {
6212                return Some(target.clone());
6213            }
6214            if matches!(alias_target, StructuredAliasTarget::Builtin) {
6215                return matched_target
6216                    .then(|| target.clone())
6217                    .or_else(|| current.is_class().then_some(current));
6218            }
6219            // A non-template alias can name a template alias with explicit
6220            // arguments (for example, `using Result = Expected<int>`).  When
6221            // the requested target is that alias's primary declaration, keep
6222            // the primary identity before expanding the RHS arguments.  The
6223            // expansion would otherwise canonicalize through the underlying
6224            // implementation type and lose the target spelling used by the
6225            // forward resolver.
6226            if !self.cpp_template_metadata.contains_key(&current)
6227                && let Some(primary) =
6228                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
6229                && (same_visible_symbol(&primary, target)
6230                    || self.compatible_primary_template_redeclarations(&primary, target))
6231            {
6232                return Some(target.clone());
6233            }
6234            if same_visible_symbol(&current, target) {
6235                return Some(target.clone());
6236            }
6237            if self.cpp_template_metadata.contains_key(&current) {
6238                return None;
6239            }
6240            let Some(next) =
6241                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
6242            else {
6243                return matched_target.then(|| target.clone());
6244            };
6245            current = next;
6246            matched_target |= same_visible_symbol(&current, target)
6247                || self.compatible_primary_template_redeclarations(&current, target);
6248        }
6249    }
6250
6251    fn compatible_primary_template_redeclarations(
6252        &self,
6253        left: &CodeUnit,
6254        right: &CodeUnit,
6255    ) -> bool {
6256        let (Some(left_metadata), Some(right_metadata)) = (
6257            self.cpp_template_metadata.get(left),
6258            self.cpp_template_metadata.get(right),
6259        ) else {
6260            return false;
6261        };
6262        left_metadata.primary_fq_name == right_metadata.primary_fq_name
6263            && left_metadata.is_primary()
6264            && right_metadata.is_primary()
6265            && cpp_reconcile_primary_template_parameters(
6266                &[(left, left_metadata), (right, right_metadata)],
6267                right,
6268            )
6269            .is_some()
6270    }
6271
6272    fn alias_candidate_may_preserve_target(
6273        &self,
6274        analyzer: &CppGraphSource<'_>,
6275        visible_from: &ProjectFile,
6276        candidate: &CodeUnit,
6277        target: &CodeUnit,
6278    ) -> bool {
6279        let mut current = candidate.clone();
6280        let mut seen = HashSet::default();
6281        loop {
6282            if same_visible_symbol(&current, target)
6283                || self.compatible_primary_template_redeclarations(&current, target)
6284            {
6285                return true;
6286            }
6287            if self.cpp_template_metadata.contains_key(&current) {
6288                return true;
6289            }
6290            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
6291                return false;
6292            };
6293            let StructuredAliasTarget::Named {
6294                components,
6295                global,
6296                arguments,
6297            } = alias_target
6298            else {
6299                return false;
6300            };
6301            if arguments.is_some() || !seen.insert(current.clone()) {
6302                return true;
6303            }
6304            let qualified = components.join("::");
6305            let next = if global {
6306                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
6307            } else {
6308                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
6309            };
6310            let Some(next) = next else {
6311                return true;
6312            };
6313            current = next;
6314        }
6315    }
6316
6317    /// Every indexed type declaration `raw_name` names when it is written in
6318    /// `declaration`'s namespace: the innermost enclosing namespace that holds
6319    /// the name wins, otherwise the name is looked up unqualified.
6320    fn type_candidates_for_declaration<'b>(
6321        &'b self,
6322        visible_from: &ProjectFile,
6323        declaration: &CodeUnit,
6324        raw_name: &str,
6325    ) -> Vec<&'b CodeUnit> {
6326        let Some(normalized) = normalize_reference_name(raw_name) else {
6327            return Vec::new();
6328        };
6329        if let Some(namespace) = cpp_namespace_for(declaration) {
6330            for prefix in namespace_prefixes(&namespace) {
6331                let qualified = format!("{prefix}::{normalized}");
6332                let candidates = self.type_candidates(visible_from, &qualified);
6333                if !candidates.is_empty() {
6334                    return candidates;
6335                }
6336            }
6337        }
6338        self.type_candidates(visible_from, &normalized)
6339    }
6340
6341    fn resolve_unique_type_for_declaration(
6342        &self,
6343        visible_from: &ProjectFile,
6344        declaration: &CodeUnit,
6345        raw_name: &str,
6346    ) -> Option<CodeUnit> {
6347        unique_logical_type_candidate(self.type_candidates_for_declaration(
6348            visible_from,
6349            declaration,
6350            raw_name,
6351        ))
6352    }
6353
6354    pub fn resolves_to_type(
6355        &self,
6356        analyzer: &CppGraphSource<'_>,
6357        file: &ProjectFile,
6358        raw_name: &str,
6359        target: &CodeUnit,
6360    ) -> bool {
6361        let Some(normalized) = normalize_reference_name(raw_name) else {
6362            return false;
6363        };
6364        let candidates = self.type_candidates(file, &normalized);
6365        if candidates.is_empty() {
6366            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
6367        }
6368        let Some(resolved) =
6369            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
6370        else {
6371            return false;
6372        };
6373        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
6374    }
6375
6376    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
6377        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
6378        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
6379        match resolved.kind() {
6380            CodeUnitType::Class => Some(resolved),
6381            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
6382            _ => None,
6383        }
6384    }
6385
6386    /// Whether two callable declarations declare one function.
6387    ///
6388    /// [`same_logical_symbol`] compares the persisted signature strings, which
6389    /// embed each parameter type exactly as it was spelled. A header
6390    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
6391    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
6392    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
6393    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
6394    /// This resolves the written parameter names before comparing them and
6395    /// reports the same answer the language does for the cases it can prove.
6396    ///
6397    /// Everything it cannot prove stays two symbols: a template declaration, a
6398    /// parameter with no comparable shape, a name that resolves on one side
6399    /// only, and an alias chain it cannot follow safely (#2010).
6400    pub fn same_logical_callable(
6401        &self,
6402        analyzer: &CppGraphSource<'_>,
6403        left: &CodeUnit,
6404        right: &CodeUnit,
6405    ) -> bool {
6406        if same_logical_symbol(left, right) {
6407            return true;
6408        }
6409        if left.kind() != right.kind()
6410            || !left.is_callable()
6411            || !right.is_callable()
6412            || left.fq_name() != right.fq_name()
6413        {
6414            return false;
6415        }
6416        // A template declaration and its out-of-line body can also diverge
6417        // outside the parameter list - `template <class T>` against
6418        // `template <typename T>` - and the template head is part of the
6419        // persisted signature. Deciding template-head equivalence is a
6420        // separate question, so templates keep string identity.
6421        if self.callable_is_template_declaration(analyzer, left)
6422            || self.callable_is_template_declaration(analyzer, right)
6423        {
6424            return false;
6425        }
6426        let (Some(left_comparable), Some(right_comparable)) = (
6427            self.callable_comparable(analyzer, left),
6428            self.callable_comparable(analyzer, right),
6429        ) else {
6430            return false;
6431        };
6432        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
6433        // return type and requires-clause are part of C++ callable identity and
6434        // an out-of-line definition repeats them verbatim, so they must agree
6435        // as written.
6436        if left_comparable.suffix != right_comparable.suffix
6437            || left_comparable.shapes.len() != right_comparable.shapes.len()
6438        {
6439            return false;
6440        }
6441        left_comparable
6442            .shapes
6443            .iter()
6444            .zip(right_comparable.shapes.iter())
6445            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
6446                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
6447                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
6448                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
6449                }
6450                // An unstructured parameter records that the reduction failed,
6451                // not that the two spellings mean the same type, so it agrees
6452                // with nothing - including another unstructured parameter.
6453                _ => false,
6454            })
6455    }
6456
6457    /// Compare two parameter shapes node by node with an explicit paired stack.
6458    ///
6459    /// Shape variants and cv-qualifiers must agree exactly at every level; only
6460    /// the named leaves may be spelled differently, and they agree when they
6461    /// resolve to one type declaration.
6462    fn comparable_shapes_agree(
6463        &self,
6464        analyzer: &CppGraphSource<'_>,
6465        left: &CppComparableParameter,
6466        right: &CppComparableParameter,
6467    ) -> bool {
6468        let mut stack = vec![(left.root(), right.root())];
6469        while let Some((left_index, right_index)) = stack.pop() {
6470            match (left.node(left_index), right.node(right_index)) {
6471                (
6472                    CppComparableNode::Named {
6473                        name: left_name,
6474                        primitive: left_primitive,
6475                        konst: left_konst,
6476                        volatil: left_volatil,
6477                    },
6478                    CppComparableNode::Named {
6479                        name: right_name,
6480                        primitive: right_primitive,
6481                        konst: right_konst,
6482                        volatil: right_volatil,
6483                    },
6484                ) => {
6485                    if left_konst != right_konst
6486                        || left_volatil != right_volatil
6487                        || left_primitive != right_primitive
6488                        || !self.comparable_names_agree(
6489                            analyzer,
6490                            left_name,
6491                            right_name,
6492                            *left_primitive,
6493                        )
6494                    {
6495                        return false;
6496                    }
6497                }
6498                (
6499                    CppComparableNode::Pointer {
6500                        inner: left_inner,
6501                        konst: left_konst,
6502                        volatil: left_volatil,
6503                    },
6504                    CppComparableNode::Pointer {
6505                        inner: right_inner,
6506                        konst: right_konst,
6507                        volatil: right_volatil,
6508                    },
6509                ) => {
6510                    if left_konst != right_konst || left_volatil != right_volatil {
6511                        return false;
6512                    }
6513                    stack.push((*left_inner, *right_inner));
6514                }
6515                (
6516                    CppComparableNode::Reference { inner: left_inner },
6517                    CppComparableNode::Reference { inner: right_inner },
6518                )
6519                | (
6520                    CppComparableNode::Array { inner: left_inner },
6521                    CppComparableNode::Array { inner: right_inner },
6522                ) => stack.push((*left_inner, *right_inner)),
6523                (
6524                    CppComparableNode::Generic {
6525                        base: left_base,
6526                        arguments: left_arguments,
6527                    },
6528                    CppComparableNode::Generic {
6529                        base: right_base,
6530                        arguments: right_arguments,
6531                    },
6532                ) => {
6533                    if left_arguments.len() != right_arguments.len() {
6534                        return false;
6535                    }
6536                    stack.push((*left_base, *right_base));
6537                    stack.extend(
6538                        left_arguments.iter().zip(right_arguments.iter()).map(
6539                            |(left_argument, right_argument)| (*left_argument, *right_argument),
6540                        ),
6541                    );
6542                }
6543                _ => return false,
6544            }
6545        }
6546        true
6547    }
6548
6549    /// Whether two written type names denote one type.
6550    ///
6551    /// A primitive denotes the same type in every scope, so its recorded
6552    /// lexical scope is noise and its spelling decides. A nominal name is
6553    /// resolved on each side independently: two resolved names agree when they
6554    /// reach one type declaration, and two unresolved names agree only on
6555    /// exact agreement of what was written, which is no weaker than the
6556    /// whole-signature string equality this comparison replaces. Resolution on
6557    /// one side only is evidence of difference, never of agreement.
6558    fn comparable_names_agree(
6559        &self,
6560        analyzer: &CppGraphSource<'_>,
6561        left: &StructuredTypeName,
6562        right: &StructuredTypeName,
6563        primitive: bool,
6564    ) -> bool {
6565        if primitive {
6566            return left.path() == right.path();
6567        }
6568        match (
6569            self.comparable_name_terminal(analyzer, left),
6570            self.comparable_name_terminal(analyzer, right),
6571        ) {
6572            (Some(left_terminal), Some(right_terminal)) => {
6573                same_logical_symbol(&left_terminal, &right_terminal)
6574            }
6575            (None, None) => {
6576                left.path() == right.path() && left.is_absolute() == right.is_absolute()
6577            }
6578            _ => false,
6579        }
6580    }
6581
6582    /// The class declaration a written type name denotes, or `None` when the
6583    /// workspace cannot prove one.
6584    ///
6585    /// The lookup is a closure-independent lexical-scope prefix walk over the
6586    /// workspace definition index rather than a visibility lookup: the index
6587    /// handed to a definition query is rooted at the reference file, and a
6588    /// body's `.cpp` is almost never in that file's include closure. Any name
6589    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
6590    /// cannot invent a type the compiler could not see; `using`-directives are
6591    /// not modelled, and a name that needs one stays unresolved.
6592    fn comparable_name_terminal(
6593        &self,
6594        analyzer: &CppGraphSource<'_>,
6595        name: &StructuredTypeName,
6596    ) -> Option<CodeUnit> {
6597        let mut current = self.comparable_name_declaration(analyzer, name)?;
6598        let mut visited = HashSet::default();
6599        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
6600            // The alias question is asked before the class question, and
6601            // through `declared_type_alias` rather than `is_type_alias`,
6602            // because extraction records `using A8 = A7;` as a *Class* unit
6603            // whose signature is the alias declaration. Reading the kind first
6604            // would end the chase on the alias itself and report an alias
6605            // spelling and its underlying class as two types (#2010).
6606            if !declared_type_alias(analyzer, &current) {
6607                return current.is_class().then_some(current);
6608            }
6609            if !visited.insert(current.clone()) {
6610                return None;
6611            }
6612            let signature = current.signature()?;
6613            // `cpp_alias_declaration_target_text` reads the declaration's
6614            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
6615            // pointer is silently dropped. Substituting such an alias would
6616            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
6617            if cpp_alias_declaration_adds_indirection(signature) {
6618                return None;
6619            }
6620            let raw_target = cpp_alias_declaration_target_text(signature)?;
6621            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
6622        }
6623        None
6624    }
6625
6626    /// The declaration one alias hop lands on: the type `raw_target` names,
6627    /// looked up from the alias declaration's own enclosing namespace.
6628    ///
6629    /// The hop takes the same closure-independent prefix walk the first lookup
6630    /// took, and deliberately not `resolve_type_for_declaration`: that one
6631    /// answers out of the `VisibilityIndex`, which is rooted at the reference
6632    /// file, while the alias declaration this hop starts from is reached
6633    /// through the workspace definition index and its file need not be in that
6634    /// root's include closure - where the visibility lookup answers nothing and
6635    /// the chase would stop on the alias itself (#2010).
6636    fn comparable_alias_target(
6637        &self,
6638        analyzer: &CppGraphSource<'_>,
6639        alias: &CodeUnit,
6640        raw_target: &str,
6641    ) -> Option<CodeUnit> {
6642        // `raw_target` is the alias declaration's written type text, so it is a
6643        // plain `::`-joined qualified-id: the same domain the shared symbol-path
6644        // parser reads, and the same leading `::` that marks an absolute name
6645        // everywhere else this crate normalizes a reference.
6646        let absolute = raw_target.trim_start().starts_with("::");
6647        let normalized = normalize_reference_name(raw_target)?;
6648        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6649            brokk_bifrost_core::analyzer::Language::Cpp,
6650            &normalized,
6651        );
6652        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
6653            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6654                brokk_bifrost_core::analyzer::Language::Cpp,
6655                &namespace,
6656            )
6657        });
6658        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
6659        self.comparable_name_declaration(analyzer, &name)
6660    }
6661
6662    /// The one type declaration `name` names, by enclosing scope, innermost
6663    /// first.
6664    ///
6665    /// The first prefix depth that names anything decides: an inner scope hides
6666    /// an outer one, so a match there is the answer even when an outer scope
6667    /// also declares the name. Several logically distinct declarations at that
6668    /// depth are an ambiguity this comparison must not guess at.
6669    fn comparable_name_declaration(
6670        &self,
6671        analyzer: &CppGraphSource<'_>,
6672        name: &StructuredTypeName,
6673    ) -> Option<CodeUnit> {
6674        let definitions = analyzer.workspace_definitions();
6675        let interner = segment_interner();
6676        let first_depth = if name.is_absolute() {
6677            0
6678        } else {
6679            name.lexical_scope().len()
6680        };
6681        for depth in (0..=first_depth).rev() {
6682            let mut structured = FqName::new();
6683            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
6684                structured.push(interner.intern(component, SegmentKind::Unknown));
6685            }
6686            let mut candidates = definitions
6687                .identifier(&structured)
6688                .into_iter()
6689                .filter(|unit| unit.fq().same_segment_texts(&structured))
6690                .filter(|unit| {
6691                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
6692                });
6693            let Some(first) = candidates.next() else {
6694                continue;
6695            };
6696            return candidates
6697                .all(|unit| same_logical_symbol(&unit, &first))
6698                .then_some(first);
6699        }
6700        None
6701    }
6702
6703    /// The comparison inputs of one callable declaration, extracted once.
6704    ///
6705    /// The comparison itself runs only when two candidates share kind and fully
6706    /// qualified name but not signature, which is rare; re-reading the same
6707    /// declaration for every pair in a candidate set is not.
6708    fn callable_comparable(
6709        &self,
6710        analyzer: &CppGraphSource<'_>,
6711        unit: &CodeUnit,
6712    ) -> Option<Arc<ExtractedComparable>> {
6713        if let Some(cached) = self
6714            .callable_comparables
6715            .lock()
6716            .expect("C++ callable comparable cache poisoned")
6717            .get(unit)
6718            .cloned()
6719        {
6720            return cached;
6721        }
6722        let extracted = self
6723            .extract_callable_comparable(analyzer, unit)
6724            .map(Arc::new);
6725        self.callable_comparables
6726            .lock()
6727            .expect("C++ callable comparable cache poisoned")
6728            .insert(unit.clone(), extracted.clone());
6729        extracted
6730    }
6731
6732    fn extract_callable_comparable(
6733        &self,
6734        analyzer: &CppGraphSource<'_>,
6735        unit: &CodeUnit,
6736    ) -> Option<ExtractedComparable> {
6737        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6738        let root = prepared.tree().root_node();
6739        let declarator = analyzer
6740            .ranges(unit)
6741            .into_iter()
6742            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6743        Some(ExtractedComparable {
6744            // One question about one declarator: indexing the file's tree would
6745            // cost more than the walk it saves.
6746            shapes: cpp_comparable_parameter_shapes(
6747                declarator,
6748                prepared.source(),
6749                &ParentIndex::unindexed(),
6750            ),
6751            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6752        })
6753    }
6754
6755    pub fn canonical_type_for_reference(
6756        &self,
6757        file: &ProjectFile,
6758        raw_name: &str,
6759    ) -> Option<CodeUnit> {
6760        let resolved = self.resolve_type(file, raw_name)?;
6761        self.alias_target(&resolved).or(Some(resolved))
6762    }
6763
6764    pub fn parser_alias_resolves_to_type(
6765        &self,
6766        analyzer: &CppGraphSource<'_>,
6767        file: &ProjectFile,
6768        raw_name: &str,
6769        target: &CodeUnit,
6770    ) -> bool {
6771        let Some(alias_name) = normalize_reference_name(raw_name) else {
6772            return false;
6773        };
6774        let Some(cpp) = analyzer.cpp else {
6775            return false;
6776        };
6777        let matches_file = |source_file: &ProjectFile| {
6778            self.file_alias_matches(cpp, source_file, &alias_name, target)
6779        };
6780        self.visible_source_files_by_root.get(file).map_or_else(
6781            || matches_file(file),
6782            |files| files.iter().any(matches_file),
6783        )
6784    }
6785
6786    fn file_alias_matches(
6787        &self,
6788        cpp: &dyn CppSource,
6789        file: &ProjectFile,
6790        alias_name: &str,
6791        target: &CodeUnit,
6792    ) -> bool {
6793        let cell = {
6794            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6795            Arc::clone(
6796                cells
6797                    .entry(file.clone())
6798                    .or_insert_with(|| Arc::new(OnceLock::new())),
6799            )
6800        };
6801        cell.get_or_init(|| {
6802            self.parser_alias_source_parses
6803                .fetch_add(1, Ordering::Relaxed);
6804            #[cfg(any(test, feature = "test-support"))]
6805            {
6806                *self
6807                    .alias_source_parse_counts
6808                    .lock()
6809                    .expect("alias source parse count lock")
6810                    .entry(file.clone())
6811                    .or_default() += 1;
6812            }
6813            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
6814        })
6815        .iter()
6816        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
6817    }
6818
6819    #[cfg(any(test, feature = "test-support"))]
6820    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
6821        self.visible_source_files_by_root
6822            .get(file)
6823            .cloned()
6824            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
6825    }
6826
6827    #[cfg(any(test, feature = "test-support"))]
6828    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
6829        self.alias_source_parse_counts
6830            .lock()
6831            .expect("alias source parse count lock")
6832            .get(file)
6833            .copied()
6834            .unwrap_or(0)
6835    }
6836
6837    pub fn resolve_named(
6838        &self,
6839        file: &ProjectFile,
6840        raw_name: &str,
6841        kind: TargetKind,
6842    ) -> Option<CodeUnit> {
6843        let normalized = normalize_reference_name(raw_name)?;
6844        self.named_candidates_for_normalized(file, &normalized, kind)
6845            .into_iter()
6846            .next()
6847            .cloned()
6848    }
6849
6850    pub fn contains_named_symbol(
6851        &self,
6852        file: &ProjectFile,
6853        raw_name: &str,
6854        kind: TargetKind,
6855        target: &CodeUnit,
6856    ) -> bool {
6857        let Some(normalized) = normalize_reference_name(raw_name) else {
6858            return false;
6859        };
6860        self.named_candidates_for_normalized(file, &normalized, kind)
6861            .into_iter()
6862            .any(|unit| {
6863                matches_kind_for_lookup(unit, kind)
6864                    && reference_matches_unit(&normalized, unit)
6865                    && same_visible_symbol(unit, target)
6866            })
6867    }
6868
6869    pub fn named_candidates(
6870        &self,
6871        file: &ProjectFile,
6872        raw_name: &str,
6873        kind: TargetKind,
6874    ) -> Vec<CodeUnit> {
6875        let Some(normalized) = normalize_reference_name(raw_name) else {
6876            return Vec::new();
6877        };
6878        self.named_candidates_for_normalized(file, &normalized, kind)
6879            .into_iter()
6880            .cloned()
6881            .collect()
6882    }
6883
6884    pub fn resolve_known_non_target(
6885        &self,
6886        file: &ProjectFile,
6887        raw_name: &str,
6888        kind: TargetKind,
6889        target: &CodeUnit,
6890    ) -> bool {
6891        let Some(normalized) = normalize_reference_name(raw_name) else {
6892            return false;
6893        };
6894        normalized.contains("::")
6895            && self
6896                .named_candidates_for_normalized(file, &normalized, kind)
6897                .into_iter()
6898                .any(|unit| {
6899                    matches_kind_for_lookup(unit, kind)
6900                        && reference_matches_unit(&normalized, unit)
6901                        && !same_visible_symbol(unit, target)
6902                })
6903    }
6904
6905    pub fn resolve_call_return_binding(
6906        &self,
6907        analyzer: &CppGraphSource<'_>,
6908        file: &ProjectFile,
6909        raw_name: &str,
6910        arity: usize,
6911        lexical_namespace: Option<&str>,
6912        direct_type: Option<&CodeUnit>,
6913    ) -> Option<CppScanBinding> {
6914        let normalized = normalize_reference_name(raw_name)?;
6915        let mut candidates = Vec::new();
6916        for function in
6917            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6918        {
6919            if cpp_callable_arity(analyzer, function).accepts(arity)
6920                && !direct_type.is_some_and(|direct_type| {
6921                    self.callable_is_constructor_declaration(analyzer, function)
6922                        && type_owner_of(analyzer, function)
6923                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6924                })
6925            {
6926                candidates.push(function.clone());
6927            }
6928        }
6929        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6930        unanimous_return_binding(analyzer, self, file, &candidates)
6931    }
6932
6933    pub fn resolve_call_return_binding_without_arity(
6934        &self,
6935        analyzer: &CppGraphSource<'_>,
6936        file: &ProjectFile,
6937        raw_name: &str,
6938        lexical_namespace: Option<&str>,
6939        direct_type: Option<&CodeUnit>,
6940    ) -> (bool, Option<CppScanBinding>) {
6941        let Some(normalized) = normalize_reference_name(raw_name) else {
6942            return (false, None);
6943        };
6944        let mut candidates = self
6945            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6946            .into_iter()
6947            .filter(|function| {
6948                function.is_function()
6949                    && !direct_type.is_some_and(|direct_type| {
6950                        self.callable_is_constructor_declaration(analyzer, function)
6951                            && type_owner_of(analyzer, function)
6952                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6953                    })
6954            })
6955            .cloned()
6956            .collect::<Vec<_>>();
6957        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6958        let has_candidates = !candidates.is_empty();
6959        (
6960            has_candidates,
6961            unanimous_return_binding(analyzer, self, file, &candidates),
6962        )
6963    }
6964
6965    pub fn visible_identifier_candidates<'b>(
6966        &'b self,
6967        file: &ProjectFile,
6968        identifier: &str,
6969    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6970        self.visible_by_identifier
6971            .get(file)
6972            .and_then(|by_name| by_name.get(identifier))
6973            .into_iter()
6974            .flatten()
6975    }
6976
6977    /// Return terminal reference names that can denote `target` from `file`.
6978    ///
6979    /// The indexed candidate table covers ordinary declarations and aliases;
6980    /// Parser-only aliases are tested lazily when their spelling is actually
6981    /// encountered in a scanned type node. Enumerating them here would parse
6982    /// every source in the include closure even when the target's direct name
6983    /// is the only spelling present in the file.
6984    pub fn visible_type_reference_component_names_for_target(
6985        &self,
6986        analyzer: &CppGraphSource<'_>,
6987        file: &ProjectFile,
6988        target: &CodeUnit,
6989    ) -> HashSet<String> {
6990        let mut names = HashSet::from_iter([target.identifier().to_string()]);
6991        if let Some(metadata) = self.cpp_template_metadata.get(target) {
6992            names.insert(metadata.primary_name.clone());
6993        }
6994
6995        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6996            for (identifier, candidates) in by_identifier {
6997                if candidates.iter().any(|candidate| {
6998                    (candidate.is_class()
6999                        && (same_visible_symbol(candidate, target)
7000                            || self.compatible_primary_template_redeclarations(candidate, target)))
7001                        || (declared_type_alias(analyzer, candidate)
7002                            && self.alias_candidate_may_preserve_target(
7003                                analyzer, file, candidate, target,
7004                            ))
7005                }) {
7006                    names.insert(identifier.clone());
7007                }
7008            }
7009        }
7010
7011        names
7012    }
7013
7014    pub fn indexed_structural_class_scope(
7015        &self,
7016        file: &ProjectFile,
7017        class: Node<'_>,
7018        source: &str,
7019    ) -> Option<Vec<String>> {
7020        let key = (file.clone(), class.start_byte(), class.end_byte());
7021        if let Some(cached) = self
7022            .indexed_structural_class_scopes
7023            .lock()
7024            .expect("C++ indexed structural-class scope cache poisoned")
7025            .get(&key)
7026            .cloned()
7027        {
7028            return cached;
7029        }
7030        let resolved = (|| {
7031            let name = class.child_by_field_name("name")?;
7032            let identifier = if name.kind() == "template_type" {
7033                node_text(name.child_by_field_name("name")?, source).to_string()
7034            } else {
7035                let mut components = Vec::new();
7036                append_cpp_name_components(name, source, &mut components)?;
7037                components.last()?.clone()
7038            };
7039            let visible = self
7040                .visible_identifier_candidates(file, &identifier)
7041                .cloned()
7042                .collect::<Vec<_>>();
7043            let mut visible = visible;
7044            for candidate in
7045                self.visible_by_file
7046                    .get(file)
7047                    .into_iter()
7048                    .flatten()
7049                    .filter(|candidate| {
7050                        self.cpp_template_metadata
7051                            .get(candidate)
7052                            .is_some_and(|metadata| metadata.primary_name == identifier)
7053                    })
7054            {
7055                if !visible
7056                    .iter()
7057                    .any(|existing| same_logical_symbol(existing, candidate))
7058                {
7059                    visible.push(candidate.clone());
7060                }
7061            }
7062            // Built once per call rather than per candidate; `cpp_source` rebuilds
7063            // the five-field source from the same `self.cpp` on every call.
7064            let cpp_source = self.cpp_source();
7065            let candidates = visible
7066                .iter()
7067                .filter(|candidate| {
7068                    candidate.source() == file
7069                        && candidate.is_class()
7070                        && !declared_type_alias(&cpp_source, candidate)
7071                        && self.cpp.ranges(candidate).iter().any(|range| {
7072                            range.start_byte <= class.start_byte()
7073                                && class.end_byte() <= range.end_byte
7074                        })
7075                })
7076                .collect::<Vec<_>>();
7077            let owner = if name.kind() == "template_type" {
7078                let expected = normalize_cpp_whitespace(node_text(name, source));
7079                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7080                let exact = candidates
7081                    .iter()
7082                    .copied()
7083                    .filter(|candidate| {
7084                        candidate
7085                            .fq()
7086                            .segments()
7087                            .iter()
7088                            .rev()
7089                            .find_map(|&segment| {
7090                                let (text, kind) = interner.resolve(segment);
7091                                matches!(
7092                                    kind,
7093                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7094                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7095                                )
7096                                .then_some(text)
7097                            })
7098                            .is_some_and(|text| text == expected)
7099                    })
7100                    .collect::<Vec<_>>();
7101                unique_logical_type_candidate(exact)
7102                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7103            } else {
7104                unique_logical_type_candidate(candidates)?
7105            };
7106            Some(canonical_cpp_scope_components(&owner))
7107        })();
7108        self.indexed_structural_class_scopes
7109            .lock()
7110            .expect("C++ indexed structural-class scope cache poisoned")
7111            .insert(key, resolved.clone());
7112        resolved
7113    }
7114
7115    pub fn indexed_enclosing_owner_scope(
7116        &self,
7117        analyzer: &CppGraphSource<'_>,
7118        file: &ProjectFile,
7119        node: Node<'_>,
7120    ) -> Option<Vec<String>> {
7121        let anchor = std::iter::successors(Some(node), |current| current.parent())
7122            .find(|current| {
7123                matches!(
7124                    current.kind(),
7125                    "function_definition"
7126                        | "class_specifier"
7127                        | "struct_specifier"
7128                        | "union_specifier"
7129                )
7130            })
7131            .unwrap_or(node);
7132        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
7133        if let Some(cached) = self
7134            .indexed_enclosing_owner_scopes
7135            .lock()
7136            .expect("C++ indexed enclosing-owner scope cache poisoned")
7137            .get(&key)
7138            .cloned()
7139        {
7140            return cached;
7141        }
7142        let resolved = (|| {
7143            let range = Range {
7144                start_byte: node.start_byte(),
7145                end_byte: node.end_byte(),
7146                start_line: node.start_position().row,
7147                end_line: node.end_position().row,
7148            };
7149            let start = analyzer.enclosing_code_unit(file, &range)?;
7150            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
7151                start,
7152                |unit| self.cached_precise_parent_of(analyzer, unit),
7153            )
7154            .find(|unit| {
7155                unit.is_class()
7156                    && !analyzer
7157                        .type_alias_provider()
7158                        .is_some_and(|provider| provider.is_type_alias(unit))
7159            })?;
7160            Some(canonical_cpp_scope_components(&owner))
7161        })();
7162        self.indexed_enclosing_owner_scopes
7163            .lock()
7164            .expect("C++ indexed enclosing-owner scope cache poisoned")
7165            .insert(key, resolved.clone());
7166        resolved
7167    }
7168
7169    fn cached_precise_parent_of(
7170        &self,
7171        analyzer: &CppGraphSource<'_>,
7172        code_unit: &CodeUnit,
7173    ) -> Option<CodeUnit> {
7174        if let Some(cached) = self
7175            .precise_parent_cache
7176            .lock()
7177            .expect("C++ precise-parent cache poisoned")
7178            .get(code_unit)
7179            .cloned()
7180        {
7181            return cached;
7182        }
7183        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
7184        self.precise_parent_cache
7185            .lock()
7186            .expect("C++ precise-parent cache poisoned")
7187            .insert(code_unit.clone(), resolved.clone());
7188        resolved
7189    }
7190
7191    pub fn callable_is_constructor_declaration(
7192        &self,
7193        analyzer: &CppGraphSource<'_>,
7194        candidate: &CodeUnit,
7195    ) -> bool {
7196        if !candidate.is_function() {
7197            return false;
7198        }
7199        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7200            return false;
7201        };
7202        let root = prepared.tree().root_node();
7203        let candidate_ranges = analyzer.ranges(candidate);
7204        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
7205            let mut current = root
7206                .descendant_for_byte_range(range.start_byte, range.end_byte)
7207                .and_then(|node| node.parent());
7208            while let Some(node) = current {
7209                if matches!(
7210                    node.kind(),
7211                    "class_specifier" | "struct_specifier" | "union_specifier"
7212                ) {
7213                    return node
7214                        .child_by_field_name("name")
7215                        .map(|name| terminal_name(node_text(name, prepared.source())))
7216                        .is_some_and(|name| name == candidate.identifier());
7217                }
7218                current = node.parent();
7219            }
7220            false
7221        });
7222        if enclosed_by_matching_type {
7223            return true;
7224        }
7225        let indexed_containment = analyzer
7226            .declarations(candidate.source())
7227            .into_iter()
7228            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
7229            .any(|owner| {
7230                analyzer.ranges(&owner).iter().any(|owner_range| {
7231                    candidate_ranges.iter().any(|candidate_range| {
7232                        owner_range.start_byte <= candidate_range.start_byte
7233                            && candidate_range.end_byte <= owner_range.end_byte
7234                    })
7235                })
7236            });
7237        if indexed_containment {
7238            return true;
7239        }
7240        let metadata = analyzer.signature_metadata(candidate);
7241        !metadata.is_empty()
7242            && metadata
7243                .iter()
7244                .all(|signature| signature.return_type_text().is_none())
7245    }
7246
7247    /// Whether a callable declaration is a class-template deduction guide.
7248    ///
7249    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
7250    /// type field whose function declarator owns a trailing return type. This
7251    /// structured shape distinguishes a guide from both a constructor (no
7252    /// trailing return) and an ordinary trailing-return function (an `auto`
7253    /// type field).
7254    pub fn callable_is_deduction_guide_declaration(
7255        &self,
7256        analyzer: &CppGraphSource<'_>,
7257        candidate: &CodeUnit,
7258    ) -> bool {
7259        if !candidate.is_function() {
7260            return false;
7261        }
7262        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7263            return false;
7264        };
7265        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
7266            .into_iter()
7267            .any(|declaration| {
7268                if declaration.kind() != "declaration"
7269                    || declaration.child_by_field_name("type").is_some()
7270                {
7271                    return false;
7272                }
7273                let Some(declarator) = declaration.child_by_field_name("declarator") else {
7274                    return false;
7275                };
7276                if declarator.kind() != "function_declarator" {
7277                    return false;
7278                }
7279                let mut cursor = declarator.walk();
7280                let has_trailing_return = declarator
7281                    .named_children(&mut cursor)
7282                    .any(|child| child.kind() == "trailing_return_type");
7283                has_trailing_return
7284                    && declarator_name_node(declarator).is_some_and(|name| {
7285                        node_text(name, prepared.source()) == candidate.identifier()
7286                    })
7287            })
7288    }
7289
7290    /// Whether a callable occurrence is directly wrapped by a C++ template
7291    /// declaration. This deliberately inspects declaration syntax instead of
7292    /// inferring template status from the rendered signature.
7293    pub fn callable_is_template_declaration(
7294        &self,
7295        analyzer: &CppGraphSource<'_>,
7296        candidate: &CodeUnit,
7297    ) -> bool {
7298        if !candidate.is_function() {
7299            return false;
7300        }
7301        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7302            return false;
7303        };
7304        let root = prepared.tree().root_node();
7305        analyzer.ranges(candidate).iter().any(|range| {
7306            let Some(node) = node_for_exact_range(root, range)
7307                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
7308            else {
7309                return false;
7310            };
7311            node.parent().is_some_and(|parent| {
7312                parent.kind() == "template_declaration"
7313                    && parent
7314                        .named_child(parent.named_child_count().saturating_sub(1))
7315                        .is_some_and(|declaration| same_node(declaration, node))
7316            })
7317        })
7318    }
7319
7320    pub fn type_name_candidates<'b>(
7321        &'b self,
7322        file: &ProjectFile,
7323        normalized: &str,
7324    ) -> Vec<&'b CodeUnit> {
7325        self.candidate_units(file, normalized, TargetKind::Type)
7326    }
7327
7328    pub fn visible_members_for_owner_name<'b>(
7329        &'b self,
7330        file: &ProjectFile,
7331        owner: &CodeUnit,
7332        name: &str,
7333    ) -> Vec<&'b CodeUnit> {
7334        self.visible_identifier_candidates(file, name)
7335            .filter(|unit| {
7336                // Structured owner pop on the unit's own `fq()` (shared with
7337                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
7338                // string.
7339                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
7340                    .is_some_and(|parent| parent == owner.fq_name())
7341            })
7342            .collect()
7343    }
7344
7345    pub fn visible_member_for_owner_name(
7346        &self,
7347        file: &ProjectFile,
7348        owner: &CodeUnit,
7349        name: &str,
7350    ) -> VisibleMemberResolution {
7351        let candidates = self.visible_members_for_owner_name(file, owner, name);
7352        let mut callables = Vec::new();
7353        let mut non_callable = None;
7354        for candidate in candidates {
7355            if candidate.is_function() {
7356                callables.push(candidate.clone());
7357            } else if non_callable.is_none() {
7358                non_callable = Some(candidate.clone());
7359            }
7360        }
7361        match (callables.is_empty(), non_callable) {
7362            (false, None) => VisibleMemberResolution::Callable(callables),
7363            (true, Some(_)) => VisibleMemberResolution::NonCallable,
7364            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
7365            (true, None) => VisibleMemberResolution::Missing,
7366        }
7367    }
7368
7369    fn field_declared_type_fact(
7370        &self,
7371        analyzer: &CppGraphSource<'_>,
7372        field: &CodeUnit,
7373    ) -> Option<DeclaredFieldTypeFact> {
7374        if let Some(cached) = self
7375            .field_type_facts
7376            .lock()
7377            .expect("C++ field type fact cache poisoned")
7378            .get(field)
7379            .cloned()
7380        {
7381            return cached;
7382        }
7383        let decoded = decode_field_declared_type_fact(analyzer, field);
7384        self.field_type_facts
7385            .lock()
7386            .expect("C++ field type fact cache poisoned")
7387            .insert(field.clone(), decoded.clone());
7388        decoded
7389    }
7390
7391    fn structured_alias_target(
7392        &self,
7393        analyzer: &CppGraphSource<'_>,
7394        unit: &CodeUnit,
7395    ) -> Option<StructuredAliasTarget> {
7396        if let Some(cached) = self
7397            .structured_alias_targets
7398            .lock()
7399            .expect("C++ structured alias target cache poisoned")
7400            .get(unit)
7401            .cloned()
7402        {
7403            return cached;
7404        }
7405        let decoded = decode_structured_alias_target(analyzer, unit);
7406        self.structured_alias_targets
7407            .lock()
7408            .expect("C++ structured alias target cache poisoned")
7409            .insert(unit.clone(), decoded.clone());
7410        decoded
7411    }
7412
7413    pub fn type_candidates<'b>(
7414        &'b self,
7415        file: &ProjectFile,
7416        normalized: &str,
7417    ) -> Vec<&'b CodeUnit> {
7418        let mut candidates = self
7419            .candidate_units(file, normalized, TargetKind::Type)
7420            .into_iter()
7421            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
7422            .collect::<Vec<_>>();
7423        dedup_unit_refs(&mut candidates);
7424        candidates
7425    }
7426
7427    pub fn named_candidates_for_normalized<'b>(
7428        &'b self,
7429        file: &ProjectFile,
7430        normalized: &str,
7431        kind: TargetKind,
7432    ) -> Vec<&'b CodeUnit> {
7433        let mut candidates = self
7434            .candidate_units(file, normalized, kind)
7435            .into_iter()
7436            .filter(|unit| {
7437                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
7438            })
7439            .collect::<Vec<_>>();
7440        dedup_unit_refs(&mut candidates);
7441        candidates
7442    }
7443
7444    pub fn candidate_units<'b>(
7445        &'b self,
7446        file: &ProjectFile,
7447        normalized: &str,
7448        kind: TargetKind,
7449    ) -> Vec<&'b CodeUnit> {
7450        if normalized.contains("::") {
7451            // `normalized` comes from `normalize_cpp_reference_text`, which
7452            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
7453            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
7454            // kept intact by the shared splitter's operator merge — the same
7455            // domain `cpp_reference_fqn_candidates` below already parses with
7456            // the shared splitter. Re-tokenizing and taking the last segment
7457            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
7458            // scan exactly.
7459            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7460                brokk_bifrost_core::analyzer::Language::Cpp,
7461                normalized,
7462            )
7463            .pop() else {
7464                return Vec::new();
7465            };
7466            let fqns = cpp_reference_fqn_candidates(normalized, kind);
7467            return self
7468                .visible_identifier_candidates(file, &identifier)
7469                .filter(|unit| {
7470                    #[cfg(any(test, feature = "test-support"))]
7471                    self.qualified_candidate_inspections
7472                        .fetch_add(1, Ordering::Relaxed);
7473                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
7474                        || canonical_cpp_name_matches(unit, normalized)
7475                })
7476                .collect();
7477        }
7478        self.visible_identifier_candidates(file, normalized)
7479            .collect()
7480    }
7481
7482    #[cfg(any(test, feature = "test-support"))]
7483    pub fn reset_qualified_candidate_inspections(&self) {
7484        self.qualified_candidate_inspections
7485            .store(0, Ordering::Relaxed);
7486    }
7487
7488    #[cfg(any(test, feature = "test-support"))]
7489    pub fn qualified_candidate_inspections(&self) -> usize {
7490        self.qualified_candidate_inspections.load(Ordering::Relaxed)
7491    }
7492
7493    #[cfg(any(test, feature = "test-support"))]
7494    pub fn reset_target_preserving_type_resolution_count(&self) {
7495        self.target_preserving_type_resolution_count
7496            .store(0, Ordering::Relaxed);
7497    }
7498
7499    #[cfg(any(test, feature = "test-support"))]
7500    pub fn target_preserving_type_resolution_count(&self) -> usize {
7501        self.target_preserving_type_resolution_count
7502            .load(Ordering::Relaxed)
7503    }
7504
7505    #[cfg(any(test, feature = "test-support"))]
7506    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
7507        self.visible_parser_alias_name_set_build_count
7508            .load(Ordering::Relaxed)
7509    }
7510}
7511
7512#[derive(Default)]
7513struct IncludeGraph {
7514    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
7515}
7516
7517impl IncludeGraph {
7518    fn extend_with<F>(
7519        &mut self,
7520        root: &ProjectFile,
7521        cancellation: Option<&CancellationToken>,
7522        targets_for: &mut F,
7523    ) where
7524        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7525    {
7526        let mut stack = vec![root.clone()];
7527        while let Some(file) = stack.pop() {
7528            if cancellation.is_some_and(CancellationToken::is_cancelled) {
7529                break;
7530            }
7531            if self.targets_by_file.contains_key(&file) {
7532                continue;
7533            }
7534            let targets = targets_for(&file);
7535            stack.extend(targets.iter().cloned());
7536            self.targets_by_file.insert(file, targets);
7537        }
7538    }
7539
7540    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
7541        self.targets_by_file.keys()
7542    }
7543
7544    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
7545        self.targets_by_file
7546            .get(file)
7547            .map(Vec::as_slice)
7548            .unwrap_or_default()
7549    }
7550
7551    fn reachable_files(
7552        &self,
7553        root: &ProjectFile,
7554        cancellation: Option<&CancellationToken>,
7555    ) -> HashSet<ProjectFile> {
7556        let mut pending = vec![root.clone()];
7557        let mut visited = HashSet::default();
7558        while let Some(file) = pending.pop() {
7559            if cancellation.is_some_and(CancellationToken::is_cancelled) {
7560                break;
7561            }
7562            if visited.insert(file.clone()) {
7563                pending.extend(self.targets(&file).iter().cloned());
7564            }
7565        }
7566        visited
7567    }
7568}
7569
7570fn build_bounded_visible_declarations(
7571    cpp: &dyn CppSource,
7572    token: QueryToken<'_>,
7573    analyzer: &CppGraphSource<'_>,
7574    roots: &HashSet<ProjectFile>,
7575    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7576    cancellation: Option<&CancellationToken>,
7577    stats: &mut BoundedVisibilityStats,
7578) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
7579    roots
7580        .iter()
7581        .map(|root| {
7582            let reading_is_c = analyzer.reference_uses_c_semantics(root);
7583            let declarations_started = Instant::now();
7584            let root_declarations =
7585                bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
7586            stats.declaration_elapsed += declarations_started.elapsed();
7587            stats.declaration_reads += 1;
7588            stats.declaration_units += root_declarations.len();
7589            let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
7590            let mut pending_names = HashSet::default();
7591            if let Some(prepared) = cpp.prepared_syntax(token, root) {
7592                let mut pending_nodes = vec![prepared.tree().root_node()];
7593                while let Some(node) = pending_nodes.pop() {
7594                    if matches!(
7595                        node.kind(),
7596                        "identifier"
7597                            | "type_identifier"
7598                            | "field_identifier"
7599                            | "namespace_identifier"
7600                    ) {
7601                        pending_names.insert(node_text(node, prepared.source()).to_string());
7602                    }
7603                    if node.kind() == "preproc_arg" {
7604                        for reference in
7605                            object_macro_replacement_type_references(node, prepared.source())
7606                        {
7607                            pending_names.extend(reference.components);
7608                        }
7609                    }
7610                    for index in 0..node.named_child_count() {
7611                        if let Some(child) = node.named_child(index) {
7612                            pending_nodes.push(child);
7613                        }
7614                    }
7615                }
7616            }
7617            stats.root_names += pending_names.len();
7618            let mut completed_names = HashSet::default();
7619            while !pending_names.is_empty() {
7620                stats.rounds += 1;
7621                let round_names = std::mem::take(&mut pending_names);
7622                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
7623                    HashMap::default();
7624                for identifier in round_names {
7625                    if !completed_names.insert(identifier.clone())
7626                        || cancellation.is_some_and(CancellationToken::is_cancelled)
7627                    {
7628                        continue;
7629                    }
7630                    let lookup_started = Instant::now();
7631                    let candidates = cpp.visibility_identifier_candidates(&identifier);
7632                    stats.lookup_elapsed += lookup_started.elapsed();
7633                    stats.identifier_lookups += 1;
7634                    stats.candidate_units += candidates.len();
7635                    for source in candidates
7636                        .into_iter()
7637                        .map(|unit| unit.source().clone())
7638                        .collect::<HashSet<_>>()
7639                    {
7640                        if source != *root
7641                            && visible_sources
7642                                .get(root)
7643                                .is_some_and(|files| files.contains(&source))
7644                        {
7645                            requested_names_by_source
7646                                .entry(source)
7647                                .or_default()
7648                                .insert(identifier.clone());
7649                        }
7650                    }
7651                }
7652                stats.candidate_sources += requested_names_by_source.len();
7653                for (source, requested_names) in requested_names_by_source {
7654                    let declarations_started = Instant::now();
7655                    let declarations =
7656                        bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
7657                    stats.declaration_elapsed += declarations_started.elapsed();
7658                    stats.declaration_reads += 1;
7659                    stats.declaration_units += declarations.len();
7660                    for unit in declarations {
7661                        let template_metadata = unit
7662                            .is_class()
7663                            .then(|| cpp.template_metadata(&unit))
7664                            .flatten();
7665                        if !requested_names.contains(unit.identifier())
7666                            && !template_metadata.as_ref().is_some_and(|metadata| {
7667                                requested_names.contains(&metadata.primary_name)
7668                            })
7669                        {
7670                            continue;
7671                        }
7672                        stats.selected_units += 1;
7673                        if let Some(prepared) = cpp.prepared_syntax(token, &source) {
7674                            let ast_started = Instant::now();
7675                            for range in analyzer.ranges(&unit) {
7676                                let Some(declaration) =
7677                                    node_for_exact_range(prepared.tree().root_node(), &range)
7678                                else {
7679                                    continue;
7680                                };
7681                                let mut pending_nodes = vec![declaration];
7682                                while let Some(node) = pending_nodes.pop() {
7683                                    stats.dependency_ast_nodes += 1;
7684                                    if matches!(
7685                                        node.kind(),
7686                                        "type_identifier" | "namespace_identifier"
7687                                    ) {
7688                                        let name = node_text(node, prepared.source());
7689                                        if !completed_names.contains(name)
7690                                            && pending_names.insert(name.to_string())
7691                                        {
7692                                            stats.dependency_names += 1;
7693                                        }
7694                                    }
7695                                    for index in 0..node.named_child_count() {
7696                                        if let Some(child) = node.named_child(index) {
7697                                            pending_nodes.push(child);
7698                                        }
7699                                    }
7700                                }
7701                            }
7702                            stats.dependency_ast_elapsed += ast_started.elapsed();
7703                        }
7704                        if let Some(metadata) = template_metadata
7705                            && !completed_names.contains(&metadata.primary_name)
7706                        {
7707                            pending_names.insert(metadata.primary_name);
7708                        }
7709                        visible.insert(unit);
7710                    }
7711                }
7712            }
7713            (root.clone(), visible)
7714        })
7715        .collect()
7716}
7717
7718#[derive(Default)]
7719struct BoundedVisibilityStats {
7720    rounds: usize,
7721    root_names: usize,
7722    identifier_lookups: usize,
7723    candidate_units: usize,
7724    candidate_sources: usize,
7725    declaration_reads: usize,
7726    declaration_units: usize,
7727    selected_units: usize,
7728    dependency_ast_nodes: usize,
7729    dependency_names: usize,
7730    lookup_elapsed: Duration,
7731    declaration_elapsed: Duration,
7732    dependency_ast_elapsed: Duration,
7733}
7734
7735fn bounded_visibility_declarations_in_reading(
7736    analyzer: &CppGraphSource<'_>,
7737    file: &ProjectFile,
7738    c_semantics: bool,
7739) -> BTreeSet<CodeUnit> {
7740    #[cfg(any(test, feature = "test-support"))]
7741    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
7742    analyzer.declarations_in_reading(file, c_semantics)
7743}
7744
7745#[cfg(any(test, feature = "test-support"))]
7746pub fn reset_bounded_visibility_declaration_read_count_for_test() {
7747    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
7748}
7749
7750#[cfg(any(test, feature = "test-support"))]
7751pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
7752    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
7753}
7754
7755pub struct VisibilityData {
7756    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
7757    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
7758}
7759
7760/// Build the per-root include closure and the declarations each root can see
7761/// through it.
7762///
7763/// `declarations_for` takes the reading to answer in (issue #1970): a root
7764/// compiled as C sees the C reading of every file in its closure, a root
7765/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
7766/// per root. The two readings agree for all but a handful of headers, so the
7767/// C map is built only when some root actually asks for it, and only over the
7768/// files that root reaches.
7769pub fn build_visibility_data<F, R, D>(
7770    roots: &HashSet<ProjectFile>,
7771    cancellation: Option<&CancellationToken>,
7772    mut targets_for: F,
7773    mut reading_is_c_for: R,
7774    mut declarations_for: D,
7775) -> VisibilityData
7776where
7777    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7778    R: FnMut(&ProjectFile) -> bool,
7779    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
7780{
7781    let mut include_graph = IncludeGraph::default();
7782    for file in roots {
7783        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7784            break;
7785        }
7786        include_graph.extend_with(file, cancellation, &mut targets_for);
7787    }
7788    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
7789        .files()
7790        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
7791        .map(|file| (file.clone(), declarations_for(file, false)))
7792        .collect();
7793    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
7794    let mut visible_by_file = HashMap::default();
7795    let mut visible_source_files_by_root = HashMap::default();
7796    for file in roots {
7797        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7798            break;
7799        }
7800        let mut visited = HashSet::default();
7801        let mut visible = HashSet::default();
7802        let declarations_by_file = if reading_is_c_for(file) {
7803            for reached in cpp_declarations_by_file.keys() {
7804                if !c_declarations_by_file.contains_key(reached) {
7805                    let declarations = declarations_for(reached, true);
7806                    c_declarations_by_file.insert(reached.clone(), declarations);
7807                }
7808            }
7809            &c_declarations_by_file
7810        } else {
7811            &cpp_declarations_by_file
7812        };
7813        collect_visible_declarations(
7814            &include_graph,
7815            declarations_by_file,
7816            file,
7817            &mut visited,
7818            &mut visible,
7819            cancellation,
7820        );
7821        visible_by_file.insert(file.clone(), visible);
7822        visible_source_files_by_root.insert(file.clone(), visited);
7823    }
7824    VisibilityData {
7825        visible_by_file,
7826        visible_source_files_by_root,
7827    }
7828}
7829
7830/// Admit the class that an out-of-line definition proves is in scope.
7831///
7832/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
7833/// names a class-like entity in that file's scope: a member declaration can
7834/// live in a file other than its class's only when it is written out of line.
7835/// A file a build concatenates rather than compiles carries no `#include` edge
7836/// to the header declaring `Owner` -- google/wuffs
7837/// `internal/cgen/auxiliary/image.cc` defines
7838/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
7839/// every unqualified member and constructor reference in it had no candidate at
7840/// all (#1832).
7841///
7842/// The evidence is the indexed declaration's own owner name, taken from its
7843/// `FqName`, so this stays a structured answer rather than a text fallback.
7844/// Only an owner the file cannot already see is admitted: that is what keeps a
7845/// header declaring its own class from additionally seeing every same-named
7846/// class in the workspace, and it makes the pass free for the ordinary file
7847/// whose owners are all visible.
7848#[derive(Default)]
7849struct OutOfLineOwnerBindingStats {
7850    unseen_owners: usize,
7851    definition_lookups: usize,
7852    admitted: usize,
7853}
7854
7855fn extend_with_out_of_line_owner_bindings(
7856    cpp: &dyn CppSource,
7857    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
7858) -> OutOfLineOwnerBindingStats {
7859    let mut stats = OutOfLineOwnerBindingStats::default();
7860    for (file, visible) in visible_by_file.iter_mut() {
7861        // The include-closure walk seeds every root with its own declarations,
7862        // so the file's members are already here; re-reading them from the
7863        // analyzer would pay for the same declaration set twice.
7864        let mut unseen_owners: HashSet<String> = visible
7865            .iter()
7866            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
7867            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
7868            .collect();
7869        if unseen_owners.is_empty() {
7870            continue;
7871        }
7872        for unit in visible.iter().filter(|unit| unit.is_class()) {
7873            unseen_owners.remove(&unit.fq_name());
7874        }
7875        stats.unseen_owners += unseen_owners.len();
7876        stats.definition_lookups += unseen_owners.len();
7877        let admitted = unseen_owners
7878            .iter()
7879            .flat_map(|owner| cpp.definitions(owner))
7880            .filter(CodeUnit::is_class)
7881            .collect::<Vec<_>>();
7882        stats.admitted += admitted.len();
7883        visible.extend(admitted);
7884    }
7885    stats
7886}
7887
7888pub enum VisibleMemberResolution {
7889    Callable(Vec<CodeUnit>),
7890    NonCallable,
7891    AmbiguousKind,
7892    Missing,
7893}
7894
7895#[derive(Clone)]
7896pub enum EnclosingMemberOwnerResolution {
7897    Owner(CodeUnit),
7898    Ambiguous,
7899    Missing,
7900}
7901
7902pub fn resolve_declaring_member_owner(
7903    analyzer: &CppGraphSource<'_>,
7904    visibility: &VisibilityIndex<'_>,
7905    file: &ProjectFile,
7906    receiver_owner: &CodeUnit,
7907    member_name: &str,
7908) -> EnclosingMemberOwnerResolution {
7909    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7910        return EnclosingMemberOwnerResolution::Missing;
7911    };
7912    let Some(receiver_owner) =
7913        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
7914    else {
7915        return EnclosingMemberOwnerResolution::Ambiguous;
7916    };
7917    let resolve_level = |frontier: &[CodeUnit]| {
7918        let mut member_owners = Vec::new();
7919        for raw_owner in frontier {
7920            let Some(owner) =
7921                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
7922            else {
7923                return EnclosingMemberOwnerResolution::Ambiguous;
7924            };
7925            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
7926                let Some(member_owner) = type_owner_of(analyzer, member) else {
7927                    return EnclosingMemberOwnerResolution::Ambiguous;
7928                };
7929                if !member_owners
7930                    .iter()
7931                    .any(|existing| same_visible_symbol(existing, &member_owner))
7932                {
7933                    member_owners.push(member_owner);
7934                }
7935            }
7936        }
7937        match member_owners.len() {
7938            0 => EnclosingMemberOwnerResolution::Missing,
7939            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
7940            _ => EnclosingMemberOwnerResolution::Ambiguous,
7941        }
7942    };
7943    // The first declaration on each structured base path hides deeper names,
7944    // regardless of whether its callable overload is applicable at a particular
7945    // call site. Applicability is checked only after this owner is established.
7946    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
7947    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
7948        return direct;
7949    }
7950    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
7951    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7952    let mut path_matches = Vec::new();
7953    while let Some(raw_owner) = stack.pop() {
7954        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7955        else {
7956            return EnclosingMemberOwnerResolution::Ambiguous;
7957        };
7958        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
7959        // Propagate at most two occurrences of each owner: that preserves the distinction
7960        // between one and multiple resolving base paths without exponential diamond walks.
7961        let propagated = propagated_counts.entry(owner.clone()).or_default();
7962        if *propagated == 2 {
7963            continue;
7964        }
7965        *propagated += 1;
7966        match resolve_level(std::slice::from_ref(&owner)) {
7967            EnclosingMemberOwnerResolution::Owner(owner) => {
7968                path_matches.push(owner);
7969                if path_matches.len() == 2 {
7970                    return EnclosingMemberOwnerResolution::Ambiguous;
7971                }
7972            }
7973            EnclosingMemberOwnerResolution::Ambiguous => {
7974                return EnclosingMemberOwnerResolution::Ambiguous;
7975            }
7976            EnclosingMemberOwnerResolution::Missing => {
7977                stack.extend(hierarchy.get_direct_ancestors(&owner));
7978            }
7979        }
7980    }
7981    match path_matches.len() {
7982        0 => EnclosingMemberOwnerResolution::Missing,
7983        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
7984        _ => unreachable!("base-path matches are capped at one before returning"),
7985    }
7986}
7987
7988/// Resolve the declaring owner of a callable after applying a member
7989/// `using <Base>::<member>;` declaration to one exact call arity.
7990///
7991/// Ordinary member lookup is intentionally name-based: the first class that
7992/// declares a name hides the same name on deeper bases. A member
7993/// using-declaration is the one exception. When none of the declarations on
7994/// that first owner accepts the call arity, it can reintroduce an applicable
7995/// overload from the named base. If a declaration on the first owner does
7996/// accept the arity, argument types would be needed to choose between it and
7997/// a same-arity introduced overload, so this resolver conservatively keeps the
7998/// ordinary owner (#1835/#1843).
7999///
8000/// The caller supplies ordinary name-based owner resolution so a file scan can
8001/// reuse its existing owner cache before applying this callable-only exception.
8002pub fn resolve_declaring_callable_owner(
8003    analyzer: &CppGraphSource<'_>,
8004    visibility: &VisibilityIndex<'_>,
8005    file: &ProjectFile,
8006    ordinary: EnclosingMemberOwnerResolution,
8007    member_name: &str,
8008    call_arity: usize,
8009) -> EnclosingMemberOwnerResolution {
8010    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8011        return ordinary;
8012    };
8013    if visibility
8014        .visible_members_for_owner_name(file, ordinary_owner, member_name)
8015        .into_iter()
8016        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8017    {
8018        return ordinary;
8019    }
8020
8021    let mut pending = match member_using_declaration_bases(
8022        analyzer,
8023        visibility,
8024        file,
8025        ordinary_owner,
8026        member_name,
8027    ) {
8028        Ok(bases) => bases,
8029        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8030    };
8031    let mut visited = HashSet::default();
8032    let mut introduced_owners = Vec::new();
8033    while let Some(owner) = pending.pop() {
8034        if !visited.insert(owner.clone()) {
8035            continue;
8036        }
8037        let accepts_arity = visibility
8038            .visible_members_for_owner_name(file, &owner, member_name)
8039            .into_iter()
8040            .any(|unit| {
8041                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8042            });
8043        if accepts_arity {
8044            if !introduced_owners
8045                .iter()
8046                .any(|existing| same_visible_symbol(existing, &owner))
8047            {
8048                introduced_owners.push(owner);
8049            }
8050            continue;
8051        }
8052        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8053            Ok(bases) => pending.extend(bases),
8054            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8055        }
8056    }
8057    match introduced_owners.as_slice() {
8058        [] => ordinary,
8059        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8060        _ => EnclosingMemberOwnerResolution::Ambiguous,
8061    }
8062}
8063
8064fn member_using_declaration_bases(
8065    analyzer: &CppGraphSource<'_>,
8066    visibility: &VisibilityIndex<'_>,
8067    file: &ProjectFile,
8068    owner: &CodeUnit,
8069    member_name: &str,
8070) -> Result<Vec<CodeUnit>, ()> {
8071    let Some(source) = analyzer.get_source(owner, false) else {
8072        return Ok(Vec::new());
8073    };
8074    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8075    if scopes.is_empty() {
8076        return Ok(Vec::new());
8077    }
8078    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8079        return Ok(Vec::new());
8080    };
8081    let mut bases = Vec::new();
8082    for raw_ancestor in hierarchy.get_ancestors(owner) {
8083        let Some(ancestor) =
8084            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8085        else {
8086            return Err(());
8087        };
8088        let qualified = cpp_name_for(&ancestor);
8089        if scopes
8090            .iter()
8091            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8092            && !bases
8093                .iter()
8094                .any(|existing| same_visible_symbol(existing, &ancestor))
8095        {
8096            bases.push(ancestor);
8097        }
8098    }
8099    Ok(bases)
8100}
8101
8102pub fn lexical_component_tiers<'a>(
8103    components: &'a [String],
8104    global: bool,
8105    lexical_scope: &'a [String],
8106) -> impl Iterator<Item = Vec<String>> + 'a {
8107    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8108    (0..=first_prefix_len).rev().map(move |prefix_len| {
8109        let mut qualified = Vec::with_capacity(prefix_len + components.len());
8110        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8111        qualified.extend_from_slice(components);
8112        qualified
8113    })
8114}
8115
8116pub fn build_visible_identifier_index(
8117    analyzer: &CppGraphSource<'_>,
8118    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
8119    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8120    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
8121) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
8122    let mut out = HashMap::default();
8123    for (file, visible) in visible_by_file {
8124        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
8125        for unit in visible {
8126            if unit.is_field()
8127                && !visible_source_files_by_root
8128                    .get(file)
8129                    .is_some_and(|sources| sources.contains(unit.source()))
8130                && cpp_global_field_has_internal_linkage_cached(
8131                    analyzer,
8132                    global_field_internal_linkage,
8133                    unit,
8134                )
8135            {
8136                continue;
8137            }
8138            by_identifier
8139                .entry(unit.identifier().to_string())
8140                .or_default()
8141                .push(unit.clone());
8142        }
8143        for units in by_identifier.values_mut() {
8144            sort_lookup_units(units);
8145            units.dedup();
8146        }
8147        out.insert(file.clone(), by_identifier);
8148    }
8149    out
8150}
8151
8152fn sort_lookup_units(units: &mut [CodeUnit]) {
8153    units.sort_by(|left, right| {
8154        left.fq_name()
8155            .cmp(&right.fq_name())
8156            .then_with(|| left.signature().cmp(&right.signature()))
8157            .then_with(|| left.source().cmp(right.source()))
8158            .then_with(|| left.kind().cmp(&right.kind()))
8159            .then_with(|| {
8160                left.package_segment_count()
8161                    .cmp(&right.package_segment_count())
8162            })
8163            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
8164            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
8165    });
8166}
8167
8168fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
8169    let interner = segment_interner();
8170    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
8171        let (left_text, left_kind) = interner.resolve(left_id);
8172        let (right_text, right_kind) = interner.resolve(right_id);
8173        let order = left_text
8174            .cmp(right_text)
8175            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
8176        if order != CmpOrdering::Equal {
8177            return order;
8178        }
8179    }
8180    left.len().cmp(&right.len())
8181}
8182
8183const fn segment_kind_order(kind: SegmentKind) -> u8 {
8184    match kind {
8185        SegmentKind::Path => 0,
8186        SegmentKind::Package => 1,
8187        SegmentKind::Type => 2,
8188        SegmentKind::Companion => 3,
8189        SegmentKind::Nested => 4,
8190        SegmentKind::Member => 5,
8191        SegmentKind::Unknown => 6,
8192    }
8193}
8194
8195fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
8196    let mut deduped = Vec::with_capacity(units.len());
8197    for unit in units.drain(..) {
8198        if !deduped.contains(&unit) {
8199            deduped.push(unit);
8200        }
8201    }
8202    *units = deduped;
8203}
8204
8205pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
8206    // Same domain as `candidate_units` above: `reference` is a plain
8207    // `::`-joined qualified-id with operator tokens kept intact by the shared
8208    // splitter's operator merge.
8209    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8210        brokk_bifrost_core::analyzer::Language::Cpp,
8211        reference,
8212    );
8213    if parts.is_empty() {
8214        return Vec::new();
8215    }
8216
8217    let mut candidates = Vec::new();
8218    for package_len in 0..parts.len() {
8219        let package = parts[..package_len].join("::");
8220        let rest = &parts[package_len..];
8221        if rest.is_empty() {
8222            continue;
8223        }
8224        match kind {
8225            TargetKind::Type | TargetKind::Constructor => {
8226                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
8227                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8228            }
8229            TargetKind::FreeFunction
8230            | TargetKind::Method
8231            | TargetKind::GlobalField
8232            | TargetKind::MemberField
8233            | TargetKind::Macro => {
8234                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8235                if rest.len() > 1 {
8236                    let owner = rest[..rest.len() - 1].join("$");
8237                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
8238                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
8239                }
8240            }
8241        }
8242    }
8243    candidates
8244}
8245
8246fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
8247    let fqn = if package.is_empty() {
8248        short.to_string()
8249    } else {
8250        format!("{package}.{short}")
8251    };
8252    if !out.contains(&fqn) {
8253        out.push(fqn);
8254    }
8255}
8256
8257pub fn infer_cpp_initializer_type(
8258    analyzer: &CppGraphSource<'_>,
8259    visibility: &VisibilityIndex<'_>,
8260    file: &ProjectFile,
8261    source: &str,
8262    node: Node<'_>,
8263) -> Option<CodeUnit> {
8264    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
8265        .and_then(|binding| binding.unit)
8266}
8267
8268pub fn infer_cpp_initializer_binding(
8269    analyzer: &CppGraphSource<'_>,
8270    visibility: &VisibilityIndex<'_>,
8271    file: &ProjectFile,
8272    source: &str,
8273    node: Node<'_>,
8274    receiver_resolver: Option<&ReceiverResolver<'_>>,
8275) -> Option<CppScanBinding> {
8276    match node.kind() {
8277        "new_expression" => {
8278            let text = normalize_cpp_whitespace(node_text(node, source));
8279            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
8280            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
8281            let name = normalize_cpp_type_name(type_text);
8282            Some(CppScanBinding::from_type_name(
8283                name.clone(),
8284                visibility.resolve_type(file, &name),
8285                1,
8286            ))
8287        }
8288        "call_expression" => node.child_by_field_name("function").and_then(|function| {
8289            let function_text = node_text(function, source);
8290            let direct_type_binding = visibility
8291                .resolve_type(file, function_text)
8292                .map(|unit| CppScanBinding::from_unit(unit, 0));
8293            if function.kind() == "template_function" && direct_type_binding.is_some() {
8294                let lexical_namespace = enclosing_namespace_context(node, source);
8295                let arity = visibility.call_arity_evidence(file, node, source).exact();
8296                if let Some(arity) = arity
8297                    && let Some(binding) = visibility.resolve_call_return_binding(
8298                        analyzer,
8299                        file,
8300                        function_text,
8301                        arity,
8302                        lexical_namespace.as_deref(),
8303                        direct_type_binding
8304                            .as_ref()
8305                            .and_then(|binding| binding.unit.as_ref()),
8306                    )
8307                {
8308                    return Some(binding);
8309                }
8310                let (has_callable, callable_binding) = visibility
8311                    .resolve_call_return_binding_without_arity(
8312                        analyzer,
8313                        file,
8314                        function_text,
8315                        lexical_namespace.as_deref(),
8316                        direct_type_binding
8317                            .as_ref()
8318                            .and_then(|binding| binding.unit.as_ref()),
8319                    );
8320                if let Some(binding) = callable_binding {
8321                    return Some(binding);
8322                }
8323                if has_callable {
8324                    return None;
8325                }
8326                return direct_type_binding;
8327            }
8328            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
8329            let direct_type_binding_for_call = direct_type_binding.clone();
8330            resolve_static_method_call_return_binding(
8331                analyzer, visibility, file, source, function, arity,
8332            )
8333            .or_else(|| {
8334                // An applicable free function supplies the receiver value
8335                // before an unrelated visible type with the same terminal
8336                // name. The direct type still excludes its own constructor
8337                // declaration below and remains the construction fallback.
8338                visibility.resolve_call_return_binding(
8339                    analyzer,
8340                    file,
8341                    function_text,
8342                    arity,
8343                    enclosing_namespace_context(node, source).as_deref(),
8344                    direct_type_binding_for_call
8345                        .as_ref()
8346                        .and_then(|binding| binding.unit.as_ref()),
8347                )
8348            })
8349            .or(direct_type_binding)
8350            .or_else(|| {
8351                resolve_field_method_call_return_binding(
8352                    analyzer,
8353                    visibility,
8354                    file,
8355                    source,
8356                    function,
8357                    arity,
8358                    receiver_resolver,
8359                )
8360            })
8361        }),
8362        _ => None,
8363    }
8364}
8365
8366fn resolve_static_method_call_return_binding(
8367    analyzer: &CppGraphSource<'_>,
8368    visibility: &VisibilityIndex<'_>,
8369    file: &ProjectFile,
8370    source: &str,
8371    function: Node<'_>,
8372    arity: usize,
8373) -> Option<CppScanBinding> {
8374    if function.kind() != "qualified_identifier" {
8375        return None;
8376    }
8377    let qualified = normalize_cpp_reference_text(node_text(function, source));
8378    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
8379    // single component (the shared splitter's operator-token merge keeps
8380    // `operator+`-style names intact), so re-tokenizing with the shared
8381    // structured splitter and peeling the terminal segment reproduces
8382    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
8383    // `cpp_out_of_line_function_owner`'s `qualified` split above.
8384    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8385        brokk_bifrost_core::analyzer::Language::Cpp,
8386        &qualified,
8387    );
8388    let (owner_text, member_name) = match parts.split_last() {
8389        Some((member, owner_parts)) if !owner_parts.is_empty() => {
8390            (owner_parts.join("::"), member.clone())
8391        }
8392        _ => {
8393            let scope = function.child_by_field_name("scope")?;
8394            let name = function.child_by_field_name("name")?;
8395            (
8396                node_text(scope, source).to_string(),
8397                node_text(name, source).to_string(),
8398            )
8399        }
8400    };
8401    let owner = visibility.resolve_type(file, &owner_text)?;
8402    let candidates = visibility
8403        .visible_members_for_owner_name(file, &owner, &member_name)
8404        .into_iter()
8405        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
8406        .cloned()
8407        .collect::<Vec<_>>();
8408    unanimous_return_binding(analyzer, visibility, file, &candidates)
8409}
8410
8411fn resolve_field_method_call_return_binding(
8412    analyzer: &CppGraphSource<'_>,
8413    visibility: &VisibilityIndex<'_>,
8414    file: &ProjectFile,
8415    source: &str,
8416    function: Node<'_>,
8417    arity: usize,
8418    receiver_resolver: Option<&ReceiverResolver<'_>>,
8419) -> Option<CppScanBinding> {
8420    if function.kind() != "field_expression" {
8421        return None;
8422    }
8423    let receiver_resolver = receiver_resolver?;
8424    let field = function.child_by_field_name("field")?;
8425    let member_name = node_text(function_terminal_node(field), source);
8426    let receiver = function
8427        .child_by_field_name("argument")
8428        .or_else(|| function.named_child(0))?;
8429    let owners = receiver_resolver(receiver, source);
8430    let mut candidates = Vec::new();
8431    for owner in owners {
8432        let declaring_owner =
8433            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
8434                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8435                EnclosingMemberOwnerResolution::Missing => continue,
8436                EnclosingMemberOwnerResolution::Ambiguous => return None,
8437            };
8438        candidates.extend(
8439            visibility
8440                .visible_members_for_owner_name(file, &declaring_owner, member_name)
8441                .into_iter()
8442                .filter(|unit| {
8443                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
8444                })
8445                .cloned(),
8446        );
8447    }
8448    unanimous_return_binding(analyzer, visibility, file, &candidates)
8449}
8450
8451fn unanimous_return_binding(
8452    analyzer: &CppGraphSource<'_>,
8453    visibility: &VisibilityIndex<'_>,
8454    file: &ProjectFile,
8455    candidates: &[CodeUnit],
8456) -> Option<CppScanBinding> {
8457    let mut resolved_return: Option<CppScanBinding> = None;
8458    for function in candidates {
8459        let metadata = analyzer.signature_metadata(function);
8460        let return_types = if metadata.is_empty() {
8461            vec![cpp_function_return_type_text(analyzer, function)?]
8462        } else {
8463            metadata
8464                .iter()
8465                .map(|metadata| metadata.return_type_text().map(str::to_string))
8466                .collect::<Option<Vec<_>>>()?
8467        };
8468        for return_text in return_types {
8469            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
8470            let name = normalize_cpp_type_name(&return_text);
8471            let binding = CppScanBinding::from_type_name(
8472                name.clone(),
8473                visibility
8474                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
8475                indirection,
8476            );
8477            if let Some(existing) = resolved_return.as_ref()
8478                && (existing.indirection != binding.indirection
8479                    || match (&existing.unit, &binding.unit) {
8480                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
8481                        (None, None) => existing.type_name != binding.type_name,
8482                        (Some(_), None) | (None, Some(_)) => true,
8483                    })
8484            {
8485                return None;
8486            }
8487            resolved_return = Some(binding);
8488        }
8489    }
8490    resolved_return
8491}
8492
8493fn aliases_from_prepared_source(
8494    cpp: &dyn CppSource,
8495    token: QueryToken<'_>,
8496    file: &ProjectFile,
8497) -> Vec<CppAlias> {
8498    let Some(prepared) = cpp.prepared_syntax(token, file) else {
8499        return Vec::new();
8500    };
8501    let mut aliases = Vec::new();
8502    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
8503    aliases
8504}
8505
8506fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8507    let mut stack = vec![root];
8508    while let Some(node) = stack.pop() {
8509        match node.kind() {
8510            "alias_declaration" if alias_has_visible_file_scope(node) => {
8511                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
8512                    out.push(alias);
8513                }
8514            }
8515            "type_definition" if alias_has_visible_file_scope(node) => {
8516                collect_typedef_aliases(node, source, out)
8517            }
8518            _ => {}
8519        }
8520
8521        for index in (0..node.named_child_count()).rev() {
8522            if let Some(child) = node.named_child(index) {
8523                stack.push(child);
8524            }
8525        }
8526    }
8527}
8528
8529fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
8530    let mut current = node.parent();
8531    while let Some(parent) = current {
8532        match parent.kind() {
8533            "translation_unit"
8534            | "namespace_definition"
8535            | "declaration_list"
8536            | "linkage_specification" => current = parent.parent(),
8537            "template_declaration" => current = parent.parent(),
8538            _ => return false,
8539        }
8540    }
8541    true
8542}
8543
8544fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
8545    let name = node
8546        .child_by_field_name("name")
8547        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8548    let target = node
8549        .child_by_field_name("type")
8550        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8551    Some(CppAlias {
8552        name,
8553        target,
8554        namespace: enclosing_namespace_context(node, source),
8555    })
8556}
8557
8558fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8559    let Some(type_node) = node.child_by_field_name("type") else {
8560        return;
8561    };
8562    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
8563        return;
8564    };
8565
8566    let mut cursor = node.walk();
8567    for child in node.named_children(&mut cursor) {
8568        if same_node(child, type_node) {
8569            continue;
8570        }
8571        if let Some(name) = extract_typedef_declarator_name(child, source) {
8572            out.push(CppAlias {
8573                name,
8574                target: target.clone(),
8575                namespace: enclosing_namespace_context(node, source),
8576            });
8577        }
8578    }
8579}
8580
8581fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
8582    match node.kind() {
8583        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
8584            normalize_reference_name(node_text(node, source))
8585        }
8586        _ => node
8587            .child_by_field_name("declarator")
8588            .or_else(|| node.child_by_field_name("name"))
8589            .or_else(|| last_named_child(node))
8590            .and_then(|child| extract_typedef_declarator_name(child, source)),
8591    }
8592}
8593
8594fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
8595    let count = node.named_child_count();
8596    if count == 0 {
8597        None
8598    } else {
8599        node.named_child(count - 1)
8600    }
8601}
8602
8603pub fn collect_include_closure(
8604    analyzer: &CppGraphSource<'_>,
8605    include_targets: &IncludeTargetIndex,
8606    file: &ProjectFile,
8607    out: &mut HashSet<ProjectFile>,
8608    cancellation: Option<&CancellationToken>,
8609) {
8610    let mut stack = vec![file.clone()];
8611    while let Some(file) = stack.pop() {
8612        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8613            break;
8614        }
8615        if !out.insert(file.clone()) {
8616            continue;
8617        }
8618        let imports = analyzer.import_statements(&file);
8619        for include in cpp_include_paths(&imports) {
8620            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
8621                stack.push(target);
8622            }
8623        }
8624    }
8625}
8626
8627fn collect_visible_declarations(
8628    include_graph: &IncludeGraph,
8629    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
8630    file: &ProjectFile,
8631    visited: &mut HashSet<ProjectFile>,
8632    out: &mut HashSet<CodeUnit>,
8633    cancellation: Option<&CancellationToken>,
8634) {
8635    let mut stack = vec![file.clone()];
8636    while let Some(file) = stack.pop() {
8637        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8638            break;
8639        }
8640        if !visited.insert(file.clone()) {
8641            continue;
8642        }
8643        if let Some(declarations) = declarations_by_file.get(&file) {
8644            out.extend(declarations.iter().cloned());
8645        }
8646        stack.extend(include_graph.targets(&file).iter().cloned());
8647    }
8648}
8649
8650pub fn signature_arity(signature: Option<&str>) -> usize {
8651    let Some(signature) = signature else {
8652        return 0;
8653    };
8654    let inner = signature
8655        .find('(')
8656        .and_then(|open| {
8657            signature[open + 1..]
8658                .find(')')
8659                .map(|close| &signature[open + 1..open + 1 + close])
8660        })
8661        .unwrap_or(signature)
8662        .trim();
8663    if inner.is_empty() || inner == "void" {
8664        return 0;
8665    }
8666    cpp_split_top_level_commas(inner).count()
8667}
8668
8669fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
8670    let source = format!("void __bifrost_macro_parameters({replacement});");
8671    let mut parser = Parser::new();
8672    parser
8673        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8674        .ok()?;
8675    let tree = parser.parse(&source, None)?;
8676    let root = tree.root_node();
8677    if root.has_error() {
8678        return None;
8679    }
8680    let declaration = root.named_child(0)?;
8681    let declarator = declaration.child_by_field_name("declarator")?;
8682    let parameters = declarator.child_by_field_name("parameters")?;
8683    let mut required = 0;
8684    let mut total = 0;
8685    let mut repeated = false;
8686    let mut cursor = parameters.walk();
8687    for parameter in parameters.children(&mut cursor) {
8688        match parameter.kind() {
8689            "parameter_declaration" => {
8690                if parameter.child_by_field_name("declarator").is_none()
8691                    && parameter
8692                        .child_by_field_name("type")
8693                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
8694                {
8695                    continue;
8696                }
8697                required += 1;
8698                total += 1;
8699            }
8700            "optional_parameter_declaration" => total += 1,
8701            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
8702                repeated = true;
8703            }
8704            _ => {}
8705        }
8706    }
8707    Some(CallableArity::new(required, total, repeated))
8708}
8709
8710pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
8711    analyzer
8712        .signature_metadata(unit)
8713        .into_iter()
8714        .find_map(|metadata| metadata.callable_arity())
8715        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
8716}
8717
8718pub fn cpp_callable_parameter_types(
8719    analyzer: &CppGraphSource<'_>,
8720    unit: &CodeUnit,
8721) -> Option<Vec<String>> {
8722    analyzer
8723        .signature_metadata(unit)
8724        .into_iter()
8725        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
8726        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
8727}
8728
8729fn merge_compatible_callable_arities(
8730    left: CallableArity,
8731    right: CallableArity,
8732) -> Option<CallableArity> {
8733    let total = left.total();
8734    let left_repeated = left.accepts(total.saturating_add(1));
8735    let right_repeated = right.accepts(right.total().saturating_add(1));
8736    if total != right.total() || left_repeated != right_repeated {
8737        return None;
8738    }
8739    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
8740    Some(CallableArity::new(required, total, left_repeated))
8741}
8742
8743fn find_include_activation(
8744    cpp: &dyn CppSource,
8745    token: QueryToken<'_>,
8746    file: &ProjectFile,
8747    prepared: &PreparedSyntaxTree,
8748    donor_source: &ProjectFile,
8749) -> Option<usize> {
8750    let include_targets = cpp.include_target_index();
8751    let mut direct_includes = Vec::new();
8752    let mut nodes = vec![prepared.tree().root_node()];
8753    // An include activates for the whole file, so only an unconditional
8754    // directive counts here.
8755    let reference = CallableReferenceContext {
8756        file,
8757        position: None,
8758    };
8759    while let Some(node) = nodes.pop() {
8760        if node.kind() == "preproc_include" {
8761            if callable_preprocessor_context_is_visible_for_reference(
8762                node,
8763                prepared.source(),
8764                &reference,
8765            ) {
8766                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8767                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8768                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
8769                        file,
8770                        &include,
8771                        include_targets,
8772                    )) {
8773                        direct_includes.push((node.end_byte(), target));
8774                    }
8775                }
8776            }
8777            continue;
8778        }
8779        for index in (0..node.named_child_count()).rev() {
8780            if let Some(child) = node.named_child(index) {
8781                nodes.push(child);
8782            }
8783        }
8784    }
8785    direct_includes.sort_by_key(|(activation, _)| *activation);
8786    let mut known_missing = HashSet::default();
8787    direct_includes
8788        .into_iter()
8789        .find(|(_, direct)| {
8790            unconditional_include_reaches(
8791                cpp,
8792                token,
8793                include_targets,
8794                direct,
8795                donor_source,
8796                file,
8797                &mut known_missing,
8798            )
8799        })
8800        .map(|(activation, _)| activation)
8801}
8802
8803fn find_conditional_include_projection_index(
8804    cpp: &dyn CppSource,
8805    token: QueryToken<'_>,
8806    file: &ProjectFile,
8807    prepared: &PreparedSyntaxTree,
8808    on_state: &dyn Fn(),
8809) -> ConditionalIncludeProjectionIndex {
8810    let include_targets = cpp.include_target_index();
8811    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
8812        HashMap::default();
8813    let mut pending = Vec::new();
8814    let mut nodes = vec![prepared.tree().root_node()];
8815    while let Some(node) = nodes.pop() {
8816        if node.kind() == "preproc_include" {
8817            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
8818            else {
8819                continue;
8820            };
8821            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8822            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8823                let Some(target) = unique_include_target(resolve_include_targets_with_index(
8824                    file,
8825                    &include,
8826                    include_targets,
8827                )) else {
8828                    continue;
8829                };
8830                pending.push((target, node.end_byte(), required_guards.clone()));
8831            }
8832            continue;
8833        }
8834        for index in (0..node.named_child_count()).rev() {
8835            if let Some(child) = node.named_child(index) {
8836                nodes.push(child);
8837            }
8838        }
8839    }
8840
8841    // One reached file can have several distinct compatible guard paths. Each
8842    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
8843    // the consumers ask existence questions whose answers are monotone in the
8844    // guard set -- a path whose requirements hold, stay stable, and stay
8845    // compatible under one environment does so under every subset as well --
8846    // so a state subsumed by an existing subset cannot witness anything its
8847    // subset does not, and inserting a smaller set evicts the supersets it
8848    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
8849    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
8850    // path-union guard sets through it: the state space, the per-key linear
8851    // scans, and resident memory all grew without bound (#2365).
8852    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
8853        HashMap::default();
8854    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
8855        let guard_sets = expanded
8856            .entry((current_file.clone(), activation_byte))
8857            .or_default();
8858        if guard_sets
8859            .iter()
8860            .any(|existing| existing.is_subset(&required_guards))
8861        {
8862            continue;
8863        }
8864        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
8865            .drain(..)
8866            .partition(|existing| required_guards.is_subset(existing));
8867        *guard_sets = kept;
8868        guard_sets.push(required_guards.clone());
8869        if !evicted.is_empty()
8870            && let Some(projections) = projections_by_source.get_mut(&current_file)
8871        {
8872            projections.retain(|projection| {
8873                projection.activation_byte != activation_byte
8874                    || !evicted.contains(&projection.required_guards)
8875            });
8876        }
8877        on_state();
8878
8879        // A fresh minimal set has no equal in the store: equality would have
8880        // been caught by the subset check above.
8881        projections_by_source
8882            .entry(current_file.clone())
8883            .or_default()
8884            .push(ConditionalIncludeProjection {
8885                activation_byte,
8886                required_guards: required_guards.clone(),
8887            });
8888
8889        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
8890            continue;
8891        };
8892        let mut nodes = vec![current_prepared.tree().root_node()];
8893        while let Some(node) = nodes.pop() {
8894            if node.kind() == "preproc_include" {
8895                let Some(include_guards) =
8896                    preprocessor_guard_environment(node, current_prepared.source())
8897                else {
8898                    continue;
8899                };
8900                let Some(path_guards) =
8901                    merge_preprocessor_guards(&required_guards, &include_guards)
8902                else {
8903                    continue;
8904                };
8905                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
8906                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8907                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
8908                        &current_file,
8909                        &include,
8910                        include_targets,
8911                    )) else {
8912                        continue;
8913                    };
8914                    pending.push((target, activation_byte, path_guards.clone()));
8915                }
8916                continue;
8917            }
8918            for index in (0..node.named_child_count()).rev() {
8919                if let Some(child) = node.named_child(index) {
8920                    nodes.push(child);
8921                }
8922            }
8923        }
8924    }
8925
8926    projections_by_source
8927        .into_iter()
8928        .map(|(source, mut projections)| {
8929            projections.sort_by_key(|projection| projection.activation_byte);
8930            (source, Arc::from(projections))
8931        })
8932        .collect()
8933}
8934
8935/// Decide one conditional include target without materializing every source
8936/// reached by every guard combination. Paths whose requirements do not hold
8937/// at the reference cannot become feasible after adding nested include guards,
8938/// so discard them before expanding the next header.
8939#[allow(clippy::too_many_arguments)]
8940fn find_conditional_include_projection_for_source(
8941    cpp: &dyn CppSource,
8942    token: QueryToken<'_>,
8943    file: &ProjectFile,
8944    prepared: &PreparedSyntaxTree,
8945    donor_source: &ProjectFile,
8946    reference_guards: Option<&HashSet<PreprocessorGuard>>,
8947    reference_byte: usize,
8948    on_state: &dyn Fn(),
8949) -> bool {
8950    let Some(reference_guards) = reference_guards else {
8951        return false;
8952    };
8953    let include_targets = cpp.include_target_index();
8954    let mut pending = Vec::new();
8955    let mut nodes = vec![prepared.tree().root_node()];
8956    while let Some(node) = nodes.pop() {
8957        if node.kind() == "preproc_include" {
8958            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
8959            else {
8960                continue;
8961            };
8962            if node.end_byte() > reference_byte
8963                || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
8964            {
8965                continue;
8966            }
8967            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8968            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8969                let Some(target) = unique_include_target(resolve_include_targets_with_index(
8970                    file,
8971                    &include,
8972                    include_targets,
8973                )) else {
8974                    continue;
8975                };
8976                if &target == donor_source {
8977                    return true;
8978                }
8979                pending.push((target, required_guards.clone()));
8980            }
8981            continue;
8982        }
8983        for index in (0..node.named_child_count()).rev() {
8984            if let Some(child) = node.named_child(index) {
8985                nodes.push(child);
8986            }
8987        }
8988    }
8989
8990    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
8991    while let Some((current_file, required_guards)) = pending.pop() {
8992        let guard_sets = expanded.entry(current_file.clone()).or_default();
8993        if guard_sets.contains(&required_guards) {
8994            continue;
8995        }
8996        guard_sets.push(required_guards.clone());
8997        on_state();
8998
8999        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
9000            continue;
9001        };
9002        let mut nodes = vec![current_prepared.tree().root_node()];
9003        while let Some(node) = nodes.pop() {
9004            if node.kind() == "preproc_include" {
9005                let Some(include_guards) =
9006                    preprocessor_guard_environment(node, current_prepared.source())
9007                else {
9008                    continue;
9009                };
9010                let Some(path_guards) =
9011                    merge_preprocessor_guards(&required_guards, &include_guards)
9012                else {
9013                    continue;
9014                };
9015                if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9016                    continue;
9017                }
9018                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9019                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9020                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
9021                        &current_file,
9022                        &include,
9023                        include_targets,
9024                    )) else {
9025                        continue;
9026                    };
9027                    if &target == donor_source {
9028                        return true;
9029                    }
9030                    pending.push((target, path_guards.clone()));
9031                }
9032                continue;
9033            }
9034            for index in (0..node.named_child_count()).rev() {
9035                if let Some(child) = node.named_child(index) {
9036                    nodes.push(child);
9037                }
9038            }
9039        }
9040    }
9041    false
9042}
9043
9044fn unconditional_include_reaches(
9045    cpp: &dyn CppSource,
9046    token: QueryToken<'_>,
9047    include_targets: &IncludeTargetIndex,
9048    first: &ProjectFile,
9049    donor_source: &ProjectFile,
9050    reference_file: &ProjectFile,
9051    known_missing: &mut HashSet<ProjectFile>,
9052) -> bool {
9053    if first == donor_source {
9054        return true;
9055    }
9056    if known_missing.contains(first) {
9057        return false;
9058    }
9059    let reference_is_c = reference_file
9060        .rel_path()
9061        .extension()
9062        .and_then(|extension| extension.to_str())
9063        == Some("c");
9064    if let Some(reaches) =
9065        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
9066    {
9067        return reaches;
9068    }
9069    let mut visited = HashSet::default();
9070    let mut files = vec![first.clone()];
9071    // Only an unconditional directive extends the include reach, so the walk
9072    // asks the question without a reference position.
9073    let reference = CallableReferenceContext {
9074        file: reference_file,
9075        position: None,
9076    };
9077    while let Some(file) = files.pop() {
9078        if file == *donor_source {
9079            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
9080            return true;
9081        }
9082        if known_missing.contains(&file) || !visited.insert(file.clone()) {
9083            continue;
9084        }
9085        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
9086            continue;
9087        };
9088        let mut nodes = vec![prepared.tree().root_node()];
9089        while let Some(node) = nodes.pop() {
9090            if node.kind() == "preproc_include" {
9091                if callable_preprocessor_context_is_visible_for_reference(
9092                    node,
9093                    prepared.source(),
9094                    &reference,
9095                ) {
9096                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9097                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9098                        if let Some(target) = unique_include_target(
9099                            resolve_include_targets_with_index(&file, &include, include_targets),
9100                        ) {
9101                            files.push(target);
9102                        }
9103                    }
9104                }
9105                continue;
9106            }
9107            for index in (0..node.named_child_count()).rev() {
9108                if let Some(child) = node.named_child(index) {
9109                    nodes.push(child);
9110                }
9111            }
9112        }
9113    }
9114    known_missing.extend(visited);
9115    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
9116    false
9117}
9118
9119fn declaration_guard_requirements(
9120    analyzer: &CppGraphSource<'_>,
9121    cpp: &dyn CppSource,
9122    candidate: &CodeUnit,
9123) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
9124    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
9125        return Vec::new();
9126    };
9127    let root = prepared.tree().root_node();
9128    analyzer
9129        .ranges(candidate)
9130        .into_iter()
9131        .filter_map(|range| {
9132            root.descendant_for_byte_range(range.start_byte, range.end_byte)
9133                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
9134                // A class name is injected into its own body at the declaration's
9135                // introduction point, not after the complete class range. Using
9136                // the start also preserves normal before/after ordering for aliases.
9137                .map(|required| (range.start_byte, required))
9138        })
9139        .collect()
9140}
9141
9142fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
9143    analyzer
9144        .ranges(candidate)
9145        .into_iter()
9146        .map(|range| range.start_byte)
9147        .min()
9148}
9149
9150/// The macro names every configuration in `contexts` defines -- the fact set
9151/// one file's compile-database coverage proves (#2011). `None` when the
9152/// database has no entry for the file, which is different from an empty
9153/// intersection: no entry means no coverage, while an empty intersection is
9154/// covered-and-proves-nothing.
9155fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
9156    let (first, rest) = contexts.split_first()?;
9157    Some(
9158        first
9159            .defined_macros
9160            .iter()
9161            .filter(|name| {
9162                rest.iter()
9163                    .all(|context| context.defined_macros.contains(*name))
9164            })
9165            .cloned()
9166            .collect(),
9167    )
9168}
9169
9170fn guard_requirements_hold_at_reference(
9171    required: &HashSet<PreprocessorGuard>,
9172    reference: Option<&HashSet<PreprocessorGuard>>,
9173) -> bool {
9174    reference.is_some_and(|active| {
9175        required
9176            .iter()
9177            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
9178    })
9179}
9180
9181fn preprocessor_guard_holds_at_reference(
9182    required: &PreprocessorGuard,
9183    active: &HashSet<PreprocessorGuard>,
9184) -> bool {
9185    if active.contains(required) {
9186        return true;
9187    }
9188    let active_expression = BooleanGuardExpression::all(
9189        active
9190            .iter()
9191            .filter_map(PreprocessorGuard::as_boolean_expression),
9192    );
9193    required
9194        .as_boolean_expression()
9195        .is_some_and(|required| active_expression.implies(&required))
9196}
9197
9198/// Cross-file guard rule: two guard sets are compatible when neither one
9199/// contradicts the other. Use this instead of the subset test whenever the
9200/// guards come from a foreign file, which resolves its own conditionals
9201/// independently of the reference.
9202fn guards_compatible_at_reference(
9203    declaration: &HashSet<PreprocessorGuard>,
9204    reference: Option<&HashSet<PreprocessorGuard>>,
9205) -> bool {
9206    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
9207}
9208
9209/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
9210/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
9211/// conditional.
9212///
9213/// Two declarations of one name that report the same chain stand in different
9214/// branches of it, so at most one of them is compiled in any configuration.
9215/// They are alternate spellings of a single declaration, not competing
9216/// declarations, and navigation must not present them as an ambiguity.
9217pub fn preprocessor_conditional_family_range(
9218    root: Node<'_>,
9219    start_byte: usize,
9220    end_byte: usize,
9221) -> Option<(usize, usize)> {
9222    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
9223    let mut ancestor = Some(node);
9224    while let Some(current) = ancestor {
9225        if is_preprocessor_conditional(current)
9226            && preprocessor_conditional_contains_descendant(current, node)
9227        {
9228            let family = preprocessor_conditional_family_root(current);
9229            return Some((family.start_byte(), family.end_byte()));
9230        }
9231        ancestor = current.parent();
9232    }
9233    None
9234}
9235
9236fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
9237    let mut ancestor = node.parent();
9238    while let Some(current) = ancestor {
9239        if is_preprocessor_conditional(current)
9240            && preprocessor_conditional_contains_descendant(current, node)
9241        {
9242            let family = preprocessor_conditional_family_root(current);
9243            if preprocessor_conditional_family_has_terminal_else(family) {
9244                return Some(family);
9245            }
9246        }
9247        ancestor = current.parent();
9248    }
9249    None
9250}
9251
9252fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
9253    while let Some(parent) = conditional.parent() {
9254        let is_alternative = parent
9255            .child_by_field_name("alternative")
9256            .is_some_and(|alternative| {
9257                alternative.start_byte() == conditional.start_byte()
9258                    && alternative.end_byte() == conditional.end_byte()
9259            });
9260        if !is_alternative {
9261            break;
9262        }
9263        conditional = parent;
9264    }
9265    conditional
9266}
9267
9268fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
9269    loop {
9270        let Some(alternative) = conditional.child_by_field_name("alternative") else {
9271            return false;
9272        };
9273        match alternative.kind() {
9274            "preproc_else" => return true,
9275            "preproc_elif" => conditional = alternative,
9276            _ => return false,
9277        }
9278    }
9279}
9280
9281pub fn preprocessor_guard_environment(
9282    node: Node<'_>,
9283    source: &str,
9284) -> Option<HashSet<PreprocessorGuard>> {
9285    let mut guards = HashSet::default();
9286    let mut ancestor = node.parent();
9287    while let Some(conditional) = ancestor {
9288        if matches!(
9289            conditional.kind(),
9290            "preproc_if" | "preproc_ifdef" | "preproc_elif"
9291        ) && !is_file_covering_include_guard(conditional, source)
9292            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9293            && preprocessor_conditional_contains_descendant(conditional, node)
9294        {
9295            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
9296            match guard {
9297                PreprocessorGuard::Constant(true) => {
9298                    ancestor = conditional.parent();
9299                    continue;
9300                }
9301                PreprocessorGuard::Constant(false) => return None,
9302                _ => {}
9303            }
9304            if guards.contains(&guard.negated()) {
9305                return None;
9306            }
9307            guards.insert(guard);
9308        }
9309        ancestor = conditional.parent();
9310    }
9311    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
9312        match guard {
9313            PreprocessorGuard::Constant(true) => {}
9314            PreprocessorGuard::Constant(false) => return None,
9315            _ => {
9316                if guards.contains(&guard.negated()) {
9317                    return None;
9318                }
9319                guards.insert(guard);
9320            }
9321        }
9322    }
9323    Some(guards)
9324}
9325
9326fn fragmented_statement_preprocessor_guard(
9327    descendant: Node<'_>,
9328    source: &str,
9329) -> Option<PreprocessorGuard> {
9330    // A conditional that starts before `} else if (...) {` crosses the
9331    // enclosing statement's grammar boundary. tree-sitter leaves its opener
9332    // as a `preproc_if` with a missing terminator in the consequence and
9333    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
9334    // those structured nodes before restoring the guard to intervening uses.
9335    let mut ancestor = descendant.parent();
9336    while let Some(statement) = ancestor {
9337        if statement.kind() == "if_statement"
9338            && let (Some(consequence), Some(alternative)) = (
9339                statement.child_by_field_name("consequence"),
9340                statement.child_by_field_name("alternative"),
9341            )
9342            && alternative.start_byte() <= descendant.start_byte()
9343            && descendant.end_byte() <= alternative.end_byte()
9344        {
9345            let mut cursor = consequence.walk();
9346            let openers = consequence
9347                .named_children(&mut cursor)
9348                .filter(|child| {
9349                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
9350                        && child
9351                            .child(child.child_count().saturating_sub(1))
9352                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
9353                })
9354                .collect::<Vec<_>>();
9355            if openers.len() != 1 {
9356                ancestor = statement.parent();
9357                continue;
9358            }
9359
9360            let mut terminators = Vec::new();
9361            let mut stack = vec![alternative];
9362            while let Some(node) = stack.pop() {
9363                if node.kind() == "preproc_call"
9364                    && node.start_byte() >= descendant.end_byte()
9365                    && node
9366                        .child_by_field_name("directive")
9367                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
9368                {
9369                    terminators.push(node);
9370                    continue;
9371                }
9372                for index in (0..node.named_child_count()).rev() {
9373                    if let Some(child) = node.named_child(index) {
9374                        stack.push(child);
9375                    }
9376                }
9377            }
9378            if terminators.len() == 1 {
9379                return simple_preprocessor_guard(openers[0], source);
9380            }
9381        }
9382        ancestor = statement.parent();
9383    }
9384    None
9385}
9386
9387fn preprocessor_guard_for_descendant(
9388    conditional: Node<'_>,
9389    descendant: Node<'_>,
9390    source: &str,
9391) -> Option<PreprocessorGuard> {
9392    let mut guard = simple_preprocessor_guard(conditional, source)?;
9393    if conditional
9394        .child_by_field_name("alternative")
9395        .is_some_and(|alternative| {
9396            alternative.start_byte() <= descendant.start_byte()
9397                && descendant.end_byte() <= alternative.end_byte()
9398        })
9399    {
9400        let alternative = conditional.child_by_field_name("alternative")?;
9401        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
9402        // descendant in any later branch must first exclude the parent branch,
9403        // then collect the nested `preproc_elif` guard from its own ancestor.
9404        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
9405            return None;
9406        }
9407        guard = guard.negated();
9408    }
9409    Some(guard)
9410}
9411
9412fn preprocessor_conditional_contains_descendant(
9413    conditional: Node<'_>,
9414    descendant: Node<'_>,
9415) -> bool {
9416    cpp_displaced_preprocessor_boundary(conditional)
9417        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
9418}
9419
9420pub fn merge_preprocessor_guards(
9421    left: &HashSet<PreprocessorGuard>,
9422    right: &HashSet<PreprocessorGuard>,
9423) -> Option<HashSet<PreprocessorGuard>> {
9424    let mut merged = left.clone();
9425    for guard in right {
9426        if merged.contains(&guard.negated()) {
9427            return None;
9428        }
9429        merged.insert(guard.clone());
9430    }
9431    Some(merged)
9432}
9433
9434fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
9435    if conditional.kind() == "preproc_ifdef" {
9436        let name = conditional.child_by_field_name("name")?;
9437        let name = node_text(name, source).to_string();
9438        return match conditional.child(0)?.kind() {
9439            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
9440            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
9441            _ => None,
9442        };
9443    }
9444    let condition = conditional.child_by_field_name("condition")?;
9445    simple_preprocessor_expression_guard(condition, source).or_else(|| {
9446        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
9447            node_text(condition, source),
9448        )))
9449    })
9450}
9451
9452fn simple_preprocessor_expression_guard(
9453    expression: Node<'_>,
9454    source: &str,
9455) -> Option<PreprocessorGuard> {
9456    match expression.kind() {
9457        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
9458            node_text(expression, source).to_string(),
9459        ))),
9460        "number_literal" => match node_text(expression, source).trim() {
9461            "0" => Some(PreprocessorGuard::Constant(false)),
9462            "1" => Some(PreprocessorGuard::Constant(true)),
9463            _ => None,
9464        },
9465        "preproc_defined" => {
9466            let identifier = (0..expression.named_child_count())
9467                .filter_map(|index| expression.named_child(index))
9468                .find(|child| child.kind() == "identifier")?;
9469            Some(PreprocessorGuard::Defined(
9470                node_text(identifier, source).to_string(),
9471            ))
9472        }
9473        "unary_expression"
9474            if expression
9475                .child_by_field_name("operator")
9476                .is_some_and(|operator| operator.kind() == "!") =>
9477        {
9478            simple_preprocessor_expression_guard(
9479                expression.child_by_field_name("argument")?,
9480                source,
9481            )
9482            .map(|guard| guard.negated())
9483        }
9484        "parenthesized_expression" => (0..expression.named_child_count())
9485            .filter_map(|index| expression.named_child(index))
9486            .next()
9487            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
9488        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
9489            expression, source,
9490        ))),
9491        _ => None,
9492    }
9493}
9494
9495fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
9496    match expression.kind() {
9497        "number_literal" => match node_text(expression, source).trim() {
9498            "0" => BooleanGuardExpression::Constant(false),
9499            "1" => BooleanGuardExpression::Constant(true),
9500            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9501                expression, source,
9502            ))),
9503        },
9504        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
9505        "preproc_defined" => {
9506            let identifier = (0..expression.named_child_count())
9507                .filter_map(|index| expression.named_child(index))
9508                .find(|child| child.kind() == "identifier");
9509            identifier.map_or_else(
9510                || {
9511                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9512                        expression, source,
9513                    )))
9514                },
9515                |identifier| {
9516                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
9517                },
9518            )
9519        }
9520        "unary_expression"
9521            if expression
9522                .child_by_field_name("operator")
9523                .is_some_and(|operator| operator.kind() == "!") =>
9524        {
9525            expression.child_by_field_name("argument").map_or_else(
9526                || {
9527                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9528                        expression, source,
9529                    )))
9530                },
9531                |argument| boolean_preprocessor_expression(argument, source).negated(),
9532            )
9533        }
9534        "parenthesized_expression" => (0..expression.named_child_count())
9535            .filter_map(|index| expression.named_child(index))
9536            .next()
9537            .map_or_else(
9538                || {
9539                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9540                        expression, source,
9541                    )))
9542                },
9543                |child| boolean_preprocessor_expression(child, source),
9544            ),
9545        "binary_expression" => {
9546            let operands = || {
9547                Some((
9548                    boolean_preprocessor_expression(
9549                        expression.child_by_field_name("left")?,
9550                        source,
9551                    ),
9552                    boolean_preprocessor_expression(
9553                        expression.child_by_field_name("right")?,
9554                        source,
9555                    ),
9556                ))
9557            };
9558            match expression
9559                .child_by_field_name("operator")
9560                .map(|operator| operator.kind())
9561            {
9562                Some("&&") => operands().map_or_else(
9563                    || {
9564                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9565                            expression, source,
9566                        )))
9567                    },
9568                    |(left, right)| BooleanGuardExpression::all([left, right]),
9569                ),
9570                Some("||") => operands().map_or_else(
9571                    || {
9572                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9573                            expression, source,
9574                        )))
9575                    },
9576                    |(left, right)| BooleanGuardExpression::any([left, right]),
9577                ),
9578                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9579                    expression, source,
9580                ))),
9581            }
9582        }
9583        _ => {
9584            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
9585        }
9586    }
9587}
9588
9589fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
9590    if targets.len() == 1 {
9591        targets.pop()
9592    } else {
9593        None
9594    }
9595}
9596
9597/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
9598/// later reference can name.
9599///
9600/// A declaration inside a real function body, lambda, or nested block is block
9601/// local and is dropped. A declaration inside a parser-recovery wrapper that
9602/// merely looks callable -- an export macro between `class` and its name, or a
9603/// namespace-opening macro token before `namespace x {` -- keeps class or
9604/// namespace scope and is kept.
9605fn nameable_callable_declaration_nodes<'tree>(
9606    analyzer: &CppGraphSource<'_>,
9607    prepared: &'tree PreparedSyntaxTree,
9608    candidate: &CodeUnit,
9609) -> Vec<Node<'tree>> {
9610    let root = prepared.tree().root_node();
9611    analyzer
9612        .ranges(candidate)
9613        .into_iter()
9614        .filter_map(|range| {
9615            let mut declaration =
9616                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
9617            while !matches!(
9618                declaration.kind(),
9619                "declaration" | "field_declaration" | "function_definition"
9620            ) {
9621                declaration = declaration.parent()?;
9622            }
9623            let mut ancestor = declaration.parent();
9624            while let Some(node) = ancestor {
9625                if node.kind() == "function_definition"
9626                    && is_recovered_declaration_scope_container(node, prepared.source())
9627                {
9628                    ancestor = node.parent();
9629                    continue;
9630                }
9631                if node.kind() == "compound_statement"
9632                    && node.parent().is_some_and(|parent| {
9633                        is_recovered_declaration_scope_container(parent, prepared.source())
9634                    })
9635                {
9636                    ancestor = node.parent().and_then(|parent| parent.parent());
9637                    continue;
9638                }
9639                if matches!(
9640                    node.kind(),
9641                    "compound_statement" | "function_definition" | "lambda_expression"
9642                ) {
9643                    return None;
9644                }
9645                ancestor = node.parent();
9646            }
9647            Some(declaration)
9648        })
9649        .collect()
9650}
9651
9652fn callable_declaration_activation_in_file(
9653    analyzer: &CppGraphSource<'_>,
9654    prepared: &PreparedSyntaxTree,
9655    candidate: &CodeUnit,
9656    reference: &CallableReferenceContext<'_>,
9657) -> Option<usize> {
9658    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
9659        .into_iter()
9660        .filter(|declaration| {
9661            callable_preprocessor_context_is_visible_for_reference(
9662                *declaration,
9663                prepared.source(),
9664                reference,
9665            )
9666        })
9667        .map(callable_declaration_activation_byte)
9668        .min()
9669}
9670
9671/// C and C++ activate a declared name at the end of its declarator, not at the
9672/// end of the whole declaration. A function definition ends at the closing
9673/// brace of its body, so the declaration end byte would hide the function from
9674/// its own body and make self recursion unresolvable without a prototype.
9675fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
9676    if declaration.kind() != "function_definition" {
9677        return declaration.end_byte();
9678    }
9679    declaration
9680        .child_by_field_name("declarator")
9681        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
9682}
9683
9684/// The reference side of a callable visibility question.
9685///
9686/// An include-graph walk and a whole-file arity activation ask the question
9687/// without one reference position, so they carry no `position` and therefore no
9688/// guard environment.
9689struct CallableReferenceContext<'a> {
9690    file: &'a ProjectFile,
9691    position: Option<CallableReferencePosition<'a>>,
9692}
9693
9694/// One reference position plus its preprocessor guard environment. The
9695/// environment is computed on demand because most declarations carry no
9696/// non-trivial guard.
9697struct CallableReferencePosition<'a> {
9698    prepared: &'a PreparedSyntaxTree,
9699    byte: usize,
9700    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
9701}
9702
9703impl CallableReferenceContext<'_> {
9704    fn is_c(&self) -> bool {
9705        self.file
9706            .rel_path()
9707            .extension()
9708            .and_then(|extension| extension.to_str())
9709            == Some("c")
9710    }
9711
9712    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
9713        let position = self.position.as_ref()?;
9714        position
9715            .guards
9716            .get_or_init(|| {
9717                position
9718                    .prepared
9719                    .tree()
9720                    .root_node()
9721                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
9722                    .and_then(|node| {
9723                        preprocessor_guard_environment(node, position.prepared.source())
9724                    })
9725            })
9726            .as_ref()
9727    }
9728}
9729
9730fn callable_preprocessor_context_is_visible_for_reference(
9731    node: Node<'_>,
9732    source: &str,
9733    reference: &CallableReferenceContext<'_>,
9734) -> bool {
9735    let reference_is_c = reference.is_c();
9736    let mut ancestor = node.parent();
9737    while let Some(conditional) = ancestor {
9738        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
9739            && !is_file_covering_include_guard(conditional, source)
9740            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9741            && preprocessor_conditional_contains_descendant(conditional, node)
9742        {
9743            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
9744                return false;
9745            };
9746            match guard {
9747                PreprocessorGuard::Constant(true) => {}
9748                PreprocessorGuard::Constant(false) => return false,
9749                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
9750                    if reference_is_c {
9751                        return false;
9752                    }
9753                }
9754                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
9755                    if !reference_is_c {
9756                        return false;
9757                    }
9758                }
9759                // The declaration stands under a guard whose value this
9760                // analyzer cannot decide. It is still co-active with a
9761                // reference whose active guards imply it. Collecting one guard
9762                // per ancestor makes the whole walk a conjunction of the
9763                // declaration requirements.
9764                guard => {
9765                    if !reference
9766                        .guards()
9767                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
9768                    {
9769                        return false;
9770                    }
9771                }
9772            }
9773        }
9774        ancestor = conditional.parent();
9775    }
9776    true
9777}
9778
9779fn flattened_macro_namespace_declaration_matches(
9780    analyzer: &CppGraphSource<'_>,
9781    cpp: &dyn CppSource,
9782    reference_file: &ProjectFile,
9783    visible_declaration: &CodeUnit,
9784    qualified_candidate: &CodeUnit,
9785    reference_byte: usize,
9786) -> bool {
9787    // Namespace-opening macros can leave tree-sitter unable to retain the
9788    // namespace owner after a later recovery point. In that shape the forward
9789    // declaration is indexed at translation-unit scope, while the definition
9790    // still has its qualified owner. Require all surviving structural evidence
9791    // before treating the declaration as activation for that definition.
9792    if visible_declaration.kind() != qualified_candidate.kind()
9793        || visible_declaration.identifier() != qualified_candidate.identifier()
9794        || visible_declaration.signature() != qualified_candidate.signature()
9795        || !visible_declaration.package_name().is_empty()
9796        || qualified_candidate.package_name().is_empty()
9797    {
9798        return false;
9799    }
9800
9801    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
9802        return false;
9803    };
9804    let root = prepared.tree().root_node();
9805    let closing_brace_limit = if visible_declaration.source() == reference_file {
9806        reference_byte
9807    } else {
9808        usize::MAX
9809    };
9810
9811    analyzer
9812        .ranges(visible_declaration)
9813        .into_iter()
9814        .any(|range| {
9815            let Some(mut declaration) =
9816                root.descendant_for_byte_range(range.start_byte, range.end_byte)
9817            else {
9818                return false;
9819            };
9820            while !matches!(
9821                declaration.kind(),
9822                "declaration" | "field_declaration" | "function_definition"
9823            ) {
9824                let Some(parent) = declaration.parent() else {
9825                    return false;
9826                };
9827                declaration = parent;
9828            }
9829            if declaration
9830                .parent()
9831                .is_none_or(|parent| parent.kind() != "translation_unit")
9832                || !macro_displaced_cpp_return_type(declaration, prepared.source())
9833            {
9834                return false;
9835            }
9836
9837            let mut cursor = root.walk();
9838            root.named_children(&mut cursor).any(|sibling| {
9839                sibling.start_byte() >= declaration.end_byte()
9840                    && sibling.start_byte() < closing_brace_limit
9841                    && direct_unmatched_closing_brace(sibling)
9842            })
9843        })
9844}
9845
9846fn flattened_macro_namespace_components(
9847    declaration: Node<'_>,
9848    source: &str,
9849) -> Option<Vec<String>> {
9850    flattened_macro_function_namespace_components(declaration, source)
9851        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
9852}
9853
9854fn flattened_macro_function_namespace_components(
9855    declaration: Node<'_>,
9856    source: &str,
9857) -> Option<Vec<String>> {
9858    let body = declaration
9859        .parent()
9860        .filter(|parent| parent.kind() == "compound_statement")?;
9861    let function = body.parent()?;
9862    if function.child_by_field_name("body") != Some(body) {
9863        return None;
9864    }
9865    let namespace_name = recovered_macro_namespace_name(function, source)?;
9866    let mut components = enclosing_namespace_components(declaration, source)?;
9867    components.push(namespace_name);
9868    Some(components)
9869}
9870
9871/// The namespace name a namespace-opening macro token displaced into a
9872/// synthetic `function_definition`, or `None` when `function` is not that
9873/// recovery shape.
9874///
9875/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
9876/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
9877/// the macro token, whose declarator is the namespace name behind an `ERROR`
9878/// holding the `namespace` keyword, and whose body spans the whole namespace
9879/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
9880/// artifact from a real function definition.
9881fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
9882    if function.kind() != "function_definition" || !function.has_error() {
9883        return None;
9884    }
9885    let body = function
9886        .child_by_field_name("body")
9887        .filter(|body| body.kind() == "compound_statement")?;
9888    let mut cursor = function.walk();
9889    let prefix = function
9890        .named_children(&mut cursor)
9891        .take_while(|child| child.start_byte() < body.start_byte())
9892        .filter(|child| child.kind() != "comment")
9893        .collect::<Vec<_>>();
9894    let begin_index = prefix.iter().rposition(|child| {
9895        flattened_macro_sentinel_name(*child, source)
9896            .is_some_and(|name| is_namespace_begin_sentinel(&name))
9897    })?;
9898    let mut identifiers = Vec::new();
9899    let mut stack = prefix[begin_index + 1..]
9900        .iter()
9901        .rev()
9902        .copied()
9903        .collect::<Vec<_>>();
9904    while let Some(current) = stack.pop() {
9905        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
9906            identifiers.push(identifier);
9907            continue;
9908        }
9909        let mut cursor = current.walk();
9910        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
9911        stack.extend(children.into_iter().rev());
9912    }
9913    let [keyword, namespace_name] = identifiers.as_slice() else {
9914        return None;
9915    };
9916    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
9917    {
9918        return None;
9919    }
9920    let mut next = function.next_named_sibling();
9921    let next = loop {
9922        let candidate = next?;
9923        next = candidate.next_named_sibling();
9924        if candidate.kind() != "comment" {
9925            break candidate;
9926        }
9927    };
9928    flattened_macro_sentinel_name(next, source)
9929        .is_some_and(|name| is_namespace_end_sentinel(&name))
9930        .then(|| namespace_name.clone())
9931}
9932
9933/// A `function_definition` that exists only because tree-sitter recovered a
9934/// macro-decorated class head or a namespace-opening macro token. A declaration
9935/// in such a body keeps class or namespace scope, so a scope walk must step over
9936/// the wrapper instead of treating the declaration as block local.
9937fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
9938    crate::declarations::is_recovered_exported_class_container(node, source)
9939        || recovered_macro_namespace_name(node, source).is_some()
9940}
9941
9942fn flattened_macro_error_namespace_components(
9943    declaration: Node<'_>,
9944    source: &str,
9945) -> Option<Vec<String>> {
9946    let parent = declaration
9947        .parent()
9948        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
9949    let mut cursor = parent.walk();
9950    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
9951    let declaration_index = siblings
9952        .iter()
9953        .position(|candidate| same_node(*candidate, declaration))?;
9954    let begin_index = (0..declaration_index).rev().find(|index| {
9955        flattened_macro_sentinel_name(siblings[*index], source)
9956            .is_some_and(|name| is_namespace_begin_sentinel(&name))
9957    })?;
9958
9959    let significant = siblings[begin_index + 1..declaration_index]
9960        .iter()
9961        .copied()
9962        .filter(|node| node.kind() != "comment")
9963        .collect::<Vec<_>>();
9964    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
9965        return None;
9966    };
9967    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
9968        return None;
9969    }
9970    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
9971    if significant[2..].iter().any(|node| {
9972        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
9973            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
9974        })
9975    }) {
9976        return None;
9977    }
9978
9979    let mut saw_namespace_close = false;
9980    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
9981        if sibling.kind() == "comment" {
9982            continue;
9983        }
9984        if !saw_namespace_close {
9985            if direct_unmatched_closing_brace(sibling) {
9986                saw_namespace_close = true;
9987                continue;
9988            }
9989            if flattened_macro_sentinel_name(sibling, source).is_some() {
9990                return None;
9991            }
9992            continue;
9993        }
9994        if !flattened_macro_sentinel_name(sibling, source)
9995            .is_some_and(|name| is_namespace_end_sentinel(&name))
9996        {
9997            return None;
9998        }
9999        let mut components = enclosing_namespace_components(declaration, source)?;
10000        components.push(namespace_name);
10001        return Some(components);
10002    }
10003    None
10004}
10005
10006fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10007    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
10008    // an `expression_statement` with a missing semicolon; inside a namespace
10009    // body the same token stays a bare `type_identifier`.
10010    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10011        node.named_child(0)?
10012    } else {
10013        node
10014    };
10015    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10016        node.child_by_field_name("type")
10017            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10018    })?;
10019    (cpp_export_macro_token(&candidate)
10020        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10021    .then_some(candidate)
10022}
10023
10024/// Namespace-opening macros are spelled both ways in the wild:
10025/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
10026fn is_namespace_begin_sentinel(name: &str) -> bool {
10027    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10028}
10029
10030fn is_namespace_end_sentinel(name: &str) -> bool {
10031    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10032}
10033
10034fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10035    if node.kind() != "ERROR" || node.named_child_count() != 1 {
10036        return None;
10037    }
10038    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10039    (!cpp_export_macro_token(&name)).then_some(name)
10040}
10041
10042fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10043    if !matches!(
10044        node.kind(),
10045        "identifier" | "namespace_identifier" | "type_identifier"
10046    ) {
10047        return None;
10048    }
10049    let name = normalize_cpp_whitespace(node_text(node, source));
10050    (!name.is_empty()).then_some(name)
10051}
10052
10053fn guard_requirement_sets_match(
10054    left: &[(usize, HashSet<PreprocessorGuard>)],
10055    right: &[(usize, HashSet<PreprocessorGuard>)],
10056) -> bool {
10057    left.len() == right.len()
10058        && left.iter().all(|(_, left_guards)| {
10059            right
10060                .iter()
10061                .any(|(_, right_guards)| left_guards == right_guards)
10062        })
10063        && right.iter().all(|(_, right_guards)| {
10064            left.iter()
10065                .any(|(_, left_guards)| right_guards == left_guards)
10066        })
10067}
10068
10069fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
10070    let Some(type_node) = declaration.child_by_field_name("type") else {
10071        return false;
10072    };
10073    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
10074    !type_name.is_empty()
10075        && type_name
10076            .chars()
10077            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
10078        && (0..declaration.named_child_count()).any(|index| {
10079            declaration
10080                .named_child(index)
10081                .is_some_and(|child| child.kind() == "ERROR")
10082        })
10083}
10084
10085fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
10086    node.kind() == "ERROR"
10087        && (0..node.child_count())
10088            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
10089}
10090
10091fn unmatched_closing_brace_is_followed_by_semicolon(node: Node<'_>) -> bool {
10092    let mut following = node.next_named_sibling();
10093    let following = loop {
10094        match following {
10095            Some(candidate) if candidate.kind() == "comment" => {
10096                following = candidate.next_named_sibling();
10097                continue;
10098            }
10099            candidate => break candidate,
10100        }
10101    };
10102    following.is_some_and(|candidate| {
10103        candidate.kind() == "expression_statement"
10104            && candidate.named_child_count() == 0
10105            && (0..candidate.child_count()).any(|index| {
10106                candidate
10107                    .child(index)
10108                    .is_some_and(|child| child.kind() == ";")
10109            })
10110    })
10111}
10112
10113pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
10114    let mut ancestor = node.parent();
10115    while let Some(parent) = ancestor {
10116        if is_preprocessor_conditional(parent)
10117            && !is_file_covering_include_guard(parent, source)
10118            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
10119        {
10120            return false;
10121        }
10122        ancestor = parent.parent();
10123    }
10124    true
10125}
10126
10127fn is_split_cpp_language_linkage_wrapper(
10128    conditional: Node<'_>,
10129    descendant: Node<'_>,
10130    source: &str,
10131) -> bool {
10132    if conditional.child_by_field_name("alternative").is_some()
10133        || !matches!(
10134            simple_preprocessor_guard(conditional, source),
10135            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10136        )
10137    {
10138        return false;
10139    }
10140    let mut current = descendant.parent();
10141    let linkage = loop {
10142        let Some(node) = current else {
10143            return false;
10144        };
10145        if node == conditional {
10146            return false;
10147        }
10148        if node.kind() == "linkage_specification" {
10149            break node;
10150        }
10151        current = node.parent();
10152    };
10153    if linkage
10154        .child_by_field_name("value")
10155        .is_none_or(|value| node_text(value, source) != "\"C\"")
10156    {
10157        return false;
10158    }
10159    let Some(body) = linkage.child_by_field_name("body") else {
10160        return false;
10161    };
10162    let closes_opening_branch = (0..body.named_child_count())
10163        .filter_map(|index| body.named_child(index))
10164        .take_while(|child| child.end_byte() <= descendant.start_byte())
10165        .any(|child| {
10166            child.kind() == "preproc_call"
10167                && child
10168                    .child_by_field_name("directive")
10169                    .is_some_and(|directive| node_text(directive, source) == "#endif")
10170        });
10171    let reopens_for_closing_brace = (0..body.named_child_count())
10172        .filter_map(|index| body.named_child(index))
10173        .skip_while(|child| child.start_byte() < descendant.end_byte())
10174        .any(|child| {
10175            matches!(
10176                simple_preprocessor_guard(child, source),
10177                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10178            ) && (0..child.child_count()).any(|index| {
10179                child
10180                    .child(index)
10181                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
10182            })
10183        });
10184    closes_opening_branch && reopens_for_closing_brace
10185}
10186
10187pub fn call_arity(node: Node<'_>) -> usize {
10188    node.child_by_field_name("arguments")
10189        .or_else(|| node.child_by_field_name("parameters"))
10190        .or_else(|| node.child_by_field_name("value"))
10191        .or_else(|| first_named_child_of_kind(node, "argument_list"))
10192        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
10193        .map(|args| argument_children(args).count())
10194        .unwrap_or(0)
10195}
10196
10197pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
10198    let recovered_block_arguments = recovered_block_literal_arguments(node);
10199    (0..node.child_count())
10200        .filter_map(move |index| node.child(index))
10201        .filter(|child| child.is_named() && !child.is_extra())
10202        .flat_map(move |child| {
10203            if let Some((raw, left, right)) = recovered_block_arguments
10204                && child == raw
10205            {
10206                [Some(left), Some(right)]
10207            } else {
10208                [Some(child), None]
10209            }
10210        })
10211        .flatten()
10212}
10213
10214fn recovered_c_keyword_argument_count(
10215    file: &ProjectFile,
10216    call: Node<'_>,
10217    arguments: Node<'_>,
10218    source: &str,
10219) -> usize {
10220    // A C identifier that is a C++ keyword can be displaced twice by the C++
10221    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
10222    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
10223    // the enclosing C function before restoring the otherwise dropped slot.
10224    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
10225        return 0;
10226    }
10227    let mut ancestor = Some(call);
10228    let function = loop {
10229        let Some(current) = ancestor else {
10230            return 0;
10231        };
10232        if current.kind() == "function_definition" {
10233            break current;
10234        }
10235        ancestor = current.parent();
10236    };
10237    let Some(parameters) = function
10238        .child_by_field_name("declarator")
10239        .and_then(|declarator| declarator.child_by_field_name("parameters"))
10240    else {
10241        return 0;
10242    };
10243    let displaced_parameter_keywords = (0..parameters.child_count())
10244        .filter_map(|index| parameters.child(index))
10245        .filter(|error| error.kind() == "ERROR")
10246        .filter_map(|error| {
10247            let parameter = error.prev_named_sibling()?;
10248            if parameter.kind() != "parameter_declaration"
10249                || parameter.end_byte() != error.start_byte()
10250                || extract_variable_name(parameter, source).is_some()
10251            {
10252                return None;
10253            }
10254            let mut children = (0..error.child_count())
10255                .filter_map(|index| error.child(index))
10256                .filter(|child| !child.is_extra() && !child.is_missing());
10257            let keyword = children.next()?;
10258            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
10259                .then_some(keyword)
10260        })
10261        .collect::<Vec<_>>();
10262    if displaced_parameter_keywords.is_empty() {
10263        return 0;
10264    }
10265
10266    (0..arguments.child_count())
10267        .filter_map(|index| arguments.child(index))
10268        .filter(|error| error.kind() == "ERROR" && error.is_extra())
10269        .filter(|error| {
10270            let mut children = (0..error.child_count())
10271                .filter_map(|index| error.child(index))
10272                .filter(|child| !child.is_extra() && !child.is_missing());
10273            let Some(comma) = children.next() else {
10274                return false;
10275            };
10276            let Some(keyword) = children.next() else {
10277                return false;
10278            };
10279            children.next().is_none()
10280                && comma.kind() == ","
10281                && !keyword.is_named()
10282                && keyword.child_count() == 0
10283                && displaced_parameter_keywords
10284                    .iter()
10285                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
10286        })
10287        .count()
10288}
10289
10290fn recovered_block_literal_arguments<'tree>(
10291    arguments: Node<'tree>,
10292) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
10293    if arguments.kind() != "argument_list" {
10294        return None;
10295    }
10296    let mut raw_arguments = (0..arguments.child_count())
10297        .filter_map(|index| arguments.child(index))
10298        .filter(|child| child.is_named() && !child.is_extra());
10299    let raw = raw_arguments.next()?;
10300    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
10301        return None;
10302    }
10303
10304    let left = raw.child_by_field_name("left")?;
10305    if left.is_missing() || left.start_byte() == left.end_byte() {
10306        return None;
10307    }
10308    let right = raw.child_by_field_name("right")?;
10309    if right.kind() != "compound_literal_expression"
10310        || right.is_missing()
10311        || right
10312            .child_by_field_name("type")
10313            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
10314        || right
10315            .child_by_field_name("value")
10316            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
10317    {
10318        return None;
10319    }
10320    let has_intervening_error = (0..raw.child_count())
10321        .filter_map(|index| raw.child(index))
10322        .any(|child| {
10323            child.kind() == "ERROR"
10324                && !child.is_missing()
10325                && child.start_byte() >= left.end_byte()
10326                && child.end_byte() <= right.start_byte()
10327        });
10328    has_intervening_error.then_some((raw, left, right))
10329}
10330
10331pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
10332    match node.kind() {
10333        "new_expression" => node
10334            .child_by_field_name("type")
10335            .or_else(|| node.named_child(0)),
10336        "compound_literal_expression" => node.child_by_field_name("type"),
10337        "call_expression" => node.child_by_field_name("function"),
10338        _ => None,
10339    }
10340}
10341
10342pub fn field_initializer_constructs_target(
10343    node: Node<'_>,
10344    ctx: &ScanCtx<'_>,
10345    owner: &CodeUnit,
10346) -> bool {
10347    // A qualified name in a constructor initializer denotes a base
10348    // subobject constructor (`namespace::Base(args)`), not a member field.  The
10349    // field-initializer grammar exposes the qualified name as one structured
10350    // `qualified_identifier`; resolve its owner through the same lexical type
10351    // machinery used for ordinary C++ type references before considering the
10352    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
10353    // qualified non-constructor member, and an unresolved owner out of the
10354    // target constructor's inverse usage set.
10355    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
10356        return qualified_base_initializer_constructs_target(node, ctx, owner);
10357    }
10358    let Some(name) = node
10359        .child_by_field_name("name")
10360        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
10361        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
10362    else {
10363        return false;
10364    };
10365    let field_name = node_text(name, ctx.source);
10366    ctx.visibility
10367        .visible_identifier_candidates(ctx.file, field_name)
10368        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
10369        .any(|unit| field_declares_type(unit, ctx, owner))
10370}
10371
10372fn qualified_base_initializer_constructs_target(
10373    node: Node<'_>,
10374    ctx: &ScanCtx<'_>,
10375    owner: &CodeUnit,
10376) -> bool {
10377    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
10378        return false;
10379    };
10380    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
10381        return false;
10382    };
10383    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
10384        return false;
10385    };
10386    let resolves_target = |components: &[String]| {
10387        matches!(
10388            ctx.visibility.resolve_type_components_lexically_for_target(
10389                &ctx.analyzer,
10390                ctx.file,
10391                components,
10392                is_globally_qualified_cpp_name(qualified),
10393                &lexical_scope,
10394                owner,
10395            ),
10396            LexicalTypeResolution::Resolved { unit, .. }
10397                if same_visible_symbol(&unit, owner)
10398        )
10399    };
10400    if resolves_target(&components) {
10401        return true;
10402    }
10403
10404    // Some real-world code spells a base mem-initializer as
10405    // `Base::Base(args)`. In that structured path the final component repeats
10406    // the constructor name; resolve the preceding type path. The terminal
10407    // identity check prevents an arbitrary qualified member from taking this
10408    // route.
10409    components
10410        .last()
10411        .is_some_and(|terminal| terminal == owner.identifier())
10412        && resolves_target(&components[..components.len() - 1])
10413}
10414
10415fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
10416    unit.signature()
10417        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
10418        || ctx
10419            .analyzer
10420            .get_source(unit, false)
10421            .is_some_and(|declaration| {
10422                field_declaration_type_matches(&declaration, unit, ctx, owner)
10423            })
10424}
10425
10426pub fn field_declared_binding(
10427    analyzer: &CppGraphSource<'_>,
10428    visibility: &VisibilityIndex<'_>,
10429    visible_from: &ProjectFile,
10430    field: &CodeUnit,
10431) -> Option<CppScanBinding> {
10432    let fact = visibility.field_declared_type_fact(analyzer, field)?;
10433    let normalized = normalize_field_type_text(&fact.type_text);
10434    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
10435        analyzer,
10436        visible_from,
10437        field,
10438        &normalized,
10439    );
10440    let resolved = match (resolved, fact.template_arguments.as_deref()) {
10441        (Some(primary), Some(arguments)) => visibility
10442            .resolve_template_arguments(visible_from, primary, arguments)
10443            .ok(),
10444        (resolved, None) => resolved,
10445        (None, Some(_)) => None,
10446    };
10447    Some(CppScanBinding::from_type_name(
10448        normalized,
10449        resolved,
10450        fact.indirection,
10451    ))
10452}
10453
10454/// The one logical type the candidates name, or why they do not name one.
10455fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
10456    let Some(first) = candidates.first() else {
10457        return Err(TypeCandidateFailure::Unresolvable);
10458    };
10459    if candidates
10460        .iter()
10461        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
10462    {
10463        Ok((*first).clone())
10464    } else {
10465        Err(TypeCandidateFailure::Ambiguous)
10466    }
10467}
10468
10469fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
10470    logical_type_candidate(candidates).ok()
10471}
10472
10473fn unique_type_candidate_preserving_alias(
10474    analyzer: &CppGraphSource<'_>,
10475    candidates: &[&CodeUnit],
10476) -> Option<CodeUnit> {
10477    let first = *candidates.first()?;
10478    if declared_type_alias(analyzer, first) {
10479        return candidates
10480            .iter()
10481            .all(|candidate| {
10482                declared_type_alias(analyzer, candidate)
10483                    && candidate.kind() == first.kind()
10484                    && candidate.fq_name() == first.fq_name()
10485                    && candidate.source() == first.source()
10486            })
10487            .then(|| first.clone());
10488    }
10489    candidates
10490        .iter()
10491        .all(|candidate| {
10492            !declared_type_alias(analyzer, candidate)
10493                && candidate.kind() == first.kind()
10494                && candidate.fq_name() == first.fq_name()
10495        })
10496        .then(|| first.clone())
10497}
10498
10499fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10500    is_type_alias(unit)
10501        || analyzer
10502            .type_alias_provider()
10503            .is_some_and(|provider| provider.is_type_alias(unit))
10504}
10505
10506pub fn field_declared_type_binding(
10507    analyzer: &CppGraphSource<'_>,
10508    visibility: &VisibilityIndex<'_>,
10509    visible_from: &ProjectFile,
10510    field: &CodeUnit,
10511) -> Option<(String, Option<CodeUnit>, i32)> {
10512    let fact = visibility.field_declared_type_fact(analyzer, field)?;
10513    let normalized = normalize_field_type_text(&fact.type_text);
10514    let primary = visibility.resolve_unique_canonical_type_for_declaration(
10515        analyzer,
10516        visible_from,
10517        field,
10518        &normalized,
10519    );
10520    let resolved = match (primary, fact.template_arguments.as_deref()) {
10521        (Some(primary), Some(arguments)) => visibility
10522            .resolve_template_arguments(visible_from, primary, arguments)
10523            .ok(),
10524        (resolved, None) => resolved,
10525        (None, Some(_)) => None,
10526    };
10527    Some((normalized, resolved, fact.indirection))
10528}
10529
10530fn decode_field_declared_type_fact(
10531    analyzer: &CppGraphSource<'_>,
10532    field: &CodeUnit,
10533) -> Option<DeclaredFieldTypeFact> {
10534    let declaration = analyzer.get_source(field, false)?;
10535    let mut parser = Parser::new();
10536    parser
10537        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10538        .ok()?;
10539    let tree = parser.parse(&declaration, None)?;
10540    let mut stack = vec![tree.root_node()];
10541    while let Some(node) = stack.pop() {
10542        if matches!(node.kind(), "declaration" | "field_declaration")
10543            && let Some(type_node) = node
10544                .child_by_field_name("type")
10545                .or_else(|| first_type_child(node))
10546            && let Some(indirection) =
10547                declared_name_indirection(node, type_node, field.identifier(), &declaration)
10548        {
10549            let declared_type = if matches!(
10550                type_node.kind(),
10551                "class_specifier" | "struct_specifier" | "union_specifier"
10552            ) {
10553                type_node.child_by_field_name("name")
10554            } else {
10555                Some(type_node)
10556            };
10557            let type_text = declared_type.map_or_else(
10558                || field.identifier().to_string(),
10559                |declared_type| node_text(declared_type, &declaration).to_string(),
10560            );
10561            return Some(DeclaredFieldTypeFact {
10562                type_text,
10563                indirection,
10564                template_arguments: declared_type.and_then(|declared_type| {
10565                    cpp_template_reference_arguments(declared_type, &declaration)
10566                }),
10567            });
10568        }
10569        let mut cursor = node.walk();
10570        stack.extend(node.named_children(&mut cursor));
10571    }
10572    None
10573}
10574
10575/// Text of the type that a C or C++ alias declaration names, read from the
10576/// `type_definition` or `alias_declaration` node's `type` field.
10577///
10578/// The declaration text is never scanned. A function-pointer typedef
10579/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
10580/// so no prefix or suffix of the spelling isolates the target.
10581///
10582/// An alias whose declarator is a function declarator names a function type:
10583/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
10584/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
10585/// so such an alias has no canonical target. Its `type` field holds the return
10586/// type `R`, which is a different type from the alias, so this returns `None`
10587/// rather than that return type.
10588pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
10589    let mut parser = Parser::new();
10590    parser
10591        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10592        .ok()?;
10593    let tree = parser.parse(declaration, None)?;
10594    let mut stack = vec![tree.root_node()];
10595    while let Some(node) = stack.pop() {
10596        let type_node = match node.kind() {
10597            "type_definition" => {
10598                let mut cursor = node.walk();
10599                if node
10600                    .children_by_field_name("declarator", &mut cursor)
10601                    .any(declarator_names_function_type)
10602                {
10603                    return None;
10604                }
10605                node.child_by_field_name("type")?
10606            }
10607            "alias_declaration" => {
10608                let type_node = node.child_by_field_name("type")?;
10609                if type_node
10610                    .child_by_field_name("declarator")
10611                    .is_some_and(declarator_names_function_type)
10612                {
10613                    return None;
10614                }
10615                type_node
10616            }
10617            _ => {
10618                let mut cursor = node.walk();
10619                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10620                stack.extend(children.into_iter().rev());
10621                continue;
10622            }
10623        };
10624        return Some(node_text(type_node, declaration).to_string());
10625    }
10626    None
10627}
10628
10629/// Whether an alias declaration's own declarator adds indirection that
10630/// [`cpp_alias_declaration_target_text`] does not report.
10631///
10632/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
10633/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
10634/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
10635/// prove the alias adds no indirection must refuse to follow it. A declaration
10636/// this cannot read at all is refused for the same reason.
10637fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
10638    let mut parser = Parser::new();
10639    if parser
10640        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10641        .is_err()
10642    {
10643        return true;
10644    }
10645    let Some(tree) = parser.parse(declaration, None) else {
10646        return true;
10647    };
10648    let mut stack = vec![tree.root_node()];
10649    while let Some(node) = stack.pop() {
10650        let declarators = match node.kind() {
10651            "type_definition" => {
10652                let mut cursor = node.walk();
10653                node.children_by_field_name("declarator", &mut cursor)
10654                    .collect::<Vec<_>>()
10655            }
10656            "alias_declaration" => node
10657                .child_by_field_name("type")
10658                .and_then(|type_node| type_node.child_by_field_name("declarator"))
10659                .into_iter()
10660                .collect::<Vec<_>>(),
10661            _ => {
10662                let mut cursor = node.walk();
10663                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10664                stack.extend(children.into_iter().rev());
10665                continue;
10666            }
10667        };
10668        return declarators.into_iter().any(cpp_declarator_adds_indirection);
10669    }
10670    true
10671}
10672
10673/// True when an alias declarator names a function type.
10674///
10675/// The declarator chain is walked through the `declarator` field, so the
10676/// parameter list -- a sibling field -- is never entered and a parameter's own
10677/// function declarator cannot be mistaken for the alias's.
10678fn declarator_names_function_type(declarator: Node<'_>) -> bool {
10679    let mut current = Some(declarator);
10680    while let Some(node) = current {
10681        match node.kind() {
10682            "function_declarator" | "abstract_function_declarator" => return true,
10683            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
10684                current = node.named_child(0);
10685            }
10686            _ => current = node.child_by_field_name("declarator"),
10687        }
10688    }
10689    false
10690}
10691
10692/// Whether one indexed field declaration is a function or function-pointer
10693/// value. This follows tree-sitter declarator fields and never infers
10694/// callability from source spelling.
10695pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
10696    let mut parser = Parser::new();
10697    if parser
10698        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10699        .is_err()
10700    {
10701        return false;
10702    }
10703    let Some(tree) = parser.parse(declaration, None) else {
10704        return false;
10705    };
10706    let mut stack = vec![tree.root_node()];
10707    while let Some(node) = stack.pop() {
10708        if matches!(node.kind(), "declaration" | "field_declaration") {
10709            let mut cursor = node.walk();
10710            if node
10711                .children_by_field_name("declarator", &mut cursor)
10712                .any(|declarator| {
10713                    declarator_name_node(declarator).is_some_and(|name| {
10714                        node_text(name, declaration) == field_name
10715                            && declarator_names_function_type(declarator)
10716                    })
10717                })
10718            {
10719                return true;
10720            }
10721        }
10722        let mut cursor = node.walk();
10723        stack.extend(node.named_children(&mut cursor));
10724    }
10725    false
10726}
10727
10728/// Whether one indexed alias declaration names a function or function-pointer
10729/// type. The alias name is matched through the declarator field so a function
10730/// type used by a parameter cannot be mistaken for the alias itself.
10731pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
10732    let mut parser = Parser::new();
10733    if parser
10734        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10735        .is_err()
10736    {
10737        return false;
10738    }
10739    let Some(tree) = parser.parse(declaration, None) else {
10740        return false;
10741    };
10742    let mut stack = vec![tree.root_node()];
10743    while let Some(node) = stack.pop() {
10744        match node.kind() {
10745            "type_definition" => {
10746                let mut cursor = node.walk();
10747                if node
10748                    .children_by_field_name("declarator", &mut cursor)
10749                    .any(|declarator| {
10750                        extract_typedef_declarator_name(declarator, declaration)
10751                            .is_some_and(|name| name == alias_name)
10752                            && declarator_names_function_type(declarator)
10753                    })
10754                {
10755                    return true;
10756                }
10757            }
10758            "alias_declaration" => {
10759                let names_alias = node
10760                    .child_by_field_name("name")
10761                    .is_some_and(|name| node_text(name, declaration) == alias_name);
10762                if names_alias
10763                    && node
10764                        .child_by_field_name("type")
10765                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
10766                        .is_some_and(declarator_names_function_type)
10767                {
10768                    return true;
10769                }
10770            }
10771            _ => {}
10772        }
10773        let mut cursor = node.walk();
10774        stack.extend(node.named_children(&mut cursor));
10775    }
10776    false
10777}
10778
10779fn decode_structured_alias_target(
10780    analyzer: &CppGraphSource<'_>,
10781    unit: &CodeUnit,
10782) -> Option<StructuredAliasTarget> {
10783    analyzer
10784        .get_source(unit, false)
10785        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
10786        .or_else(|| {
10787            let signature = unit.signature()?;
10788            decode_structured_alias_target_source(unit, signature, false)
10789        })
10790}
10791
10792fn decode_structured_alias_target_source(
10793    unit: &CodeUnit,
10794    declaration: &str,
10795    require_top_level: bool,
10796) -> Option<StructuredAliasTarget> {
10797    let mut parser = Parser::new();
10798    parser
10799        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10800        .ok()?;
10801    let tree = parser.parse(declaration, None)?;
10802    let mut stack = vec![tree.root_node()];
10803    while let Some(node) = stack.pop() {
10804        let type_node = match node.kind() {
10805            "type_definition" => {
10806                if require_top_level
10807                    && node
10808                        .parent()
10809                        .is_none_or(|parent| parent.kind() != "translation_unit")
10810                {
10811                    let mut cursor = node.walk();
10812                    stack.extend(node.named_children(&mut cursor));
10813                    continue;
10814                }
10815                let mut declarator_cursor = node.walk();
10816                let declarator = node
10817                    .children_by_field_name("declarator", &mut declarator_cursor)
10818                    .find(|declarator| {
10819                        extract_typedef_declarator_name(*declarator, declaration)
10820                            .is_some_and(|name| name == unit.identifier())
10821                    })?;
10822                if declarator_names_function_type(declarator) {
10823                    return None;
10824                }
10825                node.child_by_field_name("type")?
10826            }
10827            "alias_declaration" => {
10828                if require_top_level
10829                    && node
10830                        .parent()
10831                        .is_none_or(|parent| parent.kind() != "translation_unit")
10832                {
10833                    let mut cursor = node.walk();
10834                    stack.extend(node.named_children(&mut cursor));
10835                    continue;
10836                }
10837                let name = node.child_by_field_name("name")?;
10838                if node_text(name, declaration) != unit.identifier() {
10839                    return None;
10840                }
10841                let type_node = node.child_by_field_name("type")?;
10842                if type_node
10843                    .child_by_field_name("declarator")
10844                    .is_some_and(declarator_names_function_type)
10845                {
10846                    return None;
10847                }
10848                type_node
10849            }
10850            _ => {
10851                let mut cursor = node.walk();
10852                stack.extend(node.named_children(&mut cursor));
10853                continue;
10854            }
10855        };
10856        return structured_alias_type_target(type_node, declaration);
10857    }
10858    None
10859}
10860
10861fn structured_alias_type_target(
10862    mut type_node: Node<'_>,
10863    source: &str,
10864) -> Option<StructuredAliasTarget> {
10865    while type_node.kind() == "type_descriptor" {
10866        type_node = type_node.child_by_field_name("type")?;
10867    }
10868    if type_node.kind() == "primitive_type" {
10869        return Some(StructuredAliasTarget::Builtin);
10870    }
10871    if matches!(
10872        type_node.kind(),
10873        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10874    ) {
10875        type_node = type_node.child_by_field_name("name")?;
10876    }
10877    let global = type_node.child_by_field_name("scope").is_none()
10878        && type_node.child(0).is_some_and(|child| child.kind() == "::");
10879    let mut components = Vec::new();
10880    append_structured_type_components(type_node, source, &mut components)?;
10881    let arguments = cpp_template_reference_arguments(type_node, source);
10882    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
10883        components,
10884        global,
10885        arguments,
10886    })
10887}
10888
10889fn append_structured_type_components(
10890    node: Node<'_>,
10891    source: &str,
10892    out: &mut Vec<String>,
10893) -> Option<()> {
10894    match node.kind() {
10895        "identifier" | "namespace_identifier" | "type_identifier" => {
10896            out.push(node_text(node, source).to_string());
10897            Some(())
10898        }
10899        "template_type" => {
10900            append_structured_type_components(node.child_by_field_name("name")?, source, out)
10901        }
10902        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
10903            if let Some(scope) = node.child_by_field_name("scope") {
10904                append_structured_type_components(scope, source, out)?;
10905            }
10906            append_structured_type_components(node.child_by_field_name("name")?, source, out)
10907        }
10908        _ => None,
10909    }
10910}
10911
10912fn declared_name_indirection(
10913    declaration: Node<'_>,
10914    type_node: Node<'_>,
10915    field_name: &str,
10916    source: &str,
10917) -> Option<i32> {
10918    let mut stack = Vec::new();
10919    let mut cursor = declaration.walk();
10920    stack.extend(
10921        declaration
10922            .named_children(&mut cursor)
10923            .filter(|child| !same_node(*child, type_node)),
10924    );
10925    while let Some(node) = stack.pop() {
10926        if matches!(node.kind(), "identifier" | "field_identifier")
10927            && node_text(node, source) == field_name
10928        {
10929            let mut indirection = 0;
10930            let mut current = node.parent();
10931            while let Some(parent) = current {
10932                if same_node(parent, declaration) {
10933                    return Some(indirection);
10934                }
10935                if parent.kind() == "pointer_declarator" {
10936                    indirection += 1;
10937                }
10938                current = parent.parent();
10939            }
10940            return None;
10941        }
10942        let mut cursor = node.walk();
10943        stack.extend(node.named_children(&mut cursor));
10944    }
10945    None
10946}
10947
10948fn field_declaration_type_matches(
10949    declaration: &str,
10950    unit: &CodeUnit,
10951    ctx: &ScanCtx<'_>,
10952    owner: &CodeUnit,
10953) -> bool {
10954    ctx.visibility
10955        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
10956        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
10957            let normalized = normalize_field_type_text(type_text);
10958            ctx.visibility
10959                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
10960                || ctx.visibility.resolves_to_type(
10961                    &ctx.analyzer,
10962                    ctx.file,
10963                    normalized.as_str(),
10964                    owner,
10965                )
10966        })
10967}
10968
10969fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
10970    let declaration = declaration
10971        .split(['=', ';'])
10972        .next()
10973        .unwrap_or(declaration)
10974        .trim();
10975    let index = declaration.rfind(field_name)?;
10976    let before = &declaration[..index];
10977    let after = &declaration[index + field_name.len()..];
10978    if before.chars().next_back().is_some_and(is_identifier_char)
10979        || after.chars().next().is_some_and(is_identifier_char)
10980    {
10981        return None;
10982    }
10983    Some(before.trim())
10984}
10985
10986fn normalize_field_type_text(type_text: &str) -> String {
10987    const FIELD_SPECIFIERS: [&str; 8] = [
10988        "extern ",
10989        "static ",
10990        "mutable ",
10991        "constexpr ",
10992        "constinit ",
10993        "inline ",
10994        "volatile ",
10995        "const ",
10996    ];
10997
10998    let mut normalized = normalize_type_text(type_text);
10999    loop {
11000        let Some(stripped) = FIELD_SPECIFIERS
11001            .iter()
11002            .find_map(|specifier| normalized.strip_prefix(specifier))
11003        else {
11004            return normalized;
11005        };
11006        normalized = normalize_type_text(stripped);
11007    }
11008}
11009
11010fn is_identifier_char(ch: char) -> bool {
11011    ch == '_' || ch.is_ascii_alphanumeric()
11012}
11013
11014pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11015    let Some(type_node) = node.child_by_field_name("type") else {
11016        return false;
11017    };
11018    ctx.visibility.resolves_to_type(
11019        &ctx.analyzer,
11020        ctx.file,
11021        node_text(type_node, ctx.source),
11022        owner,
11023    )
11024}
11025
11026pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11027    !ctx.analyzer
11028        .declarations(ctx.file)
11029        .into_iter()
11030        .filter(|unit| unit.is_function())
11031        .any(|unit| {
11032            ctx.analyzer.ranges(&unit).iter().any(|range| {
11033                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
11034            })
11035        })
11036}
11037
11038pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
11039    let mut cursor = node.walk();
11040    for child in node.named_children(&mut cursor) {
11041        if child.kind() == "init_declarator" {
11042            return child
11043                .child_by_field_name("value")
11044                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
11045                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
11046                .map(declaration_init_value_arity)
11047                .unwrap_or(0);
11048        }
11049        if is_declarator_node(child) {
11050            return declaration_declarator_arity(child);
11051        }
11052    }
11053    0
11054}
11055
11056fn declaration_init_value_arity(value: Node<'_>) -> usize {
11057    match value.kind() {
11058        "argument_list" | "initializer_list" => argument_children(value).count(),
11059        "compound_literal_expression" => call_arity(value),
11060        _ => 1,
11061    }
11062}
11063
11064fn declaration_declarator_arity(node: Node<'_>) -> usize {
11065    if let Some(parameters) = node.child_by_field_name("parameters") {
11066        return argument_children(parameters).count();
11067    }
11068    node.child_by_field_name("declarator")
11069        .map(declaration_declarator_arity)
11070        .unwrap_or(0)
11071}
11072
11073pub(super) fn first_named_child_of_kind<'tree>(
11074    node: Node<'tree>,
11075    kind: &str,
11076) -> Option<Node<'tree>> {
11077    let mut cursor = node.walk();
11078    node.named_children(&mut cursor)
11079        .find(|child| child.kind() == kind)
11080}
11081
11082fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
11083    let mut stack = vec![root];
11084    while let Some(node) = stack.pop() {
11085        if node.kind() == kind {
11086            return Some(node);
11087        }
11088        for index in (0..node.named_child_count()).rev() {
11089            if let Some(child) = node.named_child(index) {
11090                stack.push(child);
11091            }
11092        }
11093    }
11094    None
11095}
11096
11097fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
11098    if node.kind() == "identifier" {
11099        return true;
11100    }
11101    if node.kind() == "parenthesized_expression" {
11102        return false;
11103    }
11104    if node.kind() == "call_expression" {
11105        return node
11106            .child_by_field_name("function")
11107            .is_some_and(|function| function.kind() == "identifier");
11108    }
11109    let mut stack = vec![node];
11110    while let Some(descendant) = stack.pop() {
11111        if descendant != node && descendant.kind() == "parenthesized_expression" {
11112            continue;
11113        }
11114        if descendant.kind() == "identifier" {
11115            return true;
11116        }
11117        if descendant.kind() == "call_expression" {
11118            if descendant
11119                .child_by_field_name("function")
11120                .is_some_and(|function| function.kind() == "identifier")
11121            {
11122                return true;
11123            }
11124            continue;
11125        }
11126        for index in (0..descendant.named_child_count()).rev() {
11127            if let Some(child) = descendant.named_child(index) {
11128                stack.push(child);
11129            }
11130        }
11131    }
11132    false
11133}
11134
11135fn macro_expansion_shape_is_safe(
11136    node: Node<'_>,
11137    source: &str,
11138    parameters: &[String],
11139    environment: &MacroEnvironment,
11140) -> bool {
11141    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
11142        return true;
11143    }
11144    if node.kind() == "call_expression" {
11145        let Some(function) = node.child_by_field_name("function") else {
11146            return true;
11147        };
11148        if function.kind() != "identifier" {
11149            return true;
11150        }
11151        let function_name = node_text(function, source);
11152        if parameters
11153            .iter()
11154            .any(|parameter| parameter == function_name)
11155        {
11156            return false;
11157        }
11158        if !environment.may_bind(function_name) {
11159            return true;
11160        }
11161        let Some(arguments) = node.child_by_field_name("arguments") else {
11162            return false;
11163        };
11164        return argument_children(arguments).all(|argument| {
11165            if argument.kind() == "identifier"
11166                && parameters
11167                    .iter()
11168                    .any(|parameter| parameter == node_text(argument, source))
11169            {
11170                return false;
11171            }
11172            macro_expansion_shape_is_safe(argument, source, parameters, environment)
11173        });
11174    }
11175    let mut stack = vec![node];
11176    while let Some(descendant) = stack.pop() {
11177        if descendant != node {
11178            if descendant.kind() == "parenthesized_expression" {
11179                continue;
11180            }
11181            if descendant.kind() == "call_expression" {
11182                let expands = descendant
11183                    .child_by_field_name("function")
11184                    .filter(|function| function.kind() == "identifier")
11185                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
11186                if expands {
11187                    return false;
11188                }
11189                continue;
11190            }
11191        }
11192        if descendant.kind() == "identifier" {
11193            let identifier = node_text(descendant, source);
11194            if parameters.iter().any(|parameter| parameter == identifier)
11195                || environment.may_bind(identifier)
11196            {
11197                return false;
11198            }
11199        }
11200        for index in (0..descendant.named_child_count()).rev() {
11201            if let Some(child) = descendant.named_child(index) {
11202                stack.push(child);
11203            }
11204        }
11205    }
11206    true
11207}
11208
11209fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
11210    let text = node_text(path, source);
11211    match path.kind() {
11212        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
11213        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
11214        _ => None,
11215    }
11216}
11217
11218fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
11219    let descendant = node;
11220    while let Some(parent) = node.parent() {
11221        if is_preprocessor_conditional(parent)
11222            && !is_file_covering_include_guard(parent, source)
11223            && preprocessor_conditional_contains_descendant(parent, descendant)
11224        {
11225            return true;
11226        }
11227        node = parent;
11228    }
11229    false
11230}
11231
11232fn is_preprocessor_conditional(node: Node<'_>) -> bool {
11233    matches!(
11234        node.kind(),
11235        "preproc_if"
11236            | "preproc_ifdef"
11237            | "preproc_ifndef"
11238            | "preproc_elif"
11239            | "preproc_elifdef"
11240            | "preproc_else"
11241    )
11242}
11243
11244fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
11245    node.parent()
11246        .filter(|parent| parent.kind() == "translation_unit")
11247        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
11248        && is_canonical_include_guard(node, source)
11249}
11250
11251fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
11252    if node.kind() != "preproc_ifdef"
11253        || node
11254            .child(0)
11255            .is_none_or(|directive| directive.kind() != "#ifndef")
11256        || node.child_by_field_name("alternative").is_some()
11257    {
11258        return false;
11259    }
11260    let Some(guard_name) = node.child_by_field_name("name") else {
11261        return false;
11262    };
11263    let mut cursor = node.walk();
11264    node.named_children(&mut cursor)
11265        .find(|child| *child != guard_name && child.kind() != "comment")
11266        .filter(|child| child.kind() == "preproc_def")
11267        .and_then(|definition| definition.child_by_field_name("name"))
11268        .is_some_and(|defined_name| {
11269            node_text(defined_name, source) == node_text(guard_name, source)
11270        })
11271}
11272
11273fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
11274    let mut guard = None;
11275    for index in 0..root.named_child_count() {
11276        let Some(child) = root.named_child(index) else {
11277            continue;
11278        };
11279        if child.kind() == "comment" || is_pragma_once(child, source) {
11280            continue;
11281        }
11282        if guard.is_none() && is_canonical_include_guard(child, source) {
11283            guard = Some(child);
11284        } else {
11285            return None;
11286        }
11287    }
11288    guard
11289        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
11290        .map(|name| node_text(name, source).to_string())
11291}
11292
11293fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
11294    if (0..root.named_child_count())
11295        .filter_map(|index| root.named_child(index))
11296        .any(|child| is_pragma_once(child, source))
11297    {
11298        return MacroIncludeProtection::PragmaOnce;
11299    }
11300    top_level_canonical_include_guard_name(root, source)
11301        .map(MacroIncludeProtection::MacroGuard)
11302        .unwrap_or(MacroIncludeProtection::None)
11303}
11304
11305fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
11306    node.kind() == "preproc_call"
11307        && node
11308            .child_by_field_name("directive")
11309            .is_some_and(|directive| node_text(directive, source) == "#pragma")
11310        && node
11311            .child_by_field_name("argument")
11312            .is_some_and(|argument| node_text(argument, source).trim() == "once")
11313}
11314
11315fn parse_preproc_identifier(argument: &str) -> Option<String> {
11316    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
11317    let mut parser = Parser::new();
11318    parser
11319        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11320        .ok()?;
11321    let tree = parser.parse(&sentinel, None)?;
11322    if tree.root_node().has_error() {
11323        return None;
11324    }
11325    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
11326    let identifier = statement.named_child(0)?;
11327    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
11328        .then(|| node_text(identifier, &sentinel).to_string())
11329}
11330
11331pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
11332    match node.kind() {
11333        "identifier" | "field_identifier" => {
11334            let name = node_text(node, source).trim();
11335            (!name.is_empty()).then(|| name.to_string())
11336        }
11337        "abstract_array_declarator"
11338        | "abstract_function_declarator"
11339        | "abstract_parenthesized_declarator"
11340        | "abstract_pointer_declarator"
11341        | "abstract_reference_declarator" => None,
11342        "function_declarator" => node
11343            .child_by_field_name("declarator")
11344            .or_else(|| node.child_by_field_name("name"))
11345            .and_then(|child| extract_variable_name(child, source)),
11346        _ => node
11347            .child_by_field_name("declarator")
11348            .or_else(|| node.child_by_field_name("name"))
11349            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
11350            .and_then(|child| extract_variable_name(child, source)),
11351    }
11352}
11353
11354/// Whether `file` is proven to use plain-C source semantics.
11355///
11356/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
11357/// compilation dialect on their own, so only an exact `.c` source extension is
11358/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
11359/// identifiers.
11360///
11361/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
11362/// which extraction reads too (a `.c` file is extracted with C tag scope), so
11363/// the doctrine has exactly one definition.
11364pub fn is_c_source_file(file: &ProjectFile) -> bool {
11365    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
11366}
11367
11368/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
11369/// identifier even though `T` may denote a typedef.
11370///
11371/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
11372/// semantic information. Keep this helper structural and narrow; callers must
11373/// still prove a visible type and reject an active ordinary-namespace shadow.
11374pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
11375    if !is_c_source_file(file) || node.kind() != "identifier" {
11376        return false;
11377    }
11378    let mut operand = node;
11379    while let Some(parent) = operand.parent().filter(|parent| {
11380        parent.kind() == "parenthesized_expression"
11381            && parent.named_child_count() == 1
11382            && parent.named_child(0) == Some(operand)
11383    }) {
11384        operand = parent;
11385    }
11386    operand.parent().is_some_and(|parent| {
11387        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
11388    })
11389}
11390
11391/// Whether a reference written in `file` reads C++ source with C semantics.
11392///
11393/// [`is_c_source_file`] answers the half a path settles on its own. The other
11394/// half is a header, which has no dialect of its own: it is read as C exactly
11395/// when every workspace translation unit that provably compiles it compiles it
11396/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
11397///
11398/// This is the gate for anything that is really about the compilation
11399/// language of the code being read -- which reading of an included header's
11400/// declarations is in scope, whether `this` is an ordinary identifier. It is
11401/// NOT the gate for a question that is genuinely about a `.c` file on disk;
11402/// those keep calling [`is_c_source_file`].
11403pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
11404    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
11405}
11406
11407pub fn is_declarator_node(node: Node<'_>) -> bool {
11408    matches!(
11409        node.kind(),
11410        "identifier"
11411            | "field_identifier"
11412            | "pointer_declarator"
11413            | "reference_declarator"
11414            | "array_declarator"
11415            | "parenthesized_declarator"
11416            | "function_declarator"
11417    )
11418}
11419
11420#[derive(Clone, Default)]
11421pub struct OrphanedNamespaceTypeScopeIndex {
11422    scopes: Vec<OrphanedNamespaceTypeScope>,
11423}
11424
11425#[derive(Clone)]
11426struct OrphanedNamespaceTypeScope {
11427    body_end: usize,
11428    scope_end: usize,
11429    components: Vec<String>,
11430}
11431
11432impl OrphanedNamespaceTypeScopeIndex {
11433    /// Index the physical namespace interval that remains after tree-sitter
11434    /// prematurely closes an error-marked namespace at a recovered class body.
11435    /// The later unmatched `}` is the structured upper bound. A nested damaged
11436    /// namespace can lose that token to its still-open enclosing namespace; in
11437    /// that shape the enclosing namespace body's end is the tighter surviving
11438    /// bound. Declarations after either bound do not enter the recovered scope.
11439    pub fn build(root: Node<'_>, source: &str) -> Self {
11440        let mut scopes = Vec::new();
11441        let mut stack = vec![root];
11442        while let Some(current) = stack.pop() {
11443            if current.kind() == "namespace_definition"
11444                && current.has_error()
11445                && let Some(body) = current.child_by_field_name("body")
11446                && current.end_byte() == body.end_byte()
11447                && let Some(name) = current.child_by_field_name("name")
11448            {
11449                let mut components =
11450                    enclosing_namespace_components(current, source).unwrap_or_default();
11451                if append_cpp_name_components(name, source, &mut components).is_some()
11452                    && !components.is_empty()
11453                {
11454                    let mut scope_end = None;
11455                    let mut following = current.next_named_sibling();
11456                    while let Some(candidate) = following {
11457                        if direct_unmatched_closing_brace(candidate)
11458                            && !unmatched_closing_brace_is_followed_by_semicolon(candidate)
11459                        {
11460                            scope_end = Some(candidate.start_byte());
11461                            break;
11462                        }
11463                        following = candidate.next_named_sibling();
11464                    }
11465                    let scope_end = scope_end.or_else(|| {
11466                        std::iter::successors(current.parent(), |ancestor| ancestor.parent())
11467                            .filter(|ancestor| ancestor.kind() == "namespace_definition")
11468                            .filter_map(|ancestor| ancestor.child_by_field_name("body"))
11469                            .map(|body| body.end_byte())
11470                            .find(|end| *end > body.end_byte())
11471                    });
11472                    if let Some(scope_end) = scope_end {
11473                        scopes.push(OrphanedNamespaceTypeScope {
11474                            body_end: body.end_byte(),
11475                            scope_end,
11476                            components,
11477                        });
11478                    }
11479                }
11480            }
11481            if !current.has_error() {
11482                continue;
11483            }
11484            let mut cursor = current.walk();
11485            stack.extend(
11486                current
11487                    .named_children(&mut cursor)
11488                    .filter(|child| child.has_error()),
11489            );
11490        }
11491        Self { scopes }
11492    }
11493
11494    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
11495        self.scopes
11496            .iter()
11497            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
11498            .max_by_key(|scope| (scope.components.len(), scope.body_end))
11499            .map(|scope| (scope.body_end, scope.components.as_slice()))
11500    }
11501}
11502
11503#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11504pub enum RecoveredDeclaratorTypeContext {
11505    Declaration,
11506    FunctionDefinition,
11507    Parameter,
11508}
11509
11510/// Recognize a real type displaced into a qualified declarator by parser
11511/// recovery.
11512///
11513/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
11514/// type and `Result` were the scope of a qualified declarator with a missing
11515/// `::`. A template return such as `API Result<T> make()` uses a
11516/// `template_type` for the same recovered scope. The same recovery occurs for
11517/// macro-prefixed definitions, extern variables, and macro-decorated
11518/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
11519/// the macro). Keep this intentionally structural: the recovered scope must
11520/// have the grammar's missing separator, the qualified node must occupy the
11521/// declaration's declarator chain, a separate nonempty type must occupy the
11522/// normal type field, and the recovered name must unwrap to a real declarator
11523/// name.
11524pub fn recovered_macro_decorated_declarator_type(
11525    node: Node<'_>,
11526) -> Option<RecoveredDeclaratorTypeContext> {
11527    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
11528}
11529
11530/// Return the declaration/function `type` displaced by a macro-shaped
11531/// qualified declarator, together with the enclosing declaration context.
11532/// Callers use the macro scope only as structural admission evidence; the
11533/// returned node is the real type reference to resolve and record.
11534pub fn recovered_macro_decorated_type_node(
11535    node: Node<'_>,
11536) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11537    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
11538        return None;
11539    }
11540    let qualified = node.parent()?;
11541    if qualified.kind() != "qualified_identifier"
11542        || qualified.child_by_field_name("scope") != Some(node)
11543        || !(0..qualified.child_count())
11544            .filter_map(|index| qualified.child(index))
11545            .any(|child| child.kind() == "::" && child.is_missing())
11546    {
11547        return None;
11548    }
11549    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
11550        return None;
11551    }
11552
11553    let (declaration, context) = recovered_declarator_container(qualified)?;
11554    let type_node = declaration
11555        .child_by_field_name("type")
11556        .filter(|type_node| {
11557            *type_node != qualified
11558                && !type_node.is_missing()
11559                && type_node.start_byte() != type_node.end_byte()
11560        })?;
11561    Some((type_node, context))
11562}
11563
11564fn recovered_declarator_container(
11565    mut declarator: Node<'_>,
11566) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11567    loop {
11568        let parent = declarator.parent()?;
11569        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
11570            return Some((
11571                parent
11572                    .parent()
11573                    .filter(|declaration| declaration.kind() == "declaration")?,
11574                RecoveredDeclaratorTypeContext::Declaration,
11575            ));
11576        }
11577        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
11578            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
11579        }
11580        if parent.kind() == "function_definition"
11581            && has_field_child(parent, "declarator", declarator)
11582        {
11583            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
11584        }
11585        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
11586        // level down: the parameter's `type` field takes the macro token and
11587        // the real type `T` becomes the recovered scope of the declarator.
11588        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
11589        // candidate at all (#1830).
11590        if matches!(
11591            parent.kind(),
11592            "parameter_declaration" | "optional_parameter_declaration"
11593        ) && has_field_child(parent, "declarator", declarator)
11594        {
11595            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
11596        }
11597        if !matches!(
11598            parent.kind(),
11599            "array_declarator"
11600                | "function_declarator"
11601                | "parenthesized_declarator"
11602                | "pointer_declarator"
11603                | "pointer_type_declarator"
11604                | "reference_declarator"
11605        ) || !has_field_child(parent, "declarator", declarator)
11606        {
11607            return None;
11608        }
11609        declarator = parent;
11610    }
11611}
11612
11613fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
11614    let mut cursor = parent.walk();
11615    parent
11616        .children_by_field_name(field, &mut cursor)
11617        .any(|child| child == target)
11618}
11619
11620fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
11621    loop {
11622        if node.is_missing() || node.start_byte() == node.end_byte() {
11623            return false;
11624        }
11625        match node.kind() {
11626            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
11627                return true;
11628            }
11629            "array_declarator"
11630            | "function_declarator"
11631            | "parenthesized_declarator"
11632            | "pointer_declarator"
11633            | "pointer_type_declarator"
11634            | "reference_declarator" => {
11635                let Some(declarator) = node.child_by_field_name("declarator") else {
11636                    return false;
11637                };
11638                node = declarator;
11639            }
11640            _ => return false,
11641        }
11642    }
11643}
11644
11645/// Aggregate-owner proof for a structurally recognized designated initializer.
11646pub enum DesignatedInitializerOwner {
11647    Resolved(CodeUnit),
11648    Unresolved,
11649}
11650
11651/// Recognize a designated-initializer field and, when possible, resolve its
11652/// aggregate owner.
11653///
11654/// Covers both the grammar's ordinary `field_designator` shape and the exact
11655/// recovery used for `.field = value` after a preprocessor-split array
11656/// initializer. Nested aggregate levels are deliberately left unresolved unless
11657/// the single outer level is the containing array initializer: resolving those
11658/// would require following the enclosing field's declared type. `None` means the
11659/// node is not a designator at all; an unresolved designator remains classified so
11660/// callers cannot fall through to unrelated global/member heuristics.
11661pub fn designated_initializer_owner(
11662    visibility: &VisibilityIndex<'_>,
11663    file: &ProjectFile,
11664    source: &str,
11665    node: Node<'_>,
11666) -> Option<DesignatedInitializerOwner> {
11667    if let Some(designator) = node
11668        .parent()
11669        .filter(|parent| parent.kind() == "field_designator")
11670    {
11671        let pair = designator.parent()?;
11672        if pair.kind() != "initializer_pair"
11673            || pair.child_by_field_name("designator") != Some(designator)
11674        {
11675            return None;
11676        }
11677        let initializer = pair.parent()?;
11678        if initializer.kind() != "initializer_list" {
11679            return None;
11680        }
11681        return Some(classified_designated_owner(initializer_list_owner(
11682            visibility,
11683            file,
11684            source,
11685            initializer,
11686        )));
11687    }
11688
11689    let init_declarator = node.parent()?;
11690    if init_declarator.child_by_field_name("declarator") != Some(node)
11691        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
11692    {
11693        return None;
11694    }
11695    Some(classified_designated_owner(declaration_owner(
11696        visibility,
11697        file,
11698        source,
11699        init_declarator.parent()?,
11700    )))
11701}
11702
11703fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
11704    owner.map_or(
11705        DesignatedInitializerOwner::Unresolved,
11706        DesignatedInitializerOwner::Resolved,
11707    )
11708}
11709
11710fn initializer_list_owner(
11711    visibility: &VisibilityIndex<'_>,
11712    file: &ProjectFile,
11713    source: &str,
11714    initializer: Node<'_>,
11715) -> Option<CodeUnit> {
11716    let mut current = initializer;
11717    let mut outer_initializer_lists = 0usize;
11718    loop {
11719        let parent = current.parent()?;
11720        match parent.kind() {
11721            "initializer_pair" => return None,
11722            "initializer_list" => {
11723                outer_initializer_lists += 1;
11724                if outer_initializer_lists > 1 {
11725                    return None;
11726                }
11727                current = parent;
11728            }
11729            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
11730                let declaration = parent.parent()?;
11731                if outer_initializer_lists == 1
11732                    && !parent
11733                        .child_by_field_name("declarator")
11734                        .is_some_and(contains_array_declarator)
11735                {
11736                    return None;
11737                }
11738                return declaration_owner(visibility, file, source, declaration);
11739            }
11740            "compound_literal_expression"
11741                if parent.child_by_field_name("value") == Some(current)
11742                    && outer_initializer_lists == 0 =>
11743            {
11744                let type_node = parent.child_by_field_name("type")?;
11745                return resolve_designated_owner_type(visibility, file, source, type_node);
11746            }
11747            "ERROR" => current = parent,
11748            _ => return None,
11749        }
11750    }
11751}
11752
11753fn declaration_owner(
11754    visibility: &VisibilityIndex<'_>,
11755    file: &ProjectFile,
11756    source: &str,
11757    declaration: Node<'_>,
11758) -> Option<CodeUnit> {
11759    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
11760        return None;
11761    }
11762    let type_node = declaration
11763        .child_by_field_name("type")
11764        .or_else(|| first_type_child(declaration))?;
11765    resolve_designated_owner_type(visibility, file, source, type_node)
11766}
11767
11768fn resolve_designated_owner_type(
11769    visibility: &VisibilityIndex<'_>,
11770    file: &ProjectFile,
11771    source: &str,
11772    type_node: Node<'_>,
11773) -> Option<CodeUnit> {
11774    let type_name = normalize_type_text(node_text(type_node, source));
11775    visibility
11776        .resolve_type(file, &type_name)
11777        .filter(CodeUnit::is_class)
11778}
11779
11780fn contains_array_declarator(declarator: Node<'_>) -> bool {
11781    let mut stack = vec![declarator];
11782    while let Some(node) = stack.pop() {
11783        if node.kind() == "array_declarator" {
11784            return true;
11785        }
11786        if matches!(node.kind(), "initializer_list" | "compound_statement") {
11787            continue;
11788        }
11789        let mut cursor = node.walk();
11790        stack.extend(node.named_children(&mut cursor));
11791    }
11792    false
11793}
11794
11795pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
11796    let mut cursor = node.walk();
11797    node.named_children(&mut cursor).find(|child| {
11798        matches!(
11799            child.kind(),
11800            "type_identifier"
11801                | "primitive_type"
11802                | "qualified_identifier"
11803                | "scoped_type_identifier"
11804                | "struct_specifier"
11805                | "union_specifier"
11806                | "enum_specifier"
11807        )
11808    })
11809}
11810
11811pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
11812    visibility: &VisibilityIndex<'_>,
11813    file: &ProjectFile,
11814    source: &str,
11815    declarator: Node<'_>,
11816    type_text: Option<&str>,
11817    bindings: &LocalInferenceEngine<T>,
11818) -> bool {
11819    if !has_ancestor_kind(declarator, "compound_statement") {
11820        return false;
11821    }
11822    if declarator
11823        .child_by_field_name("declarator")
11824        .is_none_or(|declarator| declarator.kind() != "identifier")
11825    {
11826        return false;
11827    }
11828    if !type_text
11829        .and_then(|text| visibility.resolve_type(file, text))
11830        .is_some_and(|unit| unit.is_class())
11831    {
11832        return false;
11833    }
11834    declarator
11835        .child_by_field_name("parameters")
11836        .is_some_and(|parameters| {
11837            constructor_parameters_look_like_expressions(parameters, source, bindings)
11838        })
11839}
11840
11841fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
11842    parameters: Node<'_>,
11843    source: &str,
11844    bindings: &LocalInferenceEngine<T>,
11845) -> bool {
11846    let mut cursor = parameters.walk();
11847    parameters.named_children(&mut cursor).any(|parameter| {
11848        !matches!(
11849            parameter.kind(),
11850            "parameter_declaration" | "optional_parameter_declaration"
11851        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
11852    })
11853}
11854
11855fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
11856    parameter: Node<'_>,
11857    source: &str,
11858    bindings: &LocalInferenceEngine<T>,
11859) -> bool {
11860    let text = node_text(parameter, source).trim();
11861    if text
11862        .chars()
11863        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
11864        && bindings.is_shadowed(text)
11865    {
11866        return true;
11867    }
11868
11869    let Some(base) = parameter
11870        .child_by_field_name("type")
11871        .filter(|base| base.kind() == "type_identifier")
11872    else {
11873        return false;
11874    };
11875    let Some(subscript) = parameter
11876        .child_by_field_name("declarator")
11877        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
11878    else {
11879        return false;
11880    };
11881    subscript.child_by_field_name("size").is_some()
11882        && bindings.is_shadowed(node_text(base, source).trim())
11883}
11884
11885pub fn is_declaration_name(node: Node<'_>) -> bool {
11886    let Some(parent) = node.parent() else {
11887        return false;
11888    };
11889    if parent
11890        .child_by_field_name("name")
11891        .is_some_and(|name| same_node(name, node))
11892    {
11893        if matches!(
11894            parent.kind(),
11895            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11896        ) {
11897            return cpp_tag_specifier_declares_name(parent);
11898        }
11899        if matches!(
11900            parent.kind(),
11901            "namespace_definition"
11902                | "namespace_alias_definition"
11903                | "alias_declaration"
11904                | "enumerator"
11905        ) {
11906            return true;
11907        }
11908    }
11909
11910    let mut current = Some(parent);
11911    while let Some(ancestor) = current {
11912        let type_definition = ancestor.kind() == "type_definition";
11913        let mut declarator_cursor = ancestor.walk();
11914        if ancestor
11915            .children_by_field_name("declarator", &mut declarator_cursor)
11916            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
11917        {
11918            return true;
11919        }
11920        if matches!(
11921            ancestor.kind(),
11922            "declaration"
11923                | "field_declaration"
11924                | "parameter_declaration"
11925                | "optional_parameter_declaration"
11926                | "function_definition"
11927                | "type_definition"
11928                | "alias_declaration"
11929                | "class_specifier"
11930                | "struct_specifier"
11931                | "union_specifier"
11932                | "enum_specifier"
11933        ) {
11934            return false;
11935        }
11936        current = ancestor.parent();
11937    }
11938    false
11939}
11940
11941/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
11942/// declaration's declarator inside a malformed class body.
11943///
11944/// An export macro between `class` and the class name can make the containing
11945/// body parse as a function body. A source declaration such as
11946/// `friend class internal::Friend;` then retains this exact structure:
11947/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
11948/// The declarator is a type reference despite its field role.
11949pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
11950    if !matches!(
11951        node.kind(),
11952        "qualified_identifier" | "scoped_type_identifier"
11953    ) {
11954        return false;
11955    }
11956    let Some(declaration) = node
11957        .parent()
11958        .filter(|parent| parent.kind() == "declaration")
11959    else {
11960        return false;
11961    };
11962    if declaration.child_by_field_name("declarator") != Some(node)
11963        || !declaration
11964            .child_by_field_name("type")
11965            .is_some_and(|friend| {
11966                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
11967            })
11968    {
11969        return false;
11970    }
11971    let mut cursor = declaration.walk();
11972    let mut errors = declaration
11973        .named_children(&mut cursor)
11974        .filter(|child| child.kind() == "ERROR");
11975    let Some(error) = errors.next() else {
11976        return false;
11977    };
11978    errors.next().is_none()
11979        && error.named_child_count() == 1
11980        && error.named_child(0).is_some_and(|class| {
11981            class.kind() == "identifier" && node_text(class, source) == "class"
11982        })
11983}
11984
11985pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
11986    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
11987        return false;
11988    }
11989    if let Some(parent) = node.parent() {
11990        if parent.kind() == "call_expression"
11991            && parent.child_by_field_name("function") == Some(node)
11992        {
11993            return false;
11994        }
11995        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
11996            && parent.child_by_field_name("label") == Some(node)
11997        {
11998            return false;
11999        }
12000    }
12001    let mut current = node.parent();
12002    while let Some(ancestor) = current {
12003        match ancestor.kind() {
12004            "preproc_ifdef" | "preproc_ifndef" => {
12005                if ancestor
12006                    .child_by_field_name("name")
12007                    .is_some_and(|name| node_range_contains(name, node))
12008                {
12009                    return false;
12010                }
12011            }
12012            "preproc_if" | "preproc_elif" => {
12013                if ancestor
12014                    .child_by_field_name("condition")
12015                    .is_some_and(|condition| node_range_contains(condition, node))
12016                {
12017                    return false;
12018                }
12019            }
12020            "preproc_else" => {}
12021            kind if kind.starts_with("preproc_") => return false,
12022            _ => {}
12023        }
12024        if matches!(
12025            ancestor.kind(),
12026            "translation_unit" | "function_definition" | "compound_statement"
12027        ) {
12028            break;
12029        }
12030        current = ancestor.parent();
12031    }
12032    true
12033}
12034
12035fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
12036    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
12037}
12038
12039fn recovered_c_reference_node(
12040    visibility: &VisibilityIndex<'_>,
12041    file: &ProjectFile,
12042    node: Node<'_>,
12043    source: &str,
12044) -> bool {
12045    if node.start_byte() >= node.end_byte()
12046        || node.is_error()
12047        || node.is_missing()
12048        || !matches!(
12049            node.kind(),
12050            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
12051        )
12052        || recovered_c_macro_binding_role(node)
12053        || recovered_c_label_role(node)
12054    {
12055        return false;
12056    }
12057
12058    let name = node_text(node, source);
12059    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
12060        return true;
12061    }
12062    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
12063        return true;
12064    }
12065    if is_declaration_name(node) {
12066        return false;
12067    }
12068    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
12069        return true;
12070    }
12071    recovered_c_reference_anchor(node)
12072}
12073
12074fn recovered_c_explicit_assignment_callee(
12075    visibility: &VisibilityIndex<'_>,
12076    file: &ProjectFile,
12077    node: Node<'_>,
12078    name: &str,
12079) -> bool {
12080    let mut current = node;
12081    let error = loop {
12082        let Some(parent) = current.parent() else {
12083            return false;
12084        };
12085        if parent.is_error() {
12086            break parent;
12087        }
12088        current = parent;
12089    };
12090    let mut cursor = error.walk();
12091    let explicit_recovery_precedes_callee = error
12092        .named_children(&mut cursor)
12093        .take_while(|child| child.start_byte() < node.start_byte())
12094        .any(|child| child.kind() == "explicit_function_specifier");
12095    if !explicit_recovery_precedes_callee {
12096        return false;
12097    }
12098    visibility
12099        .cpp
12100        .declarations(file)
12101        .iter()
12102        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
12103        .any(|candidate| candidate.identifier() == name && candidate.is_function())
12104}
12105
12106fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
12107    while let Some(parent) = node.parent() {
12108        if matches!(
12109            parent.kind(),
12110            "preproc_def" | "preproc_function_def" | "preproc_params"
12111        ) {
12112            return true;
12113        }
12114        if parent.is_error()
12115            || matches!(
12116                parent.kind(),
12117                "translation_unit" | "function_definition" | "compound_statement"
12118            )
12119        {
12120            return false;
12121        }
12122        node = parent;
12123    }
12124    false
12125}
12126
12127fn recovered_c_label_role(node: Node<'_>) -> bool {
12128    node.parent().is_some_and(|parent| {
12129        matches!(parent.kind(), "labeled_statement" | "goto_statement")
12130            && parent.child_by_field_name("label") == Some(node)
12131    })
12132}
12133
12134fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
12135    while let Some(parent) = node.parent() {
12136        if parent.is_error() {
12137            return false;
12138        }
12139        if parent.kind().ends_with("_expression")
12140            || matches!(
12141                parent.kind(),
12142                "argument_list"
12143                    | "return_statement"
12144                    | "expression_statement"
12145                    | "case_statement"
12146                    | "initializer_list"
12147                    | "init_declarator"
12148                    | "array_declarator"
12149                    | "field_designator"
12150                    | "enumerator"
12151            )
12152        {
12153            return true;
12154        }
12155        if matches!(
12156            parent.kind(),
12157            "translation_unit"
12158                | "function_definition"
12159                | "compound_statement"
12160                | "declaration"
12161                | "field_declaration"
12162                | "parameter_declaration"
12163        ) {
12164            return false;
12165        }
12166        node = parent;
12167    }
12168    false
12169}
12170
12171/// Whether a parameter declaration belongs to the callable scope whose body can
12172/// contain references to it.
12173///
12174/// Error recovery can wrap a macro-decorated class body in a synthetic outer
12175/// `function_definition`. Merely finding any callable ancestor would then leak
12176/// parameters from member prototypes into later member bodies. Require the
12177/// parameter to be inside that definition's own declarator instead.
12178pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
12179    let mut current = parameter.parent();
12180    while let Some(ancestor) = current {
12181        if ancestor.kind() == "lambda_expression" {
12182            return ancestor
12183                .child_by_field_name("declarator")
12184                .is_some_and(|declarator| {
12185                    declarator.start_byte() <= parameter.start_byte()
12186                        && parameter.end_byte() <= declarator.end_byte()
12187                });
12188        }
12189        if ancestor.kind() == "function_definition" {
12190            return ancestor
12191                .child_by_field_name("declarator")
12192                .is_some_and(|declarator| {
12193                    declarator.start_byte() <= parameter.start_byte()
12194                        && parameter.end_byte() <= declarator.end_byte()
12195                });
12196        }
12197        current = ancestor.parent();
12198    }
12199    false
12200}
12201
12202pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
12203    let mut current = node.parent();
12204    while let Some(ancestor) = current {
12205        if matches!(
12206            ancestor.kind(),
12207            "parameter_declaration" | "optional_parameter_declaration"
12208        ) {
12209            return ancestor
12210                .child_by_field_name("type")
12211                .is_some_and(|type_node| {
12212                    type_node.start_byte() <= node.start_byte()
12213                        && node.end_byte() <= type_node.end_byte()
12214                });
12215        }
12216        if matches!(
12217            ancestor.kind(),
12218            "function_definition" | "lambda_expression" | "compound_statement"
12219        ) {
12220            return false;
12221        }
12222        current = ancestor.parent();
12223    }
12224    false
12225}
12226
12227fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
12228    if specifier.child_by_field_name("body").is_some() {
12229        return true;
12230    }
12231    let mut current = specifier.parent();
12232    while let Some(ancestor) = current {
12233        match ancestor.kind() {
12234            "type_descriptor"
12235            | "parameter_declaration"
12236            | "optional_parameter_declaration"
12237            | "template_argument_list"
12238            | "cast_expression" => return false,
12239            "declaration" | "field_declaration" => {
12240                let mut cursor = ancestor.walk();
12241                return ancestor
12242                    .children_by_field_name("declarator", &mut cursor)
12243                    .next()
12244                    .is_none();
12245            }
12246            "translation_unit" => return true,
12247            _ => current = ancestor.parent(),
12248        }
12249    }
12250    false
12251}
12252
12253pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
12254    match node.kind() {
12255        "identifier"
12256        | "field_identifier"
12257        | "qualified_identifier"
12258        | "scoped_identifier"
12259        | "operator_name"
12260        | "destructor_name"
12261        | "literal_operator_name" => Some(node),
12262        "reference_declarator" | "parenthesized_declarator" => {
12263            node.named_child(0).and_then(declarator_name_node)
12264        }
12265        _ => node
12266            .child_by_field_name("declarator")
12267            .or_else(|| node.child_by_field_name("name"))
12268            .or_else(|| node.child_by_field_name("field"))
12269            .and_then(declarator_name_node),
12270    }
12271}
12272
12273fn declarator_name_path_contains(
12274    declarator: Node<'_>,
12275    candidate: Node<'_>,
12276    allow_type_identifier: bool,
12277) -> bool {
12278    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
12279        return false;
12280    };
12281    let mut current = Some(declarator);
12282    while let Some(node) = current {
12283        if same_node(node, candidate) {
12284            return true;
12285        }
12286        if same_node(node, name) {
12287            return false;
12288        }
12289        current = node
12290            .child_by_field_name("declarator")
12291            .or_else(|| node.child_by_field_name("name"))
12292            .or_else(|| node.child_by_field_name("field"));
12293    }
12294    false
12295}
12296
12297fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
12298    match node.kind() {
12299        "identifier"
12300        | "field_identifier"
12301        | "operator_name"
12302        | "destructor_name"
12303        | "literal_operator_name" => Some(node),
12304        "type_identifier" if allow_type_identifier => Some(node),
12305        _ => node
12306            .child_by_field_name("declarator")
12307            .or_else(|| node.child_by_field_name("name"))
12308            .or_else(|| node.child_by_field_name("field"))
12309            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
12310    }
12311}
12312
12313/// True when `node` is a component of a larger structured type node whose outer
12314/// range is the single reference surfaced to callers.
12315pub fn is_nested_type_node(node: Node<'_>) -> bool {
12316    node.parent().is_some_and(|parent| {
12317        matches!(
12318            parent.kind(),
12319            "qualified_identifier" | "scoped_type_identifier" | "template_type"
12320        )
12321    })
12322}
12323
12324pub struct OutOfLineMemberDefinitionOwners<'tree> {
12325    pub owners: Vec<(Node<'tree>, CodeUnit)>,
12326    innermost: Option<(Node<'tree>, CodeUnit)>,
12327}
12328
12329impl OutOfLineMemberDefinitionOwners<'_> {
12330    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
12331        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
12332    }
12333}
12334
12335pub struct QualifiedOwnerComponents<'tree> {
12336    pub nodes: Vec<Node<'tree>>,
12337    pub names: Vec<String>,
12338    pub global: bool,
12339}
12340
12341/// True when each structured qualifier on the callable-name path has a real
12342/// `::` token. A macro-prefixed return type can make tree-sitter insert a
12343/// zero-width missing separator and parse `TYPE Result<T> method()` as the
12344/// false qualified declarator `Result<T>::method`.
12345pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
12346    let mut stack = vec![node];
12347    let mut found_separator = false;
12348    while let Some(current) = stack.pop() {
12349        if !matches!(
12350            current.kind(),
12351            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12352        ) {
12353            continue;
12354        }
12355        let mut current_has_separator = false;
12356        for index in 0..current.child_count() {
12357            let Some(child) = current.child(index) else {
12358                continue;
12359            };
12360            if child.kind() == "::" {
12361                if child.is_missing() {
12362                    return false;
12363                }
12364                current_has_separator = true;
12365                found_separator = true;
12366            }
12367        }
12368        if !current_has_separator {
12369            return false;
12370        }
12371        for field in ["scope", "name"] {
12372            if let Some(child) = current.child_by_field_name(field)
12373                && matches!(
12374                    child.kind(),
12375                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12376                )
12377            {
12378                stack.push(child);
12379            }
12380        }
12381    }
12382    found_separator
12383}
12384
12385pub fn qualified_owner_components<'tree>(
12386    node: Node<'tree>,
12387    source: &str,
12388) -> Option<QualifiedOwnerComponents<'tree>> {
12389    if !qualified_name_has_concrete_scope_separators(node) {
12390        return None;
12391    }
12392    let mut nodes = cpp_name_component_nodes(node)?;
12393    nodes.pop()?;
12394    if nodes.is_empty() {
12395        return None;
12396    }
12397    let names = nodes
12398        .iter()
12399        .map(|component| node_text(*component, source).to_string())
12400        .collect();
12401    Some(QualifiedOwnerComponents {
12402        nodes,
12403        names,
12404        global: is_globally_qualified_cpp_name(node),
12405    })
12406}
12407
12408pub fn out_of_line_member_definition_owner<'tree>(
12409    analyzer: &CppGraphSource<'_>,
12410    visibility: &VisibilityIndex<'_>,
12411    file: &ProjectFile,
12412    source: &str,
12413    node: Node<'tree>,
12414) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
12415    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
12416        || !has_ancestor_kind(node, "function_definition")
12417        || !is_function_declarator_name_root(node)
12418    {
12419        return None;
12420    }
12421    let qualified = qualified_owner_components(node, source)?;
12422    let lexical_scope = enclosing_namespace_components(node, source)?;
12423    let mut owners = Vec::new();
12424    let mut innermost = None;
12425
12426    for component_count in 1..=qualified.names.len() {
12427        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
12428            .resolve_type_components_lexically(
12429                analyzer,
12430                file,
12431                &qualified.names[..component_count],
12432                qualified.global,
12433                &lexical_scope,
12434            )
12435            && !owners
12436                .iter()
12437                .any(|(_, existing)| same_visible_symbol(existing, &unit))
12438        {
12439            if component_count == qualified.names.len() {
12440                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
12441            }
12442            owners.push((qualified.nodes[component_count - 1], unit));
12443        }
12444    }
12445
12446    // The C++ analyzer has already reconciled an indexed out-of-line callable
12447    // against the include-visible class table. Consult that canonical owner
12448    // chain only when ordinary lexical lookup could not recover the innermost
12449    // owner.  A one-segment qualifier is safe here only when the enclosing
12450    // indexed callable has an authoritative class owner and the parser's
12451    // namespace path is a (possibly sparse) subsequence of that owner path.
12452    // The latter is what lets macro-wrapped namespace sentinels recover a
12453    // missing `time_internal`/`cord_internal` component without guessing an
12454    // unrelated short name.
12455    if innermost.is_none() {
12456        let indexed_owner_components = visibility
12457            .indexed_enclosing_owner_scope(analyzer, file, node)
12458            .or_else(|| {
12459                // Retain the legacy rendered-name fallback for the existing
12460                // multi-segment path when an enclosing owner chain is not
12461                // available (for example, cache-loaded units without parent
12462                // links).  One-segment recovery must stay canonical-only.
12463                if qualified.names.len() <= 1 {
12464                    return None;
12465                }
12466                let range = Range {
12467                    start_byte: node.start_byte(),
12468                    end_byte: node.end_byte(),
12469                    start_line: node.start_position().row,
12470                    end_line: node.end_position().row,
12471                };
12472                let start = analyzer.enclosing_code_unit(file, &range)?;
12473                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12474                    brokk_bifrost_core::analyzer::Language::Cpp,
12475                    &cpp_name_for(&start),
12476                );
12477                components.pop();
12478                Some(components)
12479            });
12480        if let Some(indexed_owner_components) = indexed_owner_components
12481            && indexed_owner_components.len() > qualified.names.len()
12482            && indexed_owner_components.ends_with(&qualified.names)
12483            && indexed_namespace_path_is_recoverable(
12484                &lexical_scope,
12485                &indexed_owner_components,
12486                qualified.names.len(),
12487            )
12488            // A globally-qualified one-segment owner is an explicit request
12489            // for the top-level binding; do not reinterpret it as a missing
12490            // namespace component.  Existing multi-segment global lookups
12491            // retain their historical indexed recovery.
12492            && (qualified.names.len() > 1 || !qualified.global)
12493        {
12494            let namespace_count = indexed_owner_components.len() - qualified.names.len();
12495            for component_count in 1..=qualified.names.len() {
12496                let expected = &indexed_owner_components[..namespace_count + component_count];
12497                let owner_node = qualified.nodes[component_count - 1];
12498                for owner in visibility
12499                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
12500                    .filter(|candidate| candidate.is_class())
12501                    .filter(|candidate| {
12502                        canonical_cpp_scope_components(candidate) == expected
12503                            && visibility.external_type_candidate_visible_in_context(
12504                                analyzer, file, candidate, node,
12505                            )
12506                    })
12507                {
12508                    if component_count == qualified.names.len() && innermost.is_none() {
12509                        innermost = Some((owner_node, owner.clone()));
12510                    }
12511                    if !owners
12512                        .iter()
12513                        .any(|(_, existing)| same_symbol(existing, owner))
12514                    {
12515                        owners.push((owner_node, owner.clone()));
12516                    }
12517                }
12518            }
12519        }
12520    }
12521    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
12522}
12523
12524fn is_function_declarator_name_root(node: Node<'_>) -> bool {
12525    let mut current = node;
12526    while let Some(parent) = current.parent() {
12527        if parent.kind() == "function_declarator" {
12528            return parent.child_by_field_name("declarator") == Some(current);
12529        }
12530        if matches!(
12531            parent.kind(),
12532            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
12533        ) && parent.child_by_field_name("declarator") == Some(current)
12534        {
12535            current = parent;
12536            continue;
12537        }
12538        return false;
12539    }
12540    false
12541}
12542
12543pub fn append_cpp_name_components(
12544    node: Node<'_>,
12545    source: &str,
12546    out: &mut Vec<String>,
12547) -> Option<()> {
12548    out.extend(
12549        cpp_name_component_nodes(node)?
12550            .into_iter()
12551            .map(|component| node_text(component, source).to_string()),
12552    );
12553    Some(())
12554}
12555
12556pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
12557    let mut components = Vec::new();
12558    append_cpp_name_components(node, source, &mut components)?;
12559    Some(components)
12560}
12561
12562/// Resolve a structured type spelling from an object-like macro replacement
12563/// when definition-site source order has no answer.
12564///
12565/// Macro replacement tokens are looked up where the macro is expanded, so a
12566/// type declared later in the defining header can still be their destination.
12567/// Without expanding every invocation, accept only one include-visible logical
12568/// class or alias whose structured path ends in the replacement components.
12569/// An ordinary lexical answer always takes precedence at the call site.
12570pub fn unique_macro_replacement_type_candidate(
12571    analyzer: &CppGraphSource<'_>,
12572    visibility: &VisibilityIndex<'_>,
12573    file: &ProjectFile,
12574    components: &[String],
12575) -> Option<CodeUnit> {
12576    let terminal = components.last()?;
12577    let mut candidates = Vec::new();
12578    for candidate in visibility
12579        .visible_identifier_candidates(file, terminal)
12580        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
12581        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
12582    {
12583        if !candidates
12584            .iter()
12585            .any(|existing| same_logical_symbol(existing, candidate))
12586        {
12587            candidates.push(candidate.clone());
12588        }
12589    }
12590    (candidates.len() == 1).then(|| candidates.remove(0))
12591}
12592
12593/// The base scopes named by member using-declarations for `member` in one
12594/// class source range.
12595///
12596/// The grammar supplies the qualified identifier and each component. Keep
12597/// this interpretation shared between forward overload lookup and inverse
12598/// owner routing rather than reparsing a rendered `Base::member` string at
12599/// either call site.
12600pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
12601    let mut parser = Parser::new();
12602    if parser
12603        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12604        .is_err()
12605    {
12606        return Vec::new();
12607    }
12608    let Some(tree) = parser.parse(source, None) else {
12609        return Vec::new();
12610    };
12611    let mut scopes = Vec::new();
12612    let mut pending = vec![tree.root_node()];
12613    while let Some(node) = pending.pop() {
12614        if node.kind() == "using_declaration" {
12615            let Some(imported) = node.named_child(0) else {
12616                continue;
12617            };
12618            let Some(mut components) = cpp_type_name_components(imported, source) else {
12619                continue;
12620            };
12621            if components.pop().as_deref() == Some(member) && !components.is_empty() {
12622                scopes.push(components.join("::"));
12623            }
12624            continue;
12625        }
12626        for index in (0..node.named_child_count()).rev() {
12627            if let Some(child) = node.named_child(index) {
12628                pending.push(child);
12629            }
12630        }
12631    }
12632    scopes
12633}
12634
12635/// Whether a structured using-declaration scope can name `qualified` as an
12636/// ancestor class. The boundary check prevents `Base` from matching
12637/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
12638pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
12639    qualified == scope
12640        || qualified
12641            .strip_suffix(scope)
12642            .is_some_and(|prefix| prefix.ends_with("::"))
12643}
12644
12645/// Whether `node` is the direct structured type payload of a template
12646/// argument. This role remains meaningful even when a surrounding expression
12647/// is below tree-sitter recovery, because both the `template_argument_list`
12648/// and the `type_descriptor` retain their named fields.
12649pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
12650    let Some(type_descriptor) = node.parent() else {
12651        return false;
12652    };
12653    if type_descriptor.kind() != "type_descriptor"
12654        || type_descriptor.child_by_field_name("type") != Some(node)
12655    {
12656        return false;
12657    }
12658    let Some(arguments) = type_descriptor.parent() else {
12659        return false;
12660    };
12661    if arguments.kind() != "template_argument_list" {
12662        return false;
12663    }
12664    arguments.parent().is_some_and(|parent| {
12665        matches!(parent.kind(), "template_type" | "template_function")
12666            && parent.child_by_field_name("arguments") == Some(arguments)
12667    })
12668}
12669
12670pub fn cpp_template_reference_arguments(
12671    mut node: Node<'_>,
12672    source: &str,
12673) -> Option<Vec<CppTemplateExpression>> {
12674    loop {
12675        match node.kind() {
12676            "template_type" | "template_function" => {
12677                let arguments = node.child_by_field_name("arguments")?;
12678                let mut cursor = arguments.walk();
12679                return Some(
12680                    arguments
12681                        .named_children(&mut cursor)
12682                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
12683                        .map(|argument| CppTemplateExpression {
12684                            text: normalize_cpp_whitespace(node_text(argument, source)),
12685                            // One template term from a resolver query; see `ParentIndex::unindexed`.
12686                            term: cpp_template_term(
12687                                argument,
12688                                source,
12689                                &[],
12690                                &ParentIndex::unindexed(),
12691                            ),
12692                        })
12693                        .collect(),
12694                );
12695            }
12696            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
12697                node = node
12698                    .child_by_field_name("name")
12699                    .or_else(|| node.child_by_field_name("type"))?;
12700            }
12701            _ => return None,
12702        }
12703    }
12704}
12705
12706fn cpp_reconcile_primary_template_parameters(
12707    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
12708    preferred: &CodeUnit,
12709) -> Option<Vec<CppTemplateParameterMetadata>> {
12710    let canonical = candidates
12711        .iter()
12712        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
12713    let mut merged = canonical
12714        .parameters
12715        .iter()
12716        .map(|parameter| CppTemplateParameterMetadata {
12717            name: parameter.name.clone(),
12718            kind: parameter.kind,
12719            variadic: parameter.variadic,
12720            default: None,
12721        })
12722        .collect::<Vec<_>>();
12723
12724    for (_, metadata) in candidates {
12725        if metadata.parameters.len() != merged.len() {
12726            return None;
12727        }
12728        let rename_bindings = metadata
12729            .parameters
12730            .iter()
12731            .zip(&merged)
12732            .map(|(parameter, canonical)| {
12733                (
12734                    parameter.name.clone(),
12735                    CppTemplateTerm::Parameter(canonical.name.clone()),
12736                )
12737            })
12738            .collect::<HashMap<_, _>>();
12739        for ((parameter, canonical), merged_parameter) in metadata
12740            .parameters
12741            .iter()
12742            .zip(&canonical.parameters)
12743            .zip(&mut merged)
12744        {
12745            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
12746                return None;
12747            }
12748            let Some(default) = &parameter.default else {
12749                continue;
12750            };
12751            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
12752            if let Some(existing) = &merged_parameter.default {
12753                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
12754                    return None;
12755                }
12756            } else {
12757                merged_parameter.default = Some(CppTemplateExpression {
12758                    text: default.text.clone(),
12759                    term: normalized_term,
12760                });
12761            }
12762        }
12763    }
12764    Some(merged)
12765}
12766
12767pub fn cpp_bind_template_arguments(
12768    parameters: &[CppTemplateParameterMetadata],
12769    explicit_arguments: &[CppTemplateExpression],
12770) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
12771    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
12772    if variadic_index.is_some_and(|index| {
12773        index + 1 != parameters.len()
12774            || parameters[index + 1..]
12775                .iter()
12776                .any(|parameter| parameter.variadic)
12777    }) {
12778        return None;
12779    }
12780    let fixed_count = variadic_index.unwrap_or(parameters.len());
12781    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
12782        return None;
12783    }
12784    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
12785    let mut expanded = explicit_arguments[..explicit_fixed_count]
12786        .iter()
12787        .map(cpp_clone_template_expression_iterative)
12788        .collect::<Vec<_>>();
12789    let mut bindings = HashMap::default();
12790    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
12791        bindings.insert(
12792            parameter.name.clone(),
12793            cpp_clone_template_term_iterative(&argument.term),
12794        );
12795    }
12796    for parameter in &parameters[explicit_fixed_count..fixed_count] {
12797        let default = parameter.default.as_ref()?;
12798        let term = cpp_substitute_template_term(&default.term, &bindings)?;
12799        bindings.insert(parameter.name.clone(), term.clone());
12800        expanded.push(CppTemplateExpression {
12801            text: default.text.clone(),
12802            term,
12803        });
12804    }
12805    if let Some(index) = variadic_index {
12806        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
12807        expanded.extend(
12808            packed_arguments
12809                .iter()
12810                .map(cpp_clone_template_expression_iterative),
12811        );
12812        bindings.insert(
12813            parameters[index].name.clone(),
12814            CppTemplateTerm::Node {
12815                kind: "parameter_pack".to_string(),
12816                children: packed_arguments
12817                    .iter()
12818                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
12819                    .collect(),
12820            },
12821        );
12822    }
12823    Some((expanded, bindings))
12824}
12825
12826fn cpp_specialization_matches(
12827    metadata: &CppTemplateMetadata,
12828    arguments: &[CppTemplateExpression],
12829) -> bool {
12830    if metadata.specialization_arguments.len() != arguments.len() {
12831        return false;
12832    }
12833    let parameter_names = metadata
12834        .parameters
12835        .iter()
12836        .map(|parameter| parameter.name.as_str())
12837        .collect::<HashSet<_>>();
12838    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
12839    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
12840        if !cpp_unify_template_term(
12841            &pattern.term,
12842            &argument.term,
12843            &parameter_names,
12844            &mut bindings,
12845        ) {
12846            return false;
12847        }
12848    }
12849    true
12850}
12851
12852fn cpp_specialization_more_specialized(
12853    candidate: &CppTemplateMetadata,
12854    other: &CppTemplateMetadata,
12855) -> bool {
12856    cpp_specialization_pattern_accepts(other, candidate)
12857        && !cpp_specialization_pattern_accepts(candidate, other)
12858}
12859
12860fn cpp_specialization_pattern_accepts(
12861    broader: &CppTemplateMetadata,
12862    narrower: &CppTemplateMetadata,
12863) -> bool {
12864    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
12865        return false;
12866    }
12867    let parameter_names = broader
12868        .parameters
12869        .iter()
12870        .map(|parameter| parameter.name.as_str())
12871        .collect::<HashSet<_>>();
12872    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
12873    broader
12874        .specialization_arguments
12875        .iter()
12876        .zip(&narrower.specialization_arguments)
12877        .all(|(pattern, argument)| {
12878            cpp_unify_template_term(
12879                &pattern.term,
12880                &argument.term,
12881                &parameter_names,
12882                &mut bindings,
12883            )
12884        })
12885}
12886
12887pub fn cpp_substitute_template_term(
12888    term: &CppTemplateTerm,
12889    bindings: &HashMap<String, CppTemplateTerm>,
12890) -> Option<CppTemplateTerm> {
12891    enum Work<'a> {
12892        Visit(&'a CppTemplateTerm),
12893        Build { kind: String, child_count: usize },
12894    }
12895
12896    let mut work = vec![Work::Visit(term)];
12897    let mut substituted = Vec::new();
12898    while let Some(next) = work.pop() {
12899        match next {
12900            Work::Visit(CppTemplateTerm::Parameter(name)) => {
12901                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
12902            }
12903            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
12904                substituted.push(CppTemplateTerm::Atom {
12905                    kind: kind.clone(),
12906                    text: text.clone(),
12907                });
12908            }
12909            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
12910                work.push(Work::Build {
12911                    kind: kind.clone(),
12912                    child_count: children.len(),
12913                });
12914                work.extend(children.iter().rev().map(Work::Visit));
12915            }
12916            Work::Build { kind, child_count } => {
12917                let children = substituted.split_off(substituted.len() - child_count);
12918                substituted.push(CppTemplateTerm::Node { kind, children });
12919            }
12920        }
12921    }
12922    substituted.pop()
12923}
12924
12925pub fn cpp_substitute_template_arguments(
12926    arguments: &[CppTemplateExpression],
12927    bindings: &HashMap<String, CppTemplateTerm>,
12928) -> Option<Vec<CppTemplateExpression>> {
12929    let mut substituted = Vec::new();
12930    for argument in arguments {
12931        let CppTemplateTerm::Node { kind, children } = &argument.term else {
12932            substituted.push(CppTemplateExpression {
12933                text: argument.text.clone(),
12934                term: cpp_substitute_template_term(&argument.term, bindings)?,
12935            });
12936            continue;
12937        };
12938        if kind != "parameter_pack_expansion" {
12939            substituted.push(CppTemplateExpression {
12940                text: argument.text.clone(),
12941                term: cpp_substitute_template_term(&argument.term, bindings)?,
12942            });
12943            continue;
12944        }
12945        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
12946            return None;
12947        };
12948        if ellipsis != "..." {
12949            return None;
12950        }
12951
12952        let mut pack_names = Vec::new();
12953        let mut work = vec![pattern];
12954        while let Some(term) = work.pop() {
12955            match term {
12956                CppTemplateTerm::Parameter(name)
12957                    if matches!(
12958                        bindings.get(name),
12959                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
12960                    ) =>
12961                {
12962                    if !pack_names.contains(name) {
12963                        pack_names.push(name.clone());
12964                    }
12965                }
12966                CppTemplateTerm::Node { children, .. } => work.extend(children),
12967                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
12968            }
12969        }
12970        let first_pack = pack_names.first()?;
12971        let CppTemplateTerm::Node {
12972            children: first_elements,
12973            ..
12974        } = bindings.get(first_pack)?
12975        else {
12976            return None;
12977        };
12978        let pack_len = first_elements.len();
12979        for pack_name in &pack_names {
12980            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
12981                return None;
12982            };
12983            if children.len() != pack_len {
12984                return None;
12985            }
12986        }
12987        for index in 0..pack_len {
12988            let mut element_bindings = bindings.clone();
12989            for pack_name in &pack_names {
12990                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
12991                    return None;
12992                };
12993                element_bindings.insert(
12994                    pack_name.clone(),
12995                    cpp_clone_template_term_iterative(&children[index]),
12996                );
12997            }
12998            substituted.push(CppTemplateExpression {
12999                text: argument.text.clone(),
13000                term: cpp_substitute_template_term(pattern, &element_bindings)?,
13001            });
13002        }
13003    }
13004    Some(substituted)
13005}
13006
13007fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
13008    enum Work<'a> {
13009        Visit(&'a CppTemplateTerm),
13010        Build { kind: String, child_count: usize },
13011    }
13012
13013    let mut work = vec![Work::Visit(term)];
13014    let mut cloned = Vec::new();
13015    while let Some(next) = work.pop() {
13016        match next {
13017            Work::Visit(CppTemplateTerm::Parameter(name)) => {
13018                cloned.push(CppTemplateTerm::Parameter(name.clone()));
13019            }
13020            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13021                cloned.push(CppTemplateTerm::Atom {
13022                    kind: kind.clone(),
13023                    text: text.clone(),
13024                });
13025            }
13026            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13027                work.push(Work::Build {
13028                    kind: kind.clone(),
13029                    child_count: children.len(),
13030                });
13031                work.extend(children.iter().rev().map(Work::Visit));
13032            }
13033            Work::Build { kind, child_count } => {
13034                let children = cloned.split_off(cloned.len() - child_count);
13035                cloned.push(CppTemplateTerm::Node { kind, children });
13036            }
13037        }
13038    }
13039    cloned
13040        .pop()
13041        .expect("template term traversal emits one root")
13042}
13043
13044fn cpp_clone_template_expression_iterative(
13045    expression: &CppTemplateExpression,
13046) -> CppTemplateExpression {
13047    CppTemplateExpression {
13048        text: expression.text.clone(),
13049        term: cpp_clone_template_term_iterative(&expression.term),
13050    }
13051}
13052
13053pub fn cpp_unify_template_term(
13054    pattern: &CppTemplateTerm,
13055    argument: &CppTemplateTerm,
13056    parameters: &HashSet<&str>,
13057    bindings: &mut HashMap<String, CppTemplateTerm>,
13058) -> bool {
13059    let mut work = vec![(pattern, argument)];
13060    while let Some((pattern, argument)) = work.pop() {
13061        match pattern {
13062            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
13063                if let Some(bound) = bindings.get(name) {
13064                    if !cpp_template_terms_equal(bound, argument) {
13065                        return false;
13066                    }
13067                } else {
13068                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
13069                }
13070            }
13071            CppTemplateTerm::Atom {
13072                kind: pattern_kind,
13073                text: pattern_text,
13074            } => {
13075                if !matches!(
13076                    argument,
13077                    CppTemplateTerm::Atom { kind, text }
13078                        if kind == pattern_kind && text == pattern_text
13079                ) {
13080                    return false;
13081                }
13082            }
13083            CppTemplateTerm::Node {
13084                kind: pattern_kind,
13085                children: pattern_children,
13086            } => {
13087                let CppTemplateTerm::Node { kind, children } = argument else {
13088                    return false;
13089                };
13090                if kind != pattern_kind || children.len() != pattern_children.len() {
13091                    return false;
13092                }
13093                work.extend(pattern_children.iter().zip(children).rev());
13094            }
13095            CppTemplateTerm::Parameter(_) => return false,
13096        }
13097    }
13098    true
13099}
13100
13101fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
13102    let mut work = vec![(left, right)];
13103    while let Some((left, right)) = work.pop() {
13104        match (left, right) {
13105            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
13106                if left != right {
13107                    return false;
13108                }
13109            }
13110            (
13111                CppTemplateTerm::Atom {
13112                    kind: left_kind,
13113                    text: left_text,
13114                },
13115                CppTemplateTerm::Atom {
13116                    kind: right_kind,
13117                    text: right_text,
13118                },
13119            ) => {
13120                if left_kind != right_kind || left_text != right_text {
13121                    return false;
13122                }
13123            }
13124            (
13125                CppTemplateTerm::Node {
13126                    kind: left_kind,
13127                    children: left_children,
13128                },
13129                CppTemplateTerm::Node {
13130                    kind: right_kind,
13131                    children: right_children,
13132                },
13133            ) => {
13134                if left_kind != right_kind || left_children.len() != right_children.len() {
13135                    return false;
13136                }
13137                work.extend(left_children.iter().zip(right_children).rev());
13138            }
13139            _ => return false,
13140        }
13141    }
13142    true
13143}
13144
13145pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
13146    let mut components = Vec::new();
13147    let mut stack = vec![node];
13148    while let Some(current) = stack.pop() {
13149        match current.kind() {
13150            "identifier"
13151            | "field_identifier"
13152            | "namespace_identifier"
13153            | "type_identifier"
13154            | "operator_name"
13155            | "destructor_name" => components.push(current),
13156            "template_type" | "template_function" => {
13157                stack.push(current.child_by_field_name("name")?);
13158            }
13159            "dependent_name" => stack.push(current.named_child(0)?),
13160            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13161                stack.push(current.child_by_field_name("name")?);
13162                if let Some(scope) = current.child_by_field_name("scope") {
13163                    stack.push(scope);
13164                }
13165            }
13166            "nested_namespace_specifier" => {
13167                for index in (0..current.named_child_count()).rev() {
13168                    stack.push(current.named_child(index)?);
13169                }
13170            }
13171            _ => return None,
13172        }
13173    }
13174    Some(components)
13175}
13176
13177pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
13178    node.child_by_field_name("scope").is_none()
13179        && node.child(0).is_some_and(|child| child.kind() == "::")
13180}
13181
13182fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
13183    let mut namespaces = Vec::new();
13184    let mut current = node.parent();
13185    while let Some(parent) = current {
13186        if parent.kind() == "namespace_definition"
13187            && let Some(name) = parent.child_by_field_name("name")
13188        {
13189            let mut components = Vec::new();
13190            append_cpp_name_components(name, source, &mut components)?;
13191            namespaces.push(components);
13192        }
13193        current = parent.parent();
13194    }
13195    namespaces.reverse();
13196    Some(namespaces.into_iter().flatten().collect())
13197}
13198
13199/// Whether a parser-derived namespace path can be reconciled with an indexed
13200/// owner scope without inventing an unrelated short-name binding.
13201///
13202/// Macro namespace sentinels can make tree-sitter omit one or more namespace
13203/// definitions from the ancestor chain. Preserve the order of every namespace
13204/// that did survive parsing, but allow indexed components between them. An
13205/// empty path is accepted only when the declarator itself supplies a nested
13206/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
13207/// owner chain, that suffix is structural evidence that a namespace was lost.
13208/// A one-segment owner at the translation-unit root remains insufficient.
13209fn indexed_namespace_path_is_recoverable(
13210    lexical_scope: &[String],
13211    indexed_owner_scope: &[String],
13212    explicit_owner_component_count: usize,
13213) -> bool {
13214    if lexical_scope.is_empty() {
13215        return explicit_owner_component_count > 1;
13216    }
13217    if lexical_scope.len() >= indexed_owner_scope.len() {
13218        return false;
13219    }
13220    let mut indexed = indexed_owner_scope.iter();
13221    lexical_scope
13222        .iter()
13223        .all(|component| indexed.any(|candidate| candidate == component))
13224}
13225
13226pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
13227    let mut current = node.parent();
13228    while let Some(parent) = current {
13229        if parent.kind() == kind {
13230            return true;
13231        }
13232        current = parent.parent();
13233    }
13234    false
13235}
13236
13237/// Whether a declaration type is initialized with a pointer cast.
13238///
13239/// This structured shape has an independent qualified occurrence in addition
13240/// to the cast descriptor below it. Other declarations must keep their normal
13241/// full-range occurrence only.
13242pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
13243    let mut current = Some(node);
13244    while let Some(candidate) = current {
13245        if candidate.kind() == "declaration" {
13246            let Some(type_node) = candidate.child_by_field_name("type") else {
13247                return false;
13248            };
13249            if !(type_node.start_byte() <= node.start_byte()
13250                && node.end_byte() <= type_node.end_byte())
13251            {
13252                return false;
13253            }
13254            let mut cursor = candidate.walk();
13255            return candidate.named_children(&mut cursor).any(|child| {
13256                child.kind() == "init_declarator"
13257                    && child
13258                        .child_by_field_name("value")
13259                        .is_some_and(|value| value.kind() == "cast_expression")
13260            });
13261        }
13262        current = candidate.parent();
13263    }
13264    false
13265}
13266
13267#[derive(Clone, Copy, PartialEq, Eq)]
13268pub(crate) enum QualifiedAliasReferenceKind {
13269    Ordinary,
13270    ConstructorWithExpressionArgument,
13271    ExhaustiveTemplate,
13272}
13273
13274/// Whether a qualified alias reference preserves the requested target.
13275///
13276/// The complete qualified spelling and its terminal identifier are both valid
13277/// occurrences when the visible alias path is structurally proven to name the
13278/// target. Template aliases use their bound arguments; ordinary aliases use
13279/// their structured primary chain.
13280pub(crate) fn qualified_alias_reference_preserves_target(
13281    node: Node<'_>,
13282    target: &CodeUnit,
13283    analyzer: &CppGraphSource<'_>,
13284    visibility: &VisibilityIndex<'_>,
13285    file: &ProjectFile,
13286    source: &str,
13287) -> Option<QualifiedAliasReferenceKind> {
13288    if !matches!(
13289        node.kind(),
13290        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
13291    ) {
13292        return None;
13293    }
13294    let components = cpp_type_name_components(node, source)?;
13295    let name = components.last()?;
13296    analyzer.type_alias_provider().and_then(|provider| {
13297        visibility
13298            .visible_identifier_candidates(file, name)
13299            .find_map(|candidate| {
13300                let proof = provider.is_type_alias(candidate)
13301                    && canonical_cpp_scope_components(candidate) == components
13302                    && visibility.external_type_candidate_visible_in_context(
13303                        analyzer, file, candidate, node,
13304                    )
13305                    && match cpp_template_reference_arguments(node, source) {
13306                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
13307                            analyzer, file, candidate, &arguments, target,
13308                        ),
13309                        None => visibility.structured_alias_primary_preserves_target(
13310                            analyzer, file, candidate, target,
13311                        ),
13312                    };
13313                proof.then(|| {
13314                    if cpp_template_reference_arguments(node, source).is_some()
13315                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
13316                            analyzer, file, candidate,
13317                        )
13318                    {
13319                        QualifiedAliasReferenceKind::ExhaustiveTemplate
13320                    } else if qualified_alias_constructor_has_expression_argument(node)
13321                        || qualified_alias_local_constructor_declaration(node)
13322                    {
13323                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13324                    } else {
13325                        QualifiedAliasReferenceKind::Ordinary
13326                    }
13327                })
13328            })
13329    })
13330}
13331
13332pub(crate) fn qualified_alias_reference_requires_terminal(
13333    reference: Option<QualifiedAliasReferenceKind>,
13334) -> bool {
13335    matches!(
13336        reference,
13337        Some(
13338            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13339                | QualifiedAliasReferenceKind::ExhaustiveTemplate
13340        )
13341    )
13342}
13343
13344fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
13345    let Some(declaration) = node.parent().filter(|parent| {
13346        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13347    }) else {
13348        return false;
13349    };
13350    let mut cursor = declaration.walk();
13351    declaration.named_children(&mut cursor).any(|child| {
13352        child.kind() == "init_declarator"
13353            && child
13354                .child_by_field_name("value")
13355                .filter(|value| value.kind() == "argument_list")
13356                .is_some_and(|arguments| {
13357                    let mut cursor = arguments.walk();
13358                    arguments.named_children(&mut cursor).any(|argument| {
13359                        let is_parameter = matches!(
13360                            argument.kind(),
13361                            "parameter_declaration" | "optional_parameter_declaration"
13362                        );
13363                        if is_parameter {
13364                            argument
13365                                .child_by_field_name("type")
13366                                .is_some_and(|type_node| {
13367                                    type_node.kind() == "type_identifier"
13368                                        && argument.child_by_field_name("declarator").is_none()
13369                                })
13370                        } else {
13371                            !argument.kind().ends_with("_literal")
13372                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
13373                        }
13374                    })
13375                })
13376    })
13377}
13378
13379/// Tree-sitter represents a local C++ direct construction such as
13380/// `Alias value(argument)` as a function declarator. Restrict that recovery to
13381/// declarations inside a compound statement so namespace-scope function
13382/// declarations with the same qualified return type stay full-range only.
13383fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
13384    let Some(declaration) = node.parent().filter(|parent| {
13385        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13386    }) else {
13387        return false;
13388    };
13389    if declaration
13390        .parent()
13391        .is_none_or(|parent| parent.kind() != "compound_statement")
13392    {
13393        return false;
13394    }
13395    let mut cursor = declaration.walk();
13396    declaration
13397        .named_children(&mut cursor)
13398        .any(|child| child.kind() == "function_declarator")
13399}
13400
13401/// Return the terminal identifier represented by a callable or type callee.
13402///
13403/// Qualified, scoped, template, and field wrappers are traversed through their
13404/// grammar fields so both function calls and type constructions emit the token
13405/// that names the referenced declaration.
13406pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
13407    loop {
13408        let next = match node.kind() {
13409            "qualified_identifier"
13410            | "scoped_identifier"
13411            | "template_method"
13412            | "template_function"
13413            | "template_type" => node.child_by_field_name("name"),
13414            "field_expression" => node.child_by_field_name("field"),
13415            _ => None,
13416        };
13417        let Some(next) = next else {
13418            return node;
13419        };
13420        node = next;
13421    }
13422}
13423
13424#[derive(Clone, Copy)]
13425pub struct RecoveredRelationalTemplateMemberCall<'tree> {
13426    pub receiver: Node<'tree>,
13427    pub member: Node<'tree>,
13428    pub arity: usize,
13429}
13430
13431/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
13432/// nested relational expressions instead of a `template_method` call.
13433///
13434/// The recovery uses only grammar fields: the selected field must be the left
13435/// side of `<`, that expression must be the left side of `>`, and the right
13436/// side of `>` must be the parenthesized call arguments. Semantic callers must
13437/// additionally prove the receiver owner and the member's template status.
13438pub fn recovered_relational_template_member_call(
13439    field: Node<'_>,
13440) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
13441    if field.kind() != "field_expression" {
13442        return None;
13443    }
13444    let receiver = field
13445        .child_by_field_name("argument")
13446        .or_else(|| field.child_by_field_name("object"))?;
13447    let member = field.child_by_field_name("field")?;
13448    let less = field.parent()?;
13449    if less.kind() != "binary_expression"
13450        || less.child_by_field_name("left") != Some(field)
13451        || less
13452            .child_by_field_name("operator")
13453            .is_none_or(|operator| operator.kind() != "<")
13454        || less.child_by_field_name("right").is_none()
13455    {
13456        return None;
13457    }
13458    let greater = less.parent()?;
13459    if greater.kind() != "binary_expression"
13460        || greater.child_by_field_name("left") != Some(less)
13461        || greater
13462            .child_by_field_name("operator")
13463            .is_none_or(|operator| operator.kind() != ">")
13464    {
13465        return None;
13466    }
13467    let arguments = greater.child_by_field_name("right")?;
13468    if arguments.kind() != "parenthesized_expression" {
13469        return None;
13470    }
13471    let arity = parenthesized_call_argument_arity(arguments)?;
13472    Some(RecoveredRelationalTemplateMemberCall {
13473        receiver,
13474        member,
13475        arity,
13476    })
13477}
13478
13479fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
13480    let expression = arguments.named_child(0)?;
13481    if expression.kind() != "comma_expression" {
13482        return Some(1);
13483    }
13484    let mut arity = 0usize;
13485    let mut stack = vec![expression];
13486    while let Some(node) = stack.pop() {
13487        if node.kind() == "comma_expression" {
13488            stack.push(node.child_by_field_name("right")?);
13489            stack.push(node.child_by_field_name("left")?);
13490        } else {
13491            arity += 1;
13492        }
13493    }
13494    Some(arity)
13495}
13496
13497/// Whether `node` is part of a call's callee expression, walking only through
13498/// the grammar wrappers that can structurally contain that callee.
13499pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
13500    while let Some(parent) = node.parent() {
13501        match parent.kind() {
13502            "call_expression" => {
13503                return parent
13504                    .child_by_field_name("function")
13505                    .or_else(|| parent.named_child(0))
13506                    == Some(node);
13507            }
13508            "qualified_identifier"
13509            | "scoped_identifier"
13510            | "template_function"
13511            | "template_type"
13512            | "field_expression" => node = parent,
13513            _ => return false,
13514        }
13515    }
13516    false
13517}
13518
13519pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
13520    if is_call_callee_node(node) {
13521        function_terminal_node(node)
13522    } else {
13523        node
13524    }
13525}
13526
13527pub fn normalize_type_text(value: &str) -> String {
13528    strip_tag_type_prefix(
13529        normalize_cpp_whitespace(value)
13530            .trim_start_matches("const ")
13531            .trim_end_matches('*')
13532            .trim_end_matches('&')
13533            .trim(),
13534    )
13535    .to_string()
13536}
13537
13538fn strip_tag_type_prefix(value: &str) -> &str {
13539    let value = value.trim_start_matches("const ");
13540    value
13541        .strip_prefix("struct ")
13542        .or_else(|| value.strip_prefix("class "))
13543        .or_else(|| value.strip_prefix("enum "))
13544        .unwrap_or(value)
13545        .trim()
13546}
13547
13548pub fn normalize_reference_name(value: &str) -> Option<String> {
13549    let normalized = normalize_cpp_reference_text(value);
13550    (!normalized.is_empty()).then_some(normalized)
13551}
13552
13553pub fn normalize_cpp_reference_text(value: &str) -> String {
13554    let mut text = normalize_cpp_whitespace(value)
13555        .trim_start_matches("new ")
13556        .trim()
13557        .to_string();
13558    if let Some(index) = text.find(['(', '{']) {
13559        text.truncate(index);
13560    }
13561    if let Some(index) = text.find('<') {
13562        text.truncate(index);
13563    }
13564    let normalized = text
13565        .trim()
13566        .trim_start_matches("const ")
13567        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
13568        .trim_matches(':')
13569        .trim();
13570    strip_tag_type_prefix(normalized).to_string()
13571}
13572
13573pub fn cpp_name_for(unit: &CodeUnit) -> String {
13574    let short = unit.short_name().replace(['.', '$'], "::");
13575    if unit.package_name().is_empty() {
13576        short
13577    } else {
13578        format!("{}::{}", unit.package_name(), short)
13579    }
13580}
13581
13582/// Render an indexed C++ qualified name from its authoritative FqName
13583/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
13584/// that belong to a template argument (for example `Args...`).
13585fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
13586    let fq = unit.fq();
13587    if fq.is_empty() {
13588        return None;
13589    }
13590    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13591    Some(
13592        fq.segments()
13593            .iter()
13594            .map(|&segment| interner.resolve(segment).0)
13595            .collect::<Vec<_>>()
13596            .join("::"),
13597    )
13598}
13599
13600fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
13601    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
13602        || unit.fq().is_empty() && cpp_name_for(unit) == expected
13603}
13604
13605/// Return the indexed C++ owner scope without reparsing its rendered name.
13606///
13607/// Template spellings are opaque within an indexed `FqName` segment.  In
13608/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
13609/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
13610/// through `parse_symbol_path` would mistake those dots for component
13611/// separators.  Cache-loaded/legacy units may still have an empty structured
13612/// name, so retain the parser only as that explicit fallback.
13613pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
13614    let fq = unit.fq();
13615    if !fq.is_empty() {
13616        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13617        let scope = fq
13618            .segments()
13619            .iter()
13620            .filter_map(|&segment| {
13621                let (text, kind) = interner.resolve(segment);
13622                matches!(
13623                    kind,
13624                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
13625                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
13626                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
13627                )
13628                .then(|| text.to_string())
13629            })
13630            .collect();
13631        return scope;
13632    }
13633    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13634        brokk_bifrost_core::analyzer::Language::Cpp,
13635        &cpp_name_for(unit),
13636    )
13637}
13638
13639// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
13640// (not the substring "->"), which deliberately reduces an `operator->`-style
13641// terminal segment to an empty tail rather than keeping it intact; the shared
13642// structured splitter's cpp operator-token merge would keep `operator->`
13643// whole instead, changing this function's result — `name_matches_callable`'s
13644// `expected.starts_with("operator")` fallback exists specifically to
13645// compensate for that reduction, and a pinned regression test
13646// (`operator-> must not be reduced with terminal_name-style punctuation
13647// splitting`) asserts today's char-class behavior. Not equivalence-provable;
13648// revisit alongside that pinned test if it is ever relaxed.
13649pub fn terminal_name(value: &str) -> &str {
13650    value
13651        .rsplit("::")
13652        .next()
13653        .unwrap_or(value)
13654        .rsplit(['.', '-', '>'])
13655        .next()
13656        .unwrap_or(value)
13657        .trim()
13658}
13659
13660pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
13661    terminal_name(&normalize_cpp_reference_text(value)) == expected
13662}
13663
13664pub fn name_matches_callable(value: &str, expected: &str) -> bool {
13665    name_matches_terminal(value, expected)
13666        || expected.starts_with("operator")
13667            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
13668}
13669
13670pub fn name_mentions(value: &str, expected: &str) -> bool {
13671    normalize_cpp_reference_text(value)
13672        .split("::")
13673        .any(|part| part == expected)
13674}
13675
13676pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
13677    let cpp_name = cpp_name_for(unit);
13678    if reference.contains("::") {
13679        return reference == cpp_name;
13680    }
13681    reference == cpp_name
13682        || terminal_name(reference) == unit.identifier()
13683            && (unit.package_name().is_empty() || reference == unit.identifier())
13684}
13685
13686pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
13687    match kind {
13688        TargetKind::Type
13689        | TargetKind::Constructor
13690        | TargetKind::Method
13691        | TargetKind::MemberField => true,
13692        TargetKind::FreeFunction => unit.is_function(),
13693        TargetKind::GlobalField => unit.is_field(),
13694        TargetKind::Macro => unit.is_macro(),
13695    }
13696}
13697
13698pub fn is_type_alias(unit: &CodeUnit) -> bool {
13699    unit.kind() == CodeUnitType::Field
13700        && unit.signature().is_some_and(|signature| {
13701            signature.starts_with("typedef ") || signature.starts_with("using ")
13702        })
13703}
13704
13705fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
13706    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
13707    let target_name = cpp_name_for(target);
13708    if normalized.contains("::") {
13709        return normalized == target_name;
13710    }
13711    if let Some(namespace) = alias.namespace.as_deref() {
13712        return namespace_prefixes(namespace)
13713            .into_iter()
13714            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
13715    }
13716    target.package_name().is_empty() && normalized == target.identifier()
13717}
13718
13719/// The declared return type text of a C++ function unit, with leading declaration specifiers
13720/// stripped, e.g. `T*` for `T* operator->()`.
13721pub fn cpp_function_return_type_text(
13722    analyzer: &CppGraphSource<'_>,
13723    function: &CodeUnit,
13724) -> Option<String> {
13725    let metadata = analyzer.signature_metadata(function);
13726    if !metadata.is_empty() {
13727        let first = metadata.first()?.return_type_text()?;
13728        return metadata
13729            .iter()
13730            .all(|metadata| metadata.return_type_text() == Some(first))
13731            .then(|| first.to_string());
13732    }
13733    let signature = cpp_function_signature_text(analyzer, function)?;
13734    cpp_function_return_type_text_from_signature(&signature)
13735}
13736
13737fn cpp_function_signature_text(
13738    analyzer: &CppGraphSource<'_>,
13739    function: &CodeUnit,
13740) -> Option<String> {
13741    function
13742        .signature()
13743        .filter(|signature| signature.contains(function.identifier()))
13744        .map(str::to_string)
13745        .or_else(|| analyzer.signatures(function).first().cloned())
13746        .or_else(|| analyzer.get_source(function, false))
13747}
13748
13749fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
13750    let open = signature.find('(')?;
13751    let name_at = cpp_function_name_start(signature, open)?;
13752    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
13753        return Some(return_type);
13754    }
13755    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
13756        .split_whitespace()
13757        .filter(|token| {
13758            !matches!(
13759                *token,
13760                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
13761            )
13762        })
13763        .collect::<Vec<_>>()
13764        .join(" ");
13765    let type_text = type_text.trim();
13766    (!type_text.is_empty()).then(|| type_text.to_string())
13767}
13768
13769fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
13770    let before_parameters = &signature[..open];
13771    if let Some(operator_at) = before_parameters.rfind("operator") {
13772        let boundary = operator_at == 0
13773            || before_parameters[..operator_at]
13774                .chars()
13775                .next_back()
13776                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
13777        if boundary {
13778            return Some(operator_at);
13779        }
13780    }
13781    before_parameters
13782        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
13783        .map(|index| index + 1)
13784}
13785
13786fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
13787    let open = signature_from_name.find('(')?;
13788    let mut depth = 0i32;
13789    for (offset, ch) in signature_from_name[open..].char_indices() {
13790        match ch {
13791            '(' => depth += 1,
13792            ')' => {
13793                depth -= 1;
13794                if depth == 0 {
13795                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
13796                    let arrow = rest.find("->")?;
13797                    let return_type = rest[arrow + 2..].trim_start();
13798                    let return_type = return_type
13799                        .split(['{', ';'])
13800                        .next()
13801                        .unwrap_or(return_type)
13802                        .trim();
13803                    return (!return_type.is_empty()).then(|| return_type.to_string());
13804                }
13805            }
13806            _ => {}
13807        }
13808    }
13809    None
13810}
13811
13812/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
13813/// Returns the input unchanged when there is no such clause.
13814fn cpp_strip_leading_template_clause(text: &str) -> &str {
13815    let trimmed = text.trim_start();
13816    let Some(rest) = trimmed.strip_prefix("template") else {
13817        return text;
13818    };
13819    let rest = rest.trim_start();
13820    if !rest.starts_with('<') {
13821        return text;
13822    }
13823    let mut depth = 0i32;
13824    for (offset, ch) in rest.char_indices() {
13825        match ch {
13826            '<' => depth += 1,
13827            '>' => {
13828                depth -= 1;
13829                if depth == 0 {
13830                    return rest[offset + ch.len_utf8()..].trim_start();
13831                }
13832            }
13833            _ => {}
13834        }
13835    }
13836    text
13837}
13838
13839pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
13840    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
13841    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
13842    // the same string `default_parent_fq_name`/`fq().parent()` would render:
13843    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
13844    // `::`) between a trailing `Package` segment and a following `Type`
13845    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
13846    // popping the unit's own `fq()` segment would NOT reproduce this
13847    // fully-`::`-joined string. Left as a split on the locally-built
13848    // all-colon string rather than the unit's structured name.
13849    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
13850        namespace
13851            .strip_prefix("anonymous_namespace::")
13852            .unwrap_or(namespace)
13853            .to_string()
13854    })
13855}
13856
13857fn namespace_prefixes(namespace: &str) -> Vec<String> {
13858    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
13859    // non-`::` separator already converted to `::`, so re-tokenizing it with
13860    // the shared structured splitter and progressively popping the last
13861    // component reproduces the `rsplit_once("::")` outward walk exactly (same
13862    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
13863    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13864        brokk_bifrost_core::analyzer::Language::Cpp,
13865        namespace,
13866    );
13867    let mut prefixes = Vec::new();
13868    while !parts.is_empty() {
13869        prefixes.push(parts.join("::"));
13870        parts.pop();
13871    }
13872    prefixes
13873}
13874
13875fn nearest_namespace_candidates(
13876    candidates: Vec<CodeUnit>,
13877    normalized: &str,
13878    lexical_namespace: Option<&str>,
13879) -> Vec<CodeUnit> {
13880    if normalized.contains("::") {
13881        return candidates;
13882    }
13883    if let Some(namespace) = lexical_namespace {
13884        for prefix in namespace_prefixes(namespace) {
13885            let scoped = candidates
13886                .iter()
13887                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
13888                .cloned()
13889                .collect::<Vec<_>>();
13890            if !scoped.is_empty() {
13891                return scoped;
13892            }
13893        }
13894    }
13895    candidates
13896        .into_iter()
13897        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
13898        .collect()
13899}
13900
13901pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
13902    let mut namespaces = Vec::new();
13903    let mut current = node.parent();
13904    while let Some(parent) = current {
13905        if parent.kind() == "namespace_definition"
13906            && let Some(name) = parent.child_by_field_name("name")
13907        {
13908            let namespace = normalize_cpp_reference_text(node_text(name, source));
13909            if !namespace.is_empty() {
13910                namespaces.push(namespace);
13911            }
13912        }
13913        current = parent.parent();
13914    }
13915    if namespaces.is_empty() {
13916        None
13917    } else {
13918        namespaces.reverse();
13919        Some(namespaces.join("::"))
13920    }
13921}
13922
13923/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
13924/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
13925/// globals rather than members.
13926pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
13927    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
13928}
13929
13930fn type_owner_resolution(
13931    analyzer: &CppGraphSource<'_>,
13932    code_unit: &CodeUnit,
13933) -> Option<ResolvedTypeOwner> {
13934    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
13935}
13936
13937fn target_type_owner_resolution(
13938    analyzer: &CppGraphSource<'_>,
13939    code_unit: &CodeUnit,
13940) -> Option<ResolvedTypeOwner> {
13941    match type_owner_resolution(analyzer, code_unit) {
13942        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
13943        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
13944    }
13945}
13946
13947/// Recover method identity for an indexed out-of-line definition when the
13948/// ordinary parent edge is absent. Prefer the unique include-visible forward
13949/// declaration, then classify exact-FQN class declarations elsewhere in the
13950/// workspace. A unique complete declaration wins; otherwise multiple forward
13951/// declarations are one owner only when they all share one logical identity.
13952/// The qualified callable FQN proves that owner spelling even when its defining
13953/// header is outside the scan file's include closure, while unknown or competing
13954/// complete declarations remain ambiguous.
13955/// This is deliberately target-only: canonical declaration resolution must
13956/// continue to prefer the callable definition rather than replacing it with
13957/// the recovered owner.
13958fn target_forward_owner_resolution(
13959    analyzer: &CppGraphSource<'_>,
13960    code_unit: &CodeUnit,
13961) -> Option<ResolvedTypeOwner> {
13962    if !code_unit.is_function() {
13963        return None;
13964    }
13965    // A top-level free function has no owner at all, and `FqName::parent`
13966    // answers the empty name rather than `None` for a one-segment identity.
13967    // `default_parent_fq_name`, which this replaced, filtered that case out;
13968    // asking the relational store for the empty name is a batch error that
13969    // fails the whole target frontier.
13970    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
13971    let cpp = analyzer.cpp?;
13972    let mut visible_files = HashSet::default();
13973    collect_include_closure(
13974        analyzer,
13975        cpp.include_target_index(),
13976        code_unit.source(),
13977        &mut visible_files,
13978        None,
13979    );
13980    let candidates = analyzer.workspace_definitions().exact(&owner_name);
13981    let visible_candidates = candidates
13982        .iter()
13983        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
13984        .cloned()
13985        .collect::<Vec<_>>();
13986    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
13987        DirectOwnerResolution::UniqueFull(unit) => {
13988            return Some(ResolvedTypeOwner {
13989                unit,
13990                is_forward_declaration: false,
13991            });
13992        }
13993        DirectOwnerResolution::ForwardsOnly(forwards) => {
13994            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
13995                unit: forwards.into_iter().next().unwrap(),
13996                is_forward_declaration: true,
13997            });
13998        }
13999        DirectOwnerResolution::Ambiguous => return None,
14000        DirectOwnerResolution::None => {}
14001    }
14002
14003    let candidates = candidates
14004        .into_iter()
14005        .filter(|candidate| candidate.is_class())
14006        .collect::<Vec<_>>();
14007    let (unit, is_forward_declaration) =
14008        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
14009            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
14010            DirectOwnerResolution::ForwardsOnly(forwards) => {
14011                (unique_logical_forward_owner(forwards)?, true)
14012            }
14013            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
14014        };
14015    Some(ResolvedTypeOwner {
14016        unit,
14017        is_forward_declaration,
14018    })
14019}
14020
14021pub fn precise_parent_of(
14022    analyzer: &CppGraphSource<'_>,
14023    visibility: &VisibilityIndex<'_>,
14024    code_unit: &CodeUnit,
14025) -> Option<CodeUnit> {
14026    visibility.cached_precise_parent_of(analyzer, code_unit)
14027}
14028
14029fn precise_parent_resolution(
14030    analyzer: &CppGraphSource<'_>,
14031    code_unit: &CodeUnit,
14032) -> Option<ResolvedTypeOwner> {
14033    #[cfg(any(test, feature = "test-support"))]
14034    if let Some(cpp) = analyzer.cpp {
14035        cpp.record_cpp_parent_resolution_for_test();
14036    }
14037    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
14038        return Some(ResolvedTypeOwner {
14039            unit,
14040            is_forward_declaration: false,
14041        });
14042    }
14043    let fallback = analyzer.parent_of(code_unit);
14044    if !code_unit.owner_is_type_scope() {
14045        return fallback.map(|unit| ResolvedTypeOwner {
14046            unit,
14047            is_forward_declaration: false,
14048        });
14049    }
14050    let owner_fq = code_unit
14051        .fq()
14052        .parent()
14053        .expect("a unit with an owner identifier has a structured parent");
14054    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
14055    match same_source_owner(analyzer, code_unit, &owner_candidates) {
14056        DirectOwnerResolution::UniqueFull(owner) => {
14057            return Some(ResolvedTypeOwner {
14058                unit: owner,
14059                is_forward_declaration: false,
14060            });
14061        }
14062        DirectOwnerResolution::Ambiguous => return None,
14063        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
14064    }
14065    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
14066        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
14067            unit: owner,
14068            is_forward_declaration: false,
14069        }),
14070        DirectOwnerResolution::Ambiguous => None,
14071        DirectOwnerResolution::ForwardsOnly(forwards) => {
14072            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14073                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14074                    unit: owner,
14075                    is_forward_declaration: false,
14076                }),
14077                FullOwnerResolution::None => {
14078                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
14079                        unit,
14080                        is_forward_declaration: true,
14081                    })
14082                }
14083                FullOwnerResolution::Ambiguous => None,
14084            }
14085        }
14086        DirectOwnerResolution::None => {
14087            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14088                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14089                    unit: owner,
14090                    is_forward_declaration: false,
14091                }),
14092                FullOwnerResolution::Ambiguous => None,
14093                FullOwnerResolution::None => fallback
14094                    .filter(|parent| {
14095                        parent.source() == code_unit.source()
14096                            && parent.fq() == &owner_fq
14097                            && (!parent.is_class()
14098                                || cpp_class_declaration_strength(analyzer, parent)
14099                                    == CppClassDeclarationStrength::Full)
14100                    })
14101                    .map(|unit| ResolvedTypeOwner {
14102                        unit,
14103                        is_forward_declaration: false,
14104                    }),
14105            }
14106        }
14107    }
14108}
14109
14110fn exact_structural_type_parent(
14111    analyzer: &CppGraphSource<'_>,
14112    code_unit: &CodeUnit,
14113) -> Option<CodeUnit> {
14114    if !code_unit.is_function() && !code_unit.is_field() {
14115        return None;
14116    }
14117    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
14118    let cpp = analyzer.cpp?;
14119    let parent = cpp.structural_parent_of(code_unit)?;
14120    (!parent.is_module()
14121        && parent.source() == code_unit.source()
14122        && parent.package_name() == code_unit.package_name()
14123        && parent.short_name() == encoded_owner)
14124        .then_some(parent)
14125}
14126
14127fn same_source_owner(
14128    analyzer: &CppGraphSource<'_>,
14129    code_unit: &CodeUnit,
14130    owner_candidates: &[CodeUnit],
14131) -> DirectOwnerResolution {
14132    let candidates = owner_candidates
14133        .iter()
14134        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
14135        .cloned()
14136        .collect::<Vec<_>>();
14137    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14138    classify_direct_owner_candidates(analyzer, candidates.into_iter())
14139}
14140
14141fn visible_full_cpp_owner(
14142    analyzer: &CppGraphSource<'_>,
14143    code_unit: &CodeUnit,
14144    owner_candidates: &[CodeUnit],
14145) -> FullOwnerResolution {
14146    let Some(cpp) = analyzer.cpp else {
14147        return FullOwnerResolution::None;
14148    };
14149    let mut visible_files = HashSet::default();
14150    collect_include_closure(
14151        analyzer,
14152        cpp.include_target_index(),
14153        code_unit.source(),
14154        &mut visible_files,
14155        None,
14156    );
14157    let candidates = owner_candidates
14158        .iter()
14159        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14160        .cloned()
14161        .collect::<Vec<_>>();
14162    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14163    let mut full_definition = None;
14164    for candidate in candidates {
14165        match cpp_class_declaration_strength(analyzer, &candidate) {
14166            CppClassDeclarationStrength::Full if full_definition.is_some() => {
14167                return FullOwnerResolution::Ambiguous;
14168            }
14169            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14170            CppClassDeclarationStrength::Forward => {}
14171            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
14172        }
14173    }
14174    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
14175}
14176
14177pub enum DirectOwnerResolution {
14178    None,
14179    ForwardsOnly(Vec<CodeUnit>),
14180    UniqueFull(CodeUnit),
14181    Ambiguous,
14182}
14183
14184enum FullOwnerResolution {
14185    None,
14186    Unique(CodeUnit),
14187    Ambiguous,
14188}
14189
14190#[derive(Clone, Copy, PartialEq, Eq)]
14191pub enum CppClassDeclarationStrength {
14192    Full,
14193    Forward,
14194    Unknown,
14195}
14196
14197fn directly_included_owner(
14198    analyzer: &CppGraphSource<'_>,
14199    code_unit: &CodeUnit,
14200    owner_candidates: &[CodeUnit],
14201) -> DirectOwnerResolution {
14202    let Some(cpp) = analyzer.cpp else {
14203        return DirectOwnerResolution::None;
14204    };
14205    let imports = analyzer.import_statements(code_unit.source());
14206    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
14207        .into_iter()
14208        .flat_map(|include| {
14209            resolve_include_targets_with_index(
14210                code_unit.source(),
14211                &include,
14212                cpp.include_target_index(),
14213            )
14214        })
14215        .collect();
14216    let candidates = owner_candidates
14217        .iter()
14218        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
14219        .cloned()
14220        .collect::<Vec<_>>();
14221    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14222    classify_direct_owner_candidates(analyzer, candidates.into_iter())
14223}
14224
14225fn prefer_member_declaring_owners(
14226    analyzer: &CppGraphSource<'_>,
14227    member: &CodeUnit,
14228    candidates: Vec<CodeUnit>,
14229) -> Vec<CodeUnit> {
14230    let matching = candidates
14231        .iter()
14232        .filter(|owner| owner_declares_member(analyzer, owner, member))
14233        .cloned()
14234        .collect::<Vec<_>>();
14235    if matching.is_empty() {
14236        candidates
14237    } else {
14238        matching
14239    }
14240}
14241
14242fn owner_declares_member(
14243    analyzer: &CppGraphSource<'_>,
14244    owner: &CodeUnit,
14245    member: &CodeUnit,
14246) -> bool {
14247    analyzer.direct_children(owner).into_iter().any(|child| {
14248        child.kind() == member.kind()
14249            && child.identifier() == member.identifier()
14250            && child.signature() == member.signature()
14251    })
14252}
14253
14254fn classify_direct_owner_candidates(
14255    analyzer: &CppGraphSource<'_>,
14256    candidates: impl Iterator<Item = CodeUnit>,
14257) -> DirectOwnerResolution {
14258    collapse_owner_candidates(candidates.map(|candidate| {
14259        let strength = cpp_class_declaration_strength(analyzer, &candidate);
14260        (candidate, strength)
14261    }))
14262}
14263
14264pub fn collapse_owner_candidates(
14265    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
14266) -> DirectOwnerResolution {
14267    let mut full_definition = None;
14268    let mut forwards = Vec::new();
14269    for (candidate, strength) in candidates {
14270        match strength {
14271            CppClassDeclarationStrength::Full if full_definition.is_some() => {
14272                return DirectOwnerResolution::Ambiguous;
14273            }
14274            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14275            CppClassDeclarationStrength::Forward => forwards.push(candidate),
14276            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
14277        }
14278    }
14279    if let Some(owner) = full_definition {
14280        DirectOwnerResolution::UniqueFull(owner)
14281    } else if !forwards.is_empty() {
14282        DirectOwnerResolution::ForwardsOnly(forwards)
14283    } else {
14284        DirectOwnerResolution::None
14285    }
14286}
14287
14288#[cfg(any(test, feature = "test-support"))]
14289pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14290    unique_logical_forward_owner(forwards)
14291}
14292
14293fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14294    let first = forwards.pop()?;
14295    forwards
14296        .iter()
14297        .all(|forward| same_logical_symbol(forward, &first))
14298        .then_some(first)
14299}
14300
14301pub fn cpp_class_declaration_strength(
14302    analyzer: &CppGraphSource<'_>,
14303    candidate: &CodeUnit,
14304) -> CppClassDeclarationStrength {
14305    if let Some(prepared) = analyzer
14306        .cpp
14307        .and_then(|cpp| cpp.prepared_syntax(analyzer.token, candidate.source()))
14308    {
14309        return cpp_class_declaration_strength_in_tree(
14310            analyzer,
14311            candidate,
14312            prepared.source(),
14313            prepared.tree().root_node(),
14314        );
14315    }
14316    let Some(source) = analyzer.indexed_source(candidate.source()) else {
14317        return CppClassDeclarationStrength::Unknown;
14318    };
14319    #[cfg(any(test, feature = "test-support"))]
14320    if let Some(cpp) = analyzer.cpp {
14321        cpp.record_cpp_class_strength_parse_for_test();
14322    }
14323    let mut parser = Parser::new();
14324    if parser
14325        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14326        .is_err()
14327    {
14328        return CppClassDeclarationStrength::Unknown;
14329    }
14330    let Some(tree) = parser.parse(&source, None) else {
14331        return CppClassDeclarationStrength::Unknown;
14332    };
14333    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
14334}
14335
14336fn cpp_class_declaration_strength_in_tree(
14337    analyzer: &CppGraphSource<'_>,
14338    candidate: &CodeUnit,
14339    source: &str,
14340    root: Node<'_>,
14341) -> CppClassDeclarationStrength {
14342    let ranges = analyzer.ranges(candidate);
14343    let mut saw_forward = false;
14344    for range in ranges {
14345        let mut stack = vec![root];
14346        while let Some(node) = stack.pop() {
14347            if recovered_function_like_export_class_pair_has_body(
14348                node,
14349                source,
14350                candidate.identifier(),
14351                &range,
14352            ) {
14353                return CppClassDeclarationStrength::Full;
14354            }
14355            if recovered_embedded_function_like_export_class_has_body(
14356                node,
14357                source,
14358                candidate.identifier(),
14359                &range,
14360            ) {
14361                return CppClassDeclarationStrength::Full;
14362            }
14363            if node.start_byte() == range.start_byte
14364                && recovered_fragmented_plain_class_has_body(
14365                    node,
14366                    source,
14367                    candidate.identifier(),
14368                    &range,
14369                )
14370            {
14371                return CppClassDeclarationStrength::Full;
14372            }
14373            // Macro-decorated exported classes are recovered from a malformed
14374            // function_definition/declaration wrapper. Their indexed class range starts at
14375            // the displaced class name, while the wrapper starts at `class EXPORT`; recovery
14376            // may also extend the indexed range beyond the wrapper through trailing class
14377            // fragments. Match the structured container that owns the range start by its
14378            // recovered name instead of requiring identical boundaries.
14379            if node.start_byte() <= range.start_byte
14380                && range.start_byte < node.end_byte()
14381                && let Some(has_body) =
14382                    recovered_exported_class_has_body(node, source, candidate.identifier())
14383            {
14384                if has_body {
14385                    return CppClassDeclarationStrength::Full;
14386                }
14387                saw_forward = true;
14388                continue;
14389            }
14390            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
14391                continue;
14392            }
14393            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
14394                if matches!(
14395                    node.kind(),
14396                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14397                ) {
14398                    if cpp_class_node_has_body(node) {
14399                        return CppClassDeclarationStrength::Full;
14400                    }
14401                    saw_forward = true;
14402                } else if let Some(has_body) =
14403                    recovered_exported_class_has_body(node, source, candidate.identifier())
14404                {
14405                    if has_body {
14406                        return CppClassDeclarationStrength::Full;
14407                    }
14408                    saw_forward = true;
14409                }
14410            }
14411            let mut cursor = node.walk();
14412            stack.extend(node.named_children(&mut cursor));
14413        }
14414    }
14415    if saw_forward {
14416        CppClassDeclarationStrength::Forward
14417    } else {
14418        CppClassDeclarationStrength::Unknown
14419    }
14420}
14421
14422fn cpp_class_node_has_body(node: Node<'_>) -> bool {
14423    node.child_by_field_name("body").is_some() || {
14424        let mut cursor = node.walk();
14425        node.named_children(&mut cursor).any(|child| {
14426            matches!(
14427                child.kind(),
14428                "declaration_list" | "field_declaration_list" | "enumerator_list"
14429            )
14430        })
14431    }
14432}
14433
14434pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14435    if !code_unit.owner_is_type_scope() {
14436        return None;
14437    }
14438    let owner_fq = code_unit.fq().parent()?;
14439    ctx.analyzer
14440        .workspace_definitions()
14441        .exact(&owner_fq)
14442        .into_iter()
14443        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
14444}
14445
14446pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14447    left.kind() == right.kind()
14448        && left.fq_name() == right.fq_name()
14449        && left.signature() == right.signature()
14450        && left.source() == right.source()
14451}
14452
14453pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14454    same_symbol(left, right) || same_logical_symbol(left, right)
14455}
14456
14457pub fn same_visible_global_field_symbol(
14458    analyzer: &CppGraphSource<'_>,
14459    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
14460    left: &CodeUnit,
14461    right: &CodeUnit,
14462) -> bool {
14463    if same_symbol(left, right) {
14464        return true;
14465    }
14466    if !same_logical_symbol(left, right) {
14467        return false;
14468    }
14469    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
14470        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
14471    {
14472        left.source() == right.source()
14473    } else {
14474        true
14475    }
14476}
14477
14478fn cpp_global_field_has_internal_linkage_cached(
14479    analyzer: &CppGraphSource<'_>,
14480    cache: &mut HashMap<CodeUnit, bool>,
14481    candidate: &CodeUnit,
14482) -> bool {
14483    if let Some(internal) = cache.get(candidate) {
14484        return *internal;
14485    }
14486    #[cfg(any(test, feature = "test-support"))]
14487    note_cpp_global_field_internal_linkage_classification_for_test();
14488    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
14489    cache.insert(candidate.clone(), internal);
14490    internal
14491}
14492
14493#[cfg(any(test, feature = "test-support"))]
14494thread_local! {
14495    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14496}
14497
14498#[cfg(any(test, feature = "test-support"))]
14499fn note_cpp_global_field_internal_linkage_classification_for_test() {
14500    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14501        count.set(count.get() + 1);
14502    });
14503}
14504
14505#[cfg(any(test, feature = "test-support"))]
14506pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
14507    body: impl FnOnce() -> T,
14508) -> (T, usize) {
14509    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14510        count.set(0);
14511        let result = body();
14512        let observed = count.get();
14513        count.set(0);
14514        (result, observed)
14515    })
14516}
14517
14518pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14519    left.kind() == right.kind()
14520        && left.fq_name() == right.fq_name()
14521        && left.signature() == right.signature()
14522}
14523
14524pub fn cpp_global_field_has_internal_linkage(
14525    analyzer: &CppGraphSource<'_>,
14526    candidate: &CodeUnit,
14527) -> bool {
14528    if !candidate.is_field() || candidate.short_name().contains('.') {
14529        return false;
14530    }
14531    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
14532        return false;
14533    };
14534    match local_linkage {
14535        CppFieldLinkage::Internal => true,
14536        CppFieldLinkage::External => false,
14537        CppFieldLinkage::InternalUnlessExternalPeer => {
14538            !cpp_global_field_linkage_peers(analyzer, candidate)
14539                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
14540                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
14541        }
14542    }
14543}
14544
14545fn cpp_global_field_linkage_peers<'a>(
14546    analyzer: &CppGraphSource<'a>,
14547    candidate: &'a CodeUnit,
14548) -> impl Iterator<Item = CodeUnit> + 'a {
14549    let name = candidate.fq().clone();
14550    analyzer
14551        .workspace_definitions()
14552        .exact(&name)
14553        .into_iter()
14554        .filter(move |peer| {
14555            if peer == candidate {
14556                return false;
14557            }
14558            #[cfg(any(test, feature = "test-support"))]
14559            note_cpp_global_field_linkage_peer_inspection_for_test();
14560            same_logical_symbol(peer, candidate)
14561        })
14562}
14563
14564#[cfg(any(test, feature = "test-support"))]
14565thread_local! {
14566    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14567}
14568
14569#[cfg(any(test, feature = "test-support"))]
14570fn note_cpp_global_field_linkage_peer_inspection_for_test() {
14571    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14572        count.set(count.get() + 1);
14573    });
14574}
14575
14576#[cfg(any(test, feature = "test-support"))]
14577pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
14578    body: impl FnOnce() -> T,
14579) -> (T, usize) {
14580    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14581        count.set(0);
14582        let result = body();
14583        let observed = count.get();
14584        count.set(0);
14585        (result, observed)
14586    })
14587}
14588
14589fn cpp_global_field_declaration_linkage(
14590    analyzer: &CppGraphSource<'_>,
14591    candidate: &CodeUnit,
14592) -> Option<CppFieldLinkage> {
14593    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
14594        return Some(linkage);
14595    }
14596    let cpp = analyzer.cpp?;
14597    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
14598        return cpp_global_field_declaration_linkage_in_tree(
14599            analyzer,
14600            candidate,
14601            prepared.source(),
14602            prepared.tree().root_node(),
14603        );
14604    }
14605    let source = analyzer.indexed_source(candidate.source())?;
14606    let mut parser = Parser::new();
14607    if parser
14608        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14609        .is_err()
14610    {
14611        return None;
14612    }
14613    let tree = parser.parse(&source, None)?;
14614    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
14615}
14616
14617fn cpp_global_field_declaration_linkage_in_tree(
14618    analyzer: &CppGraphSource<'_>,
14619    candidate: &CodeUnit,
14620    source: &str,
14621    root: Node<'_>,
14622) -> Option<CppFieldLinkage> {
14623    analyzer.ranges(candidate).iter().find_map(|range| {
14624        node_for_exact_range(root, range)
14625            .and_then(enclosing_cpp_field_declaration)
14626            .map(|declaration| {
14627                // One question about one declaration; see `ParentIndex::unindexed`.
14628                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
14629            })
14630    })
14631}
14632
14633fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
14634    loop {
14635        if matches!(node.kind(), "declaration" | "field_declaration") {
14636            return Some(node);
14637        }
14638        node = node.parent()?;
14639    }
14640}
14641
14642#[cfg(test)]
14643mod tests {
14644    use super::*;
14645
14646    #[test]
14647    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
14648        let source = "int size(void) { return sizeof(((Payload))); }\n";
14649        let mut parser = Parser::new();
14650        parser
14651            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14652            .expect("C++ grammar");
14653        let tree = parser.parse(source, None).expect("fixture tree");
14654        let start = source.find("Payload").expect("sizeof operand");
14655        let node = tree
14656            .root_node()
14657            .named_descendant_for_byte_range(start, start + "Payload".len())
14658            .expect("focused operand");
14659        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
14660        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
14661
14662        assert_eq!(node.kind(), "identifier");
14663        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
14664        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
14665    }
14666
14667    #[test]
14668    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
14669        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
14670        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
14671        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
14672        assert!(indexed_namespace_path_is_recoverable(
14673            &["cache".to_string()],
14674            &indexed,
14675            1,
14676        ));
14677    }
14678
14679    #[test]
14680    fn sort_lookup_units_totally_orders_every_identity_field() {
14681        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
14682        let base = CodeUnit::with_signature(
14683            file.clone(),
14684            CodeUnitType::Function,
14685            "scope",
14686            "value",
14687            Some("()".to_string()),
14688            false,
14689        );
14690        let different_kind = CodeUnit::with_signature(
14691            file.clone(),
14692            CodeUnitType::Field,
14693            "scope",
14694            "value",
14695            Some("()".to_string()),
14696            false,
14697        );
14698        let synthetic = base.with_synthetic(true);
14699
14700        let interner = segment_interner();
14701        let mut member_fq = FqName::new();
14702        member_fq.push(interner.intern("scope", SegmentKind::Package));
14703        member_fq.push(interner.intern("value", SegmentKind::Member));
14704        let different_package_boundary = CodeUnit::from_fq(
14705            file.clone(),
14706            CodeUnitType::Function,
14707            member_fq,
14708            0,
14709            Some("()".to_string()),
14710            false,
14711        );
14712
14713        let mut unknown_fq = FqName::new();
14714        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
14715        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
14716        let different_segment_kind = CodeUnit::from_fq(
14717            file,
14718            CodeUnitType::Function,
14719            unknown_fq,
14720            1,
14721            Some("()".to_string()),
14722            false,
14723        );
14724
14725        let input = vec![
14726            base,
14727            different_kind,
14728            synthetic,
14729            different_package_boundary,
14730            different_segment_kind,
14731        ];
14732        let mut expected = input.clone();
14733        sort_lookup_units(&mut expected);
14734        assert!(expected.windows(2).all(|pair| {
14735            let mut ordered = pair.to_vec();
14736            sort_lookup_units(&mut ordered);
14737            ordered == pair && pair[0] != pair[1]
14738        }));
14739
14740        let mut reversed = input.clone();
14741        reversed.reverse();
14742        sort_lookup_units(&mut reversed);
14743        assert_eq!(reversed, expected);
14744
14745        let mut rotated = input;
14746        rotated.rotate_left(2);
14747        sort_lookup_units(&mut rotated);
14748        assert_eq!(rotated, expected);
14749    }
14750
14751    #[test]
14752    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
14753        let damaged = "#ifndef API_H\n#define API_H\nextern char option_buffer[\n#ifdef FEATURE_X\n    16 +\n#endif\n    1];\n\nvoid target(void);\n#endif\n";
14754        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
14755        let parse = |source: &str| {
14756            let mut parser = Parser::new();
14757            parser
14758                .set_language(&tree_sitter_cpp::LANGUAGE.into())
14759                .expect("C++ grammar");
14760            parser.parse(source, None).expect("fixture tree")
14761        };
14762
14763        let tree = parse(damaged);
14764        let root = tree.root_node();
14765        let target = damaged.find("target").expect("target byte");
14766        let declaration = root
14767            .descendant_for_byte_range(target, target + "target".len())
14768            .and_then(|mut node| {
14769                loop {
14770                    if node.kind() == "declaration" {
14771                        break Some(node);
14772                    }
14773                    node = node.parent()?;
14774                }
14775            })
14776            .expect("declaration after the displaced terminator");
14777        let conditional = declaration
14778            .parent()
14779            .filter(|node| node.kind() == "preproc_ifdef")
14780            .expect("damaged inner conditional");
14781        let outer = conditional
14782            .parent()
14783            .filter(|node| node.kind() == "preproc_ifdef")
14784            .expect("ordinary outer include guard");
14785        let terminator = cpp_displaced_preprocessor_terminator(conditional)
14786            .expect("structured displaced #endif");
14787        assert_eq!(node_text(terminator, damaged), "#endif");
14788        assert!(terminator.end_byte() <= declaration.start_byte());
14789        assert!(!preprocessor_conditional_contains_descendant(
14790            conditional,
14791            declaration
14792        ));
14793        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
14794        assert!(preprocessor_conditional_contains_descendant(
14795            outer,
14796            declaration
14797        ));
14798
14799        let tree = parse(guarded);
14800        let conditional = tree
14801            .root_node()
14802            .named_child(0)
14803            .filter(|node| node.kind() == "preproc_ifdef")
14804            .expect("ordinary conditional");
14805        let declaration = conditional
14806            .named_children(&mut conditional.walk())
14807            .find(|node| node.kind() == "declaration")
14808            .expect("guarded declaration");
14809        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
14810        assert!(preprocessor_conditional_contains_descendant(
14811            conditional,
14812            declaration
14813        ));
14814
14815        let damaged_alternative = format!(
14816            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
14817            "UNUSED(value)\n".repeat(64)
14818        );
14819        let tree = parse(&damaged_alternative);
14820        let conditional = tree
14821            .root_node()
14822            .named_child(0)
14823            .filter(|node| node.kind() == "preproc_ifdef")
14824            .expect("outer conditional with an alternative");
14825        assert!(conditional.has_error());
14826        assert!(conditional.child_by_field_name("alternative").is_some());
14827        assert!(
14828            conditional
14829                .child(conditional.child_count() - 1)
14830                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
14831        );
14832        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
14833
14834        let split_declaration = "struct Node;\n\ntypedef\n  #ifdef FEATURE_X\n    struct Node *\n  #else\n    UInt32\n  #endif\n  NodeRef;\n\nstatic int target(void) { return 1; }\n#ifdef LATER\nint later;\n#endif\n";
14835        let tree = parse(split_declaration);
14836        let root = tree.root_node();
14837        let conditional = root
14838            .named_children(&mut root.walk())
14839            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
14840            .expect("split declaration conditional");
14841        let target = split_declaration
14842            .find("static int target")
14843            .expect("target byte");
14844        let boundary =
14845            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
14846        assert!(boundary.end_byte <= target, "{boundary:?}");
14847        assert_eq!(boundary.end_line, 9, "{boundary:?}");
14848        let target_node = root
14849            .descendant_for_byte_range(target, target + "static".len())
14850            .expect("target node");
14851        assert!(!preprocessor_conditional_contains_descendant(
14852            conditional,
14853            target_node
14854        ));
14855    }
14856
14857    #[test]
14858    fn fragmented_reference_guard_is_recovered() {
14859        let source = "#if HAVE_ONE && HAVE_TWO\nstatic int helper(int value) { return value; }\n#endif\n\nint fragmented(int value) {\n    if (value == 0) {\n        return 0;\n#if HAVE_ONE && HAVE_TWO\n    } else if (value == 1) {\n        return helper(value);\n#endif\n    }\n    return 0;\n}\n";
14860        let mut parser = Parser::new();
14861        parser
14862            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14863            .expect("C++ grammar");
14864        let tree = parser.parse(source, None).expect("fixture tree");
14865        let start = source.rfind("helper").expect("reference byte");
14866        let node = tree
14867            .root_node()
14868            .descendant_for_byte_range(start, start + "helper".len())
14869            .expect("reference node");
14870        let mut expected = HashSet::default();
14871        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
14872            vec![
14873                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
14874                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
14875            ],
14876        )));
14877        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
14878    }
14879
14880    #[test]
14881    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
14882        let source = r#"#ifdef _WIN32
14883#if defined(__cplusplus)
14884extern "C"
14885#endif
14886int platform_api(void);
14887#endif
14888
14889#ifdef _WIN32
14890static int entropy_target(void) { return 0; }
14891#else
14892#ifdef HAVE_COMMON_RANDOM
14893static int other_target(void) { return 0; }
14894#elif defined(HAVE_GETENTROPY)
14895static int entropy_target(void) { return 1; }
14896static int use_entropy(void) { return entropy_target(); }
14897#endif
14898#endif
14899"#;
14900        let mut parser = Parser::new();
14901        parser
14902            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14903            .expect("C++ grammar");
14904        let tree = parser.parse(source, None).expect("fixture tree");
14905        let start = source.rfind("entropy_target()").expect("reference");
14906        let node = tree
14907            .root_node()
14908            .descendant_for_byte_range(start, start + "entropy_target".len())
14909            .expect("reference node");
14910        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
14911        assert!(
14912            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
14913            "{guards:#?}"
14914        );
14915        assert!(
14916            guards.contains(&PreprocessorGuard::Undefined(
14917                "HAVE_COMMON_RANDOM".to_string()
14918            )),
14919            "{guards:#?}"
14920        );
14921        assert!(
14922            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
14923            "{guards:#?}"
14924        );
14925        assert!(
14926            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
14927            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
14928        );
14929    }
14930
14931    #[test]
14932    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
14933        let source = "#define KEY 42\n#ifdef ENABLE_KEYS\nint classify(int value) {\n    switch (value) {\n        case KEY: return 1;\n        default: return 0;\n    }\n}\n#endif\n";
14934        let mut parser = Parser::new();
14935        parser
14936            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14937            .expect("C++ grammar");
14938        let tree = parser.parse(source, None).expect("fixture tree");
14939        let root = tree.root_node();
14940        let node_at = |text: &str, start: usize| {
14941            root.descendant_for_byte_range(start, start + text.len())
14942                .expect("token node")
14943        };
14944
14945        let key_start = source.find("case KEY").expect("case label") + "case ".len();
14946        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
14947        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
14948        assert!(!is_ordinary_macro_reference_node(node_at(
14949            "ENABLE_KEYS",
14950            guard_start,
14951        )));
14952    }
14953
14954    #[test]
14955    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
14956        let source = "#if HAVE_ARM_NEON\nstatic int target(void) { return 1; }\n#endif\n#if HAVE_ARM_NEON && ENABLE_FAST_PATH\nint use(void) { return target(); }\n#endif\n";
14957        let mut parser = Parser::new();
14958        parser
14959            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14960            .expect("C++ grammar");
14961        let tree = parser.parse(source, None).expect("fixture tree");
14962        let root = tree.root_node();
14963        let definition_start = source.find("target(void)").expect("definition");
14964        let reference_start = source.rfind("target()").expect("reference");
14965        let definition = root
14966            .descendant_for_byte_range(definition_start, definition_start + "target".len())
14967            .expect("definition node");
14968        let reference = root
14969            .descendant_for_byte_range(reference_start, reference_start + "target".len())
14970            .expect("reference node");
14971        let required =
14972            preprocessor_guard_environment(definition, source).expect("definition guard");
14973        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
14974        assert!(guard_requirements_hold_at_reference(
14975            &required,
14976            Some(&active)
14977        ));
14978    }
14979
14980    #[test]
14981    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
14982        let source = "g_autoptr(FuChunkArray) self = make_array();";
14983        let mut parser = Parser::new();
14984        parser
14985            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14986            .expect("C++ grammar");
14987        let tree = parser.parse(source, None).expect("fixture tree");
14988        let statement = tree.root_node().named_child(0).expect("statement");
14989        let binding =
14990            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
14991        assert_eq!(binding.name, "self");
14992        assert_eq!(binding.type_name, "FuChunkArray");
14993        assert_eq!(binding.pointer_depth, 1);
14994
14995        let near_miss = "holder(FuChunkArray) self = make_array();";
14996        let tree = parser.parse(near_miss, None).expect("near-miss tree");
14997        let statement = tree.root_node().named_child(0).expect("statement");
14998        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
14999    }
15000
15001    #[test]
15002    fn boolean_guard_normalization_proves_equivalence_and_implication() {
15003        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
15004        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
15005        let negated_windows_branch =
15006            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
15007        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
15008        assert_eq!(negated_windows_branch, portable);
15009
15010        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
15011        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
15012        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
15013        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
15014        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
15015        assert!(fallback_branch.implies(&fallback_declaration));
15016        assert!(
15017            BooleanGuardExpression::Truthy("FEATURE".to_string())
15018                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
15019        );
15020        assert!(
15021            BooleanGuardExpression::Undefined("FEATURE".to_string())
15022                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
15023        );
15024        assert!(
15025            !BooleanGuardExpression::Defined("FEATURE".to_string())
15026                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
15027        );
15028        assert!(!fallback_declaration.implies(&fallback_branch));
15029    }
15030
15031    #[test]
15032    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
15033        let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n    return helper(NULL, template); /* bound */\n}\nint unbound(void) {\n    return helper(NULL, template); /* unbound */\n}\n";
15034        let mut parser = Parser::new();
15035        parser
15036            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15037            .expect("C++ grammar");
15038        let tree = parser.parse(source, None).expect("fixture tree");
15039        let root = tree.root_node();
15040        let call = |marker: &str| {
15041            let start = source.find(marker).expect("call marker");
15042            let mut node = root
15043                .descendant_for_byte_range(start, start + "helper".len())
15044                .expect("call name node");
15045            loop {
15046                if node.kind() == "call_expression" {
15047                    break node;
15048                }
15049                node = node.parent().expect("call expression ancestor");
15050            }
15051        };
15052        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
15053        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
15054        let keyword_call = call("helper(NULL, template); /* bound */");
15055        let keyword_arguments = keyword_call
15056            .child_by_field_name("arguments")
15057            .expect("keyword argument list");
15058        assert_eq!(
15059            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
15060            1
15061        );
15062        assert_eq!(
15063            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
15064            0
15065        );
15066
15067        let unbound_call = call("helper(NULL, template); /* unbound */");
15068        let unbound_arguments = unbound_call
15069            .child_by_field_name("arguments")
15070            .expect("unbound argument list");
15071        assert_eq!(
15072            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
15073            0
15074        );
15075    }
15076
15077    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
15078        let mut parser = Parser::new();
15079        parser
15080            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15081            .expect("C++ grammar");
15082        let tree = parser.parse(source, None).expect("C++ fixture tree");
15083        let mut stack = vec![tree.root_node()];
15084        while let Some(node) = stack.pop() {
15085            if node.kind() == "enum_specifier" {
15086                return flattened_macro_namespace_components(node, source);
15087            }
15088            let mut cursor = node.walk();
15089            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
15090            stack.extend(children.into_iter().rev());
15091        }
15092        None
15093    }
15094
15095    #[test]
15096    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
15097        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15098namespace detail
15099{
15100enum class value_t { null };
15101}
15102NLOHMANN_JSON_NAMESPACE_END
15103NLOHMANN_JSON_NAMESPACE_BEGIN
15104namespace next
15105{
15106struct next_type {};
15107}
15108NLOHMANN_JSON_NAMESPACE_END
15109"#;
15110        assert_eq!(
15111            first_enum_flattened_namespace(complete),
15112            Some(vec!["detail".to_string()])
15113        );
15114
15115        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
15116        assert_eq!(
15117            first_enum_flattened_namespace(&stale_end),
15118            Some(vec!["detail".to_string()]),
15119            "a stale end marker before the begin marker must not replace the intended namespace"
15120        );
15121
15122        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15123namespace detail
15124{
15125enum class value_t { null };
15126}
15127struct next_type {};
15128"#;
15129        assert_eq!(first_enum_flattened_namespace(incomplete), None);
15130    }
15131}