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_exported_class_has_body, recovered_fragmented_plain_class_has_body,
13};
14use crate::graph::CppGraphSource;
15use crate::graph::extractor::ScanCtx;
16use crate::graph_support::CppSource;
17use crate::imports::{
18    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
19};
20use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
21use brokk_bifrost_core::analyzer::model::{
22    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
23    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
24};
25use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
26use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
27use brokk_bifrost_core::analyzer::query_token::QueryToken;
28use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
29use brokk_bifrost_core::analyzer::usages::common::same_node;
30use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
31use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
32use brokk_bifrost_core::cancellation::CancellationToken;
33use brokk_bifrost_core::hash::{HashMap, HashSet};
34use std::borrow::Cow;
35#[cfg(any(test, feature = "test-support"))]
36use std::cell::Cell;
37use std::cell::OnceCell;
38use std::cmp::Ordering as CmpOrdering;
39use std::collections::BTreeSet;
40use std::hash::Hash;
41#[cfg(any(test, feature = "test-support"))]
42use std::sync::atomic::{AtomicUsize, Ordering};
43use std::sync::{Arc, Mutex, OnceLock, RwLock};
44use std::thread::ThreadId;
45use tree_sitter::{Node, Parser, Tree};
46
47#[derive(Clone, Copy, PartialEq, Eq)]
48pub enum TargetKind {
49    Type,
50    Constructor,
51    FreeFunction,
52    Method,
53    GlobalField,
54    MemberField,
55    Macro,
56}
57
58pub enum LexicalTypeResolution {
59    Resolved {
60        unit: CodeUnit,
61        components: Vec<String>,
62        candidates: Vec<CodeUnit>,
63    },
64    Ambiguous,
65    Missing,
66}
67
68#[derive(Clone, Copy)]
69enum TypeCandidateResolution<'a> {
70    Canonical,
71    PreserveAlias,
72    PreserveTarget(&'a CodeUnit),
73}
74
75/// Why a name did not reduce to one indexed type declaration.
76///
77/// The two answers are not interchangeable. `Ambiguous` means the index holds
78/// several declarations and the caller must choose; `Unresolvable` means the
79/// index holds none, which is a boundary the workspace cannot see past. A
80/// `using`/`typedef` alias to a template parameter or to a standard-library
81/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
82/// answer with an empty candidate list (#1828).
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84enum TypeCandidateFailure {
85    Ambiguous,
86    Unresolvable,
87}
88
89impl TypeCandidateFailure {
90    fn lexical_resolution(self) -> LexicalTypeResolution {
91        match self {
92            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
93            Self::Unresolvable => LexicalTypeResolution::Missing,
94        }
95    }
96}
97
98pub enum LexicalCallableValueResolution {
99    Type(CodeUnit),
100    FreeFunction(CodeUnit),
101    Ambiguous,
102    Missing,
103}
104
105pub enum UsingEnumMemberResolution {
106    Resolved { owner: CodeUnit, member: CodeUnit },
107    Ambiguous,
108    Missing,
109}
110
111pub enum NamespaceValueResolution {
112    Resolved,
113    Ambiguous,
114    Missing,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum OrdinaryMacroReferenceResolution {
119    Resolved(CodeUnit),
120    Ambiguous,
121    Missing,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub enum RecoveredCReferenceRanges {
126    Complete(Vec<Range>),
127    LimitExceeded,
128}
129
130pub fn resolve_namespace_value(
131    analyzer: &CppGraphSource<'_>,
132    visibility: &VisibilityIndex<'_>,
133    file: &ProjectFile,
134    namespace: &str,
135    name: &str,
136    before_byte: usize,
137) -> NamespaceValueResolution {
138    let mut matches = Vec::new();
139    for candidate in visibility.visible_identifier_candidates(file, name) {
140        if type_owner_of(analyzer, candidate).is_some()
141            || candidate.package_name() != namespace
142            || (candidate.source() == file
143                && !analyzer
144                    .ranges(candidate)
145                    .iter()
146                    .any(|range| range.start_byte < before_byte))
147            || matches
148                .iter()
149                .any(|existing| same_visible_symbol(existing, candidate))
150        {
151            continue;
152        }
153        matches.push(candidate.clone());
154        if matches.len() > 1 {
155            return NamespaceValueResolution::Ambiguous;
156        }
157    }
158    matches
159        .pop()
160        .map(|_| NamespaceValueResolution::Resolved)
161        .unwrap_or(NamespaceValueResolution::Missing)
162}
163
164pub(crate) struct ScopedUsingEnumOwners {
165    scopes: Vec<Vec<CodeUnit>>,
166}
167
168/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
169/// Cross-file and inherited class imports are deliberately not inferred without persisted
170/// evidence; a missing imported enumerator therefore remains unproven rather than being
171/// misresolved.
172pub(crate) struct SemanticUsingEnumOwners {
173    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
174    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
175}
176
177pub(crate) enum SemanticUsingEnumMemberResolution {
178    Class(UsingEnumMemberResolution),
179    Namespace(UsingEnumMemberResolution),
180    Missing,
181}
182
183impl SemanticUsingEnumOwners {
184    pub(crate) fn new() -> Self {
185        Self {
186            class_imports: HashMap::default(),
187            namespace_imports: HashMap::default(),
188        }
189    }
190
191    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
192        let imports = self.class_imports.entry(class).or_default();
193        if !imports
194            .iter()
195            .any(|existing| same_visible_symbol(existing, &enum_owner))
196        {
197            imports.push(enum_owner);
198        }
199    }
200
201    pub fn import_namespace(
202        &mut self,
203        namespace: Vec<String>,
204        declaration_byte: usize,
205        enum_owner: CodeUnit,
206    ) {
207        let imports = self.namespace_imports.entry(namespace).or_default();
208        if !imports
209            .iter()
210            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
211        {
212            imports.push((declaration_byte, enum_owner));
213        }
214    }
215
216    pub fn resolve_member(
217        &self,
218        visibility: &VisibilityIndex<'_>,
219        file: &ProjectFile,
220        class: Option<&CodeUnit>,
221        namespace: &[String],
222        before_byte: usize,
223        name: &str,
224    ) -> SemanticUsingEnumMemberResolution {
225        if let Some(class) = class
226            && let Some((_, imports)) = self
227                .class_imports
228                .iter()
229                .find(|(owner, _)| same_visible_symbol(owner, class))
230        {
231            let resolution =
232                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
233            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
234                return SemanticUsingEnumMemberResolution::Class(resolution);
235            }
236        }
237        for prefix_len in (0..=namespace.len()).rev() {
238            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
239                continue;
240            };
241            let owners = imports
242                .iter()
243                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
244                .map(|(_, owner)| owner);
245            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
246            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
247                return SemanticUsingEnumMemberResolution::Namespace(resolution);
248            }
249        }
250        SemanticUsingEnumMemberResolution::Missing
251    }
252}
253
254fn resolve_using_enum_member_for_owners<'a>(
255    visibility: &VisibilityIndex<'_>,
256    file: &ProjectFile,
257    owners: impl IntoIterator<Item = &'a CodeUnit>,
258    name: &str,
259) -> UsingEnumMemberResolution {
260    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
261    for owner in owners {
262        for member in visibility.visible_members_for_owner_name(file, owner, name) {
263            if !member.is_field()
264                || matches.iter().any(|(existing_owner, existing_member)| {
265                    same_visible_symbol(existing_owner, owner)
266                        && same_visible_symbol(existing_member, member)
267                })
268            {
269                continue;
270            }
271            matches.push((owner.clone(), member.clone()));
272        }
273    }
274    match matches.len() {
275        0 => UsingEnumMemberResolution::Missing,
276        1 => {
277            let (owner, member) = matches.pop().expect("one using-enum match");
278            UsingEnumMemberResolution::Resolved { owner, member }
279        }
280        _ => UsingEnumMemberResolution::Ambiguous,
281    }
282}
283
284impl ScopedUsingEnumOwners {
285    pub(crate) fn new() -> Self {
286        Self {
287            scopes: vec![Vec::new()],
288        }
289    }
290
291    pub fn enter_scope(&mut self) {
292        self.scopes.push(Vec::new());
293    }
294
295    pub fn exit_scope(&mut self) {
296        if self.scopes.len() > 1 {
297            self.scopes.pop();
298        }
299    }
300
301    pub fn import(&mut self, owner: CodeUnit) {
302        let scope = self
303            .scopes
304            .last_mut()
305            .expect("using-enum scope stack is never empty");
306        if !scope
307            .iter()
308            .any(|existing| same_visible_symbol(existing, &owner))
309        {
310            scope.push(owner);
311        }
312    }
313
314    pub fn resolve_member(
315        &self,
316        visibility: &VisibilityIndex<'_>,
317        file: &ProjectFile,
318        name: &str,
319    ) -> UsingEnumMemberResolution {
320        for scope in self.scopes.iter().rev() {
321            let resolution =
322                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
323            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
324                return resolution;
325            }
326        }
327        UsingEnumMemberResolution::Missing
328    }
329}
330
331#[derive(Clone)]
332pub struct TargetSpec {
333    pub target: CodeUnit,
334    pub kind: TargetKind,
335    pub owner: Option<CodeUnit>,
336    pub member_name: String,
337    pub callable_arity: Option<CallableArity>,
338    pub activated_callable_arities: Vec<ActivatedCallableArity>,
339    pub param_types: Option<Vec<String>>,
340    pub enum_owner_kind: EnumOwnerKind,
341    pub owner_is_forward_declaration: bool,
342}
343
344#[derive(Clone, Copy)]
345pub struct ActivatedCallableArity {
346    pub activation_byte: usize,
347    pub arity: CallableArity,
348}
349
350#[derive(Debug, PartialEq, Eq, Hash)]
351pub struct TypeScanKey {
352    target: LogicalSymbolKey,
353    member_name: String,
354}
355
356#[derive(Clone, Debug, PartialEq, Eq, Hash)]
357struct LogicalSymbolKey {
358    kind: CodeUnitType,
359    fq_name: String,
360    signature: Option<String>,
361}
362
363struct ResolvedTypeOwner {
364    unit: CodeUnit,
365    is_forward_declaration: bool,
366}
367
368#[derive(Clone, Copy, PartialEq, Eq)]
369pub enum EnumOwnerKind {
370    Scoped,
371    Unscoped,
372    NonEnum,
373}
374
375impl TargetSpec {
376    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
377        (self.kind == TargetKind::Type).then(|| TypeScanKey {
378            target: logical_symbol_key(&self.target),
379            member_name: self.member_name.clone(),
380        })
381    }
382
383    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
384        if target.is_class() {
385            return Some(Self::new(
386                target.clone(),
387                TargetKind::Type,
388                Some(target.clone()),
389                target.identifier().to_string(),
390                None,
391                None,
392            ));
393        }
394
395        if target.is_field() {
396            // A namespace (module) is not a receiver: a namespace-scoped constant such as
397            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
398            // qualified from outside, exactly like a global. Treating a module owner as a
399            // member-field owner makes the receiver/owner-context match reject every valid
400            // reference, so resolve it as a global field instead.
401            let owner = type_owner_of(analyzer, target);
402            let kind = if owner.is_some() {
403                TargetKind::MemberField
404            } else {
405                TargetKind::GlobalField
406            };
407            let enum_owner_kind = owner
408                .as_ref()
409                .map(|owner| classify_enum_owner(analyzer, owner))
410                .unwrap_or(EnumOwnerKind::NonEnum);
411            let mut spec = Self::new(
412                target.clone(),
413                kind,
414                owner,
415                target.identifier().to_string(),
416                None,
417                None,
418            );
419            spec.enum_owner_kind = enum_owner_kind;
420            return Some(spec);
421        }
422
423        if target.is_function() {
424            // Free functions declared inside a namespace have a module owner; that namespace is
425            // not a call receiver, so resolve them as free functions rather than methods.
426            let owner_resolution = target_type_owner_resolution(analyzer, target);
427            let owner_is_forward_declaration = owner_resolution
428                .as_ref()
429                .is_some_and(|owner| owner.is_forward_declaration);
430            let owner = owner_resolution.map(|owner| owner.unit);
431            let kind = if owner.as_ref().is_some_and(|owner| {
432                target.identifier() == owner.identifier()
433                    || analyzer
434                        .cpp
435                        .and_then(|cpp| cpp.template_metadata(owner))
436                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
437            }) {
438                TargetKind::Constructor
439            } else if owner.is_some() {
440                TargetKind::Method
441            } else {
442                TargetKind::FreeFunction
443            };
444            let mut spec = Self::new(
445                target.clone(),
446                kind,
447                owner,
448                target.identifier().to_string(),
449                Some(cpp_callable_arity(analyzer, target)),
450                cpp_callable_parameter_types(analyzer, target),
451            );
452            spec.owner_is_forward_declaration = owner_is_forward_declaration;
453            return Some(spec);
454        }
455
456        if target.is_macro() {
457            return Some(Self::new(
458                target.clone(),
459                TargetKind::Macro,
460                None,
461                target.identifier().to_string(),
462                None,
463                None,
464            ));
465        }
466
467        None
468    }
469
470    pub fn with_visible_callable_arities<'a>(
471        &'a self,
472        analyzer: &CppGraphSource<'_>,
473        cpp: &dyn CppSource,
474        visibility: &VisibilityIndex<'_>,
475        file: &ProjectFile,
476        prepared: &PreparedSyntaxTree,
477    ) -> Cow<'a, Self> {
478        let macro_parameter_arity =
479            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
480        let activated_callable_arities =
481            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
482        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
483            return Cow::Borrowed(self);
484        }
485        let mut effective = self.clone();
486        if let Some(macro_parameter_arity) = macro_parameter_arity {
487            effective.callable_arity = Some(macro_parameter_arity);
488        }
489        effective.activated_callable_arities = activated_callable_arities;
490        Cow::Owned(effective)
491    }
492
493    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
494        let base = self.callable_arity?;
495        Some(
496            self.activated_callable_arities
497                .iter()
498                .filter(|candidate| candidate.activation_byte <= byte)
499                .fold(base, |arity, candidate| {
500                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
501                }),
502        )
503    }
504
505    pub fn new(
506        target: CodeUnit,
507        kind: TargetKind,
508        owner: Option<CodeUnit>,
509        member_name: String,
510        callable_arity: Option<CallableArity>,
511        param_types: Option<Vec<String>>,
512    ) -> Self {
513        Self {
514            target,
515            kind,
516            owner,
517            member_name,
518            callable_arity,
519            activated_callable_arities: Vec::new(),
520            param_types,
521            enum_owner_kind: EnumOwnerKind::NonEnum,
522            owner_is_forward_declaration: false,
523        }
524    }
525}
526
527fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
528    LogicalSymbolKey {
529        kind: unit.kind(),
530        fq_name: unit.fq_name(),
531        signature: unit.signature().map(str::to_string),
532    }
533}
534
535fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
536    let classify = |source: &str| {
537        let source = source.trim_start();
538        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
539            Some(EnumOwnerKind::Scoped)
540        } else if source.starts_with("enum ") {
541            Some(EnumOwnerKind::Unscoped)
542        } else {
543            None
544        }
545    };
546    owner
547        .signature()
548        .and_then(classify)
549        .or_else(|| {
550            analyzer
551                .get_source(owner, false)
552                .as_deref()
553                .and_then(classify)
554        })
555        .unwrap_or(EnumOwnerKind::NonEnum)
556}
557
558#[derive(Clone, PartialEq, Eq, Hash)]
559pub struct CppScanBinding {
560    pub unit: Option<CodeUnit>,
561    pub type_name: Option<String>,
562    pub indirection: i32,
563}
564
565impl CppScanBinding {
566    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
567        Self {
568            type_name: Some(cpp_name_for(&unit)),
569            unit: Some(unit),
570            indirection,
571        }
572    }
573
574    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
575        Self {
576            type_name: Some(type_name),
577            unit,
578            indirection,
579        }
580    }
581
582    pub fn as_arg_type(&self) -> Option<CppArgType> {
583        let name = self
584            .type_name
585            .clone()
586            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
587        Some(CppArgType {
588            name,
589            unit: self.unit.clone(),
590            indirection: self.indirection,
591            pointee_const: false,
592        })
593    }
594}
595
596type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
597type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
598pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
599pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
600type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
601pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
602type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
603type MacroLocalBindingTemplateCache =
604    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
605
606#[derive(Clone, Default)]
607pub struct MacroEnvironment {
608    bindings: HashMap<String, MacroBinding>,
609    known_undefined_names: HashSet<String>,
610    /// Names the translation unit's compile command proves defined (#2011):
611    /// the `-D`s that survive command ordering, intersected across every
612    /// configuration naming the TU. Seeded once at TU start. An explicit
613    /// `#undef` seen later lands in `known_undefined_names` and wins.
614    build_proven_defines: HashSet<String>,
615    unknown_names: bool,
616    applied_pragma_once_files: HashSet<ProjectFile>,
617    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
618}
619
620#[derive(Default)]
621pub struct MacroEnvironmentCursor {
622    frontier: usize,
623    environment: Arc<MacroEnvironment>,
624}
625
626impl MacroEnvironment {
627    fn binding(&self, name: &str) -> Option<&MacroBinding> {
628        self.bindings.get(name)
629    }
630
631    fn may_bind(&self, name: &str) -> bool {
632        self.bindings.contains_key(name) || self.unknown_names
633    }
634
635    fn insert(&mut self, name: String, binding: MacroBinding) {
636        self.known_undefined_names.remove(&name);
637        self.bindings.insert(name, binding);
638    }
639
640    fn remove(&mut self, name: &str) {
641        self.bindings.remove(name);
642        self.known_undefined_names.insert(name.to_string());
643    }
644
645    fn remove_known_undefined(&mut self, name: &str) {
646        self.known_undefined_names.remove(name);
647    }
648
649    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
650        for binding in self.bindings.values_mut() {
651            *binding = MacroBinding::uncertain_from(binding, source, byte);
652        }
653        self.known_undefined_names.clear();
654        // An untracked include could `#undef` a command-line define, so the
655        // may-hold filter must stop treating the build facts as decisive from
656        // here on. The additive proof path keeps its facts: they still hold at
657        // the include chain's activation point.
658        self.build_proven_defines.clear();
659        self.unknown_names = true;
660    }
661
662    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
663        guards.iter().all(|guard| self.guard_may_hold(guard))
664    }
665
666    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
667        let Some(expression) = guard.as_boolean_expression() else {
668            return true;
669        };
670        self.boolean_guard_may_hold(&expression)
671    }
672
673    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
674        match expression {
675            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
676            BooleanGuardExpression::Undefined(name) => {
677                self.bindings
678                    .get(name)
679                    .is_none_or(|binding| !binding.is_exact())
680                    && (!self.build_proven_defines.contains(name)
681                        || self.known_undefined_names.contains(name))
682            }
683            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
684            BooleanGuardExpression::Opaque(_)
685            | BooleanGuardExpression::NegatedOpaque(_)
686            | BooleanGuardExpression::Constant(true) => true,
687            BooleanGuardExpression::Constant(false) => false,
688            BooleanGuardExpression::All(expressions) => expressions
689                .iter()
690                .all(|expression| self.boolean_guard_may_hold(expression)),
691            BooleanGuardExpression::Any(expressions) => expressions
692                .iter()
693                .any(|expression| self.boolean_guard_may_hold(expression)),
694        }
695    }
696}
697
698#[derive(Clone)]
699pub enum EffectiveUsingTarget {
700    Ordinary {
701        name: String,
702        target_components: Vec<String>,
703        global: bool,
704    },
705    Namespace {
706        namespace_components: Vec<String>,
707        global: bool,
708    },
709}
710
711#[derive(Clone)]
712pub struct OrdinaryTypeImport {
713    pub target: EffectiveUsingTarget,
714    pub source: ProjectFile,
715    pub declaration_byte: usize,
716    pub scope_start: usize,
717    pub scope_end: usize,
718    pub scope_depth: usize,
719    pub block_scope: bool,
720    pub lexical_depth: usize,
721    pub declaration_namespace: Vec<String>,
722    pub namespace_scope: Option<Vec<String>>,
723    pub resolved_target_components: Option<Vec<String>>,
724    pub required_guards: HashSet<PreprocessorGuard>,
725}
726
727#[derive(Clone)]
728pub struct ConditionalIncludeProjection {
729    pub activation_byte: usize,
730    pub required_guards: HashSet<PreprocessorGuard>,
731}
732
733#[derive(Default)]
734pub struct SourceUsingIndex {
735    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
736    pub directives: Vec<OrdinaryTypeImport>,
737}
738
739#[derive(Default)]
740pub struct ProjectUsingIndex {
741    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
742    pub directives: Vec<OrdinaryTypeImport>,
743}
744
745type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
746
747pub struct EffectiveUsingIndex {
748    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
749}
750
751impl EffectiveUsingIndex {
752    fn new(_root: ProjectFile) -> Self {
753        Self {
754            projected_by_name: Mutex::new(HashMap::default()),
755        }
756    }
757
758    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
759        self.projected_by_name
760            .lock()
761            .expect("C++ effective-using projection cache poisoned")
762            .entry(name.to_string())
763            .or_default()
764            .clone()
765    }
766}
767
768pub enum OrdinaryTypeImportResolution {
769    Resolved {
770        target: CodeUnit,
771        target_components: Vec<String>,
772        lexical_depth: usize,
773        is_direct: bool,
774    },
775    Ambiguous {
776        lexical_depth: usize,
777    },
778    Missing,
779}
780
781type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
782type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
783type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
784type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
785type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
786type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
787type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
788
789/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
790/// read from its declaration syntax rather than from its persisted signature
791/// string: the comparable shape of each parameter, and the trailing identity
792/// suffix that shape does not carry.
793struct ExtractedComparable {
794    shapes: Vec<CppComparableSlot>,
795    suffix: String,
796}
797
798/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
799/// before giving up on a written type name. A visited set already stops a
800/// cycle; this stops an adversarially long chain from costing a lookup per hop.
801const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
802
803/// Per-query C++ visibility facts.
804///
805/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
806/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
807/// generations and overlays, where another generation's hydrated states would
808/// be wrong). An index that owned a clone would therefore see an inactive read
809/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
810/// the same source from the store once per candidate instead of once per query
811/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
812/// tens of thousands of times.
813pub struct VisibilityIndex<'a> {
814    cpp: &'a dyn CppSource,
815    /// Proof that the request scope the index was built under is still open.
816    /// The index is a per-query object whose lifetime is inside the scope's,
817    /// so carrying the token here instead of on ninety method signatures is
818    /// the same guarantee for far less plumbing (issue #2414 step 3).
819    token: QueryToken<'a>,
820    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
821    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
822    global_field_internal_linkage: HashMap<CodeUnit, bool>,
823    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
824    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
825    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
826    visible_parser_alias_target_names:
827        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
828    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
829    project_using_index: OnceLock<ProjectUsingIndex>,
830    callable_reference_specs:
831        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
832    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
833    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
834    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
835    #[cfg(any(test, feature = "test-support"))]
836    conditional_include_projection_index_build_count: AtomicUsize,
837    #[cfg(any(test, feature = "test-support"))]
838    conditional_include_projection_state_count: AtomicUsize,
839    #[cfg(any(test, feature = "test-support"))]
840    include_activation_build_count: AtomicUsize,
841    #[cfg(any(test, feature = "test-support"))]
842    using_donor_activation_count: AtomicUsize,
843    #[cfg(any(test, feature = "test-support"))]
844    using_namespace_lookup_count: AtomicUsize,
845    #[cfg(any(test, feature = "test-support"))]
846    using_name_candidate_inspection_count: AtomicUsize,
847    #[cfg(any(test, feature = "test-support"))]
848    callable_reference_spec_build_count: AtomicUsize,
849    #[cfg(any(test, feature = "test-support"))]
850    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
851    #[cfg(any(test, feature = "test-support"))]
852    visible_parser_alias_name_set_build_count: AtomicUsize,
853    #[cfg(any(test, feature = "test-support"))]
854    visible_parser_alias_target_names_build_count: AtomicUsize,
855    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
856    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
857    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
858    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
859    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
860    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
861    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
862    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
863    // A forward cursor is useful only while its caller visits one source in byte order. The
864    // authoritative differential shares this index across target workers, whose frontiers can
865    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
866    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
867    // immutable event and parse caches above remain shared.
868    pub macro_environment_cursors:
869        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
870    macro_replacements: Mutex<MacroReplacementCache>,
871    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
872    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
873    #[cfg(any(test, feature = "test-support"))]
874    pub macro_replacement_parse_count: AtomicUsize,
875    #[cfg(any(test, feature = "test-support"))]
876    pub macro_event_application_count: AtomicUsize,
877    #[cfg(any(test, feature = "test-support"))]
878    pub macro_environment_copy_count: AtomicUsize,
879    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
880    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
881    #[cfg(any(test, feature = "test-support"))]
882    qualified_candidate_inspections: AtomicUsize,
883    #[cfg(any(test, feature = "test-support"))]
884    target_preserving_type_resolution_count: AtomicUsize,
885}
886
887#[derive(Clone, Debug, PartialEq, Eq, Hash)]
888pub enum PreprocessorGuard {
889    Defined(String),
890    Undefined(String),
891    Boolean(BooleanGuardExpression),
892    Expression(String),
893    NegatedExpression(String),
894    Constant(bool),
895}
896
897#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
898pub enum BooleanGuardExpression {
899    Defined(String),
900    Undefined(String),
901    Truthy(String),
902    Falsy(String),
903    Opaque(String),
904    NegatedOpaque(String),
905    All(Vec<BooleanGuardExpression>),
906    Any(Vec<BooleanGuardExpression>),
907    Constant(bool),
908}
909
910impl BooleanGuardExpression {
911    fn negated(&self) -> Self {
912        match self {
913            Self::Defined(name) => Self::Undefined(name.clone()),
914            Self::Undefined(name) => Self::Defined(name.clone()),
915            Self::Truthy(name) => Self::Falsy(name.clone()),
916            Self::Falsy(name) => Self::Truthy(name.clone()),
917            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
918            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
919            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
920            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
921            Self::Constant(value) => Self::Constant(!value),
922        }
923    }
924
925    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
926        Self::normalized(expressions, true)
927    }
928
929    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
930        Self::normalized(expressions, false)
931    }
932
933    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
934        let mut normalized = Vec::new();
935        for expression in expressions {
936            match expression {
937                Self::All(nested) if conjunction => normalized.extend(nested),
938                Self::Any(nested) if !conjunction => normalized.extend(nested),
939                Self::Constant(value) if value == conjunction => {}
940                Self::Constant(value) => return Self::Constant(value),
941                expression => normalized.push(expression),
942            }
943        }
944        normalized.sort_unstable();
945        normalized.dedup();
946        match normalized.len() {
947            0 => Self::Constant(conjunction),
948            1 => normalized.pop().expect("one Boolean guard expression"),
949            _ if conjunction => Self::All(normalized),
950            _ => Self::Any(normalized),
951        }
952    }
953
954    fn implies(&self, required: &Self) -> bool {
955        if self == required
956            || matches!(self, Self::Constant(false))
957            || matches!(required, Self::Constant(true))
958        {
959            return true;
960        }
961        match self {
962            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
963            Self::All(active) => match required {
964                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
965                _ => active.iter().any(|expression| expression.implies(required)),
966            },
967            _ => match required {
968                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
969                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
970                _ => false,
971            },
972        }
973    }
974
975    pub fn heap_size(&self) -> usize {
976        match self {
977            Self::Defined(value)
978            | Self::Undefined(value)
979            | Self::Truthy(value)
980            | Self::Falsy(value)
981            | Self::Opaque(value)
982            | Self::NegatedOpaque(value) => value.len(),
983            Self::All(expressions) | Self::Any(expressions) => {
984                expressions
985                    .iter()
986                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
987                        size.saturating_add(std::mem::size_of::<Self>())
988                            .saturating_add(expression.heap_size())
989                    })
990            }
991            Self::Constant(_) => 0,
992        }
993    }
994}
995
996impl PreprocessorGuard {
997    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
998        match self {
999            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1000            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1001            Self::Boolean(expression) => Some(expression.clone()),
1002            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1003            Self::Expression(_) | Self::NegatedExpression(_) => None,
1004        }
1005    }
1006
1007    fn negated(&self) -> Self {
1008        match self {
1009            Self::Defined(name) => Self::Undefined(name.clone()),
1010            Self::Undefined(name) => Self::Defined(name.clone()),
1011            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1012            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1013            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1014            Self::Constant(value) => Self::Constant(!value),
1015        }
1016    }
1017
1018    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1019        match self {
1020            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1021            // The expression has already been isolated structurally by
1022            // tree-sitter, but its full preprocessor semantics are outside the
1023            // analyzer's guard model. Any macro mutation can therefore change
1024            // its truth value.
1025            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1026            Self::Constant(_) => false,
1027        }
1028    }
1029}
1030
1031#[derive(Clone, PartialEq, Eq)]
1032pub enum MacroDefinition {
1033    Object {
1034        replacement: String,
1035    },
1036    Function {
1037        parameters: Vec<String>,
1038        replacement: String,
1039    },
1040    Unsupported,
1041}
1042
1043#[derive(Clone, Debug, PartialEq, Eq)]
1044pub enum MacroIncludeProtection {
1045    MacroGuard(String),
1046    PragmaOnce,
1047    None,
1048}
1049
1050enum ParsedMacroReplacement {
1051    Parsed { source: String, tree: Tree },
1052    Unsupported,
1053}
1054
1055#[derive(Clone)]
1056enum MacroLocalBindingTypeTemplate {
1057    Parameter(usize),
1058    Fixed(String),
1059}
1060
1061#[derive(Clone)]
1062struct MacroLocalBindingTemplate {
1063    name: String,
1064    declared_type: MacroLocalBindingTypeTemplate,
1065    pointer_depth: i32,
1066}
1067
1068/// A local declaration contributed by one structurally known function-like macro.
1069///
1070/// `type_node` points into the invocation syntax when the replacement's type
1071/// is one of the macro parameters. Consumers can therefore use their normal
1072/// lexical type resolver without parsing replacement text themselves.
1073pub struct MacroLocalBinding<'tree> {
1074    pub name: String,
1075    pub type_name: String,
1076    pub type_node: Option<Node<'tree>>,
1077    pub pointer_depth: i32,
1078}
1079
1080/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1081/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1082/// invocation as the assignment's left operand and the declared name as one
1083/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1084fn recognized_c_macro_declarator_binding<'tree>(
1085    statement: Node<'tree>,
1086    source: &str,
1087) -> Option<MacroLocalBinding<'tree>> {
1088    let assignment = match statement.kind() {
1089        "assignment_expression" => statement,
1090        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1091        _ => return None,
1092    };
1093    if assignment.kind() != "assignment_expression" {
1094        return None;
1095    }
1096    let call = assignment.child_by_field_name("left")?;
1097    if call.kind() != "call_expression" {
1098        return None;
1099    }
1100    let function = call.child_by_field_name("function")?;
1101    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1102        return None;
1103    }
1104    let arguments = call.child_by_field_name("arguments")?;
1105    let mut actuals = argument_children(arguments);
1106    let type_node = actuals.next()?;
1107    if actuals.next().is_some()
1108        || !matches!(
1109            type_node.kind(),
1110            "identifier"
1111                | "type_identifier"
1112                | "qualified_identifier"
1113                | "scoped_type_identifier"
1114                | "template_type"
1115        )
1116    {
1117        return None;
1118    }
1119    let name_node = (0..assignment.named_child_count())
1120        .filter_map(|index| assignment.named_child(index))
1121        .filter(|child| child.kind() == "ERROR")
1122        .filter_map(|error| {
1123            (error.named_child_count() == 1)
1124                .then(|| error.named_child(0))
1125                .flatten()
1126        })
1127        .find(|node| node.kind() == "identifier")?;
1128    let name = node_text(name_node, source).trim();
1129    let type_name = node_text(type_node, source).trim();
1130    if name.is_empty() || type_name.is_empty() {
1131        return None;
1132    }
1133    Some(MacroLocalBinding {
1134        name: name.to_string(),
1135        type_name: type_name.to_string(),
1136        type_node: Some(type_node),
1137        pointer_depth: 1,
1138    })
1139}
1140
1141#[derive(Clone, PartialEq, Eq)]
1142pub struct MacroBinding {
1143    source: ProjectFile,
1144    declaration_byte: usize,
1145    definition: MacroDefinition,
1146    exact: bool,
1147}
1148
1149impl MacroBinding {
1150    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1151        Self {
1152            source: source.clone(),
1153            declaration_byte,
1154            definition: MacroDefinition::Unsupported,
1155            exact: false,
1156        }
1157    }
1158
1159    fn is_exact(&self) -> bool {
1160        self.exact
1161    }
1162
1163    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1164        Self {
1165            source: source.clone(),
1166            declaration_byte,
1167            definition: current.definition.clone(),
1168            exact: false,
1169        }
1170    }
1171}
1172
1173#[derive(Clone)]
1174pub enum MacroEvent {
1175    Define {
1176        name: String,
1177        binding: MacroBinding,
1178        byte: usize,
1179        conditional: bool,
1180    },
1181    Undef {
1182        name: String,
1183        byte: usize,
1184        conditional: bool,
1185    },
1186    Include {
1187        targets: Vec<ProjectFile>,
1188        byte: usize,
1189        conditional: bool,
1190    },
1191    Invalidate {
1192        byte: usize,
1193    },
1194}
1195
1196impl MacroEvent {
1197    pub fn byte(&self) -> usize {
1198        match self {
1199            Self::Define { byte, .. }
1200            | Self::Undef { byte, .. }
1201            | Self::Include { byte, .. }
1202            | Self::Invalidate { byte } => *byte,
1203        }
1204    }
1205}
1206
1207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1208pub enum CallArityEvidence {
1209    Exact(usize),
1210    Unknown,
1211}
1212
1213impl CallArityEvidence {
1214    pub fn exact(self) -> Option<usize> {
1215        match self {
1216            Self::Exact(arity) => Some(arity),
1217            Self::Unknown => None,
1218        }
1219    }
1220
1221    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1222        self.exact().map(|arity| expected.accepts(arity))
1223    }
1224}
1225
1226#[derive(Clone)]
1227struct DeclaredFieldTypeFact {
1228    type_text: String,
1229    indirection: i32,
1230    template_arguments: Option<Vec<CppTemplateExpression>>,
1231}
1232
1233#[derive(Clone, PartialEq, Eq)]
1234enum StructuredAliasTarget {
1235    Builtin,
1236    Named {
1237        components: Vec<String>,
1238        global: bool,
1239        arguments: Option<Vec<CppTemplateExpression>>,
1240    },
1241}
1242
1243struct CppAlias {
1244    name: String,
1245    target: String,
1246    namespace: Option<String>,
1247}
1248
1249type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1250
1251/// Why template-argument resolution failed. Definition diagnostics render
1252/// each mode differently; graph scans only care that the resolution is
1253/// unproven and match `Err(_)`.
1254#[derive(Debug, Clone, PartialEq, Eq)]
1255pub enum CppTemplateResolutionError {
1256    /// A template alias expansion revisited `alias`.
1257    AliasCycle { alias: CodeUnit },
1258    /// The explicit arguments do not bind to the declared template parameters.
1259    ArgumentBinding,
1260    /// Bound arguments do not substitute into the alias target's arguments.
1261    Substitution,
1262    /// No visible primary template declaration could be selected and
1263    /// reconciled for the specialization family.
1264    PrimarySelection,
1265    /// More than one applicable specialization remains and none is strictly
1266    /// more specialized than every other candidate.
1267    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1268}
1269
1270/// The ambiguity candidates, deduplicated to one representative per visible
1271/// symbol so a diagnostic lists each contender once.
1272fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1273    let mut distinct: Vec<CodeUnit> = Vec::new();
1274    for unit in units {
1275        if !distinct
1276            .iter()
1277            .any(|existing| same_visible_symbol(existing, unit))
1278        {
1279            distinct.push(unit.clone());
1280        }
1281    }
1282    distinct
1283}
1284
1285impl<'a> VisibilityIndex<'a> {
1286    pub fn cpp(&self) -> &'a dyn CppSource {
1287        self.cpp
1288    }
1289
1290    /// The request-scope proof this index was built with (issue #2414 step 3).
1291    pub fn token(&self) -> QueryToken<'a> {
1292        self.token
1293    }
1294
1295    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1296    /// bypassing the include-closure walk [`Self::build`] performs.
1297    ///
1298    /// The resolver's own unit tests drive the type-resolution paths against a
1299    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1300    /// because they need a real `CppAnalyzer`, so the struct literal they used
1301    /// to write inline is here instead of thirty-three public fields.
1302    #[cfg(any(test, feature = "test-support"))]
1303    pub fn from_visible_files_for_test(
1304        cpp: &'a dyn CppSource,
1305        token: QueryToken<'a>,
1306        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1307    ) -> Self {
1308        let visible_source_files_by_root = visible_by_file
1309            .iter()
1310            .map(|(file, visible)| {
1311                (
1312                    file.clone(),
1313                    visible
1314                        .iter()
1315                        .map(|unit| unit.source().clone())
1316                        .chain(std::iter::once(file.clone()))
1317                        .collect(),
1318                )
1319            })
1320            .collect();
1321        let mut global_field_internal_linkage = HashMap::default();
1322        Self {
1323            cpp,
1324            token,
1325            visible_by_identifier: build_visible_identifier_index(
1326                &CppGraphSource::from_source(cpp, token),
1327                &visible_by_file,
1328                &visible_source_files_by_root,
1329                &mut global_field_internal_linkage,
1330            ),
1331            global_field_internal_linkage,
1332            visible_by_file,
1333            visible_source_files_by_root,
1334            alias_cells: Mutex::new(HashMap::default()),
1335            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1336            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1337            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1338            project_using_index: OnceLock::new(),
1339            callable_reference_specs: Mutex::new(HashMap::default()),
1340            include_activation_cells: Mutex::new(HashMap::default()),
1341            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1342            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1343            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1344            conditional_include_projection_state_count: AtomicUsize::new(0),
1345            include_activation_build_count: AtomicUsize::new(0),
1346            using_donor_activation_count: AtomicUsize::new(0),
1347            using_namespace_lookup_count: AtomicUsize::new(0),
1348            using_name_candidate_inspection_count: AtomicUsize::new(0),
1349            callable_reference_spec_build_count: AtomicUsize::new(0),
1350            alias_source_parse_counts: Mutex::new(HashMap::default()),
1351            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1352            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1353            field_type_facts: Mutex::new(HashMap::default()),
1354            structured_alias_targets: Mutex::new(HashMap::default()),
1355            callable_comparables: Mutex::new(HashMap::default()),
1356            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1357            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1358            precise_parent_cache: Mutex::new(HashMap::default()),
1359            macro_event_cells: Mutex::new(HashMap::default()),
1360            macro_include_protection_cells: Mutex::new(HashMap::default()),
1361            macro_environment_cursors: Mutex::new(HashMap::default()),
1362            macro_replacements: Mutex::new(HashMap::default()),
1363            macro_local_binding_templates: Mutex::new(HashMap::default()),
1364            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1365            macro_replacement_parse_count: AtomicUsize::new(0),
1366            macro_event_application_count: AtomicUsize::new(0),
1367            macro_environment_copy_count: AtomicUsize::new(0),
1368            cpp_template_metadata: HashMap::default(),
1369            cpp_template_families: HashMap::default(),
1370            qualified_candidate_inspections: AtomicUsize::new(0),
1371            target_preserving_type_resolution_count: AtomicUsize::new(0),
1372        }
1373    }
1374
1375    /// The index's own C++ source, in the dispatching-analyzer shape.
1376    ///
1377    /// Four resolution paths reach the workspace through the C++ analyzer they
1378    /// already hold rather than through the analyzer the query was issued
1379    /// against; before the move they passed `&CppAnalyzer` straight into a
1380    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1381    fn cpp_source(&self) -> CppGraphSource<'a> {
1382        CppGraphSource::from_source(self.cpp, self.token)
1383    }
1384
1385    pub fn build(
1386        cpp: &'a dyn CppSource,
1387        token: QueryToken<'a>,
1388        analyzer: &CppGraphSource<'_>,
1389        roots: &HashSet<ProjectFile>,
1390    ) -> Self {
1391        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1392    }
1393
1394    pub fn build_with_cancellation(
1395        cpp: &'a dyn CppSource,
1396        token: QueryToken<'a>,
1397        analyzer: &CppGraphSource<'_>,
1398        roots: &HashSet<ProjectFile>,
1399        cancellation: Option<&CancellationToken>,
1400    ) -> Self {
1401        let include_targets = cpp.include_target_index();
1402        let VisibilityData {
1403            mut visible_by_file,
1404            visible_source_files_by_root,
1405        } = build_visibility_data(
1406            roots,
1407            cancellation,
1408            |file| {
1409                let imports = analyzer.import_statements(file);
1410                cpp_include_paths(&imports)
1411                    .into_iter()
1412                    .flat_map(|include| {
1413                        resolve_include_targets_with_index(file, &include, include_targets)
1414                    })
1415                    .collect()
1416            },
1417            |root| analyzer.reference_uses_c_semantics(root),
1418            |file, c_semantics| analyzer.declarations_in_reading(file, c_semantics),
1419        );
1420        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1421        let mut global_field_internal_linkage = HashMap::default();
1422        let visible_by_identifier = build_visible_identifier_index(
1423            analyzer,
1424            &visible_by_file,
1425            &visible_source_files_by_root,
1426            &mut global_field_internal_linkage,
1427        );
1428        let mut cpp_template_metadata = HashMap::default();
1429        for unit in visible_by_file
1430            .values()
1431            .flatten()
1432            .filter(|unit| unit.is_class())
1433        {
1434            if cpp_template_metadata.contains_key(unit) {
1435                continue;
1436            }
1437            if let Some(metadata) = cpp.template_metadata(unit) {
1438                cpp_template_metadata.insert(unit.clone(), metadata);
1439            }
1440        }
1441        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1442        for (unit, metadata) in &cpp_template_metadata {
1443            cpp_template_families
1444                .entry(metadata.primary_fq_name.clone())
1445                .or_default()
1446                .push(unit.clone());
1447        }
1448        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1449        // order above is a function of those hashes. Two mirrored headers can
1450        // declare one specialization; `select_template_specialization` treats
1451        // them as interchangeable and returns the family's first entry, so an
1452        // unsorted family made the reported declaration depend on the
1453        // workspace's absolute path and on unrelated files (#1836). Order the
1454        // family exactly as `build_visible_identifier_index` orders its
1455        // per-identifier candidate lists.
1456        for family in cpp_template_families.values_mut() {
1457            sort_lookup_units(family);
1458        }
1459        Self {
1460            cpp,
1461            token,
1462            visible_by_file,
1463            visible_by_identifier,
1464            global_field_internal_linkage,
1465            visible_source_files_by_root,
1466            alias_cells: Mutex::new(HashMap::default()),
1467            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1468            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1469            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1470            project_using_index: OnceLock::new(),
1471            callable_reference_specs: Mutex::new(HashMap::default()),
1472            include_activation_cells: Mutex::new(HashMap::default()),
1473            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1474            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1475            #[cfg(any(test, feature = "test-support"))]
1476            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1477            #[cfg(any(test, feature = "test-support"))]
1478            conditional_include_projection_state_count: AtomicUsize::new(0),
1479            #[cfg(any(test, feature = "test-support"))]
1480            include_activation_build_count: AtomicUsize::new(0),
1481            #[cfg(any(test, feature = "test-support"))]
1482            using_donor_activation_count: AtomicUsize::new(0),
1483            #[cfg(any(test, feature = "test-support"))]
1484            using_namespace_lookup_count: AtomicUsize::new(0),
1485            #[cfg(any(test, feature = "test-support"))]
1486            using_name_candidate_inspection_count: AtomicUsize::new(0),
1487            #[cfg(any(test, feature = "test-support"))]
1488            callable_reference_spec_build_count: AtomicUsize::new(0),
1489            #[cfg(any(test, feature = "test-support"))]
1490            alias_source_parse_counts: Mutex::new(HashMap::default()),
1491            #[cfg(any(test, feature = "test-support"))]
1492            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1493            #[cfg(any(test, feature = "test-support"))]
1494            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1495            field_type_facts: Mutex::new(HashMap::default()),
1496            structured_alias_targets: Mutex::new(HashMap::default()),
1497            callable_comparables: Mutex::new(HashMap::default()),
1498            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1499            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1500            precise_parent_cache: Mutex::new(HashMap::default()),
1501            macro_event_cells: Mutex::new(HashMap::default()),
1502            macro_include_protection_cells: Mutex::new(HashMap::default()),
1503            macro_environment_cursors: Mutex::new(HashMap::default()),
1504            macro_replacements: Mutex::new(HashMap::default()),
1505            macro_local_binding_templates: Mutex::new(HashMap::default()),
1506            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1507            #[cfg(any(test, feature = "test-support"))]
1508            macro_replacement_parse_count: AtomicUsize::new(0),
1509            #[cfg(any(test, feature = "test-support"))]
1510            macro_event_application_count: AtomicUsize::new(0),
1511            #[cfg(any(test, feature = "test-support"))]
1512            macro_environment_copy_count: AtomicUsize::new(0),
1513            cpp_template_metadata,
1514            cpp_template_families,
1515            #[cfg(any(test, feature = "test-support"))]
1516            qualified_candidate_inspections: AtomicUsize::new(0),
1517            #[cfg(any(test, feature = "test-support"))]
1518            target_preserving_type_resolution_count: AtomicUsize::new(0),
1519        }
1520    }
1521
1522    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1523        if file == target.source() {
1524            return true;
1525        }
1526        if self.global_field_has_internal_linkage(target) {
1527            return self
1528                .visible_source_files_by_root
1529                .get(file)
1530                .is_some_and(|sources| sources.contains(target.source()));
1531        }
1532        self.visible_by_file
1533            .get(file)
1534            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1535    }
1536
1537    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1538        self.global_field_internal_linkage
1539            .get(unit)
1540            .copied()
1541            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1542    }
1543
1544    pub fn call_arity_evidence(
1545        &self,
1546        file: &ProjectFile,
1547        call: Node<'_>,
1548        source: &str,
1549    ) -> CallArityEvidence {
1550        let Some(arguments) = call
1551            .child_by_field_name("arguments")
1552            .or_else(|| call.child_by_field_name("parameters"))
1553            .or_else(|| call.child_by_field_name("value"))
1554            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1555            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1556        else {
1557            return CallArityEvidence::Exact(0);
1558        };
1559        let recovered_c_keyword_arguments =
1560            recovered_c_keyword_argument_count(file, call, arguments, source);
1561        let arguments = argument_children(arguments).collect::<Vec<_>>();
1562        if arguments
1563            .iter()
1564            .all(|argument| !argument_shape_may_change_arity(*argument))
1565        {
1566            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1567        }
1568        let environment = self.macro_environment(file, call.start_byte());
1569        let mut stack = Vec::new();
1570        let mut total = recovered_c_keyword_arguments;
1571        for argument in arguments {
1572            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1573                return CallArityEvidence::Unknown;
1574            }
1575            let CallArityEvidence::Exact(spread) =
1576                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1577            else {
1578                return CallArityEvidence::Unknown;
1579            };
1580            total += spread;
1581        }
1582        CallArityEvidence::Exact(total)
1583    }
1584
1585    fn argument_arity_evidence(
1586        &self,
1587        argument: Node<'_>,
1588        source: &str,
1589        environment: &MacroEnvironment,
1590        stack: &mut Vec<(ProjectFile, usize)>,
1591    ) -> CallArityEvidence {
1592        let (name, invocation_arguments, function_like) = match argument.kind() {
1593            "identifier" => (node_text(argument, source), None, false),
1594            "call_expression" => {
1595                let Some(function) = argument.child_by_field_name("function") else {
1596                    return CallArityEvidence::Exact(1);
1597                };
1598                if function.kind() != "identifier" {
1599                    return CallArityEvidence::Exact(1);
1600                }
1601                let Some(arguments) = argument.child_by_field_name("arguments") else {
1602                    return CallArityEvidence::Exact(1);
1603                };
1604                (node_text(function, source), Some(arguments), true)
1605            }
1606            _ => return CallArityEvidence::Exact(1),
1607        };
1608        let Some(binding) = environment.binding(name) else {
1609            return if environment.unknown_names {
1610                CallArityEvidence::Unknown
1611            } else {
1612                CallArityEvidence::Exact(1)
1613            };
1614        };
1615        if !binding.is_exact() {
1616            return CallArityEvidence::Unknown;
1617        }
1618        match (&binding.definition, invocation_arguments, function_like) {
1619            (MacroDefinition::Object { replacement }, None, false) => self
1620                .replacement_arity_evidence(
1621                    replacement,
1622                    &[],
1623                    &[],
1624                    source,
1625                    environment,
1626                    stack,
1627                    binding,
1628                ),
1629            (
1630                MacroDefinition::Function {
1631                    parameters,
1632                    replacement,
1633                },
1634                Some(arguments),
1635                true,
1636            ) => {
1637                let actuals = argument_children(arguments).collect::<Vec<_>>();
1638                if actuals.len() != parameters.len() {
1639                    CallArityEvidence::Unknown
1640                } else {
1641                    self.replacement_arity_evidence(
1642                        replacement,
1643                        parameters,
1644                        &actuals,
1645                        source,
1646                        environment,
1647                        stack,
1648                        binding,
1649                    )
1650                }
1651            }
1652            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1653            _ => CallArityEvidence::Unknown,
1654        }
1655    }
1656
1657    #[allow(clippy::too_many_arguments)]
1658    fn replacement_arity_evidence(
1659        &self,
1660        replacement: &str,
1661        parameters: &[String],
1662        actuals: &[Node<'_>],
1663        actual_source: &str,
1664        environment: &MacroEnvironment,
1665        stack: &mut Vec<(ProjectFile, usize)>,
1666        binding: &MacroBinding,
1667    ) -> CallArityEvidence {
1668        let identity = (binding.source.clone(), binding.declaration_byte);
1669        if stack.contains(&identity) || replacement.trim().is_empty() {
1670            return CallArityEvidence::Unknown;
1671        }
1672        stack.push(identity);
1673        let parsed = self.parsed_macro_replacement(binding, replacement);
1674        let evidence = (|| {
1675            let ParsedMacroReplacement::Parsed {
1676                source: sentinel,
1677                tree,
1678            } = parsed.as_ref()
1679            else {
1680                return None;
1681            };
1682            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1683            let arguments = call.child_by_field_name("arguments")?;
1684            let mut total = 0usize;
1685            for argument in argument_children(arguments) {
1686                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1687                    return None;
1688                }
1689                if argument.kind() == "identifier"
1690                    && let Some(parameter_index) = parameters
1691                        .iter()
1692                        .position(|parameter| parameter == node_text(argument, sentinel))
1693                {
1694                    if !macro_expansion_shape_is_safe(
1695                        actuals[parameter_index],
1696                        actual_source,
1697                        &[],
1698                        environment,
1699                    ) {
1700                        return None;
1701                    }
1702                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1703                        actuals[parameter_index],
1704                        actual_source,
1705                        environment,
1706                        stack,
1707                    ) else {
1708                        return None;
1709                    };
1710                    total += spread;
1711                    continue;
1712                }
1713                let CallArityEvidence::Exact(spread) =
1714                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1715                else {
1716                    return None;
1717                };
1718                total += spread;
1719            }
1720            Some(CallArityEvidence::Exact(total))
1721        })()
1722        .unwrap_or(CallArityEvidence::Unknown);
1723        stack.pop();
1724        evidence
1725    }
1726
1727    fn parsed_macro_replacement(
1728        &self,
1729        binding: &MacroBinding,
1730        replacement: &str,
1731    ) -> Arc<ParsedMacroReplacement> {
1732        let key = (binding.source.clone(), binding.declaration_byte);
1733        let mut cache = self
1734            .macro_replacements
1735            .lock()
1736            .expect("C++ macro replacement cache poisoned");
1737        if let Some(parsed) = cache.get(&key) {
1738            return Arc::clone(parsed);
1739        }
1740        #[cfg(any(test, feature = "test-support"))]
1741        self.macro_replacement_parse_count
1742            .fetch_add(1, Ordering::Relaxed);
1743        let source =
1744            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1745        let mut parser = Parser::new();
1746        let parsed = parser
1747            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1748            .ok()
1749            .and_then(|()| parser.parse(&source, None))
1750            .filter(|tree| !tree.root_node().has_error())
1751            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1752                ParsedMacroReplacement::Parsed { source, tree }
1753            });
1754        let parsed = Arc::new(parsed);
1755        cache.insert(key, Arc::clone(&parsed));
1756        parsed
1757    }
1758
1759    /// Recover a typed local declared by an active C function-like macro.
1760    ///
1761    /// This is intentionally narrower than macro expansion. The replacement
1762    /// must parse as one declaration, and the invocation must bind every
1763    /// formal parameter to one structured argument. That is sufficient for
1764    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1765    /// can make the binding provisional without erasing its last known
1766    /// definition; an explicit conflicting definition still replaces it with
1767    /// Unsupported. Malformed and statement-producing macros also fail closed.
1768    pub fn function_macro_local_binding<'tree>(
1769        &self,
1770        file: &ProjectFile,
1771        statement: Node<'tree>,
1772        source: &str,
1773    ) -> Option<MacroLocalBinding<'tree>> {
1774        if !is_c_source_file(file) {
1775            return None;
1776        }
1777        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
1778            return Some(binding);
1779        }
1780        let call = match statement.kind() {
1781            "call_expression" => statement,
1782            "expression_statement" if statement.named_child_count() == 1 => {
1783                statement.named_child(0)?
1784            }
1785            _ => return None,
1786        };
1787        if call.kind() != "call_expression" {
1788            return None;
1789        }
1790        let function = call.child_by_field_name("function")?;
1791        if function.kind() != "identifier" {
1792            return None;
1793        }
1794        let arguments = call.child_by_field_name("arguments")?;
1795        let actuals = argument_children(arguments).collect::<Vec<_>>();
1796        let environment = self.macro_environment(file, call.start_byte());
1797        let function_name = node_text(function, source);
1798        let binding = environment.binding(function_name)?;
1799        let MacroDefinition::Function {
1800            parameters,
1801            replacement,
1802        } = &binding.definition
1803        else {
1804            return None;
1805        };
1806        if actuals.len() != parameters.len() {
1807            return None;
1808        }
1809        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1810        let (type_name, type_node) = match &template.declared_type {
1811            MacroLocalBindingTypeTemplate::Parameter(index) => {
1812                let actual = *actuals.get(*index)?;
1813                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1814                    return None;
1815                }
1816                (node_text(actual, source).trim().to_string(), Some(actual))
1817            }
1818            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1819        };
1820        if type_name.is_empty() {
1821            return None;
1822        }
1823        Some(MacroLocalBinding {
1824            name: template.name.clone(),
1825            type_name,
1826            type_node,
1827            pointer_depth: template.pointer_depth,
1828        })
1829    }
1830
1831    fn macro_local_binding_template(
1832        &self,
1833        binding: &MacroBinding,
1834        parameters: &[String],
1835        replacement: &str,
1836    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1837        let key = (binding.source.clone(), binding.declaration_byte);
1838        let mut cache = self
1839            .macro_local_binding_templates
1840            .lock()
1841            .expect("C++ macro local-binding cache poisoned");
1842        if let Some(template) = cache.get(&key) {
1843            return template.clone();
1844        }
1845        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1846        let template = (|| {
1847            let mut parser = Parser::new();
1848            parser
1849                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1850                .ok()?;
1851            let tree = parser.parse(&sentinel, None)?;
1852            if tree.root_node().has_error() {
1853                return None;
1854            }
1855            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1856            let body = function.child_by_field_name("body")?;
1857            if body.named_child_count() != 1 {
1858                return None;
1859            }
1860            let declaration = body.named_child(0)?;
1861            if declaration.kind() != "declaration" {
1862                return None;
1863            }
1864            let type_node = declaration
1865                .child_by_field_name("type")
1866                .or_else(|| first_type_child(declaration))?;
1867            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1868                let mut cursor = declaration.walk();
1869                declaration.named_children(&mut cursor).find_map(|child| {
1870                    if child.kind() == "init_declarator" {
1871                        child.child_by_field_name("declarator")
1872                    } else {
1873                        is_declarator_node(child).then_some(child)
1874                    }
1875                })
1876            })?;
1877            let name = extract_variable_name(declarator, &sentinel)?;
1878            let pointer_depth =
1879                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1880            let type_text = node_text(type_node, &sentinel).trim();
1881            let declared_type = parameters
1882                .iter()
1883                .position(|parameter| parameter == type_text)
1884                .map(MacroLocalBindingTypeTemplate::Parameter)
1885                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1886            Some(Arc::new(MacroLocalBindingTemplate {
1887                name,
1888                declared_type,
1889                pointer_depth,
1890            }))
1891        })();
1892        cache.insert(key, template.clone());
1893        template
1894    }
1895
1896    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1897        let Some(value) = node.child_by_field_name("value") else {
1898            return MacroDefinition::Unsupported;
1899        };
1900        let replacement = node_text(value, source).to_string();
1901        if node.kind() == "preproc_def" {
1902            return MacroDefinition::Object { replacement };
1903        }
1904        let Some(parameters) = node.child_by_field_name("parameters") else {
1905            return MacroDefinition::Unsupported;
1906        };
1907        if (0..parameters.child_count()).any(|index| {
1908            parameters
1909                .child(index)
1910                .is_some_and(|child| child.kind() == "...")
1911        }) {
1912            return MacroDefinition::Unsupported;
1913        }
1914        let parameters = (0..parameters.named_child_count())
1915            .filter_map(|index| parameters.named_child(index))
1916            .map(|parameter| node_text(parameter, source).to_string())
1917            .collect();
1918        MacroDefinition::Function {
1919            parameters,
1920            replacement,
1921        }
1922    }
1923
1924    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1925        self.macro_event_cells
1926            .lock()
1927            .expect("C++ macro event cache poisoned")
1928            .entry(file.clone())
1929            .or_default()
1930            .clone()
1931    }
1932
1933    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1934        let key = (file.clone(), std::thread::current().id());
1935        self.macro_environment_cursors
1936            .lock()
1937            .expect("C++ macro environment cursor cache poisoned")
1938            .entry(key)
1939            .or_default()
1940            .clone()
1941    }
1942
1943    pub fn macro_environment(
1944        &self,
1945        file: &ProjectFile,
1946        before_byte: usize,
1947    ) -> Arc<MacroEnvironment> {
1948        let cell = self.macro_event_cell(file);
1949        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1950        let frontier = events.partition_point(|event| event.byte() < before_byte);
1951        let cursor_cell = self.macro_environment_cursor_cell(file);
1952        let mut cursor = cursor_cell
1953            .lock()
1954            .expect("C++ macro environment cursor poisoned");
1955        if frontier < cursor.frontier {
1956            *cursor = MacroEnvironmentCursor::default();
1957        }
1958        // Seed the TU's build-proven defines once, before any event applies
1959        // (#2011). They are facts of the whole compile, so they hold from the
1960        // first byte; a later explicit #undef event still overrides them
1961        // through `known_undefined_names`.
1962        if cursor.frontier == 0 {
1963            let proven = self.compile_proven_guards(file);
1964            if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
1965                Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
1966                    .iter()
1967                    .filter_map(|guard| match guard {
1968                        PreprocessorGuard::Defined(name) => Some(name.clone()),
1969                        _ => None,
1970                    })
1971                    .collect();
1972            }
1973        }
1974        if frontier > cursor.frontier {
1975            #[cfg(any(test, feature = "test-support"))]
1976            if Arc::strong_count(&cursor.environment) > 1 {
1977                self.macro_environment_copy_count
1978                    .fetch_add(1, Ordering::Relaxed);
1979            }
1980            let start = cursor.frontier;
1981            let environment = Arc::make_mut(&mut cursor.environment);
1982            let mut include_stack = HashSet::from_iter([file.clone()]);
1983            for event in &events[start..frontier] {
1984                self.apply_macro_event(file, event, environment, &mut include_stack);
1985            }
1986            cursor.frontier = frontier;
1987        }
1988        Arc::clone(&cursor.environment)
1989    }
1990
1991    /// Whether `name` is bound as a macro at `before_byte` in `file`,
1992    /// including a binding this environment cannot pin to one replacement
1993    /// (a conditional `#define`, or a function-like macro).
1994    ///
1995    /// [`Self::object_macro_replacement_at`] collapses every such binding to
1996    /// `None`, which is indistinguishable from "not a macro at all". A caller
1997    /// that must not read a macro token as an ordinary type name needs the two
1998    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
1999    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2000        self.macro_environment(file, before_byte)
2001            .binding(name)
2002            .is_some()
2003    }
2004
2005    pub fn macro_name_may_be_bound_at(
2006        &self,
2007        file: &ProjectFile,
2008        name: &str,
2009        before_byte: usize,
2010    ) -> bool {
2011        self.macro_environment(file, before_byte).may_bind(name)
2012    }
2013
2014    /// Whether the active macro binding at this reference is the requested
2015    /// indexed definition. Name equality alone is not enough because two
2016    /// headers can define the same macro for different translation units.
2017    pub fn macro_binding_matches_target_at(
2018        &self,
2019        analyzer: &CppGraphSource<'_>,
2020        file: &ProjectFile,
2021        name: &str,
2022        before_byte: usize,
2023        target: &CodeUnit,
2024    ) -> bool {
2025        let environment = self.macro_environment(file, before_byte);
2026        let Some(binding) = environment.binding(name) else {
2027            return false;
2028        };
2029        // A normal header guard makes the replacement text conditional, but
2030        // it does not erase the definition site's source and byte identity.
2031        // Keep that identity even when expansion details are not exact.
2032        if binding.source != *target.source() {
2033            return false;
2034        }
2035        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2036            return false;
2037        };
2038        analyzer.ranges(target).iter().any(|range| {
2039            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2040                return false;
2041            };
2042            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2043                let Some(parent) = node.parent() else {
2044                    return false;
2045                };
2046                node = parent;
2047            }
2048            node.start_byte() == binding.declaration_byte
2049        })
2050    }
2051
2052    /// Resolve an ordinary expression-position macro token at its exact byte.
2053    ///
2054    /// Calls and preprocessor-condition tokens have separate resolution
2055    /// surfaces. Declaration names, macro parameters, and labels are not
2056    /// references. Keeping that role policy here makes forward and both
2057    /// inverse graph builders consume the same activation verdict (#2093).
2058    pub fn resolve_ordinary_macro_reference(
2059        &self,
2060        analyzer: &CppGraphSource<'_>,
2061        file: &ProjectFile,
2062        node: Node<'_>,
2063        source: &str,
2064    ) -> OrdinaryMacroReferenceResolution {
2065        if !is_ordinary_macro_reference_node(node) {
2066            return OrdinaryMacroReferenceResolution::Missing;
2067        }
2068        let name = node_text(node, source);
2069        if name.is_empty() {
2070            return OrdinaryMacroReferenceResolution::Missing;
2071        }
2072        let visible = self
2073            .visible_identifier_candidates(file, name)
2074            .filter(|candidate| candidate.is_macro())
2075            .cloned()
2076            .collect::<Vec<_>>();
2077        let mut exact = Vec::new();
2078        for candidate in &visible {
2079            if self.macro_binding_matches_target_at(
2080                analyzer,
2081                file,
2082                name,
2083                node.start_byte(),
2084                candidate,
2085            ) && !exact
2086                .iter()
2087                .any(|existing| same_visible_symbol(existing, candidate))
2088            {
2089                exact.push(candidate.clone());
2090            }
2091        }
2092        match exact.len() {
2093            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2094            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2095            0 if !visible.is_empty()
2096                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2097            {
2098                OrdinaryMacroReferenceResolution::Ambiguous
2099            }
2100            0 => OrdinaryMacroReferenceResolution::Missing,
2101        }
2102    }
2103
2104    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2105    ///
2106    /// The ordinary census deliberately skips every `ERROR` subtree. This
2107    /// separate, precision-only frontier admits only roles that retain enough
2108    /// structure for the C usage graph to interpret independently (#2089).
2109    /// Macro evidence comes from this visibility index at the exact byte; no
2110    /// source-text parsing or terminal-name fallback is used.
2111    pub fn recovered_c_reference_ranges(
2112        &self,
2113        file: &ProjectFile,
2114        root: Node<'_>,
2115        source: &str,
2116        limit: usize,
2117    ) -> RecoveredCReferenceRanges {
2118        if !is_c_source_file(file) {
2119            return RecoveredCReferenceRanges::Complete(Vec::new());
2120        }
2121        let mut ranges = Vec::new();
2122        let mut seen = HashSet::default();
2123        let mut stack = vec![(root, root.is_error())];
2124        while let Some((node, inside_error)) = stack.pop() {
2125            let inside_error = inside_error || node.is_error();
2126            if inside_error
2127                && recovered_c_reference_node(self, file, node, source)
2128                && seen.insert((node.start_byte(), node.end_byte()))
2129            {
2130                if ranges.len() == limit {
2131                    return RecoveredCReferenceRanges::LimitExceeded;
2132                }
2133                ranges.push(Range {
2134                    start_byte: node.start_byte(),
2135                    end_byte: node.end_byte(),
2136                    start_line: node.start_position().row,
2137                    end_line: node.end_position().row,
2138                });
2139            }
2140            let mut cursor = node.walk();
2141            for child in node.named_children(&mut cursor) {
2142                stack.push((child, inside_error));
2143            }
2144        }
2145        ranges.sort_unstable();
2146        RecoveredCReferenceRanges::Complete(ranges)
2147    }
2148
2149    /// Whether this target is an indexed macro visible from this file.
2150    ///
2151    /// An unresolved conditional can make more than one same-name macro a
2152    /// possible active binding. Each possible target can keep the site as an
2153    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2154    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2155        self.visible_identifier_candidates(file, target.identifier())
2156            .filter(|candidate| candidate.is_macro())
2157            .any(|candidate| {
2158                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2159            })
2160    }
2161
2162    pub fn object_macro_replacement_at(
2163        &self,
2164        file: &ProjectFile,
2165        name: &str,
2166        before_byte: usize,
2167    ) -> Option<String> {
2168        let environment = self.macro_environment(file, before_byte);
2169        let binding = environment.binding(name)?;
2170        if !binding.exact {
2171            return None;
2172        }
2173        match &binding.definition {
2174            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2175            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2176        }
2177    }
2178
2179    fn apply_macro_events(
2180        &self,
2181        file: &ProjectFile,
2182        before_byte: Option<usize>,
2183        environment: &mut MacroEnvironment,
2184        include_stack: &mut HashSet<ProjectFile>,
2185    ) {
2186        if !include_stack.insert(file.clone()) {
2187            return;
2188        }
2189        if self.cpp.prepared_syntax(self.token, file).is_none() {
2190            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2191            include_stack.remove(file);
2192            return;
2193        }
2194        match self.macro_include_protection(file) {
2195            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2196                Some(binding) if binding.is_exact() => {
2197                    include_stack.remove(file);
2198                    return;
2199                }
2200                Some(_) | None if environment.unknown_names => {
2201                    let mut ambiguous_seen = HashSet::default();
2202                    self.mark_macro_events_ambiguous(
2203                        file,
2204                        environment,
2205                        &mut ambiguous_seen,
2206                        file,
2207                        before_byte.unwrap_or_default(),
2208                    );
2209                    include_stack.remove(file);
2210                    return;
2211                }
2212                Some(_) => {
2213                    let mut ambiguous_seen = HashSet::default();
2214                    self.mark_macro_events_ambiguous(
2215                        file,
2216                        environment,
2217                        &mut ambiguous_seen,
2218                        file,
2219                        before_byte.unwrap_or_default(),
2220                    );
2221                    include_stack.remove(file);
2222                    return;
2223                }
2224                None => {}
2225            },
2226            MacroIncludeProtection::PragmaOnce => {
2227                if !environment.applied_pragma_once_files.insert(file.clone()) {
2228                    include_stack.remove(file);
2229                    return;
2230                }
2231                if environment.maybe_applied_pragma_once_files.remove(file) {
2232                    // A prior conditional include may already have consumed the pragma-once
2233                    // header. This unconditional include guarantees it is consumed now, but
2234                    // cannot prove whether its events occur before or after intervening local
2235                    // macro changes, so preserve the union as ambiguous.
2236                    let mut ambiguous_seen = HashSet::default();
2237                    environment.applied_pragma_once_files.remove(file);
2238                    self.mark_macro_events_ambiguous(
2239                        file,
2240                        environment,
2241                        &mut ambiguous_seen,
2242                        file,
2243                        before_byte.unwrap_or_default(),
2244                    );
2245                    environment.maybe_applied_pragma_once_files.remove(file);
2246                    environment.applied_pragma_once_files.insert(file.clone());
2247                    include_stack.remove(file);
2248                    return;
2249                }
2250            }
2251            MacroIncludeProtection::None => {}
2252        }
2253        let cell = self.macro_event_cell(file);
2254        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2255        for event in events {
2256            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2257                break;
2258            }
2259            self.apply_macro_event(file, event, environment, include_stack);
2260        }
2261        include_stack.remove(file);
2262    }
2263
2264    fn apply_macro_event(
2265        &self,
2266        file: &ProjectFile,
2267        event: &MacroEvent,
2268        environment: &mut MacroEnvironment,
2269        include_stack: &mut HashSet<ProjectFile>,
2270    ) {
2271        #[cfg(any(test, feature = "test-support"))]
2272        self.macro_event_application_count
2273            .fetch_add(1, Ordering::Relaxed);
2274        match event {
2275            MacroEvent::Define {
2276                name,
2277                binding,
2278                conditional,
2279                byte,
2280            } => {
2281                if *conditional {
2282                    Self::merge_conditional_macro_definition(
2283                        environment,
2284                        name,
2285                        binding,
2286                        file,
2287                        *byte,
2288                    );
2289                } else {
2290                    environment.insert(name.clone(), binding.clone());
2291                }
2292            }
2293            MacroEvent::Undef {
2294                name,
2295                conditional,
2296                byte,
2297            } => {
2298                if *conditional {
2299                    if environment.binding(name).is_some() {
2300                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2301                    }
2302                } else {
2303                    environment.remove(name);
2304                }
2305            }
2306            MacroEvent::Include {
2307                targets,
2308                conditional,
2309                byte,
2310            } => {
2311                if targets.is_empty() {
2312                    environment.mark_unknown_names(file, *byte);
2313                    return;
2314                }
2315                if *conditional || targets.len() > 1 {
2316                    let mut ambiguous_seen = HashSet::default();
2317                    for target in targets {
2318                        self.mark_macro_events_ambiguous(
2319                            target,
2320                            environment,
2321                            &mut ambiguous_seen,
2322                            file,
2323                            *byte,
2324                        );
2325                    }
2326                } else if let Some(target) = targets.first() {
2327                    self.apply_macro_events(target, None, environment, include_stack);
2328                }
2329            }
2330            MacroEvent::Invalidate { byte } => {
2331                for binding in environment.bindings.values_mut() {
2332                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2333                }
2334            }
2335        }
2336    }
2337
2338    fn mark_macro_events_ambiguous(
2339        &self,
2340        file: &ProjectFile,
2341        environment: &mut MacroEnvironment,
2342        include_stack: &mut HashSet<ProjectFile>,
2343        conditional_file: &ProjectFile,
2344        conditional_byte: usize,
2345    ) {
2346        if !include_stack.insert(file.clone()) {
2347            return;
2348        }
2349        if self.cpp.prepared_syntax(self.token, file).is_none() {
2350            environment.mark_unknown_names(conditional_file, conditional_byte);
2351            return;
2352        }
2353        match self.macro_include_protection(file) {
2354            MacroIncludeProtection::MacroGuard(guard) => {
2355                if environment
2356                    .binding(&guard)
2357                    .is_some_and(MacroBinding::is_exact)
2358                {
2359                    return;
2360                }
2361            }
2362            MacroIncludeProtection::PragmaOnce => {
2363                if environment.applied_pragma_once_files.contains(file) {
2364                    return;
2365                }
2366                environment
2367                    .maybe_applied_pragma_once_files
2368                    .insert(file.clone());
2369            }
2370            MacroIncludeProtection::None => {}
2371        }
2372        let cell = self.macro_event_cell(file);
2373        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2374        for event in events {
2375            #[cfg(any(test, feature = "test-support"))]
2376            self.macro_event_application_count
2377                .fetch_add(1, Ordering::Relaxed);
2378            match event {
2379                MacroEvent::Define { name, binding, .. } => {
2380                    Self::merge_conditional_macro_definition(
2381                        environment,
2382                        name,
2383                        binding,
2384                        conditional_file,
2385                        conditional_byte,
2386                    );
2387                }
2388                MacroEvent::Undef { name, .. } => {
2389                    if environment.binding(name).is_some() {
2390                        environment.insert(
2391                            name.clone(),
2392                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2393                        );
2394                    } else {
2395                        environment.remove_known_undefined(name);
2396                    }
2397                }
2398                MacroEvent::Include { targets, .. } => {
2399                    if targets.is_empty() {
2400                        environment.mark_unknown_names(conditional_file, conditional_byte);
2401                        continue;
2402                    }
2403                    for target in targets {
2404                        self.mark_macro_events_ambiguous(
2405                            target,
2406                            environment,
2407                            include_stack,
2408                            conditional_file,
2409                            conditional_byte,
2410                        );
2411                    }
2412                }
2413                MacroEvent::Invalidate { .. } => {
2414                    for binding in environment.bindings.values_mut() {
2415                        *binding = MacroBinding::uncertain_from(
2416                            binding,
2417                            conditional_file,
2418                            conditional_byte,
2419                        );
2420                    }
2421                }
2422            }
2423        }
2424    }
2425
2426    fn merge_conditional_macro_definition(
2427        environment: &mut MacroEnvironment,
2428        name: &str,
2429        possible_binding: &MacroBinding,
2430        conditional_file: &ProjectFile,
2431        conditional_byte: usize,
2432    ) {
2433        // A conditional include can revisit an already-active guarded header.
2434        // If the possible branch defines the exact same macro, both outcomes
2435        // leave the binding unchanged; degrading it to Unknown would discard
2436        // proof because of an unrelated unresolved macro name (#2092).
2437        if environment.binding(name).is_some_and(|current| {
2438            current.definition != MacroDefinition::Unsupported
2439                && current.definition == possible_binding.definition
2440        }) {
2441            return;
2442        }
2443        environment.insert(
2444            name.to_string(),
2445            MacroBinding::ambiguous(conditional_file, conditional_byte),
2446        );
2447    }
2448
2449    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2450        let cell = self
2451            .macro_include_protection_cells
2452            .lock()
2453            .expect("C++ include protection cache poisoned")
2454            .entry(file.clone())
2455            .or_default()
2456            .clone();
2457        cell.get_or_init(|| {
2458            self.cpp.prepared_syntax(self.token, file).map_or(
2459                MacroIncludeProtection::None,
2460                |prepared| {
2461                    top_level_macro_include_protection(
2462                        prepared.tree().root_node(),
2463                        prepared.source(),
2464                    )
2465                },
2466            )
2467        })
2468        .clone()
2469    }
2470
2471    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2472        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2473            return Vec::new();
2474        };
2475        let source = prepared.source();
2476        let mut events = Vec::new();
2477        let mut stack = vec![prepared.tree().root_node()];
2478        while let Some(node) = stack.pop() {
2479            let conditional = has_preprocessor_conditional_ancestor(node, source);
2480            match node.kind() {
2481                "preproc_def" | "preproc_function_def" => {
2482                    let Some(name) = node.child_by_field_name("name") else {
2483                        continue;
2484                    };
2485                    let name = node_text(name, source).to_string();
2486                    events.push(MacroEvent::Define {
2487                        name,
2488                        binding: MacroBinding {
2489                            source: file.clone(),
2490                            declaration_byte: node.start_byte(),
2491                            definition: Self::decode_macro_definition(node, source),
2492                            exact: true,
2493                        },
2494                        byte: node.start_byte(),
2495                        conditional,
2496                    });
2497                    continue;
2498                }
2499                "preproc_include" => {
2500                    let Some(path) = node.child_by_field_name("path") else {
2501                        events.push(MacroEvent::Include {
2502                            targets: Vec::new(),
2503                            byte: node.start_byte(),
2504                            conditional,
2505                        });
2506                        continue;
2507                    };
2508                    let targets =
2509                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2510                            resolve_include_targets_with_index(
2511                                file,
2512                                path,
2513                                self.cpp.include_target_index(),
2514                            )
2515                        });
2516                    // An unresolved angle-bracket include crosses into an external system
2517                    // boundary that is absent from the source index. It must not poison all
2518                    // later local macro evidence. Quoted/project-local and computed includes,
2519                    // by contrast, may hide indexed macro state and therefore fail closed.
2520                    if targets.is_empty() && path.kind() == "system_lib_string" {
2521                        continue;
2522                    }
2523                    events.push(MacroEvent::Include {
2524                        targets,
2525                        byte: node.start_byte(),
2526                        conditional,
2527                    });
2528                    continue;
2529                }
2530                "preproc_call" => {
2531                    let Some(directive) = node.child_by_field_name("directive") else {
2532                        continue;
2533                    };
2534                    if node_text(directive, source) != "#undef" {
2535                        continue;
2536                    }
2537                    let name = node
2538                        .child_by_field_name("argument")
2539                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2540                    if let Some(name) = name {
2541                        events.push(MacroEvent::Undef {
2542                            name,
2543                            byte: node.start_byte(),
2544                            conditional,
2545                        });
2546                    } else {
2547                        events.push(MacroEvent::Invalidate {
2548                            byte: node.start_byte(),
2549                        });
2550                    }
2551                    continue;
2552                }
2553                _ => {}
2554            }
2555            for index in (0..node.named_child_count()).rev() {
2556                if let Some(child) = node.named_child(index) {
2557                    stack.push(child);
2558                }
2559            }
2560        }
2561        events.sort_by_key(MacroEvent::byte);
2562        events
2563    }
2564
2565    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2566        self.ordinary_type_import_cells
2567            .lock()
2568            .expect("C++ ordinary type import cache poisoned")
2569            .entry(file.clone())
2570            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2571            .clone()
2572    }
2573
2574    pub fn project_using_index(
2575        &self,
2576        build: impl FnOnce() -> ProjectUsingIndex,
2577    ) -> &ProjectUsingIndex {
2578        self.project_using_index.get_or_init(build)
2579    }
2580
2581    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2582        let mut files = self
2583            .visible_source_files_by_root
2584            .values()
2585            .flatten()
2586            .cloned()
2587            .collect::<HashSet<_>>()
2588            .into_iter()
2589            .collect::<Vec<_>>();
2590        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2591        files
2592    }
2593
2594    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2595        self.visible_source_files_by_root
2596            .get(root)
2597            .is_some_and(|files| files.contains(source))
2598    }
2599
2600    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2601        let cached = self
2602            .visible_parser_alias_name_sets
2603            .read()
2604            .expect("visible parser alias-name cache poisoned")
2605            .get(file)
2606            .cloned();
2607        let cell = if let Some(cached) = cached {
2608            cached
2609        } else {
2610            let mut cells = self
2611                .visible_parser_alias_name_sets
2612                .write()
2613                .expect("visible parser alias-name cache poisoned");
2614            Arc::clone(
2615                cells
2616                    .entry(file.clone())
2617                    .or_insert_with(|| Arc::new(OnceLock::new())),
2618            )
2619        };
2620        cell.get_or_init(|| {
2621            #[cfg(any(test, feature = "test-support"))]
2622            self.visible_parser_alias_name_set_build_count
2623                .fetch_add(1, Ordering::Relaxed);
2624            let mut names = HashSet::default();
2625            let visible_files = self
2626                .visible_source_files_by_root
2627                .get(file)
2628                .cloned()
2629                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2630            for visible_file in visible_files {
2631                let aliases = {
2632                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2633                    Arc::clone(
2634                        cells
2635                            .entry(visible_file.clone())
2636                            .or_insert_with(|| Arc::new(OnceLock::new())),
2637                    )
2638                };
2639                for alias in aliases
2640                    .get_or_init(|| {
2641                        #[cfg(any(test, feature = "test-support"))]
2642                        {
2643                            *self
2644                                .alias_source_parse_counts
2645                                .lock()
2646                                .expect("alias source parse count lock")
2647                                .entry(visible_file.clone())
2648                                .or_default() += 1;
2649                        }
2650                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2651                            .into_boxed_slice()
2652                    })
2653                    .iter()
2654                {
2655                    names.insert(alias.name.clone());
2656                }
2657            }
2658            names
2659        })
2660        .contains(name)
2661    }
2662
2663    fn visible_parser_alias_names_for_target(
2664        &self,
2665        file: &ProjectFile,
2666        target: &CodeUnit,
2667    ) -> HashSet<String> {
2668        let cell = {
2669            let mut cells = self
2670                .visible_parser_alias_target_names
2671                .lock()
2672                .expect("visible parser alias-target cache poisoned");
2673            Arc::clone(
2674                cells
2675                    .entry(file.clone())
2676                    .or_insert_with(|| Arc::new(OnceLock::new())),
2677            )
2678        };
2679        let target_name = cpp_name_for(target);
2680        cell.get_or_init(|| {
2681            #[cfg(any(test, feature = "test-support"))]
2682            self.visible_parser_alias_target_names_build_count
2683                .fetch_add(1, Ordering::Relaxed);
2684            let visible_files = self
2685                .visible_source_files_by_root
2686                .get(file)
2687                .cloned()
2688                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2689            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2690            for visible_file in visible_files {
2691                let aliases = {
2692                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2693                    Arc::clone(
2694                        cells
2695                            .entry(visible_file.clone())
2696                            .or_insert_with(|| Arc::new(OnceLock::new())),
2697                    )
2698                };
2699                for alias in aliases
2700                    .get_or_init(|| {
2701                        #[cfg(any(test, feature = "test-support"))]
2702                        {
2703                            *self
2704                                .alias_source_parse_counts
2705                                .lock()
2706                                .expect("alias source parse count lock")
2707                                .entry(visible_file.clone())
2708                                .or_default() += 1;
2709                        }
2710                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2711                            .into_boxed_slice()
2712                    })
2713                    .iter()
2714                {
2715                    for target_name in parser_alias_target_names(alias) {
2716                        names_by_target
2717                            .entry(target_name)
2718                            .or_default()
2719                            .insert(alias.name.clone());
2720                    }
2721                }
2722            }
2723            names_by_target
2724        })
2725        .get(&target_name)
2726        .cloned()
2727        .unwrap_or_default()
2728    }
2729
2730    fn callable_arities_for_target(
2731        &self,
2732        analyzer: &CppGraphSource<'_>,
2733        cpp: &dyn CppSource,
2734        file: &ProjectFile,
2735        prepared: &PreparedSyntaxTree,
2736        spec: &TargetSpec,
2737    ) -> Vec<ActivatedCallableArity> {
2738        let Some(signature) = spec.target.signature() else {
2739            return Vec::new();
2740        };
2741        let Some(candidates) = self
2742            .visible_by_identifier
2743            .get(file)
2744            .and_then(|by_name| by_name.get(&spec.member_name))
2745        else {
2746            return Vec::new();
2747        };
2748        let differing_candidates = candidates
2749            .iter()
2750            .filter(|candidate| {
2751                candidate.is_function()
2752                    && candidate.fq_name() == spec.target.fq_name()
2753                    && candidate.signature() == Some(signature)
2754            })
2755            .filter_map(|candidate| {
2756                analyzer
2757                    .signature_metadata(candidate)
2758                    .into_iter()
2759                    .find_map(|metadata| metadata.callable_arity())
2760                    .filter(|arity| Some(*arity) != spec.callable_arity)
2761                    .map(|arity| (candidate, arity))
2762            })
2763            .collect::<Vec<_>>();
2764        if differing_candidates.is_empty() {
2765            return Vec::new();
2766        }
2767        let mut arities = Vec::with_capacity(differing_candidates.len());
2768        // The activation ranges here describe the whole file rather than one
2769        // reference, so there is no reference guard environment to consult.
2770        let reference = CallableReferenceContext {
2771            file,
2772            position: None,
2773        };
2774        for (candidate, candidate_arity) in differing_candidates {
2775            let declaration_activation = if candidate.source() == file {
2776                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2777            } else {
2778                cpp.prepared_syntax(self.token, candidate.source())
2779                    .and_then(|syntax| {
2780                        callable_declaration_activation_in_file(
2781                            analyzer,
2782                            syntax.as_ref(),
2783                            candidate,
2784                            &reference,
2785                        )
2786                    })
2787            };
2788            let Some(declaration_activation) = declaration_activation else {
2789                continue;
2790            };
2791            let activation_byte = if candidate.source() == file {
2792                Some(declaration_activation)
2793            } else {
2794                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2795            };
2796            if let Some(activation_byte) = activation_byte {
2797                arities.push(ActivatedCallableArity {
2798                    activation_byte,
2799                    arity: candidate_arity,
2800                });
2801            }
2802        }
2803        arities
2804    }
2805
2806    fn callable_parameter_macro_arity(
2807        &self,
2808        target: &CodeUnit,
2809        signature: Option<&str>,
2810    ) -> Option<CallableArity> {
2811        let parameter_types = cpp_signature_param_types(signature?)?;
2812        let [macro_name] = parameter_types.as_slice() else {
2813            return None;
2814        };
2815        if macro_name.is_empty()
2816            || !macro_name
2817                .chars()
2818                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2819        {
2820            return None;
2821        }
2822        let cache_key = (target.source().clone(), macro_name.clone());
2823        if let Some(cached) = self
2824            .callable_parameter_macro_arities
2825            .lock()
2826            .expect("C++ callable parameter-macro arity cache poisoned")
2827            .get(&cache_key)
2828            .copied()
2829        {
2830            return cached;
2831        }
2832        let mut visible_files = HashSet::default();
2833        collect_include_closure(
2834            &self.cpp_source(),
2835            self.cpp.include_target_index(),
2836            target.source(),
2837            &mut visible_files,
2838            None,
2839        );
2840        let mut arities = Vec::new();
2841        for visible_file in visible_files {
2842            let cell = self.macro_event_cell(&visible_file);
2843            for event in
2844                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2845            {
2846                let MacroEvent::Define { name, binding, .. } = event else {
2847                    continue;
2848                };
2849                if name != macro_name {
2850                    continue;
2851                }
2852                let MacroDefinition::Object { replacement } = &binding.definition else {
2853                    continue;
2854                };
2855                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2856                    continue;
2857                };
2858                if !arities.contains(&arity) {
2859                    arities.push(arity);
2860                }
2861            }
2862        }
2863        let resolved = (|| {
2864            let required = arities
2865                .iter()
2866                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2867                .min()?;
2868            let total = arities.iter().map(|arity| arity.total()).max()?;
2869            let repeated = arities
2870                .iter()
2871                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2872            // Preprocessor conditions can leave more than one object-like parameter
2873            // bundle active in the target header's include closure. Preserve their
2874            // conservative callable envelope instead of choosing whichever definition
2875            // happened to be visited first.
2876            Some(CallableArity::new(required, total, repeated))
2877        })();
2878        self.callable_parameter_macro_arities
2879            .lock()
2880            .expect("C++ callable parameter-macro arity cache poisoned")
2881            .insert(cache_key, resolved);
2882        resolved
2883    }
2884
2885    pub fn include_activation_for_source(
2886        &self,
2887        cpp: &dyn CppSource,
2888        file: &ProjectFile,
2889        prepared: &PreparedSyntaxTree,
2890        donor_source: &ProjectFile,
2891    ) -> Option<usize> {
2892        let key = (file.clone(), donor_source.clone());
2893        if let Some(cached) = self
2894            .include_activation_cells
2895            .lock()
2896            .expect("C++ include activation cache poisoned")
2897            .get(&key)
2898            .copied()
2899        {
2900            return cached;
2901        }
2902        #[cfg(any(test, feature = "test-support"))]
2903        self.include_activation_build_count
2904            .fetch_add(1, Ordering::Relaxed);
2905        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
2906        let mut cells = self
2907            .include_activation_cells
2908            .lock()
2909            .expect("C++ include activation cache poisoned");
2910        *cells.entry(key).or_insert(activation)
2911    }
2912
2913    pub fn conditional_include_projections_for_source(
2914        &self,
2915        file: &ProjectFile,
2916        prepared: &PreparedSyntaxTree,
2917        donor_source: &ProjectFile,
2918    ) -> Arc<[ConditionalIncludeProjection]> {
2919        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2920        let cell = self
2921            .conditional_include_projection_cells
2922            .lock()
2923            .expect("C++ conditional include projection cache poisoned")
2924            .entry(file.clone())
2925            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2926            .clone();
2927        let index = cell.get_or_build_pool_independent(|| {
2928            #[cfg(any(test, feature = "test-support"))]
2929            self.conditional_include_projection_index_build_count
2930                .fetch_add(1, Ordering::Relaxed);
2931            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
2932                #[cfg(any(test, feature = "test-support"))]
2933                self.conditional_include_projection_state_count
2934                    .fetch_add(1, Ordering::Relaxed);
2935            })
2936        });
2937        index
2938            .get(donor_source)
2939            .cloned()
2940            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2941    }
2942
2943    #[cfg(any(test, feature = "test-support"))]
2944    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2945        (
2946            self.conditional_include_projection_index_build_count
2947                .load(Ordering::Relaxed),
2948            self.conditional_include_projection_state_count
2949                .load(Ordering::Relaxed),
2950        )
2951    }
2952
2953    #[cfg(any(test, feature = "test-support"))]
2954    pub fn include_activation_build_count_for_test(&self) -> usize {
2955        self.include_activation_build_count.load(Ordering::Relaxed)
2956    }
2957
2958    #[cfg(any(test, feature = "test-support"))]
2959    pub fn note_using_donor_activation_for_test(&self) {
2960        self.using_donor_activation_count
2961            .fetch_add(1, Ordering::Relaxed);
2962    }
2963
2964    #[cfg(not(any(test, feature = "test-support")))]
2965    pub fn note_using_donor_activation_for_test(&self) {}
2966
2967    #[cfg(any(test, feature = "test-support"))]
2968    pub fn note_using_namespace_lookup_for_test(&self) {
2969        self.using_namespace_lookup_count
2970            .fetch_add(1, Ordering::Relaxed);
2971    }
2972
2973    #[cfg(not(any(test, feature = "test-support")))]
2974    pub fn note_using_namespace_lookup_for_test(&self) {}
2975
2976    #[cfg(any(test, feature = "test-support"))]
2977    pub fn note_using_name_candidate_inspection_for_test(&self) {
2978        self.using_name_candidate_inspection_count
2979            .fetch_add(1, Ordering::Relaxed);
2980    }
2981
2982    #[cfg(not(any(test, feature = "test-support")))]
2983    pub fn note_using_name_candidate_inspection_for_test(&self) {}
2984
2985    #[cfg(any(test, feature = "test-support"))]
2986    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
2987        (
2988            self.using_donor_activation_count.load(Ordering::Relaxed),
2989            self.using_namespace_lookup_count.load(Ordering::Relaxed),
2990            self.callable_reference_spec_build_count
2991                .load(Ordering::Relaxed),
2992            self.using_name_candidate_inspection_count
2993                .load(Ordering::Relaxed),
2994        )
2995    }
2996
2997    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2998        file == target.source()
2999            || self
3000                .visible_by_file
3001                .get(file)
3002                .is_some_and(|visible| visible.contains(target))
3003    }
3004
3005    /// Whether some declaration of `declaration`'s logical symbol is visible at
3006    /// `reference_byte` in `file`.
3007    ///
3008    /// The question is asked of the *logical* symbol, not of the physical unit:
3009    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
3010    /// and it does not have to be - what makes the call legal is the header
3011    /// declaration that the reference file does include. Reading that relation
3012    /// through `same_logical_callable` rather than through signature strings is
3013    /// the same #2010 correction the gates make, and it matters here because
3014    /// the body and the declaration are exactly the pair that spells one
3015    /// parameter type two ways.
3016    pub fn declaration_visible_at(
3017        &self,
3018        analyzer: &CppGraphSource<'_>,
3019        file: &ProjectFile,
3020        declaration: &CodeUnit,
3021        reference_byte: usize,
3022    ) -> bool {
3023        let reference_guards = OnceCell::new();
3024        self.visible_identifier_candidates(file, declaration.identifier())
3025            .filter(|candidate| {
3026                self.same_logical_callable(analyzer, candidate, declaration)
3027                    || flattened_macro_namespace_declaration_matches(
3028                        analyzer,
3029                        self.cpp,
3030                        file,
3031                        candidate,
3032                        declaration,
3033                        reference_byte,
3034                    )
3035            })
3036            .any(|candidate| {
3037                self.physical_declaration_visible_at(
3038                    analyzer,
3039                    file,
3040                    candidate,
3041                    reference_byte,
3042                    &reference_guards,
3043                )
3044            })
3045    }
3046
3047    pub fn callable_arity_at_reference(
3048        &self,
3049        analyzer: &CppGraphSource<'_>,
3050        file: &ProjectFile,
3051        candidate: &CodeUnit,
3052        reference_byte: usize,
3053    ) -> Option<CallableArity> {
3054        let key = (file.clone(), logical_symbol_key(candidate));
3055        let cell = self
3056            .callable_reference_specs
3057            .lock()
3058            .expect("C++ callable reference-spec cache poisoned")
3059            .entry(key)
3060            .or_default()
3061            .clone();
3062        let spec = cell.get_or_init(|| {
3063            let prepared = self.cpp.prepared_syntax(self.token, file)?;
3064            let spec = TargetSpec::from_target(analyzer, candidate)?;
3065            let spec = spec
3066                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3067                .into_owned();
3068            #[cfg(any(test, feature = "test-support"))]
3069            self.callable_reference_spec_build_count
3070                .fetch_add(1, Ordering::Relaxed);
3071            Some(spec)
3072        });
3073        spec.as_ref()?.callable_arity_at(reference_byte)
3074    }
3075
3076    fn physical_declaration_visible_at(
3077        &self,
3078        analyzer: &CppGraphSource<'_>,
3079        file: &ProjectFile,
3080        declaration: &CodeUnit,
3081        reference_byte: usize,
3082        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3083    ) -> bool {
3084        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3085            return false;
3086        };
3087        let reference = CallableReferenceContext {
3088            file,
3089            position: Some(CallableReferencePosition {
3090                prepared: prepared.as_ref(),
3091                byte: reference_byte,
3092                guards: reference_guards,
3093            }),
3094        };
3095        if declaration.source() == file {
3096            return callable_declaration_activation_in_file(
3097                analyzer,
3098                prepared.as_ref(),
3099                declaration,
3100                &reference,
3101            )
3102            .or_else(|| {
3103                self.exhaustive_guard_family_activation(
3104                    analyzer,
3105                    prepared.as_ref(),
3106                    declaration,
3107                    &reference,
3108                )
3109            })
3110            .is_some_and(|activation| activation < reference_byte);
3111        }
3112        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3113            return false;
3114        };
3115        if callable_declaration_activation_in_file(
3116            analyzer,
3117            donor_syntax.as_ref(),
3118            declaration,
3119            &reference,
3120        )
3121        .or_else(|| {
3122            self.exhaustive_guard_family_activation(
3123                analyzer,
3124                donor_syntax.as_ref(),
3125                declaration,
3126                &reference,
3127            )
3128        })
3129        .is_none()
3130        {
3131            return false;
3132        }
3133        declaration_guard_requirements(analyzer, self.cpp, declaration)
3134            .into_iter()
3135            .any(|(_, declaration_guards)| {
3136                self.foreign_declaration_reachable_at_reference(
3137                    file,
3138                    prepared.as_ref(),
3139                    declaration.source(),
3140                    &declaration_guards,
3141                    reference.guards(),
3142                    reference_byte,
3143                )
3144            })
3145    }
3146
3147    pub fn external_type_candidate_visible_at(
3148        &self,
3149        file: &ProjectFile,
3150        candidate: &CodeUnit,
3151        reference_byte: usize,
3152    ) -> bool {
3153        if candidate.source() == file {
3154            return true;
3155        }
3156        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3157            return false;
3158        };
3159        self.visible_identifier_candidates(file, candidate.identifier())
3160            .filter(|peer| same_logical_symbol(candidate, peer))
3161            .any(|peer| {
3162                peer.source() == file
3163                    || self
3164                        .include_activation_for_source(
3165                            self.cpp,
3166                            file,
3167                            prepared.as_ref(),
3168                            peer.source(),
3169                        )
3170                        .is_some_and(|activation| activation <= reference_byte)
3171            })
3172    }
3173
3174    pub fn external_type_declaration_visible_at(
3175        &self,
3176        file: &ProjectFile,
3177        candidate: &CodeUnit,
3178        reference_byte: usize,
3179    ) -> bool {
3180        if candidate.source() == file {
3181            return true;
3182        }
3183        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3184            return false;
3185        };
3186        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3187            .is_some_and(|activation| activation <= reference_byte)
3188    }
3189
3190    /// The preprocessor facts the build proves for a reference sited in
3191    /// `file` (#2011).
3192    ///
3193    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
3194    /// `Defined` fact, and a fact holds only when every compile configuration
3195    /// that governs the file agrees on it (intersection). The facts are
3196    /// strictly additive to the reference's active guard set: they can prove a
3197    /// required guard, but the guard check itself is never weakened and no
3198    /// implication is ever inferred from source text.
3199    ///
3200    /// A file with its own database entry answers from that entry alone
3201    /// (phase 1). A header takes its context from the translation units whose
3202    /// include closure reaches it, intersected across all of them (phase 2):
3203    /// the header is compiled once per including TU, so a fact holds for a
3204    /// header-sited reference only when every one of those compilations
3205    /// proves it. A reaching TU the database does not cover proves nothing,
3206    /// which empties the intersection. A file nothing covers or reaches has
3207    /// no facts and every check runs on source structure alone.
3208    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3209        if let Some(cached) = self
3210            .compile_proven_guard_cells
3211            .lock()
3212            .expect("C++ compile-proven guard cache poisoned")
3213            .get(file)
3214        {
3215            return Arc::clone(cached);
3216        }
3217        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3218            Some(names) => names,
3219            None => {
3220                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3221                let seed = translation_units.next().and_then(|translation_unit| {
3222                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3223                });
3224                match seed {
3225                    None => HashSet::default(),
3226                    Some(mut names) => {
3227                        for translation_unit in translation_units {
3228                            let Some(reached) = context_fact_names(
3229                                self.cpp.compile_contexts_for(&translation_unit),
3230                            ) else {
3231                                names.clear();
3232                                break;
3233                            };
3234                            names.retain(|name| reached.contains(name));
3235                            if names.is_empty() {
3236                                break;
3237                            }
3238                        }
3239                        names
3240                    }
3241                }
3242            }
3243        };
3244        let proven = Arc::new(
3245            names
3246                .into_iter()
3247                .map(PreprocessorGuard::Defined)
3248                .collect::<HashSet<_>>(),
3249        );
3250        self.compile_proven_guard_cells
3251            .lock()
3252            .expect("C++ compile-proven guard cache poisoned")
3253            .insert(file.clone(), Arc::clone(&proven));
3254        proven
3255    }
3256
3257    /// Whether no compile data covers the compilations of `file`: it has no
3258    /// database entry of its own, and either nothing reaches it or some
3259    /// translation unit that reaches it has no entry. This is the state a
3260    /// regenerated `compile_commands.json` could decide; data that is present
3261    /// for every governing compilation but does not prove a guard is a
3262    /// decided conservative miss, not this state.
3263    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3264        if !self.cpp.compile_contexts_for(file).is_empty() {
3265            return false;
3266        }
3267        let translation_units = self.cpp.reaching_translation_units(file);
3268        translation_units.is_empty()
3269            || translation_units
3270                .iter()
3271                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3272    }
3273
3274    /// Whether a lookup miss for `identifier` in `file` is explainable by
3275    /// missing compile context (#2011): some same-name declaration is
3276    /// reachable through a conditional include whose required guards neither
3277    /// contradict the reference's active guards nor follow from them, and the
3278    /// translation unit has no compile-commands entry that could decide the
3279    /// question. Callers surface this as an explicit "requires compile
3280    /// context" incompleteness instead of an indistinguishable miss.
3281    ///
3282    /// A structurally disproven declaration (contradicting guards) and a TU
3283    /// whose compile context exists but does not prove the guard both answer
3284    /// `false`: those misses are decided, not incomplete.
3285    pub fn miss_requires_compile_context(
3286        &self,
3287        file: &ProjectFile,
3288        identifier: &str,
3289        reference: Node<'_>,
3290    ) -> bool {
3291        if !self.compile_context_is_absent(file) {
3292            return false;
3293        }
3294        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3295            return false;
3296        };
3297        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3298        let reference_byte = reference.start_byte();
3299        let mut sources = self
3300            .visible_identifier_candidates(file, identifier)
3301            .map(CodeUnit::source)
3302            .filter(|source| *source != file)
3303            .collect::<Vec<_>>();
3304        sources.sort();
3305        sources.dedup();
3306        sources.into_iter().any(|declaration_source| {
3307            self.conditional_include_projections_for_source(
3308                file,
3309                prepared.as_ref(),
3310                declaration_source,
3311            )
3312            .iter()
3313            .any(|projection| {
3314                projection.activation_byte <= reference_byte
3315                    && !guard_requirements_hold_at_reference(
3316                        &projection.required_guards,
3317                        reference_guards.as_ref(),
3318                    )
3319                    && guards_compatible_at_reference(
3320                        &projection.required_guards,
3321                        reference_guards.as_ref(),
3322                    )
3323            })
3324        })
3325    }
3326
3327    /// Decide whether a declaration that lives in another file reaches a
3328    /// reference in `file`.
3329    ///
3330    /// An external header selects its declaration branch before the reference
3331    /// file is parsed. Require compatible reference guards, but do not test
3332    /// the header's guard expression for stability in the reference file: a
3333    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3334    /// wraps every declaration of a portable C header, and demanding it would
3335    /// hide the whole header. Guards that the reference file imposes on its
3336    /// own `#include` still have to hold, and still have to be stable.
3337    fn foreign_declaration_reachable_at_reference(
3338        &self,
3339        file: &ProjectFile,
3340        prepared: &PreparedSyntaxTree,
3341        declaration_source: &ProjectFile,
3342        declaration_guards: &HashSet<PreprocessorGuard>,
3343        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3344        reference_byte: usize,
3345    ) -> bool {
3346        // The translation unit's build-proven defines join the reference's
3347        // active guard set (#2011): a conditional include like the nng
3348        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
3349        // A reference whose own environment is unknown stays unknown -- the
3350        // facts extend an environment, they never invent one.
3351        let proven = self.compile_proven_guards(file);
3352        let augmented;
3353        let reference_guards = match reference_guards {
3354            Some(active) if !proven.is_empty() => {
3355                augmented = active.union(&proven).cloned().collect();
3356                Some(&augmented)
3357            }
3358            other => other,
3359        };
3360        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3361            return false;
3362        }
3363        if self
3364            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3365            .is_some_and(|activation| activation <= reference_byte)
3366        {
3367            return true;
3368        }
3369        self.conditional_include_projections_for_source(file, prepared, declaration_source)
3370            .iter()
3371            .any(|projection| {
3372                projection.activation_byte <= reference_byte
3373                    && guard_requirements_hold_at_reference(
3374                        &projection.required_guards,
3375                        reference_guards,
3376                    )
3377                    && self.preprocessor_guards_stable_between(
3378                        file,
3379                        projection.activation_byte,
3380                        reference_byte,
3381                        &projection.required_guards,
3382                    )
3383            })
3384    }
3385
3386    pub fn external_type_candidate_visible_in_context(
3387        &self,
3388        analyzer: &CppGraphSource<'_>,
3389        file: &ProjectFile,
3390        candidate: &CodeUnit,
3391        reference: Node<'_>,
3392    ) -> bool {
3393        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3394            return false;
3395        };
3396        let macro_environment = self.macro_environment(file, reference.start_byte());
3397        let reference_guards = preprocessor_guard_environment(reference, prepared.source())
3398            .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3399
3400        let directly_visible = self
3401            .visible_identifier_candidates(file, candidate.identifier())
3402            .filter(|peer| same_logical_symbol(candidate, peer))
3403            .any(|peer| {
3404                declaration_guard_requirements(analyzer, self.cpp, peer)
3405                    .into_iter()
3406                    .any(|(declaration_byte, declaration_guards)| {
3407                        if peer.source() == file {
3408                            return declaration_byte < reference.start_byte()
3409                                && guard_requirements_hold_at_reference(
3410                                    &declaration_guards,
3411                                    reference_guards.as_ref(),
3412                                )
3413                                && self.preprocessor_guards_stable_between(
3414                                    file,
3415                                    declaration_byte,
3416                                    reference.start_byte(),
3417                                    &declaration_guards,
3418                                );
3419                        }
3420                        self.foreign_declaration_reachable_at_reference(
3421                            file,
3422                            prepared.as_ref(),
3423                            peer.source(),
3424                            &declaration_guards,
3425                            reference_guards.as_ref(),
3426                            reference.start_byte(),
3427                        )
3428                    })
3429            });
3430        let complementary = self
3431            .visible_identifier_candidates(file, candidate.identifier())
3432            .filter(|peer| {
3433                peer.kind() == candidate.kind()
3434                    && peer.fq_name() == candidate.fq_name()
3435                    && peer.source() == candidate.source()
3436            })
3437            .collect::<Vec<_>>();
3438        // A completed #if/#else family declares the shared source-level name
3439        // before this reference. A later macro mutation cannot revoke that
3440        // declaration. The family gate below rejects declarations split across
3441        // separate conditional blocks, where mutation can change coverage.
3442        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3443            declaration_guard_requirements(analyzer, self.cpp, candidate)
3444                .iter()
3445                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3446        });
3447        let complementary_visible = candidate_branch_compatible
3448            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3449            && if candidate.source() == file {
3450                declaration_guard_requirements(analyzer, self.cpp, candidate)
3451                    .iter()
3452                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3453            } else {
3454                self.include_activation_for_source(
3455                    self.cpp,
3456                    file,
3457                    prepared.as_ref(),
3458                    candidate.source(),
3459                )
3460                .is_some_and(|activation| activation <= reference.start_byte())
3461            };
3462        directly_visible || complementary_visible
3463    }
3464
3465    pub fn is_exhaustive_same_fqn_type_declaration_family(
3466        &self,
3467        analyzer: &CppGraphSource<'_>,
3468        file: &ProjectFile,
3469        candidate: &CodeUnit,
3470    ) -> bool {
3471        let candidates = self
3472            .visible_identifier_candidates(file, candidate.identifier())
3473            .filter(|peer| {
3474                peer.kind() == candidate.kind()
3475                    && peer.fq_name() == candidate.fq_name()
3476                    && peer.source() == candidate.source()
3477            })
3478            .collect::<Vec<_>>();
3479        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3480    }
3481
3482    /// Prove a nested type alias used as a dependent member-pointer owner when
3483    /// its owning class has mutually-exclusive declarations.  A common C++11
3484    /// compatibility shape provides the owning class in one preprocessor
3485    /// branch and aliases it to a standard-library type in the other branch;
3486    /// the nested fallback alias is therefore not itself active in every
3487    /// branch even though the qualified owner API is.
3488    ///
3489    /// This is deliberately narrower than ordinary type visibility.  The
3490    /// caller has already recovered a member-pointer owner path from the CST;
3491    /// this helper additionally requires the target's structured parent to
3492    /// match that path, physical source visibility, and exact preprocessor
3493    /// guard agreement with the parent declaration.  Only then may the
3494    /// parent's direct/complementary same-FQN visibility stand in for the
3495    /// nested terminal's active-branch check.
3496    pub fn dependent_member_pointer_alias_visible_in_context(
3497        &self,
3498        analyzer: &CppGraphSource<'_>,
3499        file: &ProjectFile,
3500        candidate: &CodeUnit,
3501        owner_components: &[String],
3502        reference: Node<'_>,
3503    ) -> bool {
3504        if !analyzer
3505            .type_alias_provider()
3506            .is_some_and(|provider| provider.is_type_alias(candidate))
3507        {
3508            return false;
3509        }
3510        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3511            return false;
3512        };
3513        if terminal != candidate.identifier()
3514            || canonical_cpp_scope_components(candidate) != owner_components
3515        {
3516            return false;
3517        }
3518        let Some(expected_parent_fq_name) =
3519            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3520        else {
3521            return false;
3522        };
3523        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3524            return false;
3525        };
3526        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3527            || parent_anchor.source() != candidate.source()
3528            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3529        {
3530            return false;
3531        }
3532
3533        // The ordinary path already handles unguarded aliases (and preserves
3534        // same-file declaration ordering).  This fallback is only for a
3535        // physically visible declaration whose guard is the owning branch's
3536        // guard, so reject a same-file declaration that appears after the
3537        // reference before considering guard compatibility.
3538        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3539            || candidate.source() == file
3540                && !analyzer
3541                    .ranges(candidate)
3542                    .iter()
3543                    .any(|range| range.start_byte < reference.start_byte())
3544        {
3545            return false;
3546        }
3547
3548        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3549        if candidate_guards.is_empty() {
3550            return false;
3551        }
3552        let same_guard_sets =
3553            |left: &[(usize, HashSet<PreprocessorGuard>)],
3554             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3555                left.iter().all(|(_, left_guards)| {
3556                    right
3557                        .iter()
3558                        .any(|(_, right_guards)| left_guards == right_guards)
3559                })
3560            };
3561        let parent_candidates = self
3562            .visible_identifier_candidates(file, parent_anchor.identifier())
3563            .filter(|peer| {
3564                peer.kind() == parent_anchor.kind()
3565                    && peer.fq_name() == expected_parent_fq_name.as_str()
3566                    && peer.source() == parent_anchor.source()
3567                    && canonical_cpp_scope_components(peer) == owner_prefix
3568            })
3569            .filter_map(|peer| {
3570                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3571                (candidate_guards.len() == parent_guards.len()
3572                    && same_guard_sets(&candidate_guards, &parent_guards)
3573                    && same_guard_sets(&parent_guards, &candidate_guards))
3574                .then(|| (peer.clone(), parent_guards))
3575            })
3576            .collect::<Vec<_>>();
3577        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3578            return false;
3579        };
3580
3581        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3582            return false;
3583        };
3584        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3585        else {
3586            return false;
3587        };
3588        // An external header selects its declaration branch before the
3589        // reference file is parsed. Require compatible reference guards, but
3590        // do not test the header's guard expression for stability in the
3591        // reference file. Same-file aliases still require that stability.
3592        if !candidate_guards.iter().any(|(_, target_guards)| {
3593            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3594                && (candidate.source() != file
3595                    || self.preprocessor_guards_stable_between(
3596                        file,
3597                        0,
3598                        reference.start_byte(),
3599                        target_guards,
3600                    ))
3601        }) {
3602            return false;
3603        }
3604
3605        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3606    }
3607
3608    /// Check a type candidate's preprocessor/import context without imposing
3609    /// ordinary declaration-before-reference ordering for same-file peers.
3610    ///
3611    /// C++ class scope makes member names visible throughout the complete
3612    /// class, including a trailing return type that appears before the member
3613    /// alias declaration in source order. Callers must first prove that the
3614    /// reference is inside the candidate's indexed class owner; this helper
3615    /// only relaxes the byte-order predicate while retaining guard and include
3616    /// activation checks.
3617    pub fn external_type_candidate_guard_compatible_in_context(
3618        &self,
3619        analyzer: &CppGraphSource<'_>,
3620        file: &ProjectFile,
3621        candidate: &CodeUnit,
3622        reference: Node<'_>,
3623    ) -> bool {
3624        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3625            return false;
3626        };
3627        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3628
3629        self.visible_identifier_candidates(file, candidate.identifier())
3630            .filter(|peer| same_logical_symbol(candidate, peer))
3631            .any(|peer| {
3632                declaration_guard_requirements(analyzer, self.cpp, peer)
3633                    .into_iter()
3634                    .any(|(declaration_byte, declaration_guards)| {
3635                        if peer.source() == file {
3636                            let (start, end) = if declaration_byte <= reference.start_byte() {
3637                                (declaration_byte, reference.start_byte())
3638                            } else {
3639                                (reference.start_byte(), declaration_byte)
3640                            };
3641                            return guard_requirements_hold_at_reference(
3642                                &declaration_guards,
3643                                reference_guards.as_ref(),
3644                            ) && self.preprocessor_guards_stable_between(
3645                                file,
3646                                start,
3647                                end,
3648                                &declaration_guards,
3649                            );
3650                        }
3651                        self.foreign_declaration_reachable_at_reference(
3652                            file,
3653                            prepared.as_ref(),
3654                            peer.source(),
3655                            &declaration_guards,
3656                            reference_guards.as_ref(),
3657                            reference.start_byte(),
3658                        )
3659                    })
3660            })
3661    }
3662
3663    /// Whether a same-file callable declaration is nameable from `reference`
3664    /// after deliberately relaxing declaration-before-reference ordering.
3665    ///
3666    /// Ordinary lookup still requires an earlier declaration. Definition
3667    /// navigation for incomplete C translation units may recover a later
3668    /// definition, but only when it is at file scope and its preprocessor
3669    /// requirements hold at the call (#2404).
3670    pub fn same_file_callable_guard_compatible_ignoring_order(
3671        &self,
3672        analyzer: &CppGraphSource<'_>,
3673        file: &ProjectFile,
3674        candidate: &CodeUnit,
3675        reference: Node<'_>,
3676    ) -> bool {
3677        if candidate.source() != file || !candidate.is_callable() {
3678            return false;
3679        }
3680        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3681            return false;
3682        };
3683        let guards = OnceCell::new();
3684        let context = CallableReferenceContext {
3685            file,
3686            position: Some(CallableReferencePosition {
3687                prepared: prepared.as_ref(),
3688                byte: reference.start_byte(),
3689                guards: &guards,
3690            }),
3691        };
3692        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
3693            .into_iter()
3694            .any(|declaration| {
3695                callable_preprocessor_context_is_visible_for_reference(
3696                    declaration,
3697                    prepared.source(),
3698                    &context,
3699                )
3700            })
3701    }
3702
3703    pub fn type_candidate_may_be_visible_before_reference(
3704        &self,
3705        analyzer: &CppGraphSource<'_>,
3706        file: &ProjectFile,
3707        candidate: &CodeUnit,
3708        reference_byte: usize,
3709    ) -> bool {
3710        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3711            return false;
3712        };
3713        let root = prepared.tree().root_node();
3714        let end_byte = reference_byte
3715            .saturating_add(1)
3716            .min(prepared.source().len());
3717        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3718            return false;
3719        };
3720        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3721    }
3722
3723    pub fn preprocessor_guards_stable_between(
3724        &self,
3725        file: &ProjectFile,
3726        start_byte: usize,
3727        end_byte: usize,
3728        guards: &HashSet<PreprocessorGuard>,
3729    ) -> bool {
3730        if guards.is_empty() || start_byte >= end_byte {
3731            return true;
3732        }
3733        let cell = self.macro_event_cell(file);
3734        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3735        let mut visited = HashSet::from_iter([file.clone()]);
3736        !events.iter().any(|event| {
3737            event.byte() >= start_byte
3738                && event.byte() < end_byte
3739                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3740        })
3741    }
3742
3743    fn macro_event_may_mutate_guards(
3744        &self,
3745        event: &MacroEvent,
3746        guards: &HashSet<PreprocessorGuard>,
3747        visited: &mut HashSet<ProjectFile>,
3748    ) -> bool {
3749        match event {
3750            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3751                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3752            }
3753            MacroEvent::Include { targets, .. } => {
3754                targets.is_empty()
3755                    || targets
3756                        .iter()
3757                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3758            }
3759            MacroEvent::Invalidate { .. } => true,
3760        }
3761    }
3762
3763    fn source_may_mutate_guards(
3764        &self,
3765        file: &ProjectFile,
3766        guards: &HashSet<PreprocessorGuard>,
3767        visited: &mut HashSet<ProjectFile>,
3768    ) -> bool {
3769        if !visited.insert(file.clone()) {
3770            return false;
3771        }
3772        let cell = self.macro_event_cell(file);
3773        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3774        events
3775            .iter()
3776            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3777    }
3778
3779    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3780        let normalized = normalize_reference_name(raw_name)?;
3781        self.type_candidates(file, &normalized)
3782            .into_iter()
3783            .next()
3784            .cloned()
3785    }
3786
3787    /// Mirror forward navigation's visible-name fallback for a bare parameter
3788    /// type after lexical owner and inheritance lookup is exhausted.
3789    ///
3790    /// Generated or otherwise unindexed base classes can hide the alias that
3791    /// makes a parameter type valid C++. Accept the fallback only when every
3792    /// include-visible class or alias with that spelling canonicalizes to one
3793    /// logical type. A shadowing local type resolves lexically before this
3794    /// path, while distinct visible types keep the result ambiguous.
3795    pub fn unique_visible_parameter_type_fallback(
3796        &self,
3797        analyzer: &CppGraphSource<'_>,
3798        file: &ProjectFile,
3799        node: Node<'_>,
3800        source: &str,
3801    ) -> Option<CodeUnit> {
3802        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
3803            return None;
3804        }
3805        let name = node_text(node, source);
3806        let candidates = self
3807            .visible_identifier_candidates(file, name)
3808            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
3809            .filter(|candidate| {
3810                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
3811            })
3812            .collect::<Vec<_>>();
3813        self.unique_canonical_type_candidate(analyzer, file, &candidates)
3814    }
3815
3816    pub fn resolve_type_node_result(
3817        &self,
3818        file: &ProjectFile,
3819        node: Node<'_>,
3820        source: &str,
3821    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3822        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3823            return Ok(None);
3824        };
3825        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3826            return Ok(Some(primary));
3827        };
3828        self.resolve_template_arguments(file, primary, &arguments)
3829            .map(Some)
3830    }
3831
3832    pub fn resolve_type_node_primary(
3833        &self,
3834        file: &ProjectFile,
3835        node: Node<'_>,
3836        source: &str,
3837    ) -> Option<CodeUnit> {
3838        let components = cpp_type_name_components(node, source)?;
3839        self.resolve_type(file, &components.join("::"))
3840    }
3841
3842    pub fn resolve_template_arguments(
3843        &self,
3844        file: &ProjectFile,
3845        primary: CodeUnit,
3846        arguments: &[CppTemplateExpression],
3847    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3848        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3849    }
3850
3851    fn resolve_template_arguments_inner(
3852        &self,
3853        file: &ProjectFile,
3854        primary: CodeUnit,
3855        arguments: &[CppTemplateExpression],
3856        seen_aliases: &mut HashSet<CodeUnit>,
3857    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3858        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3859            && let Some(alias_target) = &metadata.alias_target
3860        {
3861            if !seen_aliases.insert(primary.clone()) {
3862                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3863            }
3864            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3865                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3866            let target_name = alias_target.components.join("::");
3867            let target_primary = if alias_target.global {
3868                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3869            } else {
3870                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3871            };
3872            let Some(target_primary) = target_primary else {
3873                // A dependent or external RHS cannot be canonicalized from the
3874                // indexed graph. Preserve the alias's direct identity instead
3875                // of inventing a target from its source spelling.
3876                return Ok(primary);
3877            };
3878            let Some(target_arguments) = &alias_target.arguments else {
3879                return Ok(target_primary);
3880            };
3881            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3882                .ok_or(CppTemplateResolutionError::Substitution)?;
3883            return self.resolve_template_arguments_inner(
3884                file,
3885                target_primary,
3886                &target_arguments,
3887                seen_aliases,
3888            );
3889        }
3890
3891        let primary_fq_name = self
3892            .cpp_template_metadata
3893            .get(&primary)
3894            .map(|metadata| metadata.primary_fq_name.clone())
3895            .unwrap_or_else(|| primary.fq_name());
3896        let has_specialization_metadata = self
3897            .cpp_template_families
3898            .get(&primary_fq_name)
3899            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3900        if !has_specialization_metadata {
3901            return Ok(primary);
3902        }
3903        self.select_template_specialization(file, &primary, arguments)
3904    }
3905
3906    fn select_template_specialization(
3907        &self,
3908        file: &ProjectFile,
3909        resolved: &CodeUnit,
3910        explicit_arguments: &[CppTemplateExpression],
3911    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3912        let primary_fq_name = self
3913            .cpp_template_metadata
3914            .get(resolved)
3915            .map(|metadata| metadata.primary_fq_name.clone())
3916            .unwrap_or_else(|| resolved.fq_name());
3917        let family = self
3918            .cpp_template_families
3919            .get(&primary_fq_name)
3920            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3921        let primary_candidates = family
3922            .iter()
3923            .filter_map(|unit| {
3924                let metadata = self.cpp_template_metadata.get(unit)?;
3925                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
3926            })
3927            .collect::<Vec<_>>();
3928        let primary_unit = primary_candidates
3929            .iter()
3930            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3931            .or_else(|| {
3932                primary_candidates
3933                    .iter()
3934                    .map(|(unit, _)| *unit)
3935                    .min_by_key(|unit| {
3936                        (
3937                            unit.source().to_string(),
3938                            unit.signature().unwrap_or_default(),
3939                        )
3940                    })
3941            })
3942            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3943        let primary_parameters =
3944            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3945                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3946        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3947            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3948
3949        let mut applicable = Vec::new();
3950        for unit in family {
3951            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3952                continue;
3953            };
3954            if metadata.is_primary() || !self.is_visible(file, unit) {
3955                continue;
3956            }
3957            if !cpp_specialization_matches(metadata, &expanded) {
3958                continue;
3959            }
3960            applicable.push((unit, metadata));
3961        }
3962        if applicable.is_empty() {
3963            return Ok(primary_unit.clone());
3964        }
3965
3966        // A scalar constraint count cannot represent C++ partial ordering:
3967        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3968        // Select only a logical candidate whose structural pattern is strictly
3969        // more specialized than every other distinct applicable candidate.
3970        let winners = applicable
3971            .iter()
3972            .filter(|(candidate, candidate_metadata)| {
3973                applicable.iter().all(|(other, other_metadata)| {
3974                    same_visible_symbol(candidate, other)
3975                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3976                })
3977            })
3978            .copied()
3979            .collect::<Vec<_>>();
3980        let Some((selected, _)) = winners.first() else {
3981            // Mutually incomparable applicable candidates: every one of them
3982            // is a live contender.
3983            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3984                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3985            });
3986        };
3987        if winners
3988            .iter()
3989            .any(|(unit, _)| !same_visible_symbol(unit, selected))
3990        {
3991            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3992                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3993            });
3994        }
3995        Ok((*selected).clone())
3996    }
3997
3998    pub fn resolve_type_components_lexically(
3999        &self,
4000        analyzer: &CppGraphSource<'_>,
4001        file: &ProjectFile,
4002        components: &[String],
4003        global: bool,
4004        lexical_scope: &[String],
4005    ) -> LexicalTypeResolution {
4006        self.resolve_type_components_lexically_inner(
4007            analyzer,
4008            file,
4009            components,
4010            global,
4011            lexical_scope,
4012            TypeCandidateResolution::Canonical,
4013        )
4014    }
4015
4016    pub fn resolve_type_components_lexically_for_forward(
4017        &self,
4018        analyzer: &CppGraphSource<'_>,
4019        file: &ProjectFile,
4020        components: &[String],
4021        global: bool,
4022        lexical_scope: &[String],
4023    ) -> LexicalTypeResolution {
4024        self.resolve_type_components_lexically_inner(
4025            analyzer,
4026            file,
4027            components,
4028            global,
4029            lexical_scope,
4030            TypeCandidateResolution::PreserveAlias,
4031        )
4032    }
4033
4034    pub fn resolve_type_components_lexically_for_target(
4035        &self,
4036        analyzer: &CppGraphSource<'_>,
4037        file: &ProjectFile,
4038        components: &[String],
4039        global: bool,
4040        lexical_scope: &[String],
4041        target: &CodeUnit,
4042    ) -> LexicalTypeResolution {
4043        #[cfg(any(test, feature = "test-support"))]
4044        self.target_preserving_type_resolution_count
4045            .fetch_add(1, Ordering::Relaxed);
4046        self.resolve_type_components_lexically_inner(
4047            analyzer,
4048            file,
4049            components,
4050            global,
4051            lexical_scope,
4052            TypeCandidateResolution::PreserveTarget(target),
4053        )
4054    }
4055
4056    pub fn coarse_unqualified_type_reference_may_resolve(
4057        &self,
4058        file: &ProjectFile,
4059        name: &str,
4060    ) -> bool {
4061        if name.is_empty() {
4062            return true;
4063        }
4064        self.visible_identifier_candidates(file, name)
4065            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4066            || self.visible_parser_alias_name_is_visible(file, name)
4067    }
4068
4069    #[allow(clippy::too_many_arguments)]
4070    pub fn structured_type_reference_may_resolve_to_target(
4071        &self,
4072        analyzer: &CppGraphSource<'_>,
4073        file: &ProjectFile,
4074        components: &[String],
4075        global: bool,
4076        lexical_scope: &[String],
4077        target: &CodeUnit,
4078    ) -> bool {
4079        if components.is_empty() {
4080            return true;
4081        }
4082        let Some(terminal) = components.last() else {
4083            return true;
4084        };
4085        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
4086        if parser_alias_visible
4087            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
4088        {
4089            return true;
4090        }
4091        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4092            .map(|qualified| qualified.join("::"))
4093            .collect::<Vec<_>>();
4094        let target_name = cpp_name_for(target);
4095        if qualified_tiers
4096            .iter()
4097            .any(|qualified| qualified == &target_name)
4098        {
4099            return true;
4100        }
4101
4102        let mut saw_shape_candidate = parser_alias_visible;
4103        for candidate in self.visible_identifier_candidates(file, terminal) {
4104            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4105            {
4106                continue;
4107            }
4108            let candidate_name = cpp_name_for(candidate);
4109            let shape_matches = if global || components.len() > 1 {
4110                qualified_tiers
4111                    .iter()
4112                    .any(|qualified| qualified == &candidate_name)
4113            } else {
4114                true
4115            };
4116            if !shape_matches {
4117                continue;
4118            }
4119            saw_shape_candidate = true;
4120            if same_visible_symbol(candidate, target)
4121                || self.compatible_primary_template_redeclarations(candidate, target)
4122                || (declared_type_alias(analyzer, candidate)
4123                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4124            {
4125                return true;
4126            }
4127        }
4128
4129        !saw_shape_candidate
4130    }
4131
4132    pub fn target_preserving_reference_namespace(
4133        &self,
4134        analyzer: &CppGraphSource<'_>,
4135        file: &ProjectFile,
4136        identifier: &str,
4137        target: &CodeUnit,
4138    ) -> Option<Vec<String>> {
4139        let mut namespace = None;
4140        for candidate in self.visible_identifier_candidates(file, identifier) {
4141            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4142            {
4143                continue;
4144            }
4145            if !(same_visible_symbol(candidate, target)
4146                || self.compatible_primary_template_redeclarations(candidate, target)
4147                || declared_type_alias(analyzer, candidate)
4148                    && self.structured_alias_primary_preserves_target(
4149                        analyzer, file, candidate, target,
4150                    ))
4151            {
4152                continue;
4153            }
4154            if namespace
4155                .as_ref()
4156                .is_some_and(|existing| existing != candidate.package_name())
4157            {
4158                return None;
4159            }
4160            namespace = Some(candidate.package_name().to_string());
4161        }
4162        let namespace = namespace?;
4163        Some(
4164            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4165                brokk_bifrost_core::analyzer::Language::Cpp,
4166                &namespace,
4167            ),
4168        )
4169    }
4170
4171    pub fn resolve_imported_type_candidate(
4172        &self,
4173        analyzer: &CppGraphSource<'_>,
4174        file: &ProjectFile,
4175        target: &CodeUnit,
4176        target_components: &[String],
4177        direct_target: Option<&CodeUnit>,
4178        preserve_alias: bool,
4179    ) -> LexicalTypeResolution {
4180        let candidates = [target];
4181        let resolution = if preserve_alias {
4182            TypeCandidateResolution::PreserveAlias
4183        } else {
4184            direct_target.map_or(
4185                TypeCandidateResolution::Canonical,
4186                TypeCandidateResolution::PreserveTarget,
4187            )
4188        };
4189        // One candidate goes in, so a failure here is never "choose one of
4190        // these": it is the alias chain leaving the index, which must answer
4191        // missing rather than ambiguous (#1828).
4192        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4193            Ok(unit) => LexicalTypeResolution::Resolved {
4194                unit,
4195                components: target_components.to_vec(),
4196                candidates: vec![target.clone()],
4197            },
4198            Err(failure) => failure.lexical_resolution(),
4199        }
4200    }
4201
4202    fn resolve_type_components_lexically_inner(
4203        &self,
4204        analyzer: &CppGraphSource<'_>,
4205        file: &ProjectFile,
4206        components: &[String],
4207        global: bool,
4208        lexical_scope: &[String],
4209        resolution: TypeCandidateResolution<'_>,
4210    ) -> LexicalTypeResolution {
4211        if components.is_empty() {
4212            return LexicalTypeResolution::Missing;
4213        }
4214        // A C++ class injects its own name into the class scope.  The indexed
4215        // FqName for that declaration is the class path itself (for example,
4216        // `n::raw_hash_set`), not a synthetic child named
4217        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
4218        // requested identifier to every scope component, so they cannot
4219        // represent that injected binding when the enclosing class is the
4220        // closest scope.  Recover the binding from the structured class path
4221        // before allowing lookup to fall through to an outer same-spelled
4222        // declaration.
4223        let mut injected = self.resolve_injected_class_name(
4224            analyzer,
4225            file,
4226            components,
4227            global,
4228            lexical_scope,
4229            resolution,
4230        );
4231        for qualified in lexical_component_tiers(components, global, lexical_scope) {
4232            let prefix_len = qualified.len().saturating_sub(components.len());
4233            if injected
4234                .as_ref()
4235                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4236            {
4237                return injected
4238                    .take()
4239                    .expect("injected class resolution was just present")
4240                    .1;
4241            }
4242            let qualified_name = qualified.join("::");
4243            let candidates = self
4244                .type_candidates(file, &qualified_name)
4245                .into_iter()
4246                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4247                .collect::<Vec<_>>();
4248            if candidates.is_empty() {
4249                if !global && components.len() == 1 {
4250                    match self.resolve_inherited_type_for_lexical_scope(
4251                        analyzer,
4252                        file,
4253                        &qualified[..prefix_len],
4254                        &components[0],
4255                        resolution,
4256                    ) {
4257                        LexicalTypeResolution::Missing => {}
4258                        inherited => return inherited,
4259                    }
4260                }
4261                continue;
4262            }
4263            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4264                Ok(unit) => unit,
4265                Err(failure) => return failure.lexical_resolution(),
4266            };
4267            return LexicalTypeResolution::Resolved {
4268                unit,
4269                components: qualified,
4270                candidates: candidates.into_iter().cloned().collect(),
4271            };
4272        }
4273        LexicalTypeResolution::Missing
4274    }
4275
4276    fn resolve_injected_class_name(
4277        &self,
4278        analyzer: &CppGraphSource<'_>,
4279        file: &ProjectFile,
4280        components: &[String],
4281        global: bool,
4282        lexical_scope: &[String],
4283        resolution: TypeCandidateResolution<'_>,
4284    ) -> Option<(usize, LexicalTypeResolution)> {
4285        if global
4286            || components.len() != 1
4287            || file.rel_path().extension().is_some_and(|ext| ext == "c")
4288            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
4289        {
4290            return None;
4291        }
4292        let name = components.first()?;
4293        let mut matches: Vec<&CodeUnit> = Vec::new();
4294        let mut owner_len = 0;
4295        for candidate in self.visible_identifier_candidates(file, name) {
4296            if !candidate.is_class()
4297                || declared_type_alias(analyzer, candidate)
4298                || candidate.identifier() != name
4299            {
4300                continue;
4301            }
4302            let candidate_scope = canonical_cpp_scope_components(candidate);
4303            if candidate_scope.len() > lexical_scope.len()
4304                || !lexical_scope.starts_with(&candidate_scope)
4305                || candidate_scope.last().is_none_or(|last| last != name)
4306            {
4307                continue;
4308            }
4309            if candidate_scope.len() > owner_len {
4310                owner_len = candidate_scope.len();
4311                matches.clear();
4312            }
4313            if candidate_scope.len() == owner_len
4314                && !matches
4315                    .iter()
4316                    .any(|existing| same_logical_symbol(existing, candidate))
4317            {
4318                matches.push(candidate);
4319            }
4320        }
4321        if matches.is_empty() {
4322            return None;
4323        }
4324        // A same-named class at the current lexical boundary is already
4325        // represented by the ordinary namespace/class tier.  The injected
4326        // recovery is only needed when lookup is occurring inside a nested
4327        // class, where the enclosing class name is injected across that
4328        // additional class boundary.  Keeping this boundary strict avoids
4329        // treating qualified receiver/static-qualifier context as an
4330        // injected-name reference.
4331        if owner_len >= lexical_scope.len() {
4332            return None;
4333        }
4334        let owner_components = lexical_scope[..owner_len].to_vec();
4335        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
4336            Ok(unit) => LexicalTypeResolution::Resolved {
4337                unit,
4338                components: owner_components,
4339                candidates: matches.into_iter().cloned().collect(),
4340            },
4341            Err(failure) => failure.lexical_resolution(),
4342        };
4343        Some((owner_len, resolution))
4344    }
4345
4346    fn resolve_inherited_type_for_lexical_scope(
4347        &self,
4348        analyzer: &CppGraphSource<'_>,
4349        file: &ProjectFile,
4350        lexical_scope: &[String],
4351        name: &str,
4352        resolution: TypeCandidateResolution<'_>,
4353    ) -> LexicalTypeResolution {
4354        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
4355            return LexicalTypeResolution::Missing;
4356        };
4357        let lexical_owner_name = lexical_scope.join("::");
4358        if lexical_owner_name.is_empty() {
4359            return LexicalTypeResolution::Missing;
4360        }
4361        let owner_candidates = self
4362            .type_candidates(file, &lexical_owner_name)
4363            .into_iter()
4364            .filter(|candidate| {
4365                canonical_cpp_name_matches(candidate, &lexical_owner_name)
4366                    && !declared_type_alias(analyzer, candidate)
4367            })
4368            .collect::<Vec<_>>();
4369        if owner_candidates.is_empty() {
4370            return LexicalTypeResolution::Missing;
4371        }
4372        // A visible forward declaration and the physical class definition share
4373        // one FQN, but only the definition owns hierarchy facts. When lookup is
4374        // physically inside that definition, do not let an earlier header
4375        // forward declaration erase its base edges (#2240).
4376        let physical_owner_candidates = owner_candidates
4377            .iter()
4378            .copied()
4379            .filter(|candidate| candidate.source() == file)
4380            .collect::<Vec<_>>();
4381        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
4382            owner_candidates
4383        } else {
4384            physical_owner_candidates
4385        };
4386        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
4387            return LexicalTypeResolution::Ambiguous;
4388        };
4389
4390        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
4391        let mut visited_owners = HashSet::default();
4392        while !frontier.is_empty() {
4393            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
4394            let mut next_frontier = Vec::new();
4395            for owner in frontier {
4396                if !visited_owners.insert(owner.fq_name()) {
4397                    continue;
4398                }
4399                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
4400                let candidates = self
4401                    .type_candidates(file, &qualified_name)
4402                    .into_iter()
4403                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4404                    .collect::<Vec<_>>();
4405                if candidates.is_empty() {
4406                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
4407                        if !next_frontier
4408                            .iter()
4409                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
4410                        {
4411                            next_frontier.push(ancestor);
4412                        }
4413                    }
4414                    continue;
4415                }
4416                let unit =
4417                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4418                        Ok(unit) => unit,
4419                        Err(failure) => return failure.lexical_resolution(),
4420                    };
4421                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
4422            }
4423            if let Some((unit, candidates)) = level_matches.first().cloned() {
4424                let Some(first_declaration) = candidates.first() else {
4425                    return LexicalTypeResolution::Ambiguous;
4426                };
4427                if !level_matches.iter().all(|(_, declarations)| {
4428                    declarations
4429                        .iter()
4430                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
4431                }) {
4432                    return LexicalTypeResolution::Ambiguous;
4433                }
4434                let mut components = lexical_scope.to_vec();
4435                components.push(name.to_string());
4436                return LexicalTypeResolution::Resolved {
4437                    unit,
4438                    components,
4439                    candidates,
4440                };
4441            }
4442            frontier = next_frontier;
4443        }
4444        LexicalTypeResolution::Missing
4445    }
4446
4447    /// Resolve a base class through its injected class name at the nearest
4448    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
4449    pub fn inherited_injected_class_owner(
4450        &self,
4451        analyzer: &CppGraphSource<'_>,
4452        file: &ProjectFile,
4453        enclosing_owner: &CodeUnit,
4454        injected_name: &str,
4455    ) -> Option<CodeUnit> {
4456        let hierarchy = analyzer.type_hierarchy_provider()?;
4457        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4458        let mut visited = HashSet::default();
4459        while !frontier.is_empty() {
4460            let mut level_matches = Vec::new();
4461            let mut next_frontier = Vec::new();
4462            for raw_owner in frontier {
4463                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4464                if !visited.insert(owner.clone()) {
4465                    continue;
4466                }
4467                if owner.identifier() == injected_name
4468                    && !level_matches
4469                        .iter()
4470                        .any(|existing| same_logical_symbol(existing, &owner))
4471                {
4472                    level_matches.push(owner.clone());
4473                }
4474                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4475            }
4476            if let Some(first) = level_matches.first() {
4477                return level_matches
4478                    .iter()
4479                    .all(|candidate| same_logical_symbol(candidate, first))
4480                    .then(|| first.clone());
4481            }
4482            frontier = next_frontier;
4483        }
4484        None
4485    }
4486
4487    /// The one type the candidates name under `resolution`, or why they do not
4488    /// name one. The two preserving modes only ever reject candidates that
4489    /// disagree with each other, which is ambiguity; canonicalization can also
4490    /// fail because the alias chain leaves the index (#1828).
4491    fn resolve_type_candidates(
4492        &self,
4493        analyzer: &CppGraphSource<'_>,
4494        file: &ProjectFile,
4495        candidates: &[&CodeUnit],
4496        resolution: TypeCandidateResolution<'_>,
4497    ) -> Result<CodeUnit, TypeCandidateFailure> {
4498        match resolution {
4499            TypeCandidateResolution::Canonical => {
4500                self.canonical_type_candidate_resolution(analyzer, file, candidates)
4501            }
4502            TypeCandidateResolution::PreserveAlias => {
4503                // A generated index can retain identical alias spellings from
4504                // mutually exclusive headers. When the reference file
4505                // physically reaches exactly one of those source declarations,
4506                // include closure is the structured evidence that selects it;
4507                // treating the two source spellings as an overload set makes a
4508                // reachable alias appear ambiguous (#1844).
4509                let same_fqn_alias_family = candidates.len() > 1
4510                    && candidates.iter().all(|candidate| {
4511                        declared_type_alias(analyzer, candidate)
4512                            && same_logical_symbol(candidates[0], candidate)
4513                    })
4514                    && candidates
4515                        .iter()
4516                        .any(|candidate| candidate.source() != candidates[0].source());
4517                if same_fqn_alias_family {
4518                    let physically_visible = candidates
4519                        .iter()
4520                        .copied()
4521                        .filter(|candidate| self.is_physically_visible(file, candidate))
4522                        .collect::<Vec<_>>();
4523                    // The family is one logical declaration only when the
4524                    // reachable spellings agree. Two same-FQN aliases whose
4525                    // written targets differ (`using Choice = Canonical;` in
4526                    // one header, `using Choice = ::Canonical;` in another)
4527                    // are a genuine conflict, and choosing the first indexed
4528                    // one silently binds the reference to an arbitrary owner
4529                    // (#2398). Collapse only a single reachable declaration
4530                    // or reachable declarations with one structured target;
4531                    // everything else stays ambiguous below.
4532                    let one_structured_target = physically_visible.len() > 1
4533                        && physically_visible.iter().skip(1).all(|candidate| {
4534                            let target = self.structured_alias_target(analyzer, candidate);
4535                            target.is_some()
4536                                && target
4537                                    == self.structured_alias_target(analyzer, physically_visible[0])
4538                        });
4539                    if physically_visible.len() == 1 || one_structured_target {
4540                        return Ok(physically_visible[0].clone());
4541                    }
4542                }
4543                unique_type_candidate_preserving_alias(analyzer, candidates)
4544                    .ok_or(TypeCandidateFailure::Ambiguous)
4545            }
4546            TypeCandidateResolution::PreserveTarget(target) => self
4547                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4548                .ok_or(TypeCandidateFailure::Ambiguous),
4549        }
4550    }
4551
4552    pub fn resolve_callable_value_components_lexically(
4553        &self,
4554        analyzer: &CppGraphSource<'_>,
4555        file: &ProjectFile,
4556        owner_components: &[String],
4557        member_name: &str,
4558        global: bool,
4559        lexical_scope: &[String],
4560    ) -> LexicalCallableValueResolution {
4561        if owner_components.is_empty() || member_name.is_empty() {
4562            return LexicalCallableValueResolution::Missing;
4563        }
4564        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4565            let owner_name = qualified_owner.join("::");
4566            let type_candidates = self
4567                .type_candidates(file, &owner_name)
4568                .into_iter()
4569                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4570                .collect::<Vec<_>>();
4571            let resolved_type = if type_candidates.is_empty() {
4572                None
4573            } else {
4574                let Some(unit) =
4575                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4576                else {
4577                    return LexicalCallableValueResolution::Ambiguous;
4578                };
4579                Some(unit)
4580            };
4581
4582            let mut qualified_callable = qualified_owner;
4583            qualified_callable.push(member_name.to_string());
4584            let callable_name = qualified_callable.join("::");
4585            let free_function = self
4586                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4587                .into_iter()
4588                .find(|candidate| {
4589                    canonical_cpp_name_matches(candidate, &callable_name)
4590                        && type_owner_of(analyzer, candidate).is_none()
4591                })
4592                .cloned();
4593
4594            match (resolved_type, free_function) {
4595                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4596                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4597                (None, Some(function)) => {
4598                    return LexicalCallableValueResolution::FreeFunction(function);
4599                }
4600                (None, None) => {}
4601            }
4602        }
4603        LexicalCallableValueResolution::Missing
4604    }
4605
4606    fn resolve_type_for_declaration(
4607        &self,
4608        visible_from: &ProjectFile,
4609        declaration: &CodeUnit,
4610        raw_name: &str,
4611    ) -> Option<CodeUnit> {
4612        let normalized = normalize_reference_name(raw_name)?;
4613        if !normalized.contains("::")
4614            && let Some(namespace) = cpp_namespace_for(declaration)
4615        {
4616            for prefix in namespace_prefixes(&namespace) {
4617                let qualified = format!("{prefix}::{normalized}");
4618                if let Some(unit) = self
4619                    .type_candidates(visible_from, &qualified)
4620                    .into_iter()
4621                    .next()
4622                {
4623                    return Some(unit.clone());
4624                }
4625            }
4626        }
4627        self.resolve_type(visible_from, raw_name)
4628    }
4629
4630    fn resolve_unique_canonical_type_for_declaration(
4631        &self,
4632        analyzer: &CppGraphSource<'_>,
4633        visible_from: &ProjectFile,
4634        declaration: &CodeUnit,
4635        raw_name: &str,
4636    ) -> Option<CodeUnit> {
4637        let mut current =
4638            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4639        let mut seen_aliases = HashSet::default();
4640        loop {
4641            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4642                return current.is_class().then_some(current);
4643            };
4644            if matches!(target, StructuredAliasTarget::Builtin) {
4645                return current.is_class().then_some(current);
4646            }
4647            if !seen_aliases.insert(current.clone()) {
4648                return None;
4649            }
4650            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4651        }
4652    }
4653
4654    pub fn canonical_type_unit(
4655        &self,
4656        analyzer: &CppGraphSource<'_>,
4657        visible_from: &ProjectFile,
4658        unit: &CodeUnit,
4659    ) -> Option<CodeUnit> {
4660        self.canonical_type_resolution(analyzer, visible_from, unit)
4661            .ok()
4662    }
4663
4664    /// Follow `unit`'s alias chain to the class it names, or report why the
4665    /// chain does not end at one indexed class.
4666    ///
4667    /// A chain that leaves the index - an alias to a template parameter, to a
4668    /// standard-library type, or to any other declaration the workspace does
4669    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4670    /// there is still nothing to choose between.
4671    fn canonical_type_resolution(
4672        &self,
4673        analyzer: &CppGraphSource<'_>,
4674        visible_from: &ProjectFile,
4675        unit: &CodeUnit,
4676    ) -> Result<CodeUnit, TypeCandidateFailure> {
4677        let mut current = unit.clone();
4678        let mut seen_aliases = HashSet::default();
4679        loop {
4680            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4681                return current
4682                    .is_class()
4683                    .then_some(current)
4684                    .ok_or(TypeCandidateFailure::Unresolvable);
4685            };
4686            if matches!(target, StructuredAliasTarget::Builtin) {
4687                return current
4688                    .is_class()
4689                    .then_some(current)
4690                    .ok_or(TypeCandidateFailure::Unresolvable);
4691            }
4692            if !seen_aliases.insert(current.clone()) {
4693                return Err(TypeCandidateFailure::Unresolvable);
4694            }
4695            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4696        }
4697    }
4698
4699    pub fn canonical_visible_full_type_unit(
4700        &self,
4701        analyzer: &CppGraphSource<'_>,
4702        visible_from: &ProjectFile,
4703        unit: &CodeUnit,
4704    ) -> Option<CodeUnit> {
4705        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4706        if cpp_class_declaration_strength(analyzer, &canonical)
4707            != CppClassDeclarationStrength::Forward
4708        {
4709            return Some(canonical);
4710        }
4711        let mut full = Vec::new();
4712        for candidate in self
4713            .visible_identifier_candidates(visible_from, canonical.identifier())
4714            .filter(|candidate| {
4715                candidate.is_class()
4716                    && candidate.fq_name() == canonical.fq_name()
4717                    && cpp_class_declaration_strength(analyzer, candidate)
4718                        == CppClassDeclarationStrength::Full
4719            })
4720        {
4721            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4722                full.push(candidate.clone());
4723            }
4724        }
4725        match full.len() {
4726            0 => Some(canonical),
4727            1 => full.pop(),
4728            _ => None,
4729        }
4730    }
4731
4732    fn resolve_structured_alias_target(
4733        &self,
4734        visible_from: &ProjectFile,
4735        declaration: &CodeUnit,
4736        target: &StructuredAliasTarget,
4737    ) -> Option<CodeUnit> {
4738        self.structured_alias_target_resolution(visible_from, declaration, target)
4739            .ok()
4740    }
4741
4742    fn structured_alias_target_resolution(
4743        &self,
4744        visible_from: &ProjectFile,
4745        declaration: &CodeUnit,
4746        target: &StructuredAliasTarget,
4747    ) -> Result<CodeUnit, TypeCandidateFailure> {
4748        let primary =
4749            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4750        let StructuredAliasTarget::Named { arguments, .. } = target else {
4751            return Err(TypeCandidateFailure::Unresolvable);
4752        };
4753        match arguments {
4754            Some(arguments) => self
4755                .resolve_template_arguments(visible_from, primary, arguments)
4756                .map_err(|error| match error {
4757                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4758                        TypeCandidateFailure::Ambiguous
4759                    }
4760                    _ => TypeCandidateFailure::Unresolvable,
4761                }),
4762            None => Ok(primary),
4763        }
4764    }
4765
4766    fn resolve_structured_alias_primary(
4767        &self,
4768        visible_from: &ProjectFile,
4769        declaration: &CodeUnit,
4770        target: &StructuredAliasTarget,
4771    ) -> Option<CodeUnit> {
4772        self.structured_alias_primary_resolution(visible_from, declaration, target)
4773            .ok()
4774    }
4775
4776    fn structured_alias_primary_resolution(
4777        &self,
4778        visible_from: &ProjectFile,
4779        declaration: &CodeUnit,
4780        target: &StructuredAliasTarget,
4781    ) -> Result<CodeUnit, TypeCandidateFailure> {
4782        let StructuredAliasTarget::Named {
4783            components, global, ..
4784        } = target
4785        else {
4786            return Err(TypeCandidateFailure::Unresolvable);
4787        };
4788        let qualified = components.join("::");
4789        let candidates = if *global {
4790            // `::A::B` anchors at the root scope, so a candidate whose
4791            // canonical path merely ends with the spelled components does not
4792            // qualify. Without this filter a global `::Canonical` target also
4793            // collects `alpha::Canonical`, the lookup reports a false
4794            // ambiguity, and the alias arm silently drops out of its
4795            // conflicting family instead of proving the conflict (#2398).
4796            let mut candidates = self.type_candidates(visible_from, &qualified);
4797            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
4798            candidates
4799        } else {
4800            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4801        };
4802        logical_type_candidate(candidates)
4803    }
4804
4805    pub fn structured_alias_primary_preserves_target(
4806        &self,
4807        analyzer: &CppGraphSource<'_>,
4808        visible_from: &ProjectFile,
4809        candidate: &CodeUnit,
4810        target: &CodeUnit,
4811    ) -> bool {
4812        let mut current = candidate.clone();
4813        let mut seen = HashSet::default();
4814        let mut matched_target = false;
4815        loop {
4816            if same_visible_symbol(&current, target)
4817                || self.compatible_primary_template_redeclarations(&current, target)
4818            {
4819                matched_target = true;
4820            }
4821            if !seen.insert(current.clone()) {
4822                return false;
4823            }
4824            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4825                return matched_target;
4826            };
4827            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4828                return matched_target;
4829            };
4830            let Some(primary) =
4831                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4832            else {
4833                // A dependent member target such as `Detector<T>::type`
4834                // cannot be reduced to an indexed primary, but a preceding
4835                // structured alias hop may already have proven the requested
4836                // alias identity. Cycles still resolve a primary and are
4837                // rejected by `seen` above.
4838                return matched_target;
4839            };
4840            current = primary;
4841        }
4842    }
4843
4844    pub fn structured_class_alias_resolves_to_target(
4845        &self,
4846        analyzer: &CppGraphSource<'_>,
4847        visible_from: &ProjectFile,
4848        alias: &CodeUnit,
4849        target: &CodeUnit,
4850    ) -> bool {
4851        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4852            return false;
4853        };
4854        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4855            return false;
4856        };
4857        let StructuredAliasTarget::Named {
4858            components, global, ..
4859        } = &alias_target
4860        else {
4861            return false;
4862        };
4863        let lexical_scope = canonical_cpp_scope_components(&owner);
4864        match self.resolve_type_components_lexically_for_target(
4865            analyzer,
4866            visible_from,
4867            components,
4868            *global,
4869            &lexical_scope,
4870            target,
4871        ) {
4872            LexicalTypeResolution::Resolved {
4873                unit, candidates, ..
4874            } => {
4875                same_visible_symbol(&unit, target)
4876                    || self.same_template_member_identity(analyzer, &unit, target)
4877                    || candidates.iter().any(|candidate| {
4878                        same_visible_symbol(candidate, target)
4879                            || self.same_template_member_identity(analyzer, candidate, target)
4880                    })
4881            }
4882            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4883                self.structured_alias_primary_preserves_target(
4884                    analyzer,
4885                    visible_from,
4886                    alias,
4887                    target,
4888                ) || self.flattened_macro_namespace_alias_target_matches(
4889                    analyzer,
4890                    visible_from,
4891                    alias,
4892                    &alias_target,
4893                    target,
4894                )
4895            }
4896        }
4897    }
4898
4899    /// Return true when a class-owned alias names the requested type as one
4900    /// structured qualifier in its target path.
4901    ///
4902    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4903    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4904    /// canonical identity. Inverse lookup needs the same evidence when later
4905    /// references use only the alias spelling.
4906    pub fn structured_class_alias_path_preserves_target(
4907        &self,
4908        analyzer: &CppGraphSource<'_>,
4909        visible_from: &ProjectFile,
4910        alias: &CodeUnit,
4911        target: &CodeUnit,
4912    ) -> bool {
4913        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4914            return false;
4915        };
4916        let Some(StructuredAliasTarget::Named {
4917            components, global, ..
4918        }) = self.structured_alias_target(analyzer, alias)
4919        else {
4920            return false;
4921        };
4922        let lexical_scope = canonical_cpp_scope_components(&owner);
4923        (1..components.len()).rev().any(|component_count| {
4924            matches!(
4925                self.resolve_type_components_lexically_for_target(
4926                    analyzer,
4927                    visible_from,
4928                    &components[..component_count],
4929                    global,
4930                    &lexical_scope,
4931                    target,
4932                ),
4933                LexicalTypeResolution::Resolved {
4934                    ref unit,
4935                    ref candidates,
4936                    ..
4937                } if same_visible_symbol(unit, target)
4938                    || self.same_template_member_identity(analyzer, unit, target)
4939                    || candidates.iter().any(|candidate| {
4940                        same_visible_symbol(candidate, target)
4941                            || self.same_template_member_identity(analyzer, candidate, target)
4942                    })
4943            )
4944        })
4945    }
4946
4947    fn flattened_macro_namespace_alias_target_matches(
4948        &self,
4949        analyzer: &CppGraphSource<'_>,
4950        visible_from: &ProjectFile,
4951        alias: &CodeUnit,
4952        alias_target: &StructuredAliasTarget,
4953        target: &CodeUnit,
4954    ) -> bool {
4955        let StructuredAliasTarget::Named {
4956            components,
4957            global: false,
4958            arguments: None,
4959        } = alias_target
4960        else {
4961            return false;
4962        };
4963        let Some((target_name, namespace_components)) = components.split_last() else {
4964            return false;
4965        };
4966        if namespace_components.is_empty()
4967            || target_name != target.identifier()
4968            || alias.source() != target.source()
4969            || alias.source() != visible_from
4970            || !target.is_class()
4971            || declared_type_alias(analyzer, target)
4972        {
4973            return false;
4974        }
4975        if self
4976            .resolve_structured_alias_target(visible_from, alias, alias_target)
4977            .is_some()
4978        {
4979            return false;
4980        }
4981
4982        let alias_ranges = analyzer.ranges(alias);
4983        let target_ranges = analyzer.ranges(target);
4984        if alias_ranges.is_empty() || target_ranges.is_empty() {
4985            return false;
4986        }
4987        let alias_start = alias_ranges
4988            .iter()
4989            .map(|range| range.start_byte)
4990            .min()
4991            .expect("non-empty alias ranges have a minimum");
4992        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
4993            return false;
4994        };
4995        let root = prepared.tree().root_node();
4996        let has_matching_declaration = target_ranges
4997            .iter()
4998            .filter(|range| range.end_byte <= alias_start)
4999            .filter_map(|range| node_for_exact_range(root, range))
5000            .any(|node| {
5001                flattened_macro_namespace_components(node, prepared.source())
5002                    .is_some_and(|recovered| recovered == namespace_components)
5003            });
5004        if !has_matching_declaration {
5005            return false;
5006        }
5007
5008        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5009        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5010        guard_requirement_sets_match(&alias_guards, &target_guards)
5011    }
5012
5013    pub fn template_alias_arguments_preserve_target(
5014        &self,
5015        analyzer: &CppGraphSource<'_>,
5016        visible_from: &ProjectFile,
5017        alias: &CodeUnit,
5018        arguments: &[CppTemplateExpression],
5019        target: &CodeUnit,
5020    ) -> bool {
5021        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5022            return false;
5023        };
5024        if metadata.alias_target.is_none()
5025            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5026        {
5027            return false;
5028        }
5029        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5030    }
5031
5032    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5033        self.cpp_template_metadata
5034            .get(unit)
5035            .is_some_and(CppTemplateMetadata::is_primary)
5036    }
5037
5038    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5039        self.cpp_template_metadata
5040            .get(unit)
5041            .is_some_and(CppTemplateMetadata::is_specialization)
5042    }
5043
5044    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5045        same_visible_symbol(left, right)
5046            || self.compatible_primary_template_redeclarations(left, right)
5047    }
5048
5049    pub fn same_template_member_identity(
5050        &self,
5051        analyzer: &CppGraphSource<'_>,
5052        left: &CodeUnit,
5053        right: &CodeUnit,
5054    ) -> bool {
5055        if same_visible_symbol(left, right) {
5056            return true;
5057        }
5058        if left.kind() != right.kind()
5059            || left.identifier() != right.identifier()
5060            || left.signature() != right.signature()
5061        {
5062            return false;
5063        }
5064        let (Some(left_owner), Some(right_owner)) =
5065            (analyzer.parent_of(left), analyzer.parent_of(right))
5066        else {
5067            return false;
5068        };
5069        left_owner.is_class()
5070            && right_owner.is_class()
5071            && self.same_template_owner_identity(&left_owner, &right_owner)
5072    }
5073
5074    fn unique_canonical_type_candidate(
5075        &self,
5076        analyzer: &CppGraphSource<'_>,
5077        visible_from: &ProjectFile,
5078        candidates: &[&CodeUnit],
5079    ) -> Option<CodeUnit> {
5080        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5081            .ok()
5082    }
5083
5084    fn canonical_type_candidate_resolution(
5085        &self,
5086        analyzer: &CppGraphSource<'_>,
5087        visible_from: &ProjectFile,
5088        candidates: &[&CodeUnit],
5089    ) -> Result<CodeUnit, TypeCandidateFailure> {
5090        let mut canonical = Vec::new();
5091        for candidate in candidates {
5092            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
5093            if canonical
5094                .iter()
5095                .any(|existing| same_visible_symbol(existing, &resolved))
5096            {
5097                continue;
5098            }
5099            if let Some(existing) = canonical.iter_mut().find(|existing| {
5100                self.compatible_primary_template_redeclarations(existing, &resolved)
5101            }) {
5102                // A forward declaration and its full primary-template
5103                // definition are one C++ type even when they live in
5104                // different headers and alpha-rename their parameters. The
5105                // target-preserving path already reconciles this family; do
5106                // the same for ordinary canonical lookup so an out-of-line
5107                // member's lexical owner is not made ambiguous by its own
5108                // forward declaration. Retain the strongest physical
5109                // declaration for later owner/range queries.
5110                if matches!(
5111                    (
5112                        cpp_class_declaration_strength(analyzer, existing),
5113                        cpp_class_declaration_strength(analyzer, &resolved),
5114                    ),
5115                    (
5116                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
5117                        CppClassDeclarationStrength::Full,
5118                    ) | (
5119                        CppClassDeclarationStrength::Unknown,
5120                        CppClassDeclarationStrength::Forward,
5121                    )
5122                ) {
5123                    *existing = resolved;
5124                }
5125                continue;
5126            }
5127            canonical.push(resolved);
5128            if canonical.len() > 1 {
5129                return Err(TypeCandidateFailure::Ambiguous);
5130            }
5131        }
5132        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5133    }
5134
5135    pub fn unique_type_candidate_preserving_target(
5136        &self,
5137        analyzer: &CppGraphSource<'_>,
5138        visible_from: &ProjectFile,
5139        candidates: &[&CodeUnit],
5140        target: &CodeUnit,
5141    ) -> Option<CodeUnit> {
5142        // C++ headers often expose one logical type through mutually exclusive
5143        // physical declarations, for example a class in the fallback branch
5144        // and a `using` alias to the standard-library type in the configured
5145        // branch. The index intentionally retains both declarations so forward
5146        // lookup can report each target. Preserve the requested target when
5147        // that is the only ambiguity: every candidate has the same type kind,
5148        // exact canonical FQN, and source file, and the requested declaration
5149        // itself is one of the physical candidates. Do not merge same-named
5150        // declarations from different files or namespaces; those remain
5151        // ambiguous and fail closed below.
5152        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5153            return Some(target.clone());
5154        }
5155        let mut resolved_candidates = Vec::new();
5156        for candidate in candidates {
5157            // An ifdef branch that aliases an unindexed system type (for
5158            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
5159            // canonicalized. That branch does not name `target`. Dropping it
5160            // keeps the branch that does. Failing the whole family here would
5161            // deny every usage of the reachable spelling (#2368).
5162            let Some(resolved) =
5163                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5164            else {
5165                continue;
5166            };
5167            if resolved_candidates
5168                .iter()
5169                .any(|existing| same_visible_symbol(existing, &resolved))
5170            {
5171                continue;
5172            }
5173            resolved_candidates.push(resolved);
5174        }
5175        match resolved_candidates.as_slice() {
5176            [] => None,
5177            [single] => Some(single.clone()),
5178            // The branches disagree about what the name aliases. When they are
5179            // spellings of one entity (#1845) that disagreement is a build
5180            // configuration, not a choice between types, so it must not deny
5181            // the requested target its reference.
5182            _ => self
5183                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5184                .map(|_| target.clone()),
5185        }
5186    }
5187
5188    /// The declaration a same-file same-FQN family stands for when a reference
5189    /// names `target`, or `None` when the candidates are not one family or the
5190    /// family does not name `target`.
5191    ///
5192    /// A translation unit cannot hold two different types under one qualified
5193    /// name, so several same-kind declarations of one FQN in one file are
5194    /// alternate spellings of one entity - the configuration branches of an
5195    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
5196    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
5197    /// targets differ; canonicalizing each branch on its own and then demanding
5198    /// agreement reports an ambiguity that denies every declaration in the
5199    /// family its usages (#1845). The family names `target` when it declares
5200    /// it, or when one branch's alias chain reaches it.
5201    ///
5202    /// Declarations in different files or namespaces are distinct entities and
5203    /// are deliberately excluded: their disagreement is a real ambiguity.
5204    pub fn same_fqn_type_spelling_for_target<'b>(
5205        &self,
5206        analyzer: &CppGraphSource<'_>,
5207        visible_from: &ProjectFile,
5208        candidates: &[&'b CodeUnit],
5209        target: &CodeUnit,
5210    ) -> Option<&'b CodeUnit> {
5211        let [first, rest @ ..] = candidates else {
5212            return None;
5213        };
5214        if rest.is_empty()
5215            || !rest.iter().all(|candidate| {
5216                candidate.kind() == first.kind()
5217                    && candidate.fq_name() == first.fq_name()
5218                    && candidate.source() == first.source()
5219            })
5220        {
5221            return None;
5222        }
5223        candidates
5224            .iter()
5225            .copied()
5226            .find(|candidate| same_symbol(candidate, target))
5227            .or_else(|| {
5228                candidates.iter().copied().find(|candidate| {
5229                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5230                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5231                })
5232            })
5233    }
5234
5235    pub fn alternate_same_fqn_type_declarations(
5236        &self,
5237        analyzer: &CppGraphSource<'_>,
5238        candidates: &[&CodeUnit],
5239        target: &CodeUnit,
5240    ) -> bool {
5241        let Some(first) = candidates.first() else {
5242            return false;
5243        };
5244        let same_api = first.kind() == target.kind()
5245            && first.fq_name() == target.fq_name()
5246            && first.source() == target.source()
5247            && candidates.iter().all(|candidate| {
5248                candidate.kind() == target.kind()
5249                    && candidate.fq_name() == target.fq_name()
5250                    && candidate.source() == target.source()
5251            })
5252            && candidates
5253                .iter()
5254                .any(|candidate| same_symbol(candidate, target))
5255            && candidates
5256                .iter()
5257                .any(|candidate| !same_logical_symbol(candidate, target));
5258        if !same_api {
5259            return false;
5260        }
5261
5262        let requirements = candidates
5263            .iter()
5264            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5265            .collect::<Vec<_>>();
5266        requirements.len() > 1
5267            && requirements
5268                .iter()
5269                .all(|requirement| !requirement.is_empty())
5270            && requirements.iter().enumerate().all(|(index, left)| {
5271                requirements[index + 1..].iter().all(|right| {
5272                    left.iter().all(|(_, left_guards)| {
5273                        right.iter().all(|(_, right_guards)| {
5274                            merge_preprocessor_guards(left_guards, right_guards).is_none()
5275                        })
5276                    })
5277                })
5278            })
5279    }
5280
5281    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
5282        let mut pending = vec![terms.to_vec()];
5283        while let Some(branch_terms) = pending.pop() {
5284            let mut normalized = Vec::new();
5285            let mut covers_branch = false;
5286            for term in branch_terms {
5287                if term.iter().any(|guard| term.contains(&guard.negated())) {
5288                    continue;
5289                }
5290                if term.is_empty() {
5291                    covers_branch = true;
5292                    break;
5293                }
5294                if !normalized.iter().any(|existing| existing == &term) {
5295                    normalized.push(term);
5296                }
5297            }
5298            if covers_branch {
5299                continue;
5300            }
5301            let Some(split_guard) = normalized
5302                .iter()
5303                .flat_map(|term| term.iter())
5304                .next()
5305                .cloned()
5306            else {
5307                return false;
5308            };
5309            let negated_guard = split_guard.negated();
5310            let mut when_defined = Vec::new();
5311            let mut when_undefined = Vec::new();
5312            for term in normalized {
5313                if term.contains(&negated_guard) {
5314                    // This term cannot hold when `split_guard` is true.
5315                } else if term.contains(&split_guard) {
5316                    let mut reduced = term.clone();
5317                    reduced.remove(&split_guard);
5318                    when_defined.push(reduced);
5319                } else {
5320                    when_defined.push(term.clone());
5321                }
5322                if term.contains(&split_guard) {
5323                    // This term cannot hold when `split_guard` is false.
5324                } else if term.contains(&negated_guard) {
5325                    let mut reduced = term;
5326                    reduced.remove(&negated_guard);
5327                    when_undefined.push(reduced);
5328                } else {
5329                    when_undefined.push(term);
5330                }
5331            }
5332            pending.push(when_defined);
5333            pending.push(when_undefined);
5334        }
5335        true
5336    }
5337
5338    /// The byte range of the one `#if` family with a terminal `#else` that holds
5339    /// every physical declaration of every candidate, or `None` when they do not
5340    /// share one such family.
5341    ///
5342    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
5343    /// whose macros changed between declarations. Require every physical range to
5344    /// belong to one syntax-tree family with a terminal `#else` before the terms
5345    /// can prove branch coverage.
5346    fn declarations_share_exhaustive_conditional_family(
5347        &self,
5348        analyzer: &CppGraphSource<'_>,
5349        candidates: &[&CodeUnit],
5350    ) -> Option<(usize, usize)> {
5351        let mut family_range = None;
5352        for candidate in candidates {
5353            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
5354            let root = prepared.tree().root_node();
5355            let mut candidate_family = None;
5356            for range in analyzer.ranges(candidate) {
5357                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
5358                let family = preprocessor_conditional_family_for_declaration(node)?;
5359                let key = (family.start_byte(), family.end_byte());
5360                if candidate_family.is_some_and(|existing| existing != key) {
5361                    return None;
5362                }
5363                candidate_family = Some(key);
5364            }
5365            let candidate_family = candidate_family?;
5366            if family_range.is_some_and(|existing| existing != candidate_family) {
5367                return None;
5368            }
5369            family_range = Some(candidate_family);
5370        }
5371        family_range
5372    }
5373
5374    pub fn complementary_same_fqn_type_declarations(
5375        &self,
5376        analyzer: &CppGraphSource<'_>,
5377        candidates: &[&CodeUnit],
5378        target: &CodeUnit,
5379    ) -> bool {
5380        if candidates.len() < 2
5381            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
5382            || self
5383                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
5384                .is_none()
5385        {
5386            return false;
5387        }
5388        Self::preprocessor_guard_terms_cover_all_paths(
5389            &self.declaration_family_guard_terms(analyzer, candidates),
5390        )
5391    }
5392
5393    fn declaration_family_guard_terms(
5394        &self,
5395        analyzer: &CppGraphSource<'_>,
5396        candidates: &[&CodeUnit],
5397    ) -> Vec<HashSet<PreprocessorGuard>> {
5398        candidates
5399            .iter()
5400            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5401            .map(|(_, guards)| guards)
5402            .collect()
5403    }
5404
5405    /// A callable name declared on every branch of one completed `#if`/`#else`
5406    /// family is declared on every configuration path, so a reference below the
5407    /// whole family sees one of the branches whatever the preprocessor decides.
5408    /// Answer the family's end byte: only past `#endif` is every branch's
5409    /// declaration behind the reference.
5410    ///
5411    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
5412    /// and shares both of its primitives. It does not require two distinct
5413    /// `CodeUnit`s: branches that declare the same signature can collapse into
5414    /// one unit carrying one physical range per branch.
5415    ///
5416    /// The branches are alternate spellings of one declaration, never competing
5417    /// declarations, so only the first branch stands for the family. Reporting
5418    /// every branch as visible would turn a name the source declares exactly
5419    /// once into an ambiguity between build configurations.
5420    fn exhaustive_guard_family_activation(
5421        &self,
5422        analyzer: &CppGraphSource<'_>,
5423        prepared: &PreparedSyntaxTree,
5424        candidate: &CodeUnit,
5425        reference: &CallableReferenceContext<'_>,
5426    ) -> Option<usize> {
5427        // Branch coverage says nothing about scope: a block-local declaration
5428        // stays invisible however many branches declare it.
5429        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
5430            return None;
5431        }
5432        let family = self
5433            .visible_identifier_candidates(candidate.source(), candidate.identifier())
5434            .filter(|peer| {
5435                peer.kind() == candidate.kind()
5436                    && peer.fq_name() == candidate.fq_name()
5437                    && peer.source() == candidate.source()
5438            })
5439            .collect::<Vec<_>>();
5440        let (_, family_end) =
5441            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
5442        if !Self::preprocessor_guard_terms_cover_all_paths(
5443            &self.declaration_family_guard_terms(analyzer, &family),
5444        ) {
5445            return None;
5446        }
5447        // A reference whose own guards pick one branch already reaches that
5448        // branch through the ordinary same-guard path; the family must not
5449        // resurrect the branch the reference contradicts.
5450        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
5451            .iter()
5452            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
5453        {
5454            return None;
5455        }
5456        (first_declaration_byte(analyzer, candidate)?
5457            == family
5458                .iter()
5459                .filter_map(|peer| first_declaration_byte(analyzer, peer))
5460                .min()?)
5461        .then_some(family_end)
5462    }
5463
5464    fn type_candidate_preserving_target(
5465        &self,
5466        analyzer: &CppGraphSource<'_>,
5467        visible_from: &ProjectFile,
5468        candidate: &CodeUnit,
5469        target: &CodeUnit,
5470    ) -> Option<CodeUnit> {
5471        let mut current = candidate.clone();
5472        let mut matched_target = same_visible_symbol(&current, target)
5473            || self.compatible_primary_template_redeclarations(&current, target);
5474        let mut seen = HashSet::default();
5475        loop {
5476            if !seen.insert(current.clone()) {
5477                return None;
5478            }
5479            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5480                return matched_target
5481                    .then(|| target.clone())
5482                    .or_else(|| current.is_class().then_some(current));
5483            };
5484            if self.flattened_macro_namespace_alias_target_matches(
5485                analyzer,
5486                visible_from,
5487                &current,
5488                &alias_target,
5489                target,
5490            ) {
5491                return Some(target.clone());
5492            }
5493            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5494                return matched_target
5495                    .then(|| target.clone())
5496                    .or_else(|| current.is_class().then_some(current));
5497            }
5498            // A non-template alias can name a template alias with explicit
5499            // arguments (for example, `using Result = Expected<int>`).  When
5500            // the requested target is that alias's primary declaration, keep
5501            // the primary identity before expanding the RHS arguments.  The
5502            // expansion would otherwise canonicalize through the underlying
5503            // implementation type and lose the target spelling used by the
5504            // forward resolver.
5505            if !self.cpp_template_metadata.contains_key(&current)
5506                && let Some(primary) =
5507                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5508                && (same_visible_symbol(&primary, target)
5509                    || self.compatible_primary_template_redeclarations(&primary, target))
5510            {
5511                return Some(target.clone());
5512            }
5513            if same_visible_symbol(&current, target) {
5514                return Some(target.clone());
5515            }
5516            if self.cpp_template_metadata.contains_key(&current) {
5517                return None;
5518            }
5519            let Some(next) =
5520                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
5521            else {
5522                return matched_target.then(|| target.clone());
5523            };
5524            current = next;
5525            matched_target |= same_visible_symbol(&current, target)
5526                || self.compatible_primary_template_redeclarations(&current, target);
5527        }
5528    }
5529
5530    fn compatible_primary_template_redeclarations(
5531        &self,
5532        left: &CodeUnit,
5533        right: &CodeUnit,
5534    ) -> bool {
5535        let (Some(left_metadata), Some(right_metadata)) = (
5536            self.cpp_template_metadata.get(left),
5537            self.cpp_template_metadata.get(right),
5538        ) else {
5539            return false;
5540        };
5541        left_metadata.primary_fq_name == right_metadata.primary_fq_name
5542            && left_metadata.is_primary()
5543            && right_metadata.is_primary()
5544            && cpp_reconcile_primary_template_parameters(
5545                &[(left, left_metadata), (right, right_metadata)],
5546                right,
5547            )
5548            .is_some()
5549    }
5550
5551    fn alias_candidate_may_preserve_target(
5552        &self,
5553        analyzer: &CppGraphSource<'_>,
5554        visible_from: &ProjectFile,
5555        candidate: &CodeUnit,
5556        target: &CodeUnit,
5557    ) -> bool {
5558        let mut current = candidate.clone();
5559        let mut seen = HashSet::default();
5560        loop {
5561            if same_visible_symbol(&current, target)
5562                || self.compatible_primary_template_redeclarations(&current, target)
5563            {
5564                return true;
5565            }
5566            if self.cpp_template_metadata.contains_key(&current) {
5567                return true;
5568            }
5569            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5570                return false;
5571            };
5572            let StructuredAliasTarget::Named {
5573                components,
5574                global,
5575                arguments,
5576            } = alias_target
5577            else {
5578                return false;
5579            };
5580            if arguments.is_some() || !seen.insert(current.clone()) {
5581                return true;
5582            }
5583            let qualified = components.join("::");
5584            let next = if global {
5585                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5586            } else {
5587                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
5588            };
5589            let Some(next) = next else {
5590                return true;
5591            };
5592            current = next;
5593        }
5594    }
5595
5596    /// Every indexed type declaration `raw_name` names when it is written in
5597    /// `declaration`'s namespace: the innermost enclosing namespace that holds
5598    /// the name wins, otherwise the name is looked up unqualified.
5599    fn type_candidates_for_declaration<'b>(
5600        &'b self,
5601        visible_from: &ProjectFile,
5602        declaration: &CodeUnit,
5603        raw_name: &str,
5604    ) -> Vec<&'b CodeUnit> {
5605        let Some(normalized) = normalize_reference_name(raw_name) else {
5606            return Vec::new();
5607        };
5608        if let Some(namespace) = cpp_namespace_for(declaration) {
5609            for prefix in namespace_prefixes(&namespace) {
5610                let qualified = format!("{prefix}::{normalized}");
5611                let candidates = self.type_candidates(visible_from, &qualified);
5612                if !candidates.is_empty() {
5613                    return candidates;
5614                }
5615            }
5616        }
5617        self.type_candidates(visible_from, &normalized)
5618    }
5619
5620    fn resolve_unique_type_for_declaration(
5621        &self,
5622        visible_from: &ProjectFile,
5623        declaration: &CodeUnit,
5624        raw_name: &str,
5625    ) -> Option<CodeUnit> {
5626        unique_logical_type_candidate(self.type_candidates_for_declaration(
5627            visible_from,
5628            declaration,
5629            raw_name,
5630        ))
5631    }
5632
5633    pub fn resolves_to_type(
5634        &self,
5635        analyzer: &CppGraphSource<'_>,
5636        file: &ProjectFile,
5637        raw_name: &str,
5638        target: &CodeUnit,
5639    ) -> bool {
5640        let Some(normalized) = normalize_reference_name(raw_name) else {
5641            return false;
5642        };
5643        let candidates = self.type_candidates(file, &normalized);
5644        if candidates.is_empty() {
5645            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5646        }
5647        let Some(resolved) =
5648            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5649        else {
5650            return false;
5651        };
5652        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5653    }
5654
5655    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5656        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5657        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5658        match resolved.kind() {
5659            CodeUnitType::Class => Some(resolved),
5660            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5661            _ => None,
5662        }
5663    }
5664
5665    /// Whether two callable declarations declare one function.
5666    ///
5667    /// [`same_logical_symbol`] compares the persisted signature strings, which
5668    /// embed each parameter type exactly as it was spelled. A header
5669    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
5670    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
5671    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
5672    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
5673    /// This resolves the written parameter names before comparing them and
5674    /// reports the same answer the language does for the cases it can prove.
5675    ///
5676    /// Everything it cannot prove stays two symbols: a template declaration, a
5677    /// parameter with no comparable shape, a name that resolves on one side
5678    /// only, and an alias chain it cannot follow safely (#2010).
5679    pub fn same_logical_callable(
5680        &self,
5681        analyzer: &CppGraphSource<'_>,
5682        left: &CodeUnit,
5683        right: &CodeUnit,
5684    ) -> bool {
5685        if same_logical_symbol(left, right) {
5686            return true;
5687        }
5688        if left.kind() != right.kind()
5689            || !left.is_callable()
5690            || !right.is_callable()
5691            || left.fq_name() != right.fq_name()
5692        {
5693            return false;
5694        }
5695        // A template declaration and its out-of-line body can also diverge
5696        // outside the parameter list - `template <class T>` against
5697        // `template <typename T>` - and the template head is part of the
5698        // persisted signature. Deciding template-head equivalence is a
5699        // separate question, so templates keep string identity.
5700        if self.callable_is_template_declaration(analyzer, left)
5701            || self.callable_is_template_declaration(analyzer, right)
5702        {
5703            return false;
5704        }
5705        let (Some(left_comparable), Some(right_comparable)) = (
5706            self.callable_comparable(analyzer, left),
5707            self.callable_comparable(analyzer, right),
5708        ) else {
5709            return false;
5710        };
5711        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
5712        // return type and requires-clause are part of C++ callable identity and
5713        // an out-of-line definition repeats them verbatim, so they must agree
5714        // as written.
5715        if left_comparable.suffix != right_comparable.suffix
5716            || left_comparable.shapes.len() != right_comparable.shapes.len()
5717        {
5718            return false;
5719        }
5720        left_comparable
5721            .shapes
5722            .iter()
5723            .zip(right_comparable.shapes.iter())
5724            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
5725                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
5726                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
5727                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
5728                }
5729                // An unstructured parameter records that the reduction failed,
5730                // not that the two spellings mean the same type, so it agrees
5731                // with nothing - including another unstructured parameter.
5732                _ => false,
5733            })
5734    }
5735
5736    /// Compare two parameter shapes node by node with an explicit paired stack.
5737    ///
5738    /// Shape variants and cv-qualifiers must agree exactly at every level; only
5739    /// the named leaves may be spelled differently, and they agree when they
5740    /// resolve to one type declaration.
5741    fn comparable_shapes_agree(
5742        &self,
5743        analyzer: &CppGraphSource<'_>,
5744        left: &CppComparableParameter,
5745        right: &CppComparableParameter,
5746    ) -> bool {
5747        let mut stack = vec![(left.root(), right.root())];
5748        while let Some((left_index, right_index)) = stack.pop() {
5749            match (left.node(left_index), right.node(right_index)) {
5750                (
5751                    CppComparableNode::Named {
5752                        name: left_name,
5753                        primitive: left_primitive,
5754                        konst: left_konst,
5755                        volatil: left_volatil,
5756                    },
5757                    CppComparableNode::Named {
5758                        name: right_name,
5759                        primitive: right_primitive,
5760                        konst: right_konst,
5761                        volatil: right_volatil,
5762                    },
5763                ) => {
5764                    if left_konst != right_konst
5765                        || left_volatil != right_volatil
5766                        || left_primitive != right_primitive
5767                        || !self.comparable_names_agree(
5768                            analyzer,
5769                            left_name,
5770                            right_name,
5771                            *left_primitive,
5772                        )
5773                    {
5774                        return false;
5775                    }
5776                }
5777                (
5778                    CppComparableNode::Pointer {
5779                        inner: left_inner,
5780                        konst: left_konst,
5781                        volatil: left_volatil,
5782                    },
5783                    CppComparableNode::Pointer {
5784                        inner: right_inner,
5785                        konst: right_konst,
5786                        volatil: right_volatil,
5787                    },
5788                ) => {
5789                    if left_konst != right_konst || left_volatil != right_volatil {
5790                        return false;
5791                    }
5792                    stack.push((*left_inner, *right_inner));
5793                }
5794                (
5795                    CppComparableNode::Reference { inner: left_inner },
5796                    CppComparableNode::Reference { inner: right_inner },
5797                )
5798                | (
5799                    CppComparableNode::Array { inner: left_inner },
5800                    CppComparableNode::Array { inner: right_inner },
5801                ) => stack.push((*left_inner, *right_inner)),
5802                (
5803                    CppComparableNode::Generic {
5804                        base: left_base,
5805                        arguments: left_arguments,
5806                    },
5807                    CppComparableNode::Generic {
5808                        base: right_base,
5809                        arguments: right_arguments,
5810                    },
5811                ) => {
5812                    if left_arguments.len() != right_arguments.len() {
5813                        return false;
5814                    }
5815                    stack.push((*left_base, *right_base));
5816                    stack.extend(
5817                        left_arguments.iter().zip(right_arguments.iter()).map(
5818                            |(left_argument, right_argument)| (*left_argument, *right_argument),
5819                        ),
5820                    );
5821                }
5822                _ => return false,
5823            }
5824        }
5825        true
5826    }
5827
5828    /// Whether two written type names denote one type.
5829    ///
5830    /// A primitive denotes the same type in every scope, so its recorded
5831    /// lexical scope is noise and its spelling decides. A nominal name is
5832    /// resolved on each side independently: two resolved names agree when they
5833    /// reach one type declaration, and two unresolved names agree only on
5834    /// exact agreement of what was written, which is no weaker than the
5835    /// whole-signature string equality this comparison replaces. Resolution on
5836    /// one side only is evidence of difference, never of agreement.
5837    fn comparable_names_agree(
5838        &self,
5839        analyzer: &CppGraphSource<'_>,
5840        left: &StructuredTypeName,
5841        right: &StructuredTypeName,
5842        primitive: bool,
5843    ) -> bool {
5844        if primitive {
5845            return left.path() == right.path();
5846        }
5847        match (
5848            self.comparable_name_terminal(analyzer, left),
5849            self.comparable_name_terminal(analyzer, right),
5850        ) {
5851            (Some(left_terminal), Some(right_terminal)) => {
5852                same_logical_symbol(&left_terminal, &right_terminal)
5853            }
5854            (None, None) => {
5855                left.path() == right.path() && left.is_absolute() == right.is_absolute()
5856            }
5857            _ => false,
5858        }
5859    }
5860
5861    /// The class declaration a written type name denotes, or `None` when the
5862    /// workspace cannot prove one.
5863    ///
5864    /// The lookup is a closure-independent lexical-scope prefix walk over the
5865    /// workspace definition index rather than a visibility lookup: the index
5866    /// handed to a definition query is rooted at the reference file, and a
5867    /// body's `.cpp` is almost never in that file's include closure. Any name
5868    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
5869    /// cannot invent a type the compiler could not see; `using`-directives are
5870    /// not modelled, and a name that needs one stays unresolved.
5871    fn comparable_name_terminal(
5872        &self,
5873        analyzer: &CppGraphSource<'_>,
5874        name: &StructuredTypeName,
5875    ) -> Option<CodeUnit> {
5876        let mut current = self.comparable_name_declaration(analyzer, name)?;
5877        let mut visited = HashSet::default();
5878        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
5879            // The alias question is asked before the class question, and
5880            // through `declared_type_alias` rather than `is_type_alias`,
5881            // because extraction records `using A8 = A7;` as a *Class* unit
5882            // whose signature is the alias declaration. Reading the kind first
5883            // would end the chase on the alias itself and report an alias
5884            // spelling and its underlying class as two types (#2010).
5885            if !declared_type_alias(analyzer, &current) {
5886                return current.is_class().then_some(current);
5887            }
5888            if !visited.insert(current.clone()) {
5889                return None;
5890            }
5891            let signature = current.signature()?;
5892            // `cpp_alias_declaration_target_text` reads the declaration's
5893            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
5894            // pointer is silently dropped. Substituting such an alias would
5895            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
5896            if cpp_alias_declaration_adds_indirection(signature) {
5897                return None;
5898            }
5899            let raw_target = cpp_alias_declaration_target_text(signature)?;
5900            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
5901        }
5902        None
5903    }
5904
5905    /// The declaration one alias hop lands on: the type `raw_target` names,
5906    /// looked up from the alias declaration's own enclosing namespace.
5907    ///
5908    /// The hop takes the same closure-independent prefix walk the first lookup
5909    /// took, and deliberately not `resolve_type_for_declaration`: that one
5910    /// answers out of the `VisibilityIndex`, which is rooted at the reference
5911    /// file, while the alias declaration this hop starts from is reached
5912    /// through the workspace definition index and its file need not be in that
5913    /// root's include closure - where the visibility lookup answers nothing and
5914    /// the chase would stop on the alias itself (#2010).
5915    fn comparable_alias_target(
5916        &self,
5917        analyzer: &CppGraphSource<'_>,
5918        alias: &CodeUnit,
5919        raw_target: &str,
5920    ) -> Option<CodeUnit> {
5921        // `raw_target` is the alias declaration's written type text, so it is a
5922        // plain `::`-joined qualified-id: the same domain the shared symbol-path
5923        // parser reads, and the same leading `::` that marks an absolute name
5924        // everywhere else this crate normalizes a reference.
5925        let absolute = raw_target.trim_start().starts_with("::");
5926        let normalized = normalize_reference_name(raw_target)?;
5927        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5928            brokk_bifrost_core::analyzer::Language::Cpp,
5929            &normalized,
5930        );
5931        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
5932            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5933                brokk_bifrost_core::analyzer::Language::Cpp,
5934                &namespace,
5935            )
5936        });
5937        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
5938        self.comparable_name_declaration(analyzer, &name)
5939    }
5940
5941    /// The one type declaration `name` names, by enclosing scope, innermost
5942    /// first.
5943    ///
5944    /// The first prefix depth that names anything decides: an inner scope hides
5945    /// an outer one, so a match there is the answer even when an outer scope
5946    /// also declares the name. Several logically distinct declarations at that
5947    /// depth are an ambiguity this comparison must not guess at.
5948    fn comparable_name_declaration(
5949        &self,
5950        analyzer: &CppGraphSource<'_>,
5951        name: &StructuredTypeName,
5952    ) -> Option<CodeUnit> {
5953        let definitions = analyzer.global_usage_definition_index();
5954        let first_depth = if name.is_absolute() {
5955            0
5956        } else {
5957            name.lexical_scope().len()
5958        };
5959        for depth in (0..=first_depth).rev() {
5960            let mut components = Vec::with_capacity(depth.saturating_add(name.path().len()));
5961            components.extend_from_slice(&name.lexical_scope()[..depth]);
5962            components.extend_from_slice(name.path());
5963            let mut candidates =
5964                definitions
5965                    .fqn(&components.join("."))
5966                    .into_iter()
5967                    .filter(|unit| {
5968                        unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
5969                    });
5970            let Some(first) = candidates.next() else {
5971                continue;
5972            };
5973            return candidates
5974                .all(|unit| same_logical_symbol(unit, first))
5975                .then(|| first.clone());
5976        }
5977        None
5978    }
5979
5980    /// The comparison inputs of one callable declaration, extracted once.
5981    ///
5982    /// The comparison itself runs only when two candidates share kind and fully
5983    /// qualified name but not signature, which is rare; re-reading the same
5984    /// declaration for every pair in a candidate set is not.
5985    fn callable_comparable(
5986        &self,
5987        analyzer: &CppGraphSource<'_>,
5988        unit: &CodeUnit,
5989    ) -> Option<Arc<ExtractedComparable>> {
5990        if let Some(cached) = self
5991            .callable_comparables
5992            .lock()
5993            .expect("C++ callable comparable cache poisoned")
5994            .get(unit)
5995            .cloned()
5996        {
5997            return cached;
5998        }
5999        let extracted = self
6000            .extract_callable_comparable(analyzer, unit)
6001            .map(Arc::new);
6002        self.callable_comparables
6003            .lock()
6004            .expect("C++ callable comparable cache poisoned")
6005            .insert(unit.clone(), extracted.clone());
6006        extracted
6007    }
6008
6009    fn extract_callable_comparable(
6010        &self,
6011        analyzer: &CppGraphSource<'_>,
6012        unit: &CodeUnit,
6013    ) -> Option<ExtractedComparable> {
6014        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6015        let root = prepared.tree().root_node();
6016        let declarator = analyzer
6017            .ranges(unit)
6018            .into_iter()
6019            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6020        Some(ExtractedComparable {
6021            // One question about one declarator: indexing the file's tree would
6022            // cost more than the walk it saves.
6023            shapes: cpp_comparable_parameter_shapes(
6024                declarator,
6025                prepared.source(),
6026                &ParentIndex::unindexed(),
6027            ),
6028            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6029        })
6030    }
6031
6032    pub fn canonical_type_for_reference(
6033        &self,
6034        file: &ProjectFile,
6035        raw_name: &str,
6036    ) -> Option<CodeUnit> {
6037        let resolved = self.resolve_type(file, raw_name)?;
6038        self.alias_target(&resolved).or(Some(resolved))
6039    }
6040
6041    pub fn parser_alias_resolves_to_type(
6042        &self,
6043        analyzer: &CppGraphSource<'_>,
6044        file: &ProjectFile,
6045        raw_name: &str,
6046        target: &CodeUnit,
6047    ) -> bool {
6048        let Some(alias_name) = normalize_reference_name(raw_name) else {
6049            return false;
6050        };
6051        let Some(cpp) = analyzer.cpp else {
6052            return false;
6053        };
6054        let matches_file = |source_file: &ProjectFile| {
6055            self.file_alias_matches(cpp, source_file, &alias_name, target)
6056        };
6057        self.visible_source_files_by_root.get(file).map_or_else(
6058            || matches_file(file),
6059            |files| files.iter().any(matches_file),
6060        )
6061    }
6062
6063    fn file_alias_matches(
6064        &self,
6065        cpp: &dyn CppSource,
6066        file: &ProjectFile,
6067        alias_name: &str,
6068        target: &CodeUnit,
6069    ) -> bool {
6070        let cell = {
6071            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6072            Arc::clone(
6073                cells
6074                    .entry(file.clone())
6075                    .or_insert_with(|| Arc::new(OnceLock::new())),
6076            )
6077        };
6078        cell.get_or_init(|| {
6079            #[cfg(any(test, feature = "test-support"))]
6080            {
6081                *self
6082                    .alias_source_parse_counts
6083                    .lock()
6084                    .expect("alias source parse count lock")
6085                    .entry(file.clone())
6086                    .or_default() += 1;
6087            }
6088            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
6089        })
6090        .iter()
6091        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
6092    }
6093
6094    #[cfg(any(test, feature = "test-support"))]
6095    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
6096        self.visible_source_files_by_root
6097            .get(file)
6098            .cloned()
6099            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
6100    }
6101
6102    #[cfg(any(test, feature = "test-support"))]
6103    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
6104        self.alias_source_parse_counts
6105            .lock()
6106            .expect("alias source parse count lock")
6107            .get(file)
6108            .copied()
6109            .unwrap_or(0)
6110    }
6111
6112    pub fn resolve_named(
6113        &self,
6114        file: &ProjectFile,
6115        raw_name: &str,
6116        kind: TargetKind,
6117    ) -> Option<CodeUnit> {
6118        let normalized = normalize_reference_name(raw_name)?;
6119        self.named_candidates_for_normalized(file, &normalized, kind)
6120            .into_iter()
6121            .next()
6122            .cloned()
6123    }
6124
6125    pub fn contains_named_symbol(
6126        &self,
6127        file: &ProjectFile,
6128        raw_name: &str,
6129        kind: TargetKind,
6130        target: &CodeUnit,
6131    ) -> bool {
6132        let Some(normalized) = normalize_reference_name(raw_name) else {
6133            return false;
6134        };
6135        self.named_candidates_for_normalized(file, &normalized, kind)
6136            .into_iter()
6137            .any(|unit| {
6138                matches_kind_for_lookup(unit, kind)
6139                    && reference_matches_unit(&normalized, unit)
6140                    && same_visible_symbol(unit, target)
6141            })
6142    }
6143
6144    pub fn named_candidates(
6145        &self,
6146        file: &ProjectFile,
6147        raw_name: &str,
6148        kind: TargetKind,
6149    ) -> Vec<CodeUnit> {
6150        let Some(normalized) = normalize_reference_name(raw_name) else {
6151            return Vec::new();
6152        };
6153        self.named_candidates_for_normalized(file, &normalized, kind)
6154            .into_iter()
6155            .cloned()
6156            .collect()
6157    }
6158
6159    pub fn resolve_known_non_target(
6160        &self,
6161        file: &ProjectFile,
6162        raw_name: &str,
6163        kind: TargetKind,
6164        target: &CodeUnit,
6165    ) -> bool {
6166        let Some(normalized) = normalize_reference_name(raw_name) else {
6167            return false;
6168        };
6169        normalized.contains("::")
6170            && self
6171                .named_candidates_for_normalized(file, &normalized, kind)
6172                .into_iter()
6173                .any(|unit| {
6174                    matches_kind_for_lookup(unit, kind)
6175                        && reference_matches_unit(&normalized, unit)
6176                        && !same_visible_symbol(unit, target)
6177                })
6178    }
6179
6180    pub fn resolve_call_return_binding(
6181        &self,
6182        analyzer: &CppGraphSource<'_>,
6183        file: &ProjectFile,
6184        raw_name: &str,
6185        arity: usize,
6186        lexical_namespace: Option<&str>,
6187        direct_type: Option<&CodeUnit>,
6188    ) -> Option<CppScanBinding> {
6189        let normalized = normalize_reference_name(raw_name)?;
6190        let mut candidates = Vec::new();
6191        for function in
6192            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6193        {
6194            if cpp_callable_arity(analyzer, function).accepts(arity)
6195                && !direct_type.is_some_and(|direct_type| {
6196                    self.callable_is_constructor_declaration(analyzer, function)
6197                        && type_owner_of(analyzer, function)
6198                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6199                })
6200            {
6201                candidates.push(function.clone());
6202            }
6203        }
6204        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6205        unanimous_return_binding(analyzer, self, file, &candidates)
6206    }
6207
6208    pub fn resolve_call_return_binding_without_arity(
6209        &self,
6210        analyzer: &CppGraphSource<'_>,
6211        file: &ProjectFile,
6212        raw_name: &str,
6213        lexical_namespace: Option<&str>,
6214        direct_type: Option<&CodeUnit>,
6215    ) -> (bool, Option<CppScanBinding>) {
6216        let Some(normalized) = normalize_reference_name(raw_name) else {
6217            return (false, None);
6218        };
6219        let mut candidates = self
6220            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6221            .into_iter()
6222            .filter(|function| {
6223                function.is_function()
6224                    && !direct_type.is_some_and(|direct_type| {
6225                        self.callable_is_constructor_declaration(analyzer, function)
6226                            && type_owner_of(analyzer, function)
6227                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6228                    })
6229            })
6230            .cloned()
6231            .collect::<Vec<_>>();
6232        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6233        let has_candidates = !candidates.is_empty();
6234        (
6235            has_candidates,
6236            unanimous_return_binding(analyzer, self, file, &candidates),
6237        )
6238    }
6239
6240    pub fn visible_identifier_candidates<'b>(
6241        &'b self,
6242        file: &ProjectFile,
6243        identifier: &str,
6244    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6245        self.visible_by_identifier
6246            .get(file)
6247            .and_then(|by_name| by_name.get(identifier))
6248            .into_iter()
6249            .flatten()
6250    }
6251
6252    /// Return terminal reference names that can denote `target` from `file`.
6253    ///
6254    /// The indexed candidate table covers ordinary declarations and aliases;
6255    /// parser-only aliases are read through their per-file cells so this path
6256    /// never reparses a source that has already been inspected by the visibility
6257    /// index.
6258    pub fn visible_type_reference_component_names_for_target(
6259        &self,
6260        analyzer: &CppGraphSource<'_>,
6261        file: &ProjectFile,
6262        target: &CodeUnit,
6263    ) -> HashSet<String> {
6264        let mut names = HashSet::from_iter([target.identifier().to_string()]);
6265        if let Some(metadata) = self.cpp_template_metadata.get(target) {
6266            names.insert(metadata.primary_name.clone());
6267        }
6268
6269        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6270            for (identifier, candidates) in by_identifier {
6271                if candidates.iter().any(|candidate| {
6272                    (candidate.is_class()
6273                        && (same_visible_symbol(candidate, target)
6274                            || self.compatible_primary_template_redeclarations(candidate, target)))
6275                        || (declared_type_alias(analyzer, candidate)
6276                            && self.alias_candidate_may_preserve_target(
6277                                analyzer, file, candidate, target,
6278                            ))
6279                }) {
6280                    names.insert(identifier.clone());
6281                }
6282            }
6283        }
6284
6285        names.extend(self.visible_parser_alias_names_for_target(file, target));
6286
6287        names
6288    }
6289
6290    pub fn indexed_structural_class_scope(
6291        &self,
6292        file: &ProjectFile,
6293        class: Node<'_>,
6294        source: &str,
6295    ) -> Option<Vec<String>> {
6296        let key = (file.clone(), class.start_byte(), class.end_byte());
6297        if let Some(cached) = self
6298            .indexed_structural_class_scopes
6299            .lock()
6300            .expect("C++ indexed structural-class scope cache poisoned")
6301            .get(&key)
6302            .cloned()
6303        {
6304            return cached;
6305        }
6306        let resolved = (|| {
6307            let name = class.child_by_field_name("name")?;
6308            let identifier = if name.kind() == "template_type" {
6309                node_text(name.child_by_field_name("name")?, source).to_string()
6310            } else {
6311                let mut components = Vec::new();
6312                append_cpp_name_components(name, source, &mut components)?;
6313                components.last()?.clone()
6314            };
6315            let visible = self
6316                .visible_identifier_candidates(file, &identifier)
6317                .cloned()
6318                .collect::<Vec<_>>();
6319            let mut visible = visible;
6320            for candidate in
6321                self.visible_by_file
6322                    .get(file)
6323                    .into_iter()
6324                    .flatten()
6325                    .filter(|candidate| {
6326                        self.cpp_template_metadata
6327                            .get(candidate)
6328                            .is_some_and(|metadata| metadata.primary_name == identifier)
6329                    })
6330            {
6331                if !visible
6332                    .iter()
6333                    .any(|existing| same_logical_symbol(existing, candidate))
6334                {
6335                    visible.push(candidate.clone());
6336                }
6337            }
6338            // Built once per call rather than per candidate; `cpp_source` rebuilds
6339            // the five-field source from the same `self.cpp` on every call.
6340            let cpp_source = self.cpp_source();
6341            let candidates = visible
6342                .iter()
6343                .filter(|candidate| {
6344                    candidate.source() == file
6345                        && candidate.is_class()
6346                        && !declared_type_alias(&cpp_source, candidate)
6347                        && self.cpp.ranges(candidate).iter().any(|range| {
6348                            range.start_byte <= class.start_byte()
6349                                && class.end_byte() <= range.end_byte
6350                        })
6351                })
6352                .collect::<Vec<_>>();
6353            let owner = if name.kind() == "template_type" {
6354                let expected = normalize_cpp_whitespace(node_text(name, source));
6355                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
6356                let exact = candidates
6357                    .iter()
6358                    .copied()
6359                    .filter(|candidate| {
6360                        candidate
6361                            .fq()
6362                            .segments()
6363                            .iter()
6364                            .rev()
6365                            .find_map(|&segment| {
6366                                let (text, kind) = interner.resolve(segment);
6367                                matches!(
6368                                    kind,
6369                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
6370                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
6371                                )
6372                                .then_some(text)
6373                            })
6374                            .is_some_and(|text| text == expected)
6375                    })
6376                    .collect::<Vec<_>>();
6377                unique_logical_type_candidate(exact)
6378                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
6379            } else {
6380                unique_logical_type_candidate(candidates)?
6381            };
6382            Some(canonical_cpp_scope_components(&owner))
6383        })();
6384        self.indexed_structural_class_scopes
6385            .lock()
6386            .expect("C++ indexed structural-class scope cache poisoned")
6387            .insert(key, resolved.clone());
6388        resolved
6389    }
6390
6391    pub fn indexed_enclosing_owner_scope(
6392        &self,
6393        analyzer: &CppGraphSource<'_>,
6394        file: &ProjectFile,
6395        node: Node<'_>,
6396    ) -> Option<Vec<String>> {
6397        let anchor = std::iter::successors(Some(node), |current| current.parent())
6398            .find(|current| {
6399                matches!(
6400                    current.kind(),
6401                    "function_definition"
6402                        | "class_specifier"
6403                        | "struct_specifier"
6404                        | "union_specifier"
6405                )
6406            })
6407            .unwrap_or(node);
6408        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
6409        if let Some(cached) = self
6410            .indexed_enclosing_owner_scopes
6411            .lock()
6412            .expect("C++ indexed enclosing-owner scope cache poisoned")
6413            .get(&key)
6414            .cloned()
6415        {
6416            return cached;
6417        }
6418        let resolved = (|| {
6419            let range = Range {
6420                start_byte: node.start_byte(),
6421                end_byte: node.end_byte(),
6422                start_line: node.start_position().row,
6423                end_line: node.end_position().row,
6424            };
6425            let start = analyzer.enclosing_code_unit(file, &range)?;
6426            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
6427                start,
6428                |unit| self.cached_precise_parent_of(analyzer, unit),
6429            )
6430            .find(|unit| {
6431                unit.is_class()
6432                    && !analyzer
6433                        .type_alias_provider()
6434                        .is_some_and(|provider| provider.is_type_alias(unit))
6435            })?;
6436            Some(canonical_cpp_scope_components(&owner))
6437        })();
6438        self.indexed_enclosing_owner_scopes
6439            .lock()
6440            .expect("C++ indexed enclosing-owner scope cache poisoned")
6441            .insert(key, resolved.clone());
6442        resolved
6443    }
6444
6445    fn cached_precise_parent_of(
6446        &self,
6447        analyzer: &CppGraphSource<'_>,
6448        code_unit: &CodeUnit,
6449    ) -> Option<CodeUnit> {
6450        if let Some(cached) = self
6451            .precise_parent_cache
6452            .lock()
6453            .expect("C++ precise-parent cache poisoned")
6454            .get(code_unit)
6455            .cloned()
6456        {
6457            return cached;
6458        }
6459        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
6460        self.precise_parent_cache
6461            .lock()
6462            .expect("C++ precise-parent cache poisoned")
6463            .insert(code_unit.clone(), resolved.clone());
6464        resolved
6465    }
6466
6467    pub fn callable_is_constructor_declaration(
6468        &self,
6469        analyzer: &CppGraphSource<'_>,
6470        candidate: &CodeUnit,
6471    ) -> bool {
6472        if !candidate.is_function() {
6473            return false;
6474        }
6475        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6476            return false;
6477        };
6478        let root = prepared.tree().root_node();
6479        let candidate_ranges = analyzer.ranges(candidate);
6480        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
6481            let mut current = root
6482                .descendant_for_byte_range(range.start_byte, range.end_byte)
6483                .and_then(|node| node.parent());
6484            while let Some(node) = current {
6485                if matches!(
6486                    node.kind(),
6487                    "class_specifier" | "struct_specifier" | "union_specifier"
6488                ) {
6489                    return node
6490                        .child_by_field_name("name")
6491                        .map(|name| terminal_name(node_text(name, prepared.source())))
6492                        .is_some_and(|name| name == candidate.identifier());
6493                }
6494                current = node.parent();
6495            }
6496            false
6497        });
6498        if enclosed_by_matching_type {
6499            return true;
6500        }
6501        let indexed_containment = analyzer
6502            .declarations(candidate.source())
6503            .into_iter()
6504            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
6505            .any(|owner| {
6506                analyzer.ranges(&owner).iter().any(|owner_range| {
6507                    candidate_ranges.iter().any(|candidate_range| {
6508                        owner_range.start_byte <= candidate_range.start_byte
6509                            && candidate_range.end_byte <= owner_range.end_byte
6510                    })
6511                })
6512            });
6513        if indexed_containment {
6514            return true;
6515        }
6516        let metadata = analyzer.signature_metadata(candidate);
6517        !metadata.is_empty()
6518            && metadata
6519                .iter()
6520                .all(|signature| signature.return_type_text().is_none())
6521    }
6522
6523    /// Whether a callable declaration is a class-template deduction guide.
6524    ///
6525    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
6526    /// type field whose function declarator owns a trailing return type. This
6527    /// structured shape distinguishes a guide from both a constructor (no
6528    /// trailing return) and an ordinary trailing-return function (an `auto`
6529    /// type field).
6530    pub fn callable_is_deduction_guide_declaration(
6531        &self,
6532        analyzer: &CppGraphSource<'_>,
6533        candidate: &CodeUnit,
6534    ) -> bool {
6535        if !candidate.is_function() {
6536            return false;
6537        }
6538        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6539            return false;
6540        };
6541        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
6542            .into_iter()
6543            .any(|declaration| {
6544                if declaration.kind() != "declaration"
6545                    || declaration.child_by_field_name("type").is_some()
6546                {
6547                    return false;
6548                }
6549                let Some(declarator) = declaration.child_by_field_name("declarator") else {
6550                    return false;
6551                };
6552                if declarator.kind() != "function_declarator" {
6553                    return false;
6554                }
6555                let mut cursor = declarator.walk();
6556                let has_trailing_return = declarator
6557                    .named_children(&mut cursor)
6558                    .any(|child| child.kind() == "trailing_return_type");
6559                has_trailing_return
6560                    && declarator_name_node(declarator).is_some_and(|name| {
6561                        node_text(name, prepared.source()) == candidate.identifier()
6562                    })
6563            })
6564    }
6565
6566    /// Whether a callable occurrence is directly wrapped by a C++ template
6567    /// declaration. This deliberately inspects declaration syntax instead of
6568    /// inferring template status from the rendered signature.
6569    pub fn callable_is_template_declaration(
6570        &self,
6571        analyzer: &CppGraphSource<'_>,
6572        candidate: &CodeUnit,
6573    ) -> bool {
6574        if !candidate.is_function() {
6575            return false;
6576        }
6577        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6578            return false;
6579        };
6580        let root = prepared.tree().root_node();
6581        analyzer.ranges(candidate).iter().any(|range| {
6582            let Some(node) = node_for_exact_range(root, range)
6583                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
6584            else {
6585                return false;
6586            };
6587            node.parent().is_some_and(|parent| {
6588                parent.kind() == "template_declaration"
6589                    && parent
6590                        .named_child(parent.named_child_count().saturating_sub(1))
6591                        .is_some_and(|declaration| same_node(declaration, node))
6592            })
6593        })
6594    }
6595
6596    pub fn type_name_candidates<'b>(
6597        &'b self,
6598        file: &ProjectFile,
6599        normalized: &str,
6600    ) -> Vec<&'b CodeUnit> {
6601        self.candidate_units(file, normalized, TargetKind::Type)
6602    }
6603
6604    pub fn visible_members_for_owner_name<'b>(
6605        &'b self,
6606        file: &ProjectFile,
6607        owner: &CodeUnit,
6608        name: &str,
6609    ) -> Vec<&'b CodeUnit> {
6610        self.visible_identifier_candidates(file, name)
6611            .filter(|unit| {
6612                // Structured owner pop on the unit's own `fq()` (shared with
6613                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
6614                // string.
6615                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
6616                    .is_some_and(|parent| parent == owner.fq_name())
6617            })
6618            .collect()
6619    }
6620
6621    pub fn visible_member_for_owner_name(
6622        &self,
6623        file: &ProjectFile,
6624        owner: &CodeUnit,
6625        name: &str,
6626    ) -> VisibleMemberResolution {
6627        let candidates = self.visible_members_for_owner_name(file, owner, name);
6628        let mut callables = Vec::new();
6629        let mut non_callable = None;
6630        for candidate in candidates {
6631            if candidate.is_function() {
6632                callables.push(candidate.clone());
6633            } else if non_callable.is_none() {
6634                non_callable = Some(candidate.clone());
6635            }
6636        }
6637        match (callables.is_empty(), non_callable) {
6638            (false, None) => VisibleMemberResolution::Callable(callables),
6639            (true, Some(_)) => VisibleMemberResolution::NonCallable,
6640            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
6641            (true, None) => VisibleMemberResolution::Missing,
6642        }
6643    }
6644
6645    fn field_declared_type_fact(
6646        &self,
6647        analyzer: &CppGraphSource<'_>,
6648        field: &CodeUnit,
6649    ) -> Option<DeclaredFieldTypeFact> {
6650        if let Some(cached) = self
6651            .field_type_facts
6652            .lock()
6653            .expect("C++ field type fact cache poisoned")
6654            .get(field)
6655            .cloned()
6656        {
6657            return cached;
6658        }
6659        let decoded = decode_field_declared_type_fact(analyzer, field);
6660        self.field_type_facts
6661            .lock()
6662            .expect("C++ field type fact cache poisoned")
6663            .insert(field.clone(), decoded.clone());
6664        decoded
6665    }
6666
6667    fn structured_alias_target(
6668        &self,
6669        analyzer: &CppGraphSource<'_>,
6670        unit: &CodeUnit,
6671    ) -> Option<StructuredAliasTarget> {
6672        if let Some(cached) = self
6673            .structured_alias_targets
6674            .lock()
6675            .expect("C++ structured alias target cache poisoned")
6676            .get(unit)
6677            .cloned()
6678        {
6679            return cached;
6680        }
6681        let decoded = decode_structured_alias_target(analyzer, unit);
6682        self.structured_alias_targets
6683            .lock()
6684            .expect("C++ structured alias target cache poisoned")
6685            .insert(unit.clone(), decoded.clone());
6686        decoded
6687    }
6688
6689    pub fn type_candidates<'b>(
6690        &'b self,
6691        file: &ProjectFile,
6692        normalized: &str,
6693    ) -> Vec<&'b CodeUnit> {
6694        let mut candidates = self
6695            .candidate_units(file, normalized, TargetKind::Type)
6696            .into_iter()
6697            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
6698            .collect::<Vec<_>>();
6699        dedup_unit_refs(&mut candidates);
6700        candidates
6701    }
6702
6703    pub fn named_candidates_for_normalized<'b>(
6704        &'b self,
6705        file: &ProjectFile,
6706        normalized: &str,
6707        kind: TargetKind,
6708    ) -> Vec<&'b CodeUnit> {
6709        let mut candidates = self
6710            .candidate_units(file, normalized, kind)
6711            .into_iter()
6712            .filter(|unit| {
6713                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
6714            })
6715            .collect::<Vec<_>>();
6716        dedup_unit_refs(&mut candidates);
6717        candidates
6718    }
6719
6720    pub fn candidate_units<'b>(
6721        &'b self,
6722        file: &ProjectFile,
6723        normalized: &str,
6724        kind: TargetKind,
6725    ) -> Vec<&'b CodeUnit> {
6726        if normalized.contains("::") {
6727            // `normalized` comes from `normalize_cpp_reference_text`, which
6728            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
6729            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
6730            // kept intact by the shared splitter's operator merge — the same
6731            // domain `cpp_reference_fqn_candidates` below already parses with
6732            // the shared splitter. Re-tokenizing and taking the last segment
6733            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
6734            // scan exactly.
6735            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6736                brokk_bifrost_core::analyzer::Language::Cpp,
6737                normalized,
6738            )
6739            .pop() else {
6740                return Vec::new();
6741            };
6742            let fqns = cpp_reference_fqn_candidates(normalized, kind);
6743            return self
6744                .visible_identifier_candidates(file, &identifier)
6745                .filter(|unit| {
6746                    #[cfg(any(test, feature = "test-support"))]
6747                    self.qualified_candidate_inspections
6748                        .fetch_add(1, Ordering::Relaxed);
6749                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
6750                        || canonical_cpp_name_matches(unit, normalized)
6751                })
6752                .collect();
6753        }
6754        self.visible_identifier_candidates(file, normalized)
6755            .collect()
6756    }
6757
6758    #[cfg(any(test, feature = "test-support"))]
6759    pub fn reset_qualified_candidate_inspections(&self) {
6760        self.qualified_candidate_inspections
6761            .store(0, Ordering::Relaxed);
6762    }
6763
6764    #[cfg(any(test, feature = "test-support"))]
6765    pub fn qualified_candidate_inspections(&self) -> usize {
6766        self.qualified_candidate_inspections.load(Ordering::Relaxed)
6767    }
6768
6769    #[cfg(any(test, feature = "test-support"))]
6770    pub fn reset_target_preserving_type_resolution_count(&self) {
6771        self.target_preserving_type_resolution_count
6772            .store(0, Ordering::Relaxed);
6773    }
6774
6775    #[cfg(any(test, feature = "test-support"))]
6776    pub fn target_preserving_type_resolution_count(&self) -> usize {
6777        self.target_preserving_type_resolution_count
6778            .load(Ordering::Relaxed)
6779    }
6780
6781    #[cfg(any(test, feature = "test-support"))]
6782    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
6783        self.visible_parser_alias_name_set_build_count
6784            .load(Ordering::Relaxed)
6785    }
6786
6787    #[cfg(any(test, feature = "test-support"))]
6788    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
6789        self.visible_parser_alias_target_names_build_count
6790            .load(Ordering::Relaxed)
6791    }
6792}
6793
6794#[derive(Default)]
6795struct IncludeGraph {
6796    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
6797}
6798
6799impl IncludeGraph {
6800    fn extend_with<F>(
6801        &mut self,
6802        root: &ProjectFile,
6803        cancellation: Option<&CancellationToken>,
6804        targets_for: &mut F,
6805    ) where
6806        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6807    {
6808        let mut stack = vec![root.clone()];
6809        while let Some(file) = stack.pop() {
6810            if cancellation.is_some_and(CancellationToken::is_cancelled) {
6811                break;
6812            }
6813            if self.targets_by_file.contains_key(&file) {
6814                continue;
6815            }
6816            let targets = targets_for(&file);
6817            stack.extend(targets.iter().cloned());
6818            self.targets_by_file.insert(file, targets);
6819        }
6820    }
6821
6822    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
6823        self.targets_by_file.keys()
6824    }
6825
6826    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
6827        self.targets_by_file
6828            .get(file)
6829            .map(Vec::as_slice)
6830            .unwrap_or_default()
6831    }
6832}
6833
6834pub struct VisibilityData {
6835    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
6836    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
6837}
6838
6839/// Build the per-root include closure and the declarations each root can see
6840/// through it.
6841///
6842/// `declarations_for` takes the reading to answer in (issue #1970): a root
6843/// compiled as C sees the C reading of every file in its closure, a root
6844/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
6845/// per root. The two readings agree for all but a handful of headers, so the
6846/// C map is built only when some root actually asks for it, and only over the
6847/// files that root reaches.
6848pub fn build_visibility_data<F, R, D>(
6849    roots: &HashSet<ProjectFile>,
6850    cancellation: Option<&CancellationToken>,
6851    mut targets_for: F,
6852    mut reading_is_c_for: R,
6853    mut declarations_for: D,
6854) -> VisibilityData
6855where
6856    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6857    R: FnMut(&ProjectFile) -> bool,
6858    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
6859{
6860    let mut include_graph = IncludeGraph::default();
6861    for file in roots {
6862        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6863            break;
6864        }
6865        include_graph.extend_with(file, cancellation, &mut targets_for);
6866    }
6867    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
6868        .files()
6869        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
6870        .map(|file| (file.clone(), declarations_for(file, false)))
6871        .collect();
6872    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
6873    let mut visible_by_file = HashMap::default();
6874    let mut visible_source_files_by_root = HashMap::default();
6875    for file in roots {
6876        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6877            break;
6878        }
6879        let mut visited = HashSet::default();
6880        let mut visible = HashSet::default();
6881        let declarations_by_file = if reading_is_c_for(file) {
6882            for reached in cpp_declarations_by_file.keys() {
6883                if !c_declarations_by_file.contains_key(reached) {
6884                    let declarations = declarations_for(reached, true);
6885                    c_declarations_by_file.insert(reached.clone(), declarations);
6886                }
6887            }
6888            &c_declarations_by_file
6889        } else {
6890            &cpp_declarations_by_file
6891        };
6892        collect_visible_declarations(
6893            &include_graph,
6894            declarations_by_file,
6895            file,
6896            &mut visited,
6897            &mut visible,
6898            cancellation,
6899        );
6900        visible_by_file.insert(file.clone(), visible);
6901        visible_source_files_by_root.insert(file.clone(), visited);
6902    }
6903    VisibilityData {
6904        visible_by_file,
6905        visible_source_files_by_root,
6906    }
6907}
6908
6909/// Admit the class that an out-of-line definition proves is in scope.
6910///
6911/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
6912/// names a class-like entity in that file's scope: a member declaration can
6913/// live in a file other than its class's only when it is written out of line.
6914/// A file a build concatenates rather than compiles carries no `#include` edge
6915/// to the header declaring `Owner` -- google/wuffs
6916/// `internal/cgen/auxiliary/image.cc` defines
6917/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
6918/// every unqualified member and constructor reference in it had no candidate at
6919/// all (#1832).
6920///
6921/// The evidence is the indexed declaration's own owner name, taken from its
6922/// `FqName`, so this stays a structured answer rather than a text fallback.
6923/// Only an owner the file cannot already see is admitted: that is what keeps a
6924/// header declaring its own class from additionally seeing every same-named
6925/// class in the workspace, and it makes the pass free for the ordinary file
6926/// whose owners are all visible.
6927fn extend_with_out_of_line_owner_bindings(
6928    cpp: &dyn CppSource,
6929    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
6930) {
6931    for (file, visible) in visible_by_file.iter_mut() {
6932        // The include-closure walk seeds every root with its own declarations,
6933        // so the file's members are already here; re-reading them from the
6934        // analyzer would pay for the same declaration set twice.
6935        let mut unseen_owners: HashSet<String> = visible
6936            .iter()
6937            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
6938            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
6939            .collect();
6940        if unseen_owners.is_empty() {
6941            continue;
6942        }
6943        for unit in visible.iter().filter(|unit| unit.is_class()) {
6944            unseen_owners.remove(&unit.fq_name());
6945        }
6946        let admitted = unseen_owners
6947            .iter()
6948            .flat_map(|owner| cpp.definitions(owner))
6949            .filter(CodeUnit::is_class)
6950            .collect::<Vec<_>>();
6951        visible.extend(admitted);
6952    }
6953}
6954
6955pub enum VisibleMemberResolution {
6956    Callable(Vec<CodeUnit>),
6957    NonCallable,
6958    AmbiguousKind,
6959    Missing,
6960}
6961
6962#[derive(Clone)]
6963pub enum EnclosingMemberOwnerResolution {
6964    Owner(CodeUnit),
6965    Ambiguous,
6966    Missing,
6967}
6968
6969pub fn resolve_declaring_member_owner(
6970    analyzer: &CppGraphSource<'_>,
6971    visibility: &VisibilityIndex<'_>,
6972    file: &ProjectFile,
6973    receiver_owner: &CodeUnit,
6974    member_name: &str,
6975) -> EnclosingMemberOwnerResolution {
6976    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6977        return EnclosingMemberOwnerResolution::Missing;
6978    };
6979    let Some(receiver_owner) =
6980        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
6981    else {
6982        return EnclosingMemberOwnerResolution::Ambiguous;
6983    };
6984    let resolve_level = |frontier: &[CodeUnit]| {
6985        let mut member_owners = Vec::new();
6986        for raw_owner in frontier {
6987            let Some(owner) =
6988                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
6989            else {
6990                return EnclosingMemberOwnerResolution::Ambiguous;
6991            };
6992            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
6993                let Some(member_owner) = type_owner_of(analyzer, member) else {
6994                    return EnclosingMemberOwnerResolution::Ambiguous;
6995                };
6996                if !member_owners
6997                    .iter()
6998                    .any(|existing| same_visible_symbol(existing, &member_owner))
6999                {
7000                    member_owners.push(member_owner);
7001                }
7002            }
7003        }
7004        match member_owners.len() {
7005            0 => EnclosingMemberOwnerResolution::Missing,
7006            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
7007            _ => EnclosingMemberOwnerResolution::Ambiguous,
7008        }
7009    };
7010    // The first declaration on each structured base path hides deeper names,
7011    // regardless of whether its callable overload is applicable at a particular
7012    // call site. Applicability is checked only after this owner is established.
7013    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
7014    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
7015        return direct;
7016    }
7017    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
7018    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7019    let mut path_matches = Vec::new();
7020    while let Some(raw_owner) = stack.pop() {
7021        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7022        else {
7023            return EnclosingMemberOwnerResolution::Ambiguous;
7024        };
7025        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
7026        // Propagate at most two occurrences of each owner: that preserves the distinction
7027        // between one and multiple resolving base paths without exponential diamond walks.
7028        let propagated = propagated_counts.entry(owner.clone()).or_default();
7029        if *propagated == 2 {
7030            continue;
7031        }
7032        *propagated += 1;
7033        match resolve_level(std::slice::from_ref(&owner)) {
7034            EnclosingMemberOwnerResolution::Owner(owner) => {
7035                path_matches.push(owner);
7036                if path_matches.len() == 2 {
7037                    return EnclosingMemberOwnerResolution::Ambiguous;
7038                }
7039            }
7040            EnclosingMemberOwnerResolution::Ambiguous => {
7041                return EnclosingMemberOwnerResolution::Ambiguous;
7042            }
7043            EnclosingMemberOwnerResolution::Missing => {
7044                stack.extend(hierarchy.get_direct_ancestors(&owner));
7045            }
7046        }
7047    }
7048    match path_matches.len() {
7049        0 => EnclosingMemberOwnerResolution::Missing,
7050        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
7051        _ => unreachable!("base-path matches are capped at one before returning"),
7052    }
7053}
7054
7055/// Resolve the declaring owner of a callable after applying a member
7056/// `using <Base>::<member>;` declaration to one exact call arity.
7057///
7058/// Ordinary member lookup is intentionally name-based: the first class that
7059/// declares a name hides the same name on deeper bases. A member
7060/// using-declaration is the one exception. When none of the declarations on
7061/// that first owner accepts the call arity, it can reintroduce an applicable
7062/// overload from the named base. If a declaration on the first owner does
7063/// accept the arity, argument types would be needed to choose between it and
7064/// a same-arity introduced overload, so this resolver conservatively keeps the
7065/// ordinary owner (#1835/#1843).
7066///
7067/// The caller supplies ordinary name-based owner resolution so a file scan can
7068/// reuse its existing owner cache before applying this callable-only exception.
7069pub fn resolve_declaring_callable_owner(
7070    analyzer: &CppGraphSource<'_>,
7071    visibility: &VisibilityIndex<'_>,
7072    file: &ProjectFile,
7073    ordinary: EnclosingMemberOwnerResolution,
7074    member_name: &str,
7075    call_arity: usize,
7076) -> EnclosingMemberOwnerResolution {
7077    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
7078        return ordinary;
7079    };
7080    if visibility
7081        .visible_members_for_owner_name(file, ordinary_owner, member_name)
7082        .into_iter()
7083        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
7084    {
7085        return ordinary;
7086    }
7087
7088    let mut pending = match member_using_declaration_bases(
7089        analyzer,
7090        visibility,
7091        file,
7092        ordinary_owner,
7093        member_name,
7094    ) {
7095        Ok(bases) => bases,
7096        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7097    };
7098    let mut visited = HashSet::default();
7099    let mut introduced_owners = Vec::new();
7100    while let Some(owner) = pending.pop() {
7101        if !visited.insert(owner.clone()) {
7102            continue;
7103        }
7104        let accepts_arity = visibility
7105            .visible_members_for_owner_name(file, &owner, member_name)
7106            .into_iter()
7107            .any(|unit| {
7108                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
7109            });
7110        if accepts_arity {
7111            if !introduced_owners
7112                .iter()
7113                .any(|existing| same_visible_symbol(existing, &owner))
7114            {
7115                introduced_owners.push(owner);
7116            }
7117            continue;
7118        }
7119        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
7120            Ok(bases) => pending.extend(bases),
7121            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7122        }
7123    }
7124    match introduced_owners.as_slice() {
7125        [] => ordinary,
7126        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
7127        _ => EnclosingMemberOwnerResolution::Ambiguous,
7128    }
7129}
7130
7131fn member_using_declaration_bases(
7132    analyzer: &CppGraphSource<'_>,
7133    visibility: &VisibilityIndex<'_>,
7134    file: &ProjectFile,
7135    owner: &CodeUnit,
7136    member_name: &str,
7137) -> Result<Vec<CodeUnit>, ()> {
7138    let Some(source) = analyzer.get_source(owner, false) else {
7139        return Ok(Vec::new());
7140    };
7141    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
7142    if scopes.is_empty() {
7143        return Ok(Vec::new());
7144    }
7145    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7146        return Ok(Vec::new());
7147    };
7148    let mut bases = Vec::new();
7149    for raw_ancestor in hierarchy.get_ancestors(owner) {
7150        let Some(ancestor) =
7151            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
7152        else {
7153            return Err(());
7154        };
7155        let qualified = cpp_name_for(&ancestor);
7156        if scopes
7157            .iter()
7158            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
7159            && !bases
7160                .iter()
7161                .any(|existing| same_visible_symbol(existing, &ancestor))
7162        {
7163            bases.push(ancestor);
7164        }
7165    }
7166    Ok(bases)
7167}
7168
7169pub fn lexical_component_tiers<'a>(
7170    components: &'a [String],
7171    global: bool,
7172    lexical_scope: &'a [String],
7173) -> impl Iterator<Item = Vec<String>> + 'a {
7174    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
7175    (0..=first_prefix_len).rev().map(move |prefix_len| {
7176        let mut qualified = Vec::with_capacity(prefix_len + components.len());
7177        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
7178        qualified.extend_from_slice(components);
7179        qualified
7180    })
7181}
7182
7183pub fn build_visible_identifier_index(
7184    analyzer: &CppGraphSource<'_>,
7185    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
7186    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7187    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
7188) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
7189    let mut out = HashMap::default();
7190    for (file, visible) in visible_by_file {
7191        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
7192        for unit in visible {
7193            if unit.is_field()
7194                && !visible_source_files_by_root
7195                    .get(file)
7196                    .is_some_and(|sources| sources.contains(unit.source()))
7197                && cpp_global_field_has_internal_linkage_cached(
7198                    analyzer,
7199                    global_field_internal_linkage,
7200                    unit,
7201                )
7202            {
7203                continue;
7204            }
7205            by_identifier
7206                .entry(unit.identifier().to_string())
7207                .or_default()
7208                .push(unit.clone());
7209        }
7210        for units in by_identifier.values_mut() {
7211            sort_lookup_units(units);
7212            units.dedup();
7213        }
7214        out.insert(file.clone(), by_identifier);
7215    }
7216    out
7217}
7218
7219fn sort_lookup_units(units: &mut [CodeUnit]) {
7220    units.sort_by(|left, right| {
7221        left.fq_name()
7222            .cmp(&right.fq_name())
7223            .then_with(|| left.signature().cmp(&right.signature()))
7224            .then_with(|| left.source().cmp(right.source()))
7225            .then_with(|| left.kind().cmp(&right.kind()))
7226            .then_with(|| {
7227                left.package_segment_count()
7228                    .cmp(&right.package_segment_count())
7229            })
7230            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
7231            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
7232    });
7233}
7234
7235fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
7236    let interner = segment_interner();
7237    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
7238        let (left_text, left_kind) = interner.resolve(left_id);
7239        let (right_text, right_kind) = interner.resolve(right_id);
7240        let order = left_text
7241            .cmp(right_text)
7242            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
7243        if order != CmpOrdering::Equal {
7244            return order;
7245        }
7246    }
7247    left.len().cmp(&right.len())
7248}
7249
7250const fn segment_kind_order(kind: SegmentKind) -> u8 {
7251    match kind {
7252        SegmentKind::Path => 0,
7253        SegmentKind::Package => 1,
7254        SegmentKind::Type => 2,
7255        SegmentKind::Companion => 3,
7256        SegmentKind::Nested => 4,
7257        SegmentKind::Member => 5,
7258        SegmentKind::Unknown => 6,
7259    }
7260}
7261
7262fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
7263    let mut deduped = Vec::with_capacity(units.len());
7264    for unit in units.drain(..) {
7265        if !deduped.contains(&unit) {
7266            deduped.push(unit);
7267        }
7268    }
7269    *units = deduped;
7270}
7271
7272pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
7273    // Same domain as `candidate_units` above: `reference` is a plain
7274    // `::`-joined qualified-id with operator tokens kept intact by the shared
7275    // splitter's operator merge.
7276    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7277        brokk_bifrost_core::analyzer::Language::Cpp,
7278        reference,
7279    );
7280    if parts.is_empty() {
7281        return Vec::new();
7282    }
7283
7284    let mut candidates = Vec::new();
7285    for package_len in 0..parts.len() {
7286        let package = parts[..package_len].join("::");
7287        let rest = &parts[package_len..];
7288        if rest.is_empty() {
7289            continue;
7290        }
7291        match kind {
7292            TargetKind::Type | TargetKind::Constructor => {
7293                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
7294                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7295            }
7296            TargetKind::FreeFunction
7297            | TargetKind::Method
7298            | TargetKind::GlobalField
7299            | TargetKind::MemberField
7300            | TargetKind::Macro => {
7301                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7302                if rest.len() > 1 {
7303                    let owner = rest[..rest.len() - 1].join("$");
7304                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
7305                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
7306                }
7307            }
7308        }
7309    }
7310    candidates
7311}
7312
7313fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
7314    let fqn = if package.is_empty() {
7315        short.to_string()
7316    } else {
7317        format!("{package}.{short}")
7318    };
7319    if !out.contains(&fqn) {
7320        out.push(fqn);
7321    }
7322}
7323
7324pub fn infer_cpp_initializer_type(
7325    analyzer: &CppGraphSource<'_>,
7326    visibility: &VisibilityIndex<'_>,
7327    file: &ProjectFile,
7328    source: &str,
7329    node: Node<'_>,
7330) -> Option<CodeUnit> {
7331    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
7332        .and_then(|binding| binding.unit)
7333}
7334
7335pub fn infer_cpp_initializer_binding(
7336    analyzer: &CppGraphSource<'_>,
7337    visibility: &VisibilityIndex<'_>,
7338    file: &ProjectFile,
7339    source: &str,
7340    node: Node<'_>,
7341    receiver_resolver: Option<&ReceiverResolver<'_>>,
7342) -> Option<CppScanBinding> {
7343    match node.kind() {
7344        "new_expression" => {
7345            let text = normalize_cpp_whitespace(node_text(node, source));
7346            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
7347            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
7348            let name = normalize_cpp_type_name(type_text);
7349            Some(CppScanBinding::from_type_name(
7350                name.clone(),
7351                visibility.resolve_type(file, &name),
7352                1,
7353            ))
7354        }
7355        "call_expression" => node.child_by_field_name("function").and_then(|function| {
7356            let function_text = node_text(function, source);
7357            let direct_type_binding = visibility
7358                .resolve_type(file, function_text)
7359                .map(|unit| CppScanBinding::from_unit(unit, 0));
7360            if function.kind() == "template_function" && direct_type_binding.is_some() {
7361                let lexical_namespace = enclosing_namespace_context(node, source);
7362                let arity = visibility.call_arity_evidence(file, node, source).exact();
7363                if let Some(arity) = arity
7364                    && let Some(binding) = visibility.resolve_call_return_binding(
7365                        analyzer,
7366                        file,
7367                        function_text,
7368                        arity,
7369                        lexical_namespace.as_deref(),
7370                        direct_type_binding
7371                            .as_ref()
7372                            .and_then(|binding| binding.unit.as_ref()),
7373                    )
7374                {
7375                    return Some(binding);
7376                }
7377                let (has_callable, callable_binding) = visibility
7378                    .resolve_call_return_binding_without_arity(
7379                        analyzer,
7380                        file,
7381                        function_text,
7382                        lexical_namespace.as_deref(),
7383                        direct_type_binding
7384                            .as_ref()
7385                            .and_then(|binding| binding.unit.as_ref()),
7386                    );
7387                if let Some(binding) = callable_binding {
7388                    return Some(binding);
7389                }
7390                if has_callable {
7391                    return None;
7392                }
7393                return direct_type_binding;
7394            }
7395            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
7396            let direct_type_binding_for_call = direct_type_binding.clone();
7397            resolve_static_method_call_return_binding(
7398                analyzer, visibility, file, source, function, arity,
7399            )
7400            .or_else(|| {
7401                // An applicable free function supplies the receiver value
7402                // before an unrelated visible type with the same terminal
7403                // name. The direct type still excludes its own constructor
7404                // declaration below and remains the construction fallback.
7405                visibility.resolve_call_return_binding(
7406                    analyzer,
7407                    file,
7408                    function_text,
7409                    arity,
7410                    enclosing_namespace_context(node, source).as_deref(),
7411                    direct_type_binding_for_call
7412                        .as_ref()
7413                        .and_then(|binding| binding.unit.as_ref()),
7414                )
7415            })
7416            .or(direct_type_binding)
7417            .or_else(|| {
7418                resolve_field_method_call_return_binding(
7419                    analyzer,
7420                    visibility,
7421                    file,
7422                    source,
7423                    function,
7424                    arity,
7425                    receiver_resolver,
7426                )
7427            })
7428        }),
7429        _ => None,
7430    }
7431}
7432
7433fn resolve_static_method_call_return_binding(
7434    analyzer: &CppGraphSource<'_>,
7435    visibility: &VisibilityIndex<'_>,
7436    file: &ProjectFile,
7437    source: &str,
7438    function: Node<'_>,
7439    arity: usize,
7440) -> Option<CppScanBinding> {
7441    if function.kind() != "qualified_identifier" {
7442        return None;
7443    }
7444    let qualified = normalize_cpp_reference_text(node_text(function, source));
7445    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
7446    // single component (the shared splitter's operator-token merge keeps
7447    // `operator+`-style names intact), so re-tokenizing with the shared
7448    // structured splitter and peeling the terminal segment reproduces
7449    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
7450    // `cpp_out_of_line_function_owner`'s `qualified` split above.
7451    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7452        brokk_bifrost_core::analyzer::Language::Cpp,
7453        &qualified,
7454    );
7455    let (owner_text, member_name) = match parts.split_last() {
7456        Some((member, owner_parts)) if !owner_parts.is_empty() => {
7457            (owner_parts.join("::"), member.clone())
7458        }
7459        _ => {
7460            let scope = function.child_by_field_name("scope")?;
7461            let name = function.child_by_field_name("name")?;
7462            (
7463                node_text(scope, source).to_string(),
7464                node_text(name, source).to_string(),
7465            )
7466        }
7467    };
7468    let owner = visibility.resolve_type(file, &owner_text)?;
7469    let candidates = visibility
7470        .visible_members_for_owner_name(file, &owner, &member_name)
7471        .into_iter()
7472        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
7473        .cloned()
7474        .collect::<Vec<_>>();
7475    unanimous_return_binding(analyzer, visibility, file, &candidates)
7476}
7477
7478fn resolve_field_method_call_return_binding(
7479    analyzer: &CppGraphSource<'_>,
7480    visibility: &VisibilityIndex<'_>,
7481    file: &ProjectFile,
7482    source: &str,
7483    function: Node<'_>,
7484    arity: usize,
7485    receiver_resolver: Option<&ReceiverResolver<'_>>,
7486) -> Option<CppScanBinding> {
7487    if function.kind() != "field_expression" {
7488        return None;
7489    }
7490    let receiver_resolver = receiver_resolver?;
7491    let field = function.child_by_field_name("field")?;
7492    let member_name = node_text(function_terminal_node(field), source);
7493    let receiver = function
7494        .child_by_field_name("argument")
7495        .or_else(|| function.named_child(0))?;
7496    let owners = receiver_resolver(receiver, source);
7497    let mut candidates = Vec::new();
7498    for owner in owners {
7499        let declaring_owner =
7500            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
7501                EnclosingMemberOwnerResolution::Owner(owner) => owner,
7502                EnclosingMemberOwnerResolution::Missing => continue,
7503                EnclosingMemberOwnerResolution::Ambiguous => return None,
7504            };
7505        candidates.extend(
7506            visibility
7507                .visible_members_for_owner_name(file, &declaring_owner, member_name)
7508                .into_iter()
7509                .filter(|unit| {
7510                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
7511                })
7512                .cloned(),
7513        );
7514    }
7515    unanimous_return_binding(analyzer, visibility, file, &candidates)
7516}
7517
7518fn unanimous_return_binding(
7519    analyzer: &CppGraphSource<'_>,
7520    visibility: &VisibilityIndex<'_>,
7521    file: &ProjectFile,
7522    candidates: &[CodeUnit],
7523) -> Option<CppScanBinding> {
7524    let mut resolved_return: Option<CppScanBinding> = None;
7525    for function in candidates {
7526        let metadata = analyzer.signature_metadata(function);
7527        let return_types = if metadata.is_empty() {
7528            vec![cpp_function_return_type_text(analyzer, function)?]
7529        } else {
7530            metadata
7531                .iter()
7532                .map(|metadata| metadata.return_type_text().map(str::to_string))
7533                .collect::<Option<Vec<_>>>()?
7534        };
7535        for return_text in return_types {
7536            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
7537            let name = normalize_cpp_type_name(&return_text);
7538            let binding = CppScanBinding::from_type_name(
7539                name.clone(),
7540                visibility
7541                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
7542                indirection,
7543            );
7544            if let Some(existing) = resolved_return.as_ref()
7545                && (existing.indirection != binding.indirection
7546                    || match (&existing.unit, &binding.unit) {
7547                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
7548                        (None, None) => existing.type_name != binding.type_name,
7549                        (Some(_), None) | (None, Some(_)) => true,
7550                    })
7551            {
7552                return None;
7553            }
7554            resolved_return = Some(binding);
7555        }
7556    }
7557    resolved_return
7558}
7559
7560fn aliases_from_prepared_source(
7561    cpp: &dyn CppSource,
7562    token: QueryToken<'_>,
7563    file: &ProjectFile,
7564) -> Vec<CppAlias> {
7565    let Some(prepared) = cpp.prepared_syntax(token, file) else {
7566        return Vec::new();
7567    };
7568    let mut aliases = Vec::new();
7569    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
7570    aliases
7571}
7572
7573fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7574    let mut stack = vec![root];
7575    while let Some(node) = stack.pop() {
7576        match node.kind() {
7577            "alias_declaration" if alias_has_visible_file_scope(node) => {
7578                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
7579                    out.push(alias);
7580                }
7581            }
7582            "type_definition" if alias_has_visible_file_scope(node) => {
7583                collect_typedef_aliases(node, source, out)
7584            }
7585            _ => {}
7586        }
7587
7588        for index in (0..node.named_child_count()).rev() {
7589            if let Some(child) = node.named_child(index) {
7590                stack.push(child);
7591            }
7592        }
7593    }
7594}
7595
7596fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
7597    let mut current = node.parent();
7598    while let Some(parent) = current {
7599        match parent.kind() {
7600            "translation_unit"
7601            | "namespace_definition"
7602            | "declaration_list"
7603            | "linkage_specification" => current = parent.parent(),
7604            "template_declaration" => current = parent.parent(),
7605            _ => return false,
7606        }
7607    }
7608    true
7609}
7610
7611fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
7612    let name = node
7613        .child_by_field_name("name")
7614        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7615    let target = node
7616        .child_by_field_name("type")
7617        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7618    Some(CppAlias {
7619        name,
7620        target,
7621        namespace: enclosing_namespace_context(node, source),
7622    })
7623}
7624
7625fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7626    let Some(type_node) = node.child_by_field_name("type") else {
7627        return;
7628    };
7629    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
7630        return;
7631    };
7632
7633    let mut cursor = node.walk();
7634    for child in node.named_children(&mut cursor) {
7635        if same_node(child, type_node) {
7636            continue;
7637        }
7638        if let Some(name) = extract_typedef_declarator_name(child, source) {
7639            out.push(CppAlias {
7640                name,
7641                target: target.clone(),
7642                namespace: enclosing_namespace_context(node, source),
7643            });
7644        }
7645    }
7646}
7647
7648fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
7649    match node.kind() {
7650        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
7651            normalize_reference_name(node_text(node, source))
7652        }
7653        _ => node
7654            .child_by_field_name("declarator")
7655            .or_else(|| node.child_by_field_name("name"))
7656            .or_else(|| last_named_child(node))
7657            .and_then(|child| extract_typedef_declarator_name(child, source)),
7658    }
7659}
7660
7661fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
7662    let count = node.named_child_count();
7663    if count == 0 {
7664        None
7665    } else {
7666        node.named_child(count - 1)
7667    }
7668}
7669
7670pub fn collect_include_closure(
7671    analyzer: &CppGraphSource<'_>,
7672    include_targets: &IncludeTargetIndex,
7673    file: &ProjectFile,
7674    out: &mut HashSet<ProjectFile>,
7675    cancellation: Option<&CancellationToken>,
7676) {
7677    let mut stack = vec![file.clone()];
7678    while let Some(file) = stack.pop() {
7679        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7680            break;
7681        }
7682        if !out.insert(file.clone()) {
7683            continue;
7684        }
7685        let imports = analyzer.import_statements(&file);
7686        for include in cpp_include_paths(&imports) {
7687            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
7688                stack.push(target);
7689            }
7690        }
7691    }
7692}
7693
7694fn collect_visible_declarations(
7695    include_graph: &IncludeGraph,
7696    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
7697    file: &ProjectFile,
7698    visited: &mut HashSet<ProjectFile>,
7699    out: &mut HashSet<CodeUnit>,
7700    cancellation: Option<&CancellationToken>,
7701) {
7702    let mut stack = vec![file.clone()];
7703    while let Some(file) = stack.pop() {
7704        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7705            break;
7706        }
7707        if !visited.insert(file.clone()) {
7708            continue;
7709        }
7710        if let Some(declarations) = declarations_by_file.get(&file) {
7711            out.extend(declarations.iter().cloned());
7712        }
7713        stack.extend(include_graph.targets(&file).iter().cloned());
7714    }
7715}
7716
7717pub fn signature_arity(signature: Option<&str>) -> usize {
7718    let Some(signature) = signature else {
7719        return 0;
7720    };
7721    let inner = signature
7722        .find('(')
7723        .and_then(|open| {
7724            signature[open + 1..]
7725                .find(')')
7726                .map(|close| &signature[open + 1..open + 1 + close])
7727        })
7728        .unwrap_or(signature)
7729        .trim();
7730    if inner.is_empty() || inner == "void" {
7731        return 0;
7732    }
7733    cpp_split_top_level_commas(inner).count()
7734}
7735
7736fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
7737    let source = format!("void __bifrost_macro_parameters({replacement});");
7738    let mut parser = Parser::new();
7739    parser
7740        .set_language(&tree_sitter_cpp::LANGUAGE.into())
7741        .ok()?;
7742    let tree = parser.parse(&source, None)?;
7743    let root = tree.root_node();
7744    if root.has_error() {
7745        return None;
7746    }
7747    let declaration = root.named_child(0)?;
7748    let declarator = declaration.child_by_field_name("declarator")?;
7749    let parameters = declarator.child_by_field_name("parameters")?;
7750    let mut required = 0;
7751    let mut total = 0;
7752    let mut repeated = false;
7753    let mut cursor = parameters.walk();
7754    for parameter in parameters.children(&mut cursor) {
7755        match parameter.kind() {
7756            "parameter_declaration" => {
7757                if parameter.child_by_field_name("declarator").is_none()
7758                    && parameter
7759                        .child_by_field_name("type")
7760                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
7761                {
7762                    continue;
7763                }
7764                required += 1;
7765                total += 1;
7766            }
7767            "optional_parameter_declaration" => total += 1,
7768            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
7769                repeated = true;
7770            }
7771            _ => {}
7772        }
7773    }
7774    Some(CallableArity::new(required, total, repeated))
7775}
7776
7777pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
7778    analyzer
7779        .signature_metadata(unit)
7780        .into_iter()
7781        .find_map(|metadata| metadata.callable_arity())
7782        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
7783}
7784
7785pub fn cpp_callable_parameter_types(
7786    analyzer: &CppGraphSource<'_>,
7787    unit: &CodeUnit,
7788) -> Option<Vec<String>> {
7789    analyzer
7790        .signature_metadata(unit)
7791        .into_iter()
7792        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
7793        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
7794}
7795
7796fn merge_compatible_callable_arities(
7797    left: CallableArity,
7798    right: CallableArity,
7799) -> Option<CallableArity> {
7800    let total = left.total();
7801    let left_repeated = left.accepts(total.saturating_add(1));
7802    let right_repeated = right.accepts(right.total().saturating_add(1));
7803    if total != right.total() || left_repeated != right_repeated {
7804        return None;
7805    }
7806    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
7807    Some(CallableArity::new(required, total, left_repeated))
7808}
7809
7810fn find_include_activation(
7811    cpp: &dyn CppSource,
7812    token: QueryToken<'_>,
7813    file: &ProjectFile,
7814    prepared: &PreparedSyntaxTree,
7815    donor_source: &ProjectFile,
7816) -> Option<usize> {
7817    let include_targets = cpp.include_target_index();
7818    let mut direct_includes = Vec::new();
7819    let mut nodes = vec![prepared.tree().root_node()];
7820    // An include activates for the whole file, so only an unconditional
7821    // directive counts here.
7822    let reference = CallableReferenceContext {
7823        file,
7824        position: None,
7825    };
7826    while let Some(node) = nodes.pop() {
7827        if node.kind() == "preproc_include" {
7828            if callable_preprocessor_context_is_visible_for_reference(
7829                node,
7830                prepared.source(),
7831                &reference,
7832            ) {
7833                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7834                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7835                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
7836                        file,
7837                        &include,
7838                        include_targets,
7839                    )) {
7840                        direct_includes.push((node.end_byte(), target));
7841                    }
7842                }
7843            }
7844            continue;
7845        }
7846        for index in (0..node.named_child_count()).rev() {
7847            if let Some(child) = node.named_child(index) {
7848                nodes.push(child);
7849            }
7850        }
7851    }
7852    direct_includes.sort_by_key(|(activation, _)| *activation);
7853    let mut known_missing = HashSet::default();
7854    direct_includes
7855        .into_iter()
7856        .find(|(_, direct)| {
7857            unconditional_include_reaches(
7858                cpp,
7859                token,
7860                include_targets,
7861                direct,
7862                donor_source,
7863                file,
7864                &mut known_missing,
7865            )
7866        })
7867        .map(|(activation, _)| activation)
7868}
7869
7870fn find_conditional_include_projection_index(
7871    cpp: &dyn CppSource,
7872    token: QueryToken<'_>,
7873    file: &ProjectFile,
7874    prepared: &PreparedSyntaxTree,
7875    on_state: &dyn Fn(),
7876) -> ConditionalIncludeProjectionIndex {
7877    let include_targets = cpp.include_target_index();
7878    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
7879        HashMap::default();
7880    let mut pending = Vec::new();
7881    let mut nodes = vec![prepared.tree().root_node()];
7882    while let Some(node) = nodes.pop() {
7883        if node.kind() == "preproc_include" {
7884            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
7885            else {
7886                continue;
7887            };
7888            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7889            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7890                let Some(target) = unique_include_target(resolve_include_targets_with_index(
7891                    file,
7892                    &include,
7893                    include_targets,
7894                )) else {
7895                    continue;
7896                };
7897                pending.push((target, node.end_byte(), required_guards.clone()));
7898            }
7899            continue;
7900        }
7901        for index in (0..node.named_child_count()).rev() {
7902            if let Some(child) = node.named_child(index) {
7903                nodes.push(child);
7904            }
7905        }
7906    }
7907
7908    // One reached file can have several distinct compatible guard paths. Each
7909    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
7910    // the consumers ask existence questions whose answers are monotone in the
7911    // guard set -- a path whose requirements hold, stay stable, and stay
7912    // compatible under one environment does so under every subset as well --
7913    // so a state subsumed by an existing subset cannot witness anything its
7914    // subset does not, and inserting a smaller set evicts the supersets it
7915    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
7916    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
7917    // path-union guard sets through it: the state space, the per-key linear
7918    // scans, and resident memory all grew without bound (#2365).
7919    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
7920        HashMap::default();
7921    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
7922        let guard_sets = expanded
7923            .entry((current_file.clone(), activation_byte))
7924            .or_default();
7925        if guard_sets
7926            .iter()
7927            .any(|existing| existing.is_subset(&required_guards))
7928        {
7929            continue;
7930        }
7931        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
7932            .drain(..)
7933            .partition(|existing| required_guards.is_subset(existing));
7934        *guard_sets = kept;
7935        guard_sets.push(required_guards.clone());
7936        if !evicted.is_empty()
7937            && let Some(projections) = projections_by_source.get_mut(&current_file)
7938        {
7939            projections.retain(|projection| {
7940                projection.activation_byte != activation_byte
7941                    || !evicted.contains(&projection.required_guards)
7942            });
7943        }
7944        on_state();
7945
7946        // A fresh minimal set has no equal in the store: equality would have
7947        // been caught by the subset check above.
7948        projections_by_source
7949            .entry(current_file.clone())
7950            .or_default()
7951            .push(ConditionalIncludeProjection {
7952                activation_byte,
7953                required_guards: required_guards.clone(),
7954            });
7955
7956        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
7957            continue;
7958        };
7959        let mut nodes = vec![current_prepared.tree().root_node()];
7960        while let Some(node) = nodes.pop() {
7961            if node.kind() == "preproc_include" {
7962                let Some(include_guards) =
7963                    preprocessor_guard_environment(node, current_prepared.source())
7964                else {
7965                    continue;
7966                };
7967                let Some(path_guards) =
7968                    merge_preprocessor_guards(&required_guards, &include_guards)
7969                else {
7970                    continue;
7971                };
7972                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
7973                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7974                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
7975                        &current_file,
7976                        &include,
7977                        include_targets,
7978                    )) else {
7979                        continue;
7980                    };
7981                    pending.push((target, activation_byte, path_guards.clone()));
7982                }
7983                continue;
7984            }
7985            for index in (0..node.named_child_count()).rev() {
7986                if let Some(child) = node.named_child(index) {
7987                    nodes.push(child);
7988                }
7989            }
7990        }
7991    }
7992
7993    projections_by_source
7994        .into_iter()
7995        .map(|(source, mut projections)| {
7996            projections.sort_by_key(|projection| projection.activation_byte);
7997            (source, Arc::from(projections))
7998        })
7999        .collect()
8000}
8001
8002fn unconditional_include_reaches(
8003    cpp: &dyn CppSource,
8004    token: QueryToken<'_>,
8005    include_targets: &IncludeTargetIndex,
8006    first: &ProjectFile,
8007    donor_source: &ProjectFile,
8008    reference_file: &ProjectFile,
8009    known_missing: &mut HashSet<ProjectFile>,
8010) -> bool {
8011    if first == donor_source {
8012        return true;
8013    }
8014    if known_missing.contains(first) {
8015        return false;
8016    }
8017    let reference_is_c = reference_file
8018        .rel_path()
8019        .extension()
8020        .and_then(|extension| extension.to_str())
8021        == Some("c");
8022    if let Some(reaches) =
8023        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
8024    {
8025        return reaches;
8026    }
8027    let mut visited = HashSet::default();
8028    let mut files = vec![first.clone()];
8029    // Only an unconditional directive extends the include reach, so the walk
8030    // asks the question without a reference position.
8031    let reference = CallableReferenceContext {
8032        file: reference_file,
8033        position: None,
8034    };
8035    while let Some(file) = files.pop() {
8036        if file == *donor_source {
8037            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
8038            return true;
8039        }
8040        if known_missing.contains(&file) || !visited.insert(file.clone()) {
8041            continue;
8042        }
8043        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
8044            continue;
8045        };
8046        let mut nodes = vec![prepared.tree().root_node()];
8047        while let Some(node) = nodes.pop() {
8048            if node.kind() == "preproc_include" {
8049                if callable_preprocessor_context_is_visible_for_reference(
8050                    node,
8051                    prepared.source(),
8052                    &reference,
8053                ) {
8054                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8055                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8056                        if let Some(target) = unique_include_target(
8057                            resolve_include_targets_with_index(&file, &include, include_targets),
8058                        ) {
8059                            files.push(target);
8060                        }
8061                    }
8062                }
8063                continue;
8064            }
8065            for index in (0..node.named_child_count()).rev() {
8066                if let Some(child) = node.named_child(index) {
8067                    nodes.push(child);
8068                }
8069            }
8070        }
8071    }
8072    known_missing.extend(visited);
8073    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
8074    false
8075}
8076
8077fn declaration_guard_requirements(
8078    analyzer: &CppGraphSource<'_>,
8079    cpp: &dyn CppSource,
8080    candidate: &CodeUnit,
8081) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
8082    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
8083        return Vec::new();
8084    };
8085    let root = prepared.tree().root_node();
8086    analyzer
8087        .ranges(candidate)
8088        .into_iter()
8089        .filter_map(|range| {
8090            root.descendant_for_byte_range(range.start_byte, range.end_byte)
8091                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
8092                // A class name is injected into its own body at the declaration's
8093                // introduction point, not after the complete class range. Using
8094                // the start also preserves normal before/after ordering for aliases.
8095                .map(|required| (range.start_byte, required))
8096        })
8097        .collect()
8098}
8099
8100fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
8101    analyzer
8102        .ranges(candidate)
8103        .into_iter()
8104        .map(|range| range.start_byte)
8105        .min()
8106}
8107
8108/// The macro names every configuration in `contexts` defines -- the fact set
8109/// one file's compile-database coverage proves (#2011). `None` when the
8110/// database has no entry for the file, which is different from an empty
8111/// intersection: no entry means no coverage, while an empty intersection is
8112/// covered-and-proves-nothing.
8113fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
8114    let (first, rest) = contexts.split_first()?;
8115    Some(
8116        first
8117            .defined_macros
8118            .iter()
8119            .filter(|name| {
8120                rest.iter()
8121                    .all(|context| context.defined_macros.contains(*name))
8122            })
8123            .cloned()
8124            .collect(),
8125    )
8126}
8127
8128fn guard_requirements_hold_at_reference(
8129    required: &HashSet<PreprocessorGuard>,
8130    reference: Option<&HashSet<PreprocessorGuard>>,
8131) -> bool {
8132    reference.is_some_and(|active| {
8133        required
8134            .iter()
8135            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
8136    })
8137}
8138
8139fn preprocessor_guard_holds_at_reference(
8140    required: &PreprocessorGuard,
8141    active: &HashSet<PreprocessorGuard>,
8142) -> bool {
8143    if active.contains(required) {
8144        return true;
8145    }
8146    let active_expression = BooleanGuardExpression::all(
8147        active
8148            .iter()
8149            .filter_map(PreprocessorGuard::as_boolean_expression),
8150    );
8151    required
8152        .as_boolean_expression()
8153        .is_some_and(|required| active_expression.implies(&required))
8154}
8155
8156/// Cross-file guard rule: two guard sets are compatible when neither one
8157/// contradicts the other. Use this instead of the subset test whenever the
8158/// guards come from a foreign file, which resolves its own conditionals
8159/// independently of the reference.
8160fn guards_compatible_at_reference(
8161    declaration: &HashSet<PreprocessorGuard>,
8162    reference: Option<&HashSet<PreprocessorGuard>>,
8163) -> bool {
8164    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
8165}
8166
8167/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
8168/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
8169/// conditional.
8170///
8171/// Two declarations of one name that report the same chain stand in different
8172/// branches of it, so at most one of them is compiled in any configuration.
8173/// They are alternate spellings of a single declaration, not competing
8174/// declarations, and navigation must not present them as an ambiguity.
8175pub fn preprocessor_conditional_family_range(
8176    root: Node<'_>,
8177    start_byte: usize,
8178    end_byte: usize,
8179) -> Option<(usize, usize)> {
8180    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
8181    let mut ancestor = Some(node);
8182    while let Some(current) = ancestor {
8183        if is_preprocessor_conditional(current)
8184            && preprocessor_conditional_contains_descendant(current, node)
8185        {
8186            let family = preprocessor_conditional_family_root(current);
8187            return Some((family.start_byte(), family.end_byte()));
8188        }
8189        ancestor = current.parent();
8190    }
8191    None
8192}
8193
8194fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
8195    let mut ancestor = node.parent();
8196    while let Some(current) = ancestor {
8197        if is_preprocessor_conditional(current)
8198            && preprocessor_conditional_contains_descendant(current, node)
8199        {
8200            let family = preprocessor_conditional_family_root(current);
8201            if preprocessor_conditional_family_has_terminal_else(family) {
8202                return Some(family);
8203            }
8204        }
8205        ancestor = current.parent();
8206    }
8207    None
8208}
8209
8210fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
8211    while let Some(parent) = conditional.parent() {
8212        let is_alternative = parent
8213            .child_by_field_name("alternative")
8214            .is_some_and(|alternative| {
8215                alternative.start_byte() == conditional.start_byte()
8216                    && alternative.end_byte() == conditional.end_byte()
8217            });
8218        if !is_alternative {
8219            break;
8220        }
8221        conditional = parent;
8222    }
8223    conditional
8224}
8225
8226fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
8227    loop {
8228        let Some(alternative) = conditional.child_by_field_name("alternative") else {
8229            return false;
8230        };
8231        match alternative.kind() {
8232            "preproc_else" => return true,
8233            "preproc_elif" => conditional = alternative,
8234            _ => return false,
8235        }
8236    }
8237}
8238
8239pub fn preprocessor_guard_environment(
8240    node: Node<'_>,
8241    source: &str,
8242) -> Option<HashSet<PreprocessorGuard>> {
8243    let mut guards = HashSet::default();
8244    let mut ancestor = node.parent();
8245    while let Some(conditional) = ancestor {
8246        if matches!(
8247            conditional.kind(),
8248            "preproc_if" | "preproc_ifdef" | "preproc_elif"
8249        ) && !is_file_covering_include_guard(conditional, source)
8250            && preprocessor_conditional_contains_descendant(conditional, node)
8251        {
8252            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
8253            match guard {
8254                PreprocessorGuard::Constant(true) => {
8255                    ancestor = conditional.parent();
8256                    continue;
8257                }
8258                PreprocessorGuard::Constant(false) => return None,
8259                _ => {}
8260            }
8261            if guards.contains(&guard.negated()) {
8262                return None;
8263            }
8264            guards.insert(guard);
8265        }
8266        ancestor = conditional.parent();
8267    }
8268    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
8269        match guard {
8270            PreprocessorGuard::Constant(true) => {}
8271            PreprocessorGuard::Constant(false) => return None,
8272            _ => {
8273                if guards.contains(&guard.negated()) {
8274                    return None;
8275                }
8276                guards.insert(guard);
8277            }
8278        }
8279    }
8280    Some(guards)
8281}
8282
8283fn fragmented_statement_preprocessor_guard(
8284    descendant: Node<'_>,
8285    source: &str,
8286) -> Option<PreprocessorGuard> {
8287    // A conditional that starts before `} else if (...) {` crosses the
8288    // enclosing statement's grammar boundary. tree-sitter leaves its opener
8289    // as a `preproc_if` with a missing terminator in the consequence and
8290    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
8291    // those structured nodes before restoring the guard to intervening uses.
8292    let mut ancestor = descendant.parent();
8293    while let Some(statement) = ancestor {
8294        if statement.kind() == "if_statement"
8295            && let (Some(consequence), Some(alternative)) = (
8296                statement.child_by_field_name("consequence"),
8297                statement.child_by_field_name("alternative"),
8298            )
8299            && alternative.start_byte() <= descendant.start_byte()
8300            && descendant.end_byte() <= alternative.end_byte()
8301        {
8302            let mut cursor = consequence.walk();
8303            let openers = consequence
8304                .named_children(&mut cursor)
8305                .filter(|child| {
8306                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
8307                        && child
8308                            .child(child.child_count().saturating_sub(1))
8309                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
8310                })
8311                .collect::<Vec<_>>();
8312            if openers.len() != 1 {
8313                ancestor = statement.parent();
8314                continue;
8315            }
8316
8317            let mut terminators = Vec::new();
8318            let mut stack = vec![alternative];
8319            while let Some(node) = stack.pop() {
8320                if node.kind() == "preproc_call"
8321                    && node.start_byte() >= descendant.end_byte()
8322                    && node
8323                        .child_by_field_name("directive")
8324                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
8325                {
8326                    terminators.push(node);
8327                    continue;
8328                }
8329                for index in (0..node.named_child_count()).rev() {
8330                    if let Some(child) = node.named_child(index) {
8331                        stack.push(child);
8332                    }
8333                }
8334            }
8335            if terminators.len() == 1 {
8336                return simple_preprocessor_guard(openers[0], source);
8337            }
8338        }
8339        ancestor = statement.parent();
8340    }
8341    None
8342}
8343
8344fn preprocessor_guard_for_descendant(
8345    conditional: Node<'_>,
8346    descendant: Node<'_>,
8347    source: &str,
8348) -> Option<PreprocessorGuard> {
8349    let mut guard = simple_preprocessor_guard(conditional, source)?;
8350    if conditional
8351        .child_by_field_name("alternative")
8352        .is_some_and(|alternative| {
8353            alternative.start_byte() <= descendant.start_byte()
8354                && descendant.end_byte() <= alternative.end_byte()
8355        })
8356    {
8357        let alternative = conditional.child_by_field_name("alternative")?;
8358        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
8359        // descendant in any later branch must first exclude the parent branch,
8360        // then collect the nested `preproc_elif` guard from its own ancestor.
8361        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
8362            return None;
8363        }
8364        guard = guard.negated();
8365    }
8366    Some(guard)
8367}
8368
8369fn preprocessor_conditional_contains_descendant(
8370    conditional: Node<'_>,
8371    descendant: Node<'_>,
8372) -> bool {
8373    cpp_displaced_preprocessor_boundary(conditional)
8374        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
8375}
8376
8377pub fn merge_preprocessor_guards(
8378    left: &HashSet<PreprocessorGuard>,
8379    right: &HashSet<PreprocessorGuard>,
8380) -> Option<HashSet<PreprocessorGuard>> {
8381    let mut merged = left.clone();
8382    for guard in right {
8383        if merged.contains(&guard.negated()) {
8384            return None;
8385        }
8386        merged.insert(guard.clone());
8387    }
8388    Some(merged)
8389}
8390
8391fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
8392    if conditional.kind() == "preproc_ifdef" {
8393        let name = conditional.child_by_field_name("name")?;
8394        let name = node_text(name, source).to_string();
8395        return match conditional.child(0)?.kind() {
8396            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
8397            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
8398            _ => None,
8399        };
8400    }
8401    let condition = conditional.child_by_field_name("condition")?;
8402    simple_preprocessor_expression_guard(condition, source).or_else(|| {
8403        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
8404            node_text(condition, source),
8405        )))
8406    })
8407}
8408
8409fn simple_preprocessor_expression_guard(
8410    expression: Node<'_>,
8411    source: &str,
8412) -> Option<PreprocessorGuard> {
8413    match expression.kind() {
8414        "number_literal" => match node_text(expression, source).trim() {
8415            "0" => Some(PreprocessorGuard::Constant(false)),
8416            "1" => Some(PreprocessorGuard::Constant(true)),
8417            _ => None,
8418        },
8419        "preproc_defined" => {
8420            let identifier = (0..expression.named_child_count())
8421                .filter_map(|index| expression.named_child(index))
8422                .find(|child| child.kind() == "identifier")?;
8423            Some(PreprocessorGuard::Defined(
8424                node_text(identifier, source).to_string(),
8425            ))
8426        }
8427        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
8428            node_text(expression, source).to_string(),
8429        ))),
8430        "unary_expression"
8431            if expression
8432                .child_by_field_name("operator")
8433                .is_some_and(|operator| operator.kind() == "!") =>
8434        {
8435            simple_preprocessor_expression_guard(
8436                expression.child_by_field_name("argument")?,
8437                source,
8438            )
8439            .map(|guard| guard.negated())
8440        }
8441        "parenthesized_expression" => (0..expression.named_child_count())
8442            .filter_map(|index| expression.named_child(index))
8443            .next()
8444            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
8445        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
8446            expression, source,
8447        ))),
8448        _ => None,
8449    }
8450}
8451
8452fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
8453    match expression.kind() {
8454        "number_literal" => match node_text(expression, source).trim() {
8455            "0" => BooleanGuardExpression::Constant(false),
8456            "1" => BooleanGuardExpression::Constant(true),
8457            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8458                expression, source,
8459            ))),
8460        },
8461        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
8462        "preproc_defined" => {
8463            let identifier = (0..expression.named_child_count())
8464                .filter_map(|index| expression.named_child(index))
8465                .find(|child| child.kind() == "identifier");
8466            identifier.map_or_else(
8467                || {
8468                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8469                        expression, source,
8470                    )))
8471                },
8472                |identifier| {
8473                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
8474                },
8475            )
8476        }
8477        "unary_expression"
8478            if expression
8479                .child_by_field_name("operator")
8480                .is_some_and(|operator| operator.kind() == "!") =>
8481        {
8482            expression.child_by_field_name("argument").map_or_else(
8483                || {
8484                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8485                        expression, source,
8486                    )))
8487                },
8488                |argument| boolean_preprocessor_expression(argument, source).negated(),
8489            )
8490        }
8491        "parenthesized_expression" => (0..expression.named_child_count())
8492            .filter_map(|index| expression.named_child(index))
8493            .next()
8494            .map_or_else(
8495                || {
8496                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8497                        expression, source,
8498                    )))
8499                },
8500                |child| boolean_preprocessor_expression(child, source),
8501            ),
8502        "binary_expression" => {
8503            let operands = || {
8504                Some((
8505                    boolean_preprocessor_expression(
8506                        expression.child_by_field_name("left")?,
8507                        source,
8508                    ),
8509                    boolean_preprocessor_expression(
8510                        expression.child_by_field_name("right")?,
8511                        source,
8512                    ),
8513                ))
8514            };
8515            match expression
8516                .child_by_field_name("operator")
8517                .map(|operator| operator.kind())
8518            {
8519                Some("&&") => operands().map_or_else(
8520                    || {
8521                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8522                            expression, source,
8523                        )))
8524                    },
8525                    |(left, right)| BooleanGuardExpression::all([left, right]),
8526                ),
8527                Some("||") => operands().map_or_else(
8528                    || {
8529                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8530                            expression, source,
8531                        )))
8532                    },
8533                    |(left, right)| BooleanGuardExpression::any([left, right]),
8534                ),
8535                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8536                    expression, source,
8537                ))),
8538            }
8539        }
8540        _ => {
8541            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
8542        }
8543    }
8544}
8545
8546fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
8547    if targets.len() == 1 {
8548        targets.pop()
8549    } else {
8550        None
8551    }
8552}
8553
8554/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
8555/// later reference can name.
8556///
8557/// A declaration inside a real function body, lambda, or nested block is block
8558/// local and is dropped. A declaration inside a parser-recovery wrapper that
8559/// merely looks callable -- an export macro between `class` and its name, or a
8560/// namespace-opening macro token before `namespace x {` -- keeps class or
8561/// namespace scope and is kept.
8562fn nameable_callable_declaration_nodes<'tree>(
8563    analyzer: &CppGraphSource<'_>,
8564    prepared: &'tree PreparedSyntaxTree,
8565    candidate: &CodeUnit,
8566) -> Vec<Node<'tree>> {
8567    let root = prepared.tree().root_node();
8568    analyzer
8569        .ranges(candidate)
8570        .into_iter()
8571        .filter_map(|range| {
8572            let mut declaration =
8573                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
8574            while !matches!(
8575                declaration.kind(),
8576                "declaration" | "field_declaration" | "function_definition"
8577            ) {
8578                declaration = declaration.parent()?;
8579            }
8580            let mut ancestor = declaration.parent();
8581            while let Some(node) = ancestor {
8582                if node.kind() == "function_definition"
8583                    && is_recovered_declaration_scope_container(node, prepared.source())
8584                {
8585                    ancestor = node.parent();
8586                    continue;
8587                }
8588                if node.kind() == "compound_statement"
8589                    && node.parent().is_some_and(|parent| {
8590                        is_recovered_declaration_scope_container(parent, prepared.source())
8591                    })
8592                {
8593                    ancestor = node.parent().and_then(|parent| parent.parent());
8594                    continue;
8595                }
8596                if matches!(
8597                    node.kind(),
8598                    "compound_statement" | "function_definition" | "lambda_expression"
8599                ) {
8600                    return None;
8601                }
8602                ancestor = node.parent();
8603            }
8604            Some(declaration)
8605        })
8606        .collect()
8607}
8608
8609fn callable_declaration_activation_in_file(
8610    analyzer: &CppGraphSource<'_>,
8611    prepared: &PreparedSyntaxTree,
8612    candidate: &CodeUnit,
8613    reference: &CallableReferenceContext<'_>,
8614) -> Option<usize> {
8615    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
8616        .into_iter()
8617        .filter(|declaration| {
8618            callable_preprocessor_context_is_visible_for_reference(
8619                *declaration,
8620                prepared.source(),
8621                reference,
8622            )
8623        })
8624        .map(callable_declaration_activation_byte)
8625        .min()
8626}
8627
8628/// C and C++ activate a declared name at the end of its declarator, not at the
8629/// end of the whole declaration. A function definition ends at the closing
8630/// brace of its body, so the declaration end byte would hide the function from
8631/// its own body and make self recursion unresolvable without a prototype.
8632fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
8633    if declaration.kind() != "function_definition" {
8634        return declaration.end_byte();
8635    }
8636    declaration
8637        .child_by_field_name("declarator")
8638        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
8639}
8640
8641/// The reference side of a callable visibility question.
8642///
8643/// An include-graph walk and a whole-file arity activation ask the question
8644/// without one reference position, so they carry no `position` and therefore no
8645/// guard environment.
8646struct CallableReferenceContext<'a> {
8647    file: &'a ProjectFile,
8648    position: Option<CallableReferencePosition<'a>>,
8649}
8650
8651/// One reference position plus its preprocessor guard environment. The
8652/// environment is computed on demand because most declarations carry no
8653/// non-trivial guard.
8654struct CallableReferencePosition<'a> {
8655    prepared: &'a PreparedSyntaxTree,
8656    byte: usize,
8657    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
8658}
8659
8660impl CallableReferenceContext<'_> {
8661    fn is_c(&self) -> bool {
8662        self.file
8663            .rel_path()
8664            .extension()
8665            .and_then(|extension| extension.to_str())
8666            == Some("c")
8667    }
8668
8669    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
8670        let position = self.position.as_ref()?;
8671        position
8672            .guards
8673            .get_or_init(|| {
8674                position
8675                    .prepared
8676                    .tree()
8677                    .root_node()
8678                    .descendant_for_byte_range(position.byte, position.byte)
8679                    .and_then(|node| {
8680                        preprocessor_guard_environment(node, position.prepared.source())
8681                    })
8682            })
8683            .as_ref()
8684    }
8685}
8686
8687fn callable_preprocessor_context_is_visible_for_reference(
8688    node: Node<'_>,
8689    source: &str,
8690    reference: &CallableReferenceContext<'_>,
8691) -> bool {
8692    let reference_is_c = reference.is_c();
8693    let mut ancestor = node.parent();
8694    while let Some(conditional) = ancestor {
8695        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
8696            && !is_file_covering_include_guard(conditional, source)
8697            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
8698            && preprocessor_conditional_contains_descendant(conditional, node)
8699        {
8700            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
8701                return false;
8702            };
8703            match guard {
8704                PreprocessorGuard::Constant(true) => {}
8705                PreprocessorGuard::Constant(false) => return false,
8706                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
8707                    if reference_is_c {
8708                        return false;
8709                    }
8710                }
8711                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
8712                    if !reference_is_c {
8713                        return false;
8714                    }
8715                }
8716                // The declaration stands under a guard whose value this
8717                // analyzer cannot decide. It is still co-active with a
8718                // reference whose active guards imply it. Collecting one guard
8719                // per ancestor makes the whole walk a conjunction of the
8720                // declaration requirements.
8721                guard => {
8722                    if !reference
8723                        .guards()
8724                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
8725                    {
8726                        return false;
8727                    }
8728                }
8729            }
8730        }
8731        ancestor = conditional.parent();
8732    }
8733    true
8734}
8735
8736fn flattened_macro_namespace_declaration_matches(
8737    analyzer: &CppGraphSource<'_>,
8738    cpp: &dyn CppSource,
8739    reference_file: &ProjectFile,
8740    visible_declaration: &CodeUnit,
8741    qualified_candidate: &CodeUnit,
8742    reference_byte: usize,
8743) -> bool {
8744    // Namespace-opening macros can leave tree-sitter unable to retain the
8745    // namespace owner after a later recovery point. In that shape the forward
8746    // declaration is indexed at translation-unit scope, while the definition
8747    // still has its qualified owner. Require all surviving structural evidence
8748    // before treating the declaration as activation for that definition.
8749    if visible_declaration.kind() != qualified_candidate.kind()
8750        || visible_declaration.identifier() != qualified_candidate.identifier()
8751        || visible_declaration.signature() != qualified_candidate.signature()
8752        || !visible_declaration.package_name().is_empty()
8753        || qualified_candidate.package_name().is_empty()
8754    {
8755        return false;
8756    }
8757
8758    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
8759        return false;
8760    };
8761    let root = prepared.tree().root_node();
8762    let closing_brace_limit = if visible_declaration.source() == reference_file {
8763        reference_byte
8764    } else {
8765        usize::MAX
8766    };
8767
8768    analyzer
8769        .ranges(visible_declaration)
8770        .into_iter()
8771        .any(|range| {
8772            let Some(mut declaration) =
8773                root.descendant_for_byte_range(range.start_byte, range.end_byte)
8774            else {
8775                return false;
8776            };
8777            while !matches!(
8778                declaration.kind(),
8779                "declaration" | "field_declaration" | "function_definition"
8780            ) {
8781                let Some(parent) = declaration.parent() else {
8782                    return false;
8783                };
8784                declaration = parent;
8785            }
8786            if declaration
8787                .parent()
8788                .is_none_or(|parent| parent.kind() != "translation_unit")
8789                || !macro_displaced_cpp_return_type(declaration, prepared.source())
8790            {
8791                return false;
8792            }
8793
8794            let mut cursor = root.walk();
8795            root.named_children(&mut cursor).any(|sibling| {
8796                sibling.start_byte() >= declaration.end_byte()
8797                    && sibling.start_byte() < closing_brace_limit
8798                    && direct_unmatched_closing_brace(sibling)
8799            })
8800        })
8801}
8802
8803fn flattened_macro_namespace_components(
8804    declaration: Node<'_>,
8805    source: &str,
8806) -> Option<Vec<String>> {
8807    flattened_macro_function_namespace_components(declaration, source)
8808        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
8809}
8810
8811fn flattened_macro_function_namespace_components(
8812    declaration: Node<'_>,
8813    source: &str,
8814) -> Option<Vec<String>> {
8815    let body = declaration
8816        .parent()
8817        .filter(|parent| parent.kind() == "compound_statement")?;
8818    let function = body.parent()?;
8819    if function.child_by_field_name("body") != Some(body) {
8820        return None;
8821    }
8822    let namespace_name = recovered_macro_namespace_name(function, source)?;
8823    let mut components = enclosing_namespace_components(declaration, source)?;
8824    components.push(namespace_name);
8825    Some(components)
8826}
8827
8828/// The namespace name a namespace-opening macro token displaced into a
8829/// synthetic `function_definition`, or `None` when `function` is not that
8830/// recovery shape.
8831///
8832/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
8833/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
8834/// the macro token, whose declarator is the namespace name behind an `ERROR`
8835/// holding the `namespace` keyword, and whose body spans the whole namespace
8836/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
8837/// artifact from a real function definition.
8838fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
8839    if function.kind() != "function_definition" || !function.has_error() {
8840        return None;
8841    }
8842    let body = function
8843        .child_by_field_name("body")
8844        .filter(|body| body.kind() == "compound_statement")?;
8845    let mut cursor = function.walk();
8846    let prefix = function
8847        .named_children(&mut cursor)
8848        .take_while(|child| child.start_byte() < body.start_byte())
8849        .filter(|child| child.kind() != "comment")
8850        .collect::<Vec<_>>();
8851    let begin_index = prefix.iter().rposition(|child| {
8852        flattened_macro_sentinel_name(*child, source)
8853            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8854    })?;
8855    let mut identifiers = Vec::new();
8856    let mut stack = prefix[begin_index + 1..]
8857        .iter()
8858        .rev()
8859        .copied()
8860        .collect::<Vec<_>>();
8861    while let Some(current) = stack.pop() {
8862        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
8863            identifiers.push(identifier);
8864            continue;
8865        }
8866        let mut cursor = current.walk();
8867        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
8868        stack.extend(children.into_iter().rev());
8869    }
8870    let [keyword, namespace_name] = identifiers.as_slice() else {
8871        return None;
8872    };
8873    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
8874    {
8875        return None;
8876    }
8877    let mut next = function.next_named_sibling();
8878    let next = loop {
8879        let candidate = next?;
8880        next = candidate.next_named_sibling();
8881        if candidate.kind() != "comment" {
8882            break candidate;
8883        }
8884    };
8885    flattened_macro_sentinel_name(next, source)
8886        .is_some_and(|name| is_namespace_end_sentinel(&name))
8887        .then(|| namespace_name.clone())
8888}
8889
8890/// A `function_definition` that exists only because tree-sitter recovered a
8891/// macro-decorated class head or a namespace-opening macro token. A declaration
8892/// in such a body keeps class or namespace scope, so a scope walk must step over
8893/// the wrapper instead of treating the declaration as block local.
8894fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
8895    crate::declarations::is_recovered_exported_class_container(node, source)
8896        || recovered_macro_namespace_name(node, source).is_some()
8897}
8898
8899fn flattened_macro_error_namespace_components(
8900    declaration: Node<'_>,
8901    source: &str,
8902) -> Option<Vec<String>> {
8903    let parent = declaration
8904        .parent()
8905        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
8906    let mut cursor = parent.walk();
8907    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
8908    let declaration_index = siblings
8909        .iter()
8910        .position(|candidate| same_node(*candidate, declaration))?;
8911    let begin_index = (0..declaration_index).rev().find(|index| {
8912        flattened_macro_sentinel_name(siblings[*index], source)
8913            .is_some_and(|name| is_namespace_begin_sentinel(&name))
8914    })?;
8915
8916    let significant = siblings[begin_index + 1..declaration_index]
8917        .iter()
8918        .copied()
8919        .filter(|node| node.kind() != "comment")
8920        .collect::<Vec<_>>();
8921    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
8922        return None;
8923    };
8924    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
8925        return None;
8926    }
8927    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
8928    if significant[2..].iter().any(|node| {
8929        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
8930            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
8931        })
8932    }) {
8933        return None;
8934    }
8935
8936    let mut saw_namespace_close = false;
8937    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
8938        if sibling.kind() == "comment" {
8939            continue;
8940        }
8941        if !saw_namespace_close {
8942            if direct_unmatched_closing_brace(sibling) {
8943                saw_namespace_close = true;
8944                continue;
8945            }
8946            if flattened_macro_sentinel_name(sibling, source).is_some() {
8947                return None;
8948            }
8949            continue;
8950        }
8951        if !flattened_macro_sentinel_name(sibling, source)
8952            .is_some_and(|name| is_namespace_end_sentinel(&name))
8953        {
8954            return None;
8955        }
8956        let mut components = enclosing_namespace_components(declaration, source)?;
8957        components.push(namespace_name);
8958        return Some(components);
8959    }
8960    None
8961}
8962
8963fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
8964    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
8965    // an `expression_statement` with a missing semicolon; inside a namespace
8966    // body the same token stays a bare `type_identifier`.
8967    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
8968        node.named_child(0)?
8969    } else {
8970        node
8971    };
8972    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
8973        node.child_by_field_name("type")
8974            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
8975    })?;
8976    (cpp_export_macro_token(&candidate)
8977        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
8978    .then_some(candidate)
8979}
8980
8981/// Namespace-opening macros are spelled both ways in the wild:
8982/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
8983fn is_namespace_begin_sentinel(name: &str) -> bool {
8984    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
8985}
8986
8987fn is_namespace_end_sentinel(name: &str) -> bool {
8988    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
8989}
8990
8991fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
8992    if node.kind() != "ERROR" || node.named_child_count() != 1 {
8993        return None;
8994    }
8995    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
8996    (!cpp_export_macro_token(&name)).then_some(name)
8997}
8998
8999fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
9000    if !matches!(
9001        node.kind(),
9002        "identifier" | "namespace_identifier" | "type_identifier"
9003    ) {
9004        return None;
9005    }
9006    let name = normalize_cpp_whitespace(node_text(node, source));
9007    (!name.is_empty()).then_some(name)
9008}
9009
9010fn guard_requirement_sets_match(
9011    left: &[(usize, HashSet<PreprocessorGuard>)],
9012    right: &[(usize, HashSet<PreprocessorGuard>)],
9013) -> bool {
9014    left.len() == right.len()
9015        && left.iter().all(|(_, left_guards)| {
9016            right
9017                .iter()
9018                .any(|(_, right_guards)| left_guards == right_guards)
9019        })
9020        && right.iter().all(|(_, right_guards)| {
9021            left.iter()
9022                .any(|(_, left_guards)| right_guards == left_guards)
9023        })
9024}
9025
9026fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
9027    let Some(type_node) = declaration.child_by_field_name("type") else {
9028        return false;
9029    };
9030    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
9031    !type_name.is_empty()
9032        && type_name
9033            .chars()
9034            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
9035        && (0..declaration.named_child_count()).any(|index| {
9036            declaration
9037                .named_child(index)
9038                .is_some_and(|child| child.kind() == "ERROR")
9039        })
9040}
9041
9042fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
9043    node.kind() == "ERROR"
9044        && (0..node.child_count())
9045            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
9046}
9047
9048pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
9049    let mut ancestor = node.parent();
9050    while let Some(parent) = ancestor {
9051        if is_preprocessor_conditional(parent)
9052            && !is_file_covering_include_guard(parent, source)
9053            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
9054        {
9055            return false;
9056        }
9057        ancestor = parent.parent();
9058    }
9059    true
9060}
9061
9062fn is_split_cpp_language_linkage_wrapper(
9063    conditional: Node<'_>,
9064    descendant: Node<'_>,
9065    source: &str,
9066) -> bool {
9067    if conditional.child_by_field_name("alternative").is_some()
9068        || !matches!(
9069            simple_preprocessor_guard(conditional, source),
9070            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9071        )
9072    {
9073        return false;
9074    }
9075    let mut current = descendant.parent();
9076    let linkage = loop {
9077        let Some(node) = current else {
9078            return false;
9079        };
9080        if node == conditional {
9081            return false;
9082        }
9083        if node.kind() == "linkage_specification" {
9084            break node;
9085        }
9086        current = node.parent();
9087    };
9088    if linkage
9089        .child_by_field_name("value")
9090        .is_none_or(|value| node_text(value, source) != "\"C\"")
9091    {
9092        return false;
9093    }
9094    let Some(body) = linkage.child_by_field_name("body") else {
9095        return false;
9096    };
9097    let closes_opening_branch = (0..body.named_child_count())
9098        .filter_map(|index| body.named_child(index))
9099        .take_while(|child| child.end_byte() <= descendant.start_byte())
9100        .any(|child| {
9101            child.kind() == "preproc_call"
9102                && child
9103                    .child_by_field_name("directive")
9104                    .is_some_and(|directive| node_text(directive, source) == "#endif")
9105        });
9106    let reopens_for_closing_brace = (0..body.named_child_count())
9107        .filter_map(|index| body.named_child(index))
9108        .skip_while(|child| child.start_byte() < descendant.end_byte())
9109        .any(|child| {
9110            matches!(
9111                simple_preprocessor_guard(child, source),
9112                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9113            ) && (0..child.child_count()).any(|index| {
9114                child
9115                    .child(index)
9116                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
9117            })
9118        });
9119    closes_opening_branch && reopens_for_closing_brace
9120}
9121
9122pub fn call_arity(node: Node<'_>) -> usize {
9123    node.child_by_field_name("arguments")
9124        .or_else(|| node.child_by_field_name("parameters"))
9125        .or_else(|| node.child_by_field_name("value"))
9126        .or_else(|| first_named_child_of_kind(node, "argument_list"))
9127        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
9128        .map(|args| argument_children(args).count())
9129        .unwrap_or(0)
9130}
9131
9132pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
9133    let recovered_block_arguments = recovered_block_literal_arguments(node);
9134    (0..node.child_count())
9135        .filter_map(move |index| node.child(index))
9136        .filter(|child| child.is_named() && !child.is_extra())
9137        .flat_map(move |child| {
9138            if let Some((raw, left, right)) = recovered_block_arguments
9139                && child == raw
9140            {
9141                [Some(left), Some(right)]
9142            } else {
9143                [Some(child), None]
9144            }
9145        })
9146        .flatten()
9147}
9148
9149fn recovered_c_keyword_argument_count(
9150    file: &ProjectFile,
9151    call: Node<'_>,
9152    arguments: Node<'_>,
9153    source: &str,
9154) -> usize {
9155    // A C identifier that is a C++ keyword can be displaced twice by the C++
9156    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
9157    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
9158    // the enclosing C function before restoring the otherwise dropped slot.
9159    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
9160        return 0;
9161    }
9162    let mut ancestor = Some(call);
9163    let function = loop {
9164        let Some(current) = ancestor else {
9165            return 0;
9166        };
9167        if current.kind() == "function_definition" {
9168            break current;
9169        }
9170        ancestor = current.parent();
9171    };
9172    let Some(parameters) = function
9173        .child_by_field_name("declarator")
9174        .and_then(|declarator| declarator.child_by_field_name("parameters"))
9175    else {
9176        return 0;
9177    };
9178    let displaced_parameter_keywords = (0..parameters.child_count())
9179        .filter_map(|index| parameters.child(index))
9180        .filter(|error| error.kind() == "ERROR")
9181        .filter_map(|error| {
9182            let parameter = error.prev_named_sibling()?;
9183            if parameter.kind() != "parameter_declaration"
9184                || parameter.end_byte() != error.start_byte()
9185                || extract_variable_name(parameter, source).is_some()
9186            {
9187                return None;
9188            }
9189            let mut children = (0..error.child_count())
9190                .filter_map(|index| error.child(index))
9191                .filter(|child| !child.is_extra() && !child.is_missing());
9192            let keyword = children.next()?;
9193            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
9194                .then_some(keyword)
9195        })
9196        .collect::<Vec<_>>();
9197    if displaced_parameter_keywords.is_empty() {
9198        return 0;
9199    }
9200
9201    (0..arguments.child_count())
9202        .filter_map(|index| arguments.child(index))
9203        .filter(|error| error.kind() == "ERROR" && error.is_extra())
9204        .filter(|error| {
9205            let mut children = (0..error.child_count())
9206                .filter_map(|index| error.child(index))
9207                .filter(|child| !child.is_extra() && !child.is_missing());
9208            let Some(comma) = children.next() else {
9209                return false;
9210            };
9211            let Some(keyword) = children.next() else {
9212                return false;
9213            };
9214            children.next().is_none()
9215                && comma.kind() == ","
9216                && !keyword.is_named()
9217                && keyword.child_count() == 0
9218                && displaced_parameter_keywords
9219                    .iter()
9220                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
9221        })
9222        .count()
9223}
9224
9225fn recovered_block_literal_arguments<'tree>(
9226    arguments: Node<'tree>,
9227) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
9228    if arguments.kind() != "argument_list" {
9229        return None;
9230    }
9231    let mut raw_arguments = (0..arguments.child_count())
9232        .filter_map(|index| arguments.child(index))
9233        .filter(|child| child.is_named() && !child.is_extra());
9234    let raw = raw_arguments.next()?;
9235    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
9236        return None;
9237    }
9238
9239    let left = raw.child_by_field_name("left")?;
9240    if left.is_missing() || left.start_byte() == left.end_byte() {
9241        return None;
9242    }
9243    let right = raw.child_by_field_name("right")?;
9244    if right.kind() != "compound_literal_expression"
9245        || right.is_missing()
9246        || right
9247            .child_by_field_name("type")
9248            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
9249        || right
9250            .child_by_field_name("value")
9251            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
9252    {
9253        return None;
9254    }
9255    let has_intervening_error = (0..raw.child_count())
9256        .filter_map(|index| raw.child(index))
9257        .any(|child| {
9258            child.kind() == "ERROR"
9259                && !child.is_missing()
9260                && child.start_byte() >= left.end_byte()
9261                && child.end_byte() <= right.start_byte()
9262        });
9263    has_intervening_error.then_some((raw, left, right))
9264}
9265
9266pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
9267    match node.kind() {
9268        "new_expression" => node
9269            .child_by_field_name("type")
9270            .or_else(|| node.named_child(0)),
9271        "compound_literal_expression" => node.child_by_field_name("type"),
9272        "call_expression" => node.child_by_field_name("function"),
9273        _ => None,
9274    }
9275}
9276
9277pub fn field_initializer_constructs_target(
9278    node: Node<'_>,
9279    ctx: &ScanCtx<'_>,
9280    owner: &CodeUnit,
9281) -> bool {
9282    // A qualified name in a constructor initializer denotes a base
9283    // subobject constructor (`namespace::Base(args)`), not a member field.  The
9284    // field-initializer grammar exposes the qualified name as one structured
9285    // `qualified_identifier`; resolve its owner through the same lexical type
9286    // machinery used for ordinary C++ type references before considering the
9287    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
9288    // qualified non-constructor member, and an unresolved owner out of the
9289    // target constructor's inverse usage set.
9290    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
9291        return qualified_base_initializer_constructs_target(node, ctx, owner);
9292    }
9293    let Some(name) = node
9294        .child_by_field_name("name")
9295        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
9296        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
9297    else {
9298        return false;
9299    };
9300    let field_name = node_text(name, ctx.source);
9301    ctx.visibility
9302        .visible_identifier_candidates(ctx.file, field_name)
9303        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
9304        .any(|unit| field_declares_type(unit, ctx, owner))
9305}
9306
9307fn qualified_base_initializer_constructs_target(
9308    node: Node<'_>,
9309    ctx: &ScanCtx<'_>,
9310    owner: &CodeUnit,
9311) -> bool {
9312    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
9313        return false;
9314    };
9315    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
9316        return false;
9317    };
9318    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
9319        return false;
9320    };
9321    let resolves_target = |components: &[String]| {
9322        matches!(
9323            ctx.visibility.resolve_type_components_lexically_for_target(
9324                &ctx.analyzer,
9325                ctx.file,
9326                components,
9327                is_globally_qualified_cpp_name(qualified),
9328                &lexical_scope,
9329                owner,
9330            ),
9331            LexicalTypeResolution::Resolved { unit, .. }
9332                if same_visible_symbol(&unit, owner)
9333        )
9334    };
9335    if resolves_target(&components) {
9336        return true;
9337    }
9338
9339    // Some real-world code spells a base mem-initializer as
9340    // `Base::Base(args)`. In that structured path the final component repeats
9341    // the constructor name; resolve the preceding type path. The terminal
9342    // identity check prevents an arbitrary qualified member from taking this
9343    // route.
9344    components
9345        .last()
9346        .is_some_and(|terminal| terminal == owner.identifier())
9347        && resolves_target(&components[..components.len() - 1])
9348}
9349
9350fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9351    unit.signature()
9352        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
9353        || ctx
9354            .analyzer
9355            .get_source(unit, false)
9356            .is_some_and(|declaration| {
9357                field_declaration_type_matches(&declaration, unit, ctx, owner)
9358            })
9359}
9360
9361pub fn field_declared_binding(
9362    analyzer: &CppGraphSource<'_>,
9363    visibility: &VisibilityIndex<'_>,
9364    visible_from: &ProjectFile,
9365    field: &CodeUnit,
9366) -> Option<CppScanBinding> {
9367    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9368    let normalized = normalize_field_type_text(&fact.type_text);
9369    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
9370        analyzer,
9371        visible_from,
9372        field,
9373        &normalized,
9374    );
9375    let resolved = match (resolved, fact.template_arguments.as_deref()) {
9376        (Some(primary), Some(arguments)) => visibility
9377            .resolve_template_arguments(visible_from, primary, arguments)
9378            .ok(),
9379        (resolved, None) => resolved,
9380        (None, Some(_)) => None,
9381    };
9382    Some(CppScanBinding::from_type_name(
9383        normalized,
9384        resolved,
9385        fact.indirection,
9386    ))
9387}
9388
9389/// The one logical type the candidates name, or why they do not name one.
9390fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
9391    let Some(first) = candidates.first() else {
9392        return Err(TypeCandidateFailure::Unresolvable);
9393    };
9394    if candidates
9395        .iter()
9396        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
9397    {
9398        Ok((*first).clone())
9399    } else {
9400        Err(TypeCandidateFailure::Ambiguous)
9401    }
9402}
9403
9404fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
9405    logical_type_candidate(candidates).ok()
9406}
9407
9408fn unique_type_candidate_preserving_alias(
9409    analyzer: &CppGraphSource<'_>,
9410    candidates: &[&CodeUnit],
9411) -> Option<CodeUnit> {
9412    let first = *candidates.first()?;
9413    if declared_type_alias(analyzer, first) {
9414        return candidates
9415            .iter()
9416            .all(|candidate| {
9417                declared_type_alias(analyzer, candidate)
9418                    && candidate.kind() == first.kind()
9419                    && candidate.fq_name() == first.fq_name()
9420                    && candidate.source() == first.source()
9421            })
9422            .then(|| first.clone());
9423    }
9424    candidates
9425        .iter()
9426        .all(|candidate| {
9427            !declared_type_alias(analyzer, candidate)
9428                && candidate.kind() == first.kind()
9429                && candidate.fq_name() == first.fq_name()
9430        })
9431        .then(|| first.clone())
9432}
9433
9434fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
9435    is_type_alias(unit)
9436        || analyzer
9437            .type_alias_provider()
9438            .is_some_and(|provider| provider.is_type_alias(unit))
9439}
9440
9441pub fn field_declared_type_binding(
9442    analyzer: &CppGraphSource<'_>,
9443    visibility: &VisibilityIndex<'_>,
9444    visible_from: &ProjectFile,
9445    field: &CodeUnit,
9446) -> Option<(String, Option<CodeUnit>, i32)> {
9447    let fact = visibility.field_declared_type_fact(analyzer, field)?;
9448    let normalized = normalize_field_type_text(&fact.type_text);
9449    let primary = visibility.resolve_unique_canonical_type_for_declaration(
9450        analyzer,
9451        visible_from,
9452        field,
9453        &normalized,
9454    );
9455    let resolved = match (primary, fact.template_arguments.as_deref()) {
9456        (Some(primary), Some(arguments)) => visibility
9457            .resolve_template_arguments(visible_from, primary, arguments)
9458            .ok(),
9459        (resolved, None) => resolved,
9460        (None, Some(_)) => None,
9461    };
9462    Some((normalized, resolved, fact.indirection))
9463}
9464
9465fn decode_field_declared_type_fact(
9466    analyzer: &CppGraphSource<'_>,
9467    field: &CodeUnit,
9468) -> Option<DeclaredFieldTypeFact> {
9469    let declaration = analyzer.get_source(field, false)?;
9470    let mut parser = Parser::new();
9471    parser
9472        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9473        .ok()?;
9474    let tree = parser.parse(&declaration, None)?;
9475    let mut stack = vec![tree.root_node()];
9476    while let Some(node) = stack.pop() {
9477        if matches!(node.kind(), "declaration" | "field_declaration")
9478            && let Some(type_node) = node
9479                .child_by_field_name("type")
9480                .or_else(|| first_type_child(node))
9481            && let Some(indirection) =
9482                declared_name_indirection(node, type_node, field.identifier(), &declaration)
9483        {
9484            let declared_type = if matches!(
9485                type_node.kind(),
9486                "class_specifier" | "struct_specifier" | "union_specifier"
9487            ) {
9488                type_node.child_by_field_name("name")
9489            } else {
9490                Some(type_node)
9491            };
9492            let type_text = declared_type.map_or_else(
9493                || field.identifier().to_string(),
9494                |declared_type| node_text(declared_type, &declaration).to_string(),
9495            );
9496            return Some(DeclaredFieldTypeFact {
9497                type_text,
9498                indirection,
9499                template_arguments: declared_type.and_then(|declared_type| {
9500                    cpp_template_reference_arguments(declared_type, &declaration)
9501                }),
9502            });
9503        }
9504        let mut cursor = node.walk();
9505        stack.extend(node.named_children(&mut cursor));
9506    }
9507    None
9508}
9509
9510/// Text of the type that a C or C++ alias declaration names, read from the
9511/// `type_definition` or `alias_declaration` node's `type` field.
9512///
9513/// The declaration text is never scanned. A function-pointer typedef
9514/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
9515/// so no prefix or suffix of the spelling isolates the target.
9516///
9517/// An alias whose declarator is a function declarator names a function type:
9518/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
9519/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
9520/// so such an alias has no canonical target. Its `type` field holds the return
9521/// type `R`, which is a different type from the alias, so this returns `None`
9522/// rather than that return type.
9523pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
9524    let mut parser = Parser::new();
9525    parser
9526        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9527        .ok()?;
9528    let tree = parser.parse(declaration, None)?;
9529    let mut stack = vec![tree.root_node()];
9530    while let Some(node) = stack.pop() {
9531        let type_node = match node.kind() {
9532            "type_definition" => {
9533                let mut cursor = node.walk();
9534                if node
9535                    .children_by_field_name("declarator", &mut cursor)
9536                    .any(declarator_names_function_type)
9537                {
9538                    return None;
9539                }
9540                node.child_by_field_name("type")?
9541            }
9542            "alias_declaration" => {
9543                let type_node = node.child_by_field_name("type")?;
9544                if type_node
9545                    .child_by_field_name("declarator")
9546                    .is_some_and(declarator_names_function_type)
9547                {
9548                    return None;
9549                }
9550                type_node
9551            }
9552            _ => {
9553                let mut cursor = node.walk();
9554                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9555                stack.extend(children.into_iter().rev());
9556                continue;
9557            }
9558        };
9559        return Some(node_text(type_node, declaration).to_string());
9560    }
9561    None
9562}
9563
9564/// Whether an alias declaration's own declarator adds indirection that
9565/// [`cpp_alias_declaration_target_text`] does not report.
9566///
9567/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
9568/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
9569/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
9570/// prove the alias adds no indirection must refuse to follow it. A declaration
9571/// this cannot read at all is refused for the same reason.
9572fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
9573    let mut parser = Parser::new();
9574    if parser
9575        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9576        .is_err()
9577    {
9578        return true;
9579    }
9580    let Some(tree) = parser.parse(declaration, None) else {
9581        return true;
9582    };
9583    let mut stack = vec![tree.root_node()];
9584    while let Some(node) = stack.pop() {
9585        let declarators = match node.kind() {
9586            "type_definition" => {
9587                let mut cursor = node.walk();
9588                node.children_by_field_name("declarator", &mut cursor)
9589                    .collect::<Vec<_>>()
9590            }
9591            "alias_declaration" => node
9592                .child_by_field_name("type")
9593                .and_then(|type_node| type_node.child_by_field_name("declarator"))
9594                .into_iter()
9595                .collect::<Vec<_>>(),
9596            _ => {
9597                let mut cursor = node.walk();
9598                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9599                stack.extend(children.into_iter().rev());
9600                continue;
9601            }
9602        };
9603        return declarators.into_iter().any(cpp_declarator_adds_indirection);
9604    }
9605    true
9606}
9607
9608/// True when an alias declarator names a function type.
9609///
9610/// The declarator chain is walked through the `declarator` field, so the
9611/// parameter list -- a sibling field -- is never entered and a parameter's own
9612/// function declarator cannot be mistaken for the alias's.
9613fn declarator_names_function_type(declarator: Node<'_>) -> bool {
9614    let mut current = Some(declarator);
9615    while let Some(node) = current {
9616        match node.kind() {
9617            "function_declarator" | "abstract_function_declarator" => return true,
9618            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
9619                current = node.named_child(0);
9620            }
9621            _ => current = node.child_by_field_name("declarator"),
9622        }
9623    }
9624    false
9625}
9626
9627fn decode_structured_alias_target(
9628    analyzer: &CppGraphSource<'_>,
9629    unit: &CodeUnit,
9630) -> Option<StructuredAliasTarget> {
9631    analyzer
9632        .get_source(unit, false)
9633        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
9634        .or_else(|| {
9635            let signature = unit.signature()?;
9636            decode_structured_alias_target_source(unit, signature, false)
9637        })
9638}
9639
9640fn decode_structured_alias_target_source(
9641    unit: &CodeUnit,
9642    declaration: &str,
9643    require_top_level: bool,
9644) -> Option<StructuredAliasTarget> {
9645    let mut parser = Parser::new();
9646    parser
9647        .set_language(&tree_sitter_cpp::LANGUAGE.into())
9648        .ok()?;
9649    let tree = parser.parse(declaration, None)?;
9650    let mut stack = vec![tree.root_node()];
9651    while let Some(node) = stack.pop() {
9652        let type_node = match node.kind() {
9653            "type_definition" => {
9654                if require_top_level
9655                    && node
9656                        .parent()
9657                        .is_none_or(|parent| parent.kind() != "translation_unit")
9658                {
9659                    let mut cursor = node.walk();
9660                    stack.extend(node.named_children(&mut cursor));
9661                    continue;
9662                }
9663                let mut declarator_cursor = node.walk();
9664                let declarator = node
9665                    .children_by_field_name("declarator", &mut declarator_cursor)
9666                    .find(|declarator| {
9667                        extract_typedef_declarator_name(*declarator, declaration)
9668                            .is_some_and(|name| name == unit.identifier())
9669                    })?;
9670                if declarator_names_function_type(declarator) {
9671                    return None;
9672                }
9673                node.child_by_field_name("type")?
9674            }
9675            "alias_declaration" => {
9676                if require_top_level
9677                    && node
9678                        .parent()
9679                        .is_none_or(|parent| parent.kind() != "translation_unit")
9680                {
9681                    let mut cursor = node.walk();
9682                    stack.extend(node.named_children(&mut cursor));
9683                    continue;
9684                }
9685                let name = node.child_by_field_name("name")?;
9686                if node_text(name, declaration) != unit.identifier() {
9687                    return None;
9688                }
9689                let type_node = node.child_by_field_name("type")?;
9690                if type_node
9691                    .child_by_field_name("declarator")
9692                    .is_some_and(declarator_names_function_type)
9693                {
9694                    return None;
9695                }
9696                type_node
9697            }
9698            _ => {
9699                let mut cursor = node.walk();
9700                stack.extend(node.named_children(&mut cursor));
9701                continue;
9702            }
9703        };
9704        return structured_alias_type_target(type_node, declaration);
9705    }
9706    None
9707}
9708
9709fn structured_alias_type_target(
9710    mut type_node: Node<'_>,
9711    source: &str,
9712) -> Option<StructuredAliasTarget> {
9713    while type_node.kind() == "type_descriptor" {
9714        type_node = type_node.child_by_field_name("type")?;
9715    }
9716    if type_node.kind() == "primitive_type" {
9717        return Some(StructuredAliasTarget::Builtin);
9718    }
9719    if matches!(
9720        type_node.kind(),
9721        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9722    ) {
9723        type_node = type_node.child_by_field_name("name")?;
9724    }
9725    let global = type_node.child_by_field_name("scope").is_none()
9726        && type_node.child(0).is_some_and(|child| child.kind() == "::");
9727    let mut components = Vec::new();
9728    append_structured_type_components(type_node, source, &mut components)?;
9729    let arguments = cpp_template_reference_arguments(type_node, source);
9730    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
9731        components,
9732        global,
9733        arguments,
9734    })
9735}
9736
9737fn append_structured_type_components(
9738    node: Node<'_>,
9739    source: &str,
9740    out: &mut Vec<String>,
9741) -> Option<()> {
9742    match node.kind() {
9743        "identifier" | "namespace_identifier" | "type_identifier" => {
9744            out.push(node_text(node, source).to_string());
9745            Some(())
9746        }
9747        "template_type" => {
9748            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9749        }
9750        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
9751            if let Some(scope) = node.child_by_field_name("scope") {
9752                append_structured_type_components(scope, source, out)?;
9753            }
9754            append_structured_type_components(node.child_by_field_name("name")?, source, out)
9755        }
9756        _ => None,
9757    }
9758}
9759
9760fn declared_name_indirection(
9761    declaration: Node<'_>,
9762    type_node: Node<'_>,
9763    field_name: &str,
9764    source: &str,
9765) -> Option<i32> {
9766    let mut stack = Vec::new();
9767    let mut cursor = declaration.walk();
9768    stack.extend(
9769        declaration
9770            .named_children(&mut cursor)
9771            .filter(|child| !same_node(*child, type_node)),
9772    );
9773    while let Some(node) = stack.pop() {
9774        if matches!(node.kind(), "identifier" | "field_identifier")
9775            && node_text(node, source) == field_name
9776        {
9777            let mut indirection = 0;
9778            let mut current = node.parent();
9779            while let Some(parent) = current {
9780                if same_node(parent, declaration) {
9781                    return Some(indirection);
9782                }
9783                if parent.kind() == "pointer_declarator" {
9784                    indirection += 1;
9785                }
9786                current = parent.parent();
9787            }
9788            return None;
9789        }
9790        let mut cursor = node.walk();
9791        stack.extend(node.named_children(&mut cursor));
9792    }
9793    None
9794}
9795
9796fn field_declaration_type_matches(
9797    declaration: &str,
9798    unit: &CodeUnit,
9799    ctx: &ScanCtx<'_>,
9800    owner: &CodeUnit,
9801) -> bool {
9802    ctx.visibility
9803        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
9804        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
9805            let normalized = normalize_field_type_text(type_text);
9806            ctx.visibility
9807                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
9808                || ctx.visibility.resolves_to_type(
9809                    &ctx.analyzer,
9810                    ctx.file,
9811                    normalized.as_str(),
9812                    owner,
9813                )
9814        })
9815}
9816
9817fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
9818    let declaration = declaration
9819        .split(['=', ';'])
9820        .next()
9821        .unwrap_or(declaration)
9822        .trim();
9823    let index = declaration.rfind(field_name)?;
9824    let before = &declaration[..index];
9825    let after = &declaration[index + field_name.len()..];
9826    if before.chars().next_back().is_some_and(is_identifier_char)
9827        || after.chars().next().is_some_and(is_identifier_char)
9828    {
9829        return None;
9830    }
9831    Some(before.trim())
9832}
9833
9834fn normalize_field_type_text(type_text: &str) -> String {
9835    const FIELD_SPECIFIERS: [&str; 8] = [
9836        "extern ",
9837        "static ",
9838        "mutable ",
9839        "constexpr ",
9840        "constinit ",
9841        "inline ",
9842        "volatile ",
9843        "const ",
9844    ];
9845
9846    let mut normalized = normalize_type_text(type_text);
9847    loop {
9848        let Some(stripped) = FIELD_SPECIFIERS
9849            .iter()
9850            .find_map(|specifier| normalized.strip_prefix(specifier))
9851        else {
9852            return normalized;
9853        };
9854        normalized = normalize_type_text(stripped);
9855    }
9856}
9857
9858fn is_identifier_char(ch: char) -> bool {
9859    ch == '_' || ch.is_ascii_alphanumeric()
9860}
9861
9862pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9863    let Some(type_node) = node.child_by_field_name("type") else {
9864        return false;
9865    };
9866    ctx.visibility.resolves_to_type(
9867        &ctx.analyzer,
9868        ctx.file,
9869        node_text(type_node, ctx.source),
9870        owner,
9871    )
9872}
9873
9874pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9875    !ctx.analyzer
9876        .declarations(ctx.file)
9877        .into_iter()
9878        .filter(|unit| unit.is_function())
9879        .any(|unit| {
9880            ctx.analyzer.ranges(&unit).iter().any(|range| {
9881                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
9882            })
9883        })
9884}
9885
9886pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
9887    let mut cursor = node.walk();
9888    for child in node.named_children(&mut cursor) {
9889        if child.kind() == "init_declarator" {
9890            return child
9891                .child_by_field_name("value")
9892                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
9893                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
9894                .map(declaration_init_value_arity)
9895                .unwrap_or(0);
9896        }
9897        if is_declarator_node(child) {
9898            return declaration_declarator_arity(child);
9899        }
9900    }
9901    0
9902}
9903
9904fn declaration_init_value_arity(value: Node<'_>) -> usize {
9905    match value.kind() {
9906        "argument_list" | "initializer_list" => argument_children(value).count(),
9907        "compound_literal_expression" => call_arity(value),
9908        _ => 1,
9909    }
9910}
9911
9912fn declaration_declarator_arity(node: Node<'_>) -> usize {
9913    if let Some(parameters) = node.child_by_field_name("parameters") {
9914        return argument_children(parameters).count();
9915    }
9916    node.child_by_field_name("declarator")
9917        .map(declaration_declarator_arity)
9918        .unwrap_or(0)
9919}
9920
9921fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9922    let mut cursor = node.walk();
9923    node.named_children(&mut cursor)
9924        .find(|child| child.kind() == kind)
9925}
9926
9927fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9928    let mut stack = vec![root];
9929    while let Some(node) = stack.pop() {
9930        if node.kind() == kind {
9931            return Some(node);
9932        }
9933        for index in (0..node.named_child_count()).rev() {
9934            if let Some(child) = node.named_child(index) {
9935                stack.push(child);
9936            }
9937        }
9938    }
9939    None
9940}
9941
9942fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
9943    if node.kind() == "identifier" {
9944        return true;
9945    }
9946    if node.kind() == "parenthesized_expression" {
9947        return false;
9948    }
9949    if node.kind() == "call_expression" {
9950        return node
9951            .child_by_field_name("function")
9952            .is_some_and(|function| function.kind() == "identifier");
9953    }
9954    let mut stack = vec![node];
9955    while let Some(descendant) = stack.pop() {
9956        if descendant != node && descendant.kind() == "parenthesized_expression" {
9957            continue;
9958        }
9959        if descendant.kind() == "identifier" {
9960            return true;
9961        }
9962        if descendant.kind() == "call_expression" {
9963            if descendant
9964                .child_by_field_name("function")
9965                .is_some_and(|function| function.kind() == "identifier")
9966            {
9967                return true;
9968            }
9969            continue;
9970        }
9971        for index in (0..descendant.named_child_count()).rev() {
9972            if let Some(child) = descendant.named_child(index) {
9973                stack.push(child);
9974            }
9975        }
9976    }
9977    false
9978}
9979
9980fn macro_expansion_shape_is_safe(
9981    node: Node<'_>,
9982    source: &str,
9983    parameters: &[String],
9984    environment: &MacroEnvironment,
9985) -> bool {
9986    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
9987        return true;
9988    }
9989    if node.kind() == "call_expression" {
9990        let Some(function) = node.child_by_field_name("function") else {
9991            return true;
9992        };
9993        if function.kind() != "identifier" {
9994            return true;
9995        }
9996        let function_name = node_text(function, source);
9997        if parameters
9998            .iter()
9999            .any(|parameter| parameter == function_name)
10000        {
10001            return false;
10002        }
10003        if !environment.may_bind(function_name) {
10004            return true;
10005        }
10006        let Some(arguments) = node.child_by_field_name("arguments") else {
10007            return false;
10008        };
10009        return argument_children(arguments).all(|argument| {
10010            if argument.kind() == "identifier"
10011                && parameters
10012                    .iter()
10013                    .any(|parameter| parameter == node_text(argument, source))
10014            {
10015                return false;
10016            }
10017            macro_expansion_shape_is_safe(argument, source, parameters, environment)
10018        });
10019    }
10020    let mut stack = vec![node];
10021    while let Some(descendant) = stack.pop() {
10022        if descendant != node {
10023            if descendant.kind() == "parenthesized_expression" {
10024                continue;
10025            }
10026            if descendant.kind() == "call_expression" {
10027                let expands = descendant
10028                    .child_by_field_name("function")
10029                    .filter(|function| function.kind() == "identifier")
10030                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
10031                if expands {
10032                    return false;
10033                }
10034                continue;
10035            }
10036        }
10037        if descendant.kind() == "identifier" {
10038            let identifier = node_text(descendant, source);
10039            if parameters.iter().any(|parameter| parameter == identifier)
10040                || environment.may_bind(identifier)
10041            {
10042                return false;
10043            }
10044        }
10045        for index in (0..descendant.named_child_count()).rev() {
10046            if let Some(child) = descendant.named_child(index) {
10047                stack.push(child);
10048            }
10049        }
10050    }
10051    true
10052}
10053
10054fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
10055    let text = node_text(path, source);
10056    match path.kind() {
10057        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
10058        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
10059        _ => None,
10060    }
10061}
10062
10063fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
10064    let descendant = node;
10065    while let Some(parent) = node.parent() {
10066        if is_preprocessor_conditional(parent)
10067            && !is_file_covering_include_guard(parent, source)
10068            && preprocessor_conditional_contains_descendant(parent, descendant)
10069        {
10070            return true;
10071        }
10072        node = parent;
10073    }
10074    false
10075}
10076
10077fn is_preprocessor_conditional(node: Node<'_>) -> bool {
10078    matches!(
10079        node.kind(),
10080        "preproc_if"
10081            | "preproc_ifdef"
10082            | "preproc_ifndef"
10083            | "preproc_elif"
10084            | "preproc_elifdef"
10085            | "preproc_else"
10086    )
10087}
10088
10089fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
10090    node.parent()
10091        .filter(|parent| parent.kind() == "translation_unit")
10092        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
10093        && is_canonical_include_guard(node, source)
10094}
10095
10096fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
10097    if node.kind() != "preproc_ifdef"
10098        || node
10099            .child(0)
10100            .is_none_or(|directive| directive.kind() != "#ifndef")
10101        || node.child_by_field_name("alternative").is_some()
10102    {
10103        return false;
10104    }
10105    let Some(guard_name) = node.child_by_field_name("name") else {
10106        return false;
10107    };
10108    let mut cursor = node.walk();
10109    node.named_children(&mut cursor)
10110        .find(|child| *child != guard_name && child.kind() != "comment")
10111        .filter(|child| child.kind() == "preproc_def")
10112        .and_then(|definition| definition.child_by_field_name("name"))
10113        .is_some_and(|defined_name| {
10114            node_text(defined_name, source) == node_text(guard_name, source)
10115        })
10116}
10117
10118fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
10119    let mut guard = None;
10120    for index in 0..root.named_child_count() {
10121        let Some(child) = root.named_child(index) else {
10122            continue;
10123        };
10124        if child.kind() == "comment" || is_pragma_once(child, source) {
10125            continue;
10126        }
10127        if guard.is_none() && is_canonical_include_guard(child, source) {
10128            guard = Some(child);
10129        } else {
10130            return None;
10131        }
10132    }
10133    guard
10134        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
10135        .map(|name| node_text(name, source).to_string())
10136}
10137
10138fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
10139    if (0..root.named_child_count())
10140        .filter_map(|index| root.named_child(index))
10141        .any(|child| is_pragma_once(child, source))
10142    {
10143        return MacroIncludeProtection::PragmaOnce;
10144    }
10145    top_level_canonical_include_guard_name(root, source)
10146        .map(MacroIncludeProtection::MacroGuard)
10147        .unwrap_or(MacroIncludeProtection::None)
10148}
10149
10150fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
10151    node.kind() == "preproc_call"
10152        && node
10153            .child_by_field_name("directive")
10154            .is_some_and(|directive| node_text(directive, source) == "#pragma")
10155        && node
10156            .child_by_field_name("argument")
10157            .is_some_and(|argument| node_text(argument, source).trim() == "once")
10158}
10159
10160fn parse_preproc_identifier(argument: &str) -> Option<String> {
10161    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
10162    let mut parser = Parser::new();
10163    parser
10164        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10165        .ok()?;
10166    let tree = parser.parse(&sentinel, None)?;
10167    if tree.root_node().has_error() {
10168        return None;
10169    }
10170    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
10171    let identifier = statement.named_child(0)?;
10172    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
10173        .then(|| node_text(identifier, &sentinel).to_string())
10174}
10175
10176pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
10177    match node.kind() {
10178        "identifier" | "field_identifier" => {
10179            let name = node_text(node, source).trim();
10180            (!name.is_empty()).then(|| name.to_string())
10181        }
10182        "abstract_array_declarator"
10183        | "abstract_function_declarator"
10184        | "abstract_parenthesized_declarator"
10185        | "abstract_pointer_declarator"
10186        | "abstract_reference_declarator" => None,
10187        "function_declarator" => node
10188            .child_by_field_name("declarator")
10189            .or_else(|| node.child_by_field_name("name"))
10190            .and_then(|child| extract_variable_name(child, source)),
10191        _ => node
10192            .child_by_field_name("declarator")
10193            .or_else(|| node.child_by_field_name("name"))
10194            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
10195            .and_then(|child| extract_variable_name(child, source)),
10196    }
10197}
10198
10199/// Whether `file` is proven to use plain-C source semantics.
10200///
10201/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
10202/// compilation dialect on their own, so only an exact `.c` source extension is
10203/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
10204/// identifiers.
10205///
10206/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
10207/// which extraction reads too (a `.c` file is extracted with C tag scope), so
10208/// the doctrine has exactly one definition.
10209pub fn is_c_source_file(file: &ProjectFile) -> bool {
10210    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
10211}
10212
10213/// Whether a reference written in `file` reads C++ source with C semantics.
10214///
10215/// [`is_c_source_file`] answers the half a path settles on its own. The other
10216/// half is a header, which has no dialect of its own: it is read as C exactly
10217/// when every workspace translation unit that provably compiles it compiles it
10218/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
10219///
10220/// This is the gate for anything that is really about the compilation
10221/// language of the code being read -- which reading of an included header's
10222/// declarations is in scope, whether `this` is an ordinary identifier. It is
10223/// NOT the gate for a question that is genuinely about a `.c` file on disk;
10224/// those keep calling [`is_c_source_file`].
10225pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
10226    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
10227}
10228
10229pub fn is_declarator_node(node: Node<'_>) -> bool {
10230    matches!(
10231        node.kind(),
10232        "identifier"
10233            | "field_identifier"
10234            | "pointer_declarator"
10235            | "reference_declarator"
10236            | "array_declarator"
10237            | "parenthesized_declarator"
10238            | "function_declarator"
10239    )
10240}
10241
10242#[derive(Clone, Default)]
10243pub struct OrphanedNamespaceTypeScopeIndex {
10244    scopes: Vec<OrphanedNamespaceTypeScope>,
10245}
10246
10247#[derive(Clone)]
10248struct OrphanedNamespaceTypeScope {
10249    body_end: usize,
10250    scope_end: usize,
10251    components: Vec<String>,
10252}
10253
10254impl OrphanedNamespaceTypeScopeIndex {
10255    /// Index the physical namespace interval that remains after tree-sitter
10256    /// prematurely closes an error-marked namespace at a recovered class body.
10257    /// The later unmatched `}` is the structured upper bound: declarations
10258    /// between the truncated body and that token remain in the namespace, while
10259    /// declarations after it do not.
10260    pub fn build(root: Node<'_>, source: &str) -> Self {
10261        let mut scopes = Vec::new();
10262        let mut stack = vec![root];
10263        while let Some(current) = stack.pop() {
10264            if current.kind() == "namespace_definition"
10265                && current.has_error()
10266                && let Some(body) = current.child_by_field_name("body")
10267                && current.end_byte() == body.end_byte()
10268                && let Some(name) = current.child_by_field_name("name")
10269            {
10270                let mut components =
10271                    enclosing_namespace_components(current, source).unwrap_or_default();
10272                if append_cpp_name_components(name, source, &mut components).is_some()
10273                    && !components.is_empty()
10274                {
10275                    let mut following = current.next_named_sibling();
10276                    while let Some(candidate) = following {
10277                        if direct_unmatched_closing_brace(candidate) {
10278                            scopes.push(OrphanedNamespaceTypeScope {
10279                                body_end: body.end_byte(),
10280                                scope_end: candidate.start_byte(),
10281                                components,
10282                            });
10283                            break;
10284                        }
10285                        following = candidate.next_named_sibling();
10286                    }
10287                }
10288            }
10289            if !current.has_error() {
10290                continue;
10291            }
10292            let mut cursor = current.walk();
10293            stack.extend(
10294                current
10295                    .named_children(&mut cursor)
10296                    .filter(|child| child.has_error()),
10297            );
10298        }
10299        Self { scopes }
10300    }
10301
10302    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
10303        self.scopes
10304            .iter()
10305            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
10306            .max_by_key(|scope| (scope.components.len(), scope.body_end))
10307            .map(|scope| (scope.body_end, scope.components.as_slice()))
10308    }
10309}
10310
10311#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10312pub enum RecoveredDeclaratorTypeContext {
10313    Declaration,
10314    FunctionDefinition,
10315    Parameter,
10316}
10317
10318/// Recognize a real type displaced into a qualified declarator by parser
10319/// recovery.
10320///
10321/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
10322/// type and `Result` were the scope of a qualified declarator with a missing
10323/// `::`. A template return such as `API Result<T> make()` uses a
10324/// `template_type` for the same recovered scope. The same recovery occurs for
10325/// macro-prefixed definitions, extern variables, and macro-decorated
10326/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
10327/// the macro). Keep this intentionally structural: the recovered scope must
10328/// have the grammar's missing separator, the qualified node must occupy the
10329/// declaration's declarator chain, a separate nonempty type must occupy the
10330/// normal type field, and the recovered name must unwrap to a real declarator
10331/// name.
10332pub fn recovered_macro_decorated_declarator_type(
10333    node: Node<'_>,
10334) -> Option<RecoveredDeclaratorTypeContext> {
10335    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
10336}
10337
10338/// Return the declaration/function `type` displaced by a macro-shaped
10339/// qualified declarator, together with the enclosing declaration context.
10340/// Callers use the macro scope only as structural admission evidence; the
10341/// returned node is the real type reference to resolve and record.
10342pub fn recovered_macro_decorated_type_node(
10343    node: Node<'_>,
10344) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10345    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
10346        return None;
10347    }
10348    let qualified = node.parent()?;
10349    if qualified.kind() != "qualified_identifier"
10350        || qualified.child_by_field_name("scope") != Some(node)
10351        || !(0..qualified.child_count())
10352            .filter_map(|index| qualified.child(index))
10353            .any(|child| child.kind() == "::" && child.is_missing())
10354    {
10355        return None;
10356    }
10357    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
10358        return None;
10359    }
10360
10361    let (declaration, context) = recovered_declarator_container(qualified)?;
10362    let type_node = declaration
10363        .child_by_field_name("type")
10364        .filter(|type_node| {
10365            *type_node != qualified
10366                && !type_node.is_missing()
10367                && type_node.start_byte() != type_node.end_byte()
10368        })?;
10369    Some((type_node, context))
10370}
10371
10372fn recovered_declarator_container(
10373    mut declarator: Node<'_>,
10374) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10375    loop {
10376        let parent = declarator.parent()?;
10377        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
10378            return Some((
10379                parent
10380                    .parent()
10381                    .filter(|declaration| declaration.kind() == "declaration")?,
10382                RecoveredDeclaratorTypeContext::Declaration,
10383            ));
10384        }
10385        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
10386            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
10387        }
10388        if parent.kind() == "function_definition"
10389            && has_field_child(parent, "declarator", declarator)
10390        {
10391            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
10392        }
10393        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
10394        // level down: the parameter's `type` field takes the macro token and
10395        // the real type `T` becomes the recovered scope of the declarator.
10396        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
10397        // candidate at all (#1830).
10398        if matches!(
10399            parent.kind(),
10400            "parameter_declaration" | "optional_parameter_declaration"
10401        ) && has_field_child(parent, "declarator", declarator)
10402        {
10403            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
10404        }
10405        if !matches!(
10406            parent.kind(),
10407            "array_declarator"
10408                | "function_declarator"
10409                | "parenthesized_declarator"
10410                | "pointer_declarator"
10411                | "pointer_type_declarator"
10412                | "reference_declarator"
10413        ) || !has_field_child(parent, "declarator", declarator)
10414        {
10415            return None;
10416        }
10417        declarator = parent;
10418    }
10419}
10420
10421fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
10422    let mut cursor = parent.walk();
10423    parent
10424        .children_by_field_name(field, &mut cursor)
10425        .any(|child| child == target)
10426}
10427
10428fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
10429    loop {
10430        if node.is_missing() || node.start_byte() == node.end_byte() {
10431            return false;
10432        }
10433        match node.kind() {
10434            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
10435                return true;
10436            }
10437            "array_declarator"
10438            | "function_declarator"
10439            | "parenthesized_declarator"
10440            | "pointer_declarator"
10441            | "pointer_type_declarator"
10442            | "reference_declarator" => {
10443                let Some(declarator) = node.child_by_field_name("declarator") else {
10444                    return false;
10445                };
10446                node = declarator;
10447            }
10448            _ => return false,
10449        }
10450    }
10451}
10452
10453/// Aggregate-owner proof for a structurally recognized designated initializer.
10454pub enum DesignatedInitializerOwner {
10455    Resolved(CodeUnit),
10456    Unresolved,
10457}
10458
10459/// Recognize a designated-initializer field and, when possible, resolve its
10460/// aggregate owner.
10461///
10462/// Covers both the grammar's ordinary `field_designator` shape and the exact
10463/// recovery used for `.field = value` after a preprocessor-split array
10464/// initializer. Nested aggregate levels are deliberately left unresolved unless
10465/// the single outer level is the containing array initializer: resolving those
10466/// would require following the enclosing field's declared type. `None` means the
10467/// node is not a designator at all; an unresolved designator remains classified so
10468/// callers cannot fall through to unrelated global/member heuristics.
10469pub fn designated_initializer_owner(
10470    visibility: &VisibilityIndex<'_>,
10471    file: &ProjectFile,
10472    source: &str,
10473    node: Node<'_>,
10474) -> Option<DesignatedInitializerOwner> {
10475    if let Some(designator) = node
10476        .parent()
10477        .filter(|parent| parent.kind() == "field_designator")
10478    {
10479        let pair = designator.parent()?;
10480        if pair.kind() != "initializer_pair"
10481            || pair.child_by_field_name("designator") != Some(designator)
10482        {
10483            return None;
10484        }
10485        let initializer = pair.parent()?;
10486        if initializer.kind() != "initializer_list" {
10487            return None;
10488        }
10489        return Some(classified_designated_owner(initializer_list_owner(
10490            visibility,
10491            file,
10492            source,
10493            initializer,
10494        )));
10495    }
10496
10497    let init_declarator = node.parent()?;
10498    if init_declarator.child_by_field_name("declarator") != Some(node)
10499        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
10500    {
10501        return None;
10502    }
10503    Some(classified_designated_owner(declaration_owner(
10504        visibility,
10505        file,
10506        source,
10507        init_declarator.parent()?,
10508    )))
10509}
10510
10511fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
10512    owner.map_or(
10513        DesignatedInitializerOwner::Unresolved,
10514        DesignatedInitializerOwner::Resolved,
10515    )
10516}
10517
10518fn initializer_list_owner(
10519    visibility: &VisibilityIndex<'_>,
10520    file: &ProjectFile,
10521    source: &str,
10522    initializer: Node<'_>,
10523) -> Option<CodeUnit> {
10524    let mut current = initializer;
10525    let mut outer_initializer_lists = 0usize;
10526    loop {
10527        let parent = current.parent()?;
10528        match parent.kind() {
10529            "initializer_pair" => return None,
10530            "initializer_list" => {
10531                outer_initializer_lists += 1;
10532                if outer_initializer_lists > 1 {
10533                    return None;
10534                }
10535                current = parent;
10536            }
10537            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
10538                let declaration = parent.parent()?;
10539                if outer_initializer_lists == 1
10540                    && !parent
10541                        .child_by_field_name("declarator")
10542                        .is_some_and(contains_array_declarator)
10543                {
10544                    return None;
10545                }
10546                return declaration_owner(visibility, file, source, declaration);
10547            }
10548            "compound_literal_expression"
10549                if parent.child_by_field_name("value") == Some(current)
10550                    && outer_initializer_lists == 0 =>
10551            {
10552                let type_node = parent.child_by_field_name("type")?;
10553                return resolve_designated_owner_type(visibility, file, source, type_node);
10554            }
10555            "ERROR" => current = parent,
10556            _ => return None,
10557        }
10558    }
10559}
10560
10561fn declaration_owner(
10562    visibility: &VisibilityIndex<'_>,
10563    file: &ProjectFile,
10564    source: &str,
10565    declaration: Node<'_>,
10566) -> Option<CodeUnit> {
10567    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
10568        return None;
10569    }
10570    let type_node = declaration
10571        .child_by_field_name("type")
10572        .or_else(|| first_type_child(declaration))?;
10573    resolve_designated_owner_type(visibility, file, source, type_node)
10574}
10575
10576fn resolve_designated_owner_type(
10577    visibility: &VisibilityIndex<'_>,
10578    file: &ProjectFile,
10579    source: &str,
10580    type_node: Node<'_>,
10581) -> Option<CodeUnit> {
10582    let type_name = normalize_type_text(node_text(type_node, source));
10583    visibility
10584        .resolve_type(file, &type_name)
10585        .filter(CodeUnit::is_class)
10586}
10587
10588fn contains_array_declarator(declarator: Node<'_>) -> bool {
10589    let mut stack = vec![declarator];
10590    while let Some(node) = stack.pop() {
10591        if node.kind() == "array_declarator" {
10592            return true;
10593        }
10594        if matches!(node.kind(), "initializer_list" | "compound_statement") {
10595            continue;
10596        }
10597        let mut cursor = node.walk();
10598        stack.extend(node.named_children(&mut cursor));
10599    }
10600    false
10601}
10602
10603pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
10604    let mut cursor = node.walk();
10605    node.named_children(&mut cursor).find(|child| {
10606        matches!(
10607            child.kind(),
10608            "type_identifier"
10609                | "primitive_type"
10610                | "qualified_identifier"
10611                | "scoped_type_identifier"
10612                | "struct_specifier"
10613                | "union_specifier"
10614                | "enum_specifier"
10615        )
10616    })
10617}
10618
10619pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
10620    visibility: &VisibilityIndex<'_>,
10621    file: &ProjectFile,
10622    source: &str,
10623    declarator: Node<'_>,
10624    type_text: Option<&str>,
10625    bindings: &LocalInferenceEngine<T>,
10626) -> bool {
10627    if !has_ancestor_kind(declarator, "compound_statement") {
10628        return false;
10629    }
10630    if declarator
10631        .child_by_field_name("declarator")
10632        .is_none_or(|declarator| declarator.kind() != "identifier")
10633    {
10634        return false;
10635    }
10636    if !type_text
10637        .and_then(|text| visibility.resolve_type(file, text))
10638        .is_some_and(|unit| unit.is_class())
10639    {
10640        return false;
10641    }
10642    declarator
10643        .child_by_field_name("parameters")
10644        .is_some_and(|parameters| {
10645            constructor_parameters_look_like_expressions(parameters, source, bindings)
10646        })
10647}
10648
10649fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
10650    parameters: Node<'_>,
10651    source: &str,
10652    bindings: &LocalInferenceEngine<T>,
10653) -> bool {
10654    let mut cursor = parameters.walk();
10655    parameters.named_children(&mut cursor).any(|parameter| {
10656        !matches!(
10657            parameter.kind(),
10658            "parameter_declaration" | "optional_parameter_declaration"
10659        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
10660    })
10661}
10662
10663fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
10664    parameter: Node<'_>,
10665    source: &str,
10666    bindings: &LocalInferenceEngine<T>,
10667) -> bool {
10668    let text = node_text(parameter, source).trim();
10669    if text
10670        .chars()
10671        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
10672        && bindings.is_shadowed(text)
10673    {
10674        return true;
10675    }
10676
10677    let Some(base) = parameter
10678        .child_by_field_name("type")
10679        .filter(|base| base.kind() == "type_identifier")
10680    else {
10681        return false;
10682    };
10683    let Some(subscript) = parameter
10684        .child_by_field_name("declarator")
10685        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
10686    else {
10687        return false;
10688    };
10689    subscript.child_by_field_name("size").is_some()
10690        && bindings.is_shadowed(node_text(base, source).trim())
10691}
10692
10693pub fn is_declaration_name(node: Node<'_>) -> bool {
10694    let Some(parent) = node.parent() else {
10695        return false;
10696    };
10697    if parent
10698        .child_by_field_name("name")
10699        .is_some_and(|name| same_node(name, node))
10700    {
10701        if matches!(
10702            parent.kind(),
10703            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10704        ) {
10705            return cpp_tag_specifier_declares_name(parent);
10706        }
10707        if matches!(
10708            parent.kind(),
10709            "namespace_definition"
10710                | "namespace_alias_definition"
10711                | "alias_declaration"
10712                | "enumerator"
10713        ) {
10714            return true;
10715        }
10716    }
10717
10718    let mut current = Some(parent);
10719    while let Some(ancestor) = current {
10720        let type_definition = ancestor.kind() == "type_definition";
10721        let mut declarator_cursor = ancestor.walk();
10722        if ancestor
10723            .children_by_field_name("declarator", &mut declarator_cursor)
10724            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
10725        {
10726            return true;
10727        }
10728        if matches!(
10729            ancestor.kind(),
10730            "declaration"
10731                | "field_declaration"
10732                | "parameter_declaration"
10733                | "optional_parameter_declaration"
10734                | "function_definition"
10735                | "type_definition"
10736                | "alias_declaration"
10737                | "class_specifier"
10738                | "struct_specifier"
10739                | "union_specifier"
10740                | "enum_specifier"
10741        ) {
10742            return false;
10743        }
10744        current = ancestor.parent();
10745    }
10746    false
10747}
10748
10749/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
10750/// declaration's declarator inside a malformed class body.
10751///
10752/// An export macro between `class` and the class name can make the containing
10753/// body parse as a function body. A source declaration such as
10754/// `friend class internal::Friend;` then retains this exact structure:
10755/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
10756/// The declarator is a type reference despite its field role.
10757pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
10758    if !matches!(
10759        node.kind(),
10760        "qualified_identifier" | "scoped_type_identifier"
10761    ) {
10762        return false;
10763    }
10764    let Some(declaration) = node
10765        .parent()
10766        .filter(|parent| parent.kind() == "declaration")
10767    else {
10768        return false;
10769    };
10770    if declaration.child_by_field_name("declarator") != Some(node)
10771        || !declaration
10772            .child_by_field_name("type")
10773            .is_some_and(|friend| {
10774                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
10775            })
10776    {
10777        return false;
10778    }
10779    let mut cursor = declaration.walk();
10780    let mut errors = declaration
10781        .named_children(&mut cursor)
10782        .filter(|child| child.kind() == "ERROR");
10783    let Some(error) = errors.next() else {
10784        return false;
10785    };
10786    errors.next().is_none()
10787        && error.named_child_count() == 1
10788        && error.named_child(0).is_some_and(|class| {
10789            class.kind() == "identifier" && node_text(class, source) == "class"
10790        })
10791}
10792
10793pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
10794    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
10795        return false;
10796    }
10797    if let Some(parent) = node.parent() {
10798        if parent.kind() == "call_expression"
10799            && parent.child_by_field_name("function") == Some(node)
10800        {
10801            return false;
10802        }
10803        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
10804            && parent.child_by_field_name("label") == Some(node)
10805        {
10806            return false;
10807        }
10808    }
10809    let mut current = node.parent();
10810    while let Some(ancestor) = current {
10811        if ancestor.kind().starts_with("preproc_") {
10812            return false;
10813        }
10814        if matches!(
10815            ancestor.kind(),
10816            "translation_unit" | "function_definition" | "compound_statement"
10817        ) {
10818            break;
10819        }
10820        current = ancestor.parent();
10821    }
10822    true
10823}
10824
10825fn recovered_c_reference_node(
10826    visibility: &VisibilityIndex<'_>,
10827    file: &ProjectFile,
10828    node: Node<'_>,
10829    source: &str,
10830) -> bool {
10831    if node.start_byte() >= node.end_byte()
10832        || node.is_error()
10833        || node.is_missing()
10834        || !matches!(
10835            node.kind(),
10836            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
10837        )
10838        || recovered_c_macro_binding_role(node)
10839        || recovered_c_label_role(node)
10840    {
10841        return false;
10842    }
10843
10844    let name = node_text(node, source);
10845    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
10846        return true;
10847    }
10848    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
10849        return true;
10850    }
10851    if is_declaration_name(node) {
10852        return false;
10853    }
10854    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
10855        return true;
10856    }
10857    recovered_c_reference_anchor(node)
10858}
10859
10860fn recovered_c_explicit_assignment_callee(
10861    visibility: &VisibilityIndex<'_>,
10862    file: &ProjectFile,
10863    node: Node<'_>,
10864    name: &str,
10865) -> bool {
10866    let mut current = node;
10867    let error = loop {
10868        let Some(parent) = current.parent() else {
10869            return false;
10870        };
10871        if parent.is_error() {
10872            break parent;
10873        }
10874        current = parent;
10875    };
10876    let mut cursor = error.walk();
10877    let explicit_recovery_precedes_callee = error
10878        .named_children(&mut cursor)
10879        .take_while(|child| child.start_byte() < node.start_byte())
10880        .any(|child| child.kind() == "explicit_function_specifier");
10881    if !explicit_recovery_precedes_callee {
10882        return false;
10883    }
10884    visibility
10885        .cpp
10886        .declarations(file)
10887        .iter()
10888        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
10889        .any(|candidate| candidate.identifier() == name && candidate.is_function())
10890}
10891
10892fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
10893    while let Some(parent) = node.parent() {
10894        if matches!(
10895            parent.kind(),
10896            "preproc_def" | "preproc_function_def" | "preproc_params"
10897        ) {
10898            return true;
10899        }
10900        if parent.is_error()
10901            || matches!(
10902                parent.kind(),
10903                "translation_unit" | "function_definition" | "compound_statement"
10904            )
10905        {
10906            return false;
10907        }
10908        node = parent;
10909    }
10910    false
10911}
10912
10913fn recovered_c_label_role(node: Node<'_>) -> bool {
10914    node.parent().is_some_and(|parent| {
10915        matches!(parent.kind(), "labeled_statement" | "goto_statement")
10916            && parent.child_by_field_name("label") == Some(node)
10917    })
10918}
10919
10920fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
10921    while let Some(parent) = node.parent() {
10922        if parent.is_error() {
10923            return false;
10924        }
10925        if parent.kind().ends_with("_expression")
10926            || matches!(
10927                parent.kind(),
10928                "argument_list"
10929                    | "return_statement"
10930                    | "expression_statement"
10931                    | "case_statement"
10932                    | "initializer_list"
10933                    | "init_declarator"
10934                    | "array_declarator"
10935                    | "field_designator"
10936                    | "enumerator"
10937            )
10938        {
10939            return true;
10940        }
10941        if matches!(
10942            parent.kind(),
10943            "translation_unit"
10944                | "function_definition"
10945                | "compound_statement"
10946                | "declaration"
10947                | "field_declaration"
10948                | "parameter_declaration"
10949        ) {
10950            return false;
10951        }
10952        node = parent;
10953    }
10954    false
10955}
10956
10957/// Whether a parameter declaration belongs to the callable scope whose body can
10958/// contain references to it.
10959///
10960/// Error recovery can wrap a macro-decorated class body in a synthetic outer
10961/// `function_definition`. Merely finding any callable ancestor would then leak
10962/// parameters from member prototypes into later member bodies. Require the
10963/// parameter to be inside that definition's own declarator instead.
10964pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
10965    let mut current = parameter.parent();
10966    while let Some(ancestor) = current {
10967        if ancestor.kind() == "lambda_expression" {
10968            return ancestor
10969                .child_by_field_name("declarator")
10970                .is_some_and(|declarator| {
10971                    declarator.start_byte() <= parameter.start_byte()
10972                        && parameter.end_byte() <= declarator.end_byte()
10973                });
10974        }
10975        if ancestor.kind() == "function_definition" {
10976            return ancestor
10977                .child_by_field_name("declarator")
10978                .is_some_and(|declarator| {
10979                    declarator.start_byte() <= parameter.start_byte()
10980                        && parameter.end_byte() <= declarator.end_byte()
10981                });
10982        }
10983        current = ancestor.parent();
10984    }
10985    false
10986}
10987
10988pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
10989    let mut current = node.parent();
10990    while let Some(ancestor) = current {
10991        if matches!(
10992            ancestor.kind(),
10993            "parameter_declaration" | "optional_parameter_declaration"
10994        ) {
10995            return ancestor
10996                .child_by_field_name("type")
10997                .is_some_and(|type_node| {
10998                    type_node.start_byte() <= node.start_byte()
10999                        && node.end_byte() <= type_node.end_byte()
11000                });
11001        }
11002        if matches!(
11003            ancestor.kind(),
11004            "function_definition" | "lambda_expression" | "compound_statement"
11005        ) {
11006            return false;
11007        }
11008        current = ancestor.parent();
11009    }
11010    false
11011}
11012
11013fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
11014    if specifier.child_by_field_name("body").is_some() {
11015        return true;
11016    }
11017    let mut current = specifier.parent();
11018    while let Some(ancestor) = current {
11019        match ancestor.kind() {
11020            "type_descriptor"
11021            | "parameter_declaration"
11022            | "optional_parameter_declaration"
11023            | "template_argument_list"
11024            | "cast_expression" => return false,
11025            "declaration" | "field_declaration" => {
11026                let mut cursor = ancestor.walk();
11027                return ancestor
11028                    .children_by_field_name("declarator", &mut cursor)
11029                    .next()
11030                    .is_none();
11031            }
11032            "translation_unit" => return true,
11033            _ => current = ancestor.parent(),
11034        }
11035    }
11036    false
11037}
11038
11039pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
11040    match node.kind() {
11041        "identifier"
11042        | "field_identifier"
11043        | "qualified_identifier"
11044        | "scoped_identifier"
11045        | "operator_name"
11046        | "destructor_name"
11047        | "literal_operator_name" => Some(node),
11048        "reference_declarator" | "parenthesized_declarator" => {
11049            node.named_child(0).and_then(declarator_name_node)
11050        }
11051        _ => node
11052            .child_by_field_name("declarator")
11053            .or_else(|| node.child_by_field_name("name"))
11054            .or_else(|| node.child_by_field_name("field"))
11055            .and_then(declarator_name_node),
11056    }
11057}
11058
11059fn declarator_name_path_contains(
11060    declarator: Node<'_>,
11061    candidate: Node<'_>,
11062    allow_type_identifier: bool,
11063) -> bool {
11064    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
11065        return false;
11066    };
11067    let mut current = Some(declarator);
11068    while let Some(node) = current {
11069        if same_node(node, candidate) {
11070            return true;
11071        }
11072        if same_node(node, name) {
11073            return false;
11074        }
11075        current = node
11076            .child_by_field_name("declarator")
11077            .or_else(|| node.child_by_field_name("name"))
11078            .or_else(|| node.child_by_field_name("field"));
11079    }
11080    false
11081}
11082
11083fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
11084    match node.kind() {
11085        "identifier"
11086        | "field_identifier"
11087        | "operator_name"
11088        | "destructor_name"
11089        | "literal_operator_name" => Some(node),
11090        "type_identifier" if allow_type_identifier => Some(node),
11091        _ => node
11092            .child_by_field_name("declarator")
11093            .or_else(|| node.child_by_field_name("name"))
11094            .or_else(|| node.child_by_field_name("field"))
11095            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
11096    }
11097}
11098
11099/// True when `node` is a component of a larger structured type node whose outer
11100/// range is the single reference surfaced to callers.
11101pub fn is_nested_type_node(node: Node<'_>) -> bool {
11102    node.parent().is_some_and(|parent| {
11103        matches!(
11104            parent.kind(),
11105            "qualified_identifier" | "scoped_type_identifier" | "template_type"
11106        )
11107    })
11108}
11109
11110pub struct OutOfLineMemberDefinitionOwners<'tree> {
11111    pub owners: Vec<(Node<'tree>, CodeUnit)>,
11112    innermost: Option<(Node<'tree>, CodeUnit)>,
11113}
11114
11115impl OutOfLineMemberDefinitionOwners<'_> {
11116    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
11117        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
11118    }
11119}
11120
11121pub struct QualifiedOwnerComponents<'tree> {
11122    pub nodes: Vec<Node<'tree>>,
11123    pub names: Vec<String>,
11124    pub global: bool,
11125}
11126
11127/// True when each structured qualifier on the callable-name path has a real
11128/// `::` token. A macro-prefixed return type can make tree-sitter insert a
11129/// zero-width missing separator and parse `TYPE Result<T> method()` as the
11130/// false qualified declarator `Result<T>::method`.
11131pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
11132    let mut stack = vec![node];
11133    let mut found_separator = false;
11134    while let Some(current) = stack.pop() {
11135        if !matches!(
11136            current.kind(),
11137            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11138        ) {
11139            continue;
11140        }
11141        let mut current_has_separator = false;
11142        for index in 0..current.child_count() {
11143            let Some(child) = current.child(index) else {
11144                continue;
11145            };
11146            if child.kind() == "::" {
11147                if child.is_missing() {
11148                    return false;
11149                }
11150                current_has_separator = true;
11151                found_separator = true;
11152            }
11153        }
11154        if !current_has_separator {
11155            return false;
11156        }
11157        for field in ["scope", "name"] {
11158            if let Some(child) = current.child_by_field_name(field)
11159                && matches!(
11160                    child.kind(),
11161                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11162                )
11163            {
11164                stack.push(child);
11165            }
11166        }
11167    }
11168    found_separator
11169}
11170
11171pub fn qualified_owner_components<'tree>(
11172    node: Node<'tree>,
11173    source: &str,
11174) -> Option<QualifiedOwnerComponents<'tree>> {
11175    if !qualified_name_has_concrete_scope_separators(node) {
11176        return None;
11177    }
11178    let mut nodes = cpp_name_component_nodes(node)?;
11179    nodes.pop()?;
11180    if nodes.is_empty() {
11181        return None;
11182    }
11183    let names = nodes
11184        .iter()
11185        .map(|component| node_text(*component, source).to_string())
11186        .collect();
11187    Some(QualifiedOwnerComponents {
11188        nodes,
11189        names,
11190        global: is_globally_qualified_cpp_name(node),
11191    })
11192}
11193
11194/// Return the terminal type-name occurrence in an out-of-line destructor
11195/// declarator such as `endpoint::~endpoint`.  Unlike an ordinary terminal
11196/// method name, this identifier is a second reference to the owner type.
11197///
11198/// Every extra qualifier nests another `qualified_identifier` in the `name`
11199/// field, so `zmq::pair_t::~pair_t` reaches the destructor only two levels
11200/// down. Reading one level dropped the terminal occurrence for every
11201/// file-scope out-of-line member libzmq writes (#1831).
11202pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
11203    if node.kind() != "qualified_identifier" {
11204        return None;
11205    }
11206    let mut qualified = node;
11207    let destructor = loop {
11208        let name = qualified.child_by_field_name("name")?;
11209        match name.kind() {
11210            "qualified_identifier" => qualified = name,
11211            "destructor_name" => break name,
11212            _ => return None,
11213        }
11214    };
11215    (0..destructor.named_child_count())
11216        .filter_map(|index| destructor.named_child(index))
11217        .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
11218}
11219
11220pub fn out_of_line_member_definition_owner<'tree>(
11221    analyzer: &CppGraphSource<'_>,
11222    visibility: &VisibilityIndex<'_>,
11223    file: &ProjectFile,
11224    source: &str,
11225    node: Node<'tree>,
11226) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
11227    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
11228        || !has_ancestor_kind(node, "function_definition")
11229        || !is_function_declarator_name_root(node)
11230    {
11231        return None;
11232    }
11233    let qualified = qualified_owner_components(node, source)?;
11234    let lexical_scope = enclosing_namespace_components(node, source)?;
11235    let mut owners = Vec::new();
11236    let mut innermost = None;
11237
11238    for component_count in 1..=qualified.names.len() {
11239        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
11240            .resolve_type_components_lexically(
11241                analyzer,
11242                file,
11243                &qualified.names[..component_count],
11244                qualified.global,
11245                &lexical_scope,
11246            )
11247            && !owners
11248                .iter()
11249                .any(|(_, existing)| same_visible_symbol(existing, &unit))
11250        {
11251            if component_count == qualified.names.len() {
11252                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
11253            }
11254            owners.push((qualified.nodes[component_count - 1], unit));
11255        }
11256    }
11257
11258    // The C++ analyzer has already reconciled an indexed out-of-line callable
11259    // against the include-visible class table. Consult that canonical owner
11260    // chain only when ordinary lexical lookup could not recover the innermost
11261    // owner.  A one-segment qualifier is safe here only when the enclosing
11262    // indexed callable has an authoritative class owner and the parser's
11263    // namespace path is a (possibly sparse) subsequence of that owner path.
11264    // The latter is what lets macro-wrapped namespace sentinels recover a
11265    // missing `time_internal`/`cord_internal` component without guessing an
11266    // unrelated short name.
11267    if innermost.is_none() {
11268        let indexed_owner_components = visibility
11269            .indexed_enclosing_owner_scope(analyzer, file, node)
11270            .or_else(|| {
11271                // Retain the legacy rendered-name fallback for the existing
11272                // multi-segment path when an enclosing owner chain is not
11273                // available (for example, cache-loaded units without parent
11274                // links).  One-segment recovery must stay canonical-only.
11275                if qualified.names.len() <= 1 {
11276                    return None;
11277                }
11278                let range = Range {
11279                    start_byte: node.start_byte(),
11280                    end_byte: node.end_byte(),
11281                    start_line: node.start_position().row,
11282                    end_line: node.end_position().row,
11283                };
11284                let start = analyzer.enclosing_code_unit(file, &range)?;
11285                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11286                    brokk_bifrost_core::analyzer::Language::Cpp,
11287                    &cpp_name_for(&start),
11288                );
11289                components.pop();
11290                Some(components)
11291            });
11292        if let Some(indexed_owner_components) = indexed_owner_components
11293            && indexed_owner_components.len() > qualified.names.len()
11294            && indexed_owner_components.ends_with(&qualified.names)
11295            && indexed_namespace_path_is_recoverable(
11296                &lexical_scope,
11297                &indexed_owner_components,
11298                qualified.names.len(),
11299            )
11300            // A globally-qualified one-segment owner is an explicit request
11301            // for the top-level binding; do not reinterpret it as a missing
11302            // namespace component.  Existing multi-segment global lookups
11303            // retain their historical indexed recovery.
11304            && (qualified.names.len() > 1 || !qualified.global)
11305        {
11306            let namespace_count = indexed_owner_components.len() - qualified.names.len();
11307            for component_count in 1..=qualified.names.len() {
11308                let expected = &indexed_owner_components[..namespace_count + component_count];
11309                let owner_node = qualified.nodes[component_count - 1];
11310                for owner in visibility
11311                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
11312                    .filter(|candidate| candidate.is_class())
11313                    .filter(|candidate| {
11314                        canonical_cpp_scope_components(candidate) == expected
11315                            && visibility.external_type_candidate_visible_in_context(
11316                                analyzer, file, candidate, node,
11317                            )
11318                    })
11319                {
11320                    if component_count == qualified.names.len() && innermost.is_none() {
11321                        innermost = Some((owner_node, owner.clone()));
11322                    }
11323                    if !owners
11324                        .iter()
11325                        .any(|(_, existing)| same_symbol(existing, owner))
11326                    {
11327                        owners.push((owner_node, owner.clone()));
11328                    }
11329                }
11330            }
11331        }
11332    }
11333    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
11334}
11335
11336fn is_function_declarator_name_root(node: Node<'_>) -> bool {
11337    let mut current = node;
11338    while let Some(parent) = current.parent() {
11339        if parent.kind() == "function_declarator" {
11340            return parent.child_by_field_name("declarator") == Some(current);
11341        }
11342        if matches!(
11343            parent.kind(),
11344            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
11345        ) && parent.child_by_field_name("declarator") == Some(current)
11346        {
11347            current = parent;
11348            continue;
11349        }
11350        return false;
11351    }
11352    false
11353}
11354
11355pub fn append_cpp_name_components(
11356    node: Node<'_>,
11357    source: &str,
11358    out: &mut Vec<String>,
11359) -> Option<()> {
11360    out.extend(
11361        cpp_name_component_nodes(node)?
11362            .into_iter()
11363            .map(|component| node_text(component, source).to_string()),
11364    );
11365    Some(())
11366}
11367
11368pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11369    let mut components = Vec::new();
11370    append_cpp_name_components(node, source, &mut components)?;
11371    Some(components)
11372}
11373
11374/// The base scopes named by member using-declarations for `member` in one
11375/// class source range.
11376///
11377/// The grammar supplies the qualified identifier and each component. Keep
11378/// this interpretation shared between forward overload lookup and inverse
11379/// owner routing rather than reparsing a rendered `Base::member` string at
11380/// either call site.
11381pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
11382    let mut parser = Parser::new();
11383    if parser
11384        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11385        .is_err()
11386    {
11387        return Vec::new();
11388    }
11389    let Some(tree) = parser.parse(source, None) else {
11390        return Vec::new();
11391    };
11392    let mut scopes = Vec::new();
11393    let mut pending = vec![tree.root_node()];
11394    while let Some(node) = pending.pop() {
11395        if node.kind() == "using_declaration" {
11396            let Some(imported) = node.named_child(0) else {
11397                continue;
11398            };
11399            let Some(mut components) = cpp_type_name_components(imported, source) else {
11400                continue;
11401            };
11402            if components.pop().as_deref() == Some(member) && !components.is_empty() {
11403                scopes.push(components.join("::"));
11404            }
11405            continue;
11406        }
11407        for index in (0..node.named_child_count()).rev() {
11408            if let Some(child) = node.named_child(index) {
11409                pending.push(child);
11410            }
11411        }
11412    }
11413    scopes
11414}
11415
11416/// Whether a structured using-declaration scope can name `qualified` as an
11417/// ancestor class. The boundary check prevents `Base` from matching
11418/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
11419pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
11420    qualified == scope
11421        || qualified
11422            .strip_suffix(scope)
11423            .is_some_and(|prefix| prefix.ends_with("::"))
11424}
11425
11426/// Whether `node` is the direct structured type payload of a template
11427/// argument. This role remains meaningful even when a surrounding expression
11428/// is below tree-sitter recovery, because both the `template_argument_list`
11429/// and the `type_descriptor` retain their named fields.
11430pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
11431    let Some(type_descriptor) = node.parent() else {
11432        return false;
11433    };
11434    if type_descriptor.kind() != "type_descriptor"
11435        || type_descriptor.child_by_field_name("type") != Some(node)
11436    {
11437        return false;
11438    }
11439    let Some(arguments) = type_descriptor.parent() else {
11440        return false;
11441    };
11442    if arguments.kind() != "template_argument_list" {
11443        return false;
11444    }
11445    arguments.parent().is_some_and(|parent| {
11446        matches!(parent.kind(), "template_type" | "template_function")
11447            && parent.child_by_field_name("arguments") == Some(arguments)
11448    })
11449}
11450
11451pub fn cpp_template_reference_arguments(
11452    mut node: Node<'_>,
11453    source: &str,
11454) -> Option<Vec<CppTemplateExpression>> {
11455    loop {
11456        match node.kind() {
11457            "template_type" | "template_function" => {
11458                let arguments = node.child_by_field_name("arguments")?;
11459                let mut cursor = arguments.walk();
11460                return Some(
11461                    arguments
11462                        .named_children(&mut cursor)
11463                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
11464                        .map(|argument| CppTemplateExpression {
11465                            text: normalize_cpp_whitespace(node_text(argument, source)),
11466                            // One template term from a resolver query; see `ParentIndex::unindexed`.
11467                            term: cpp_template_term(
11468                                argument,
11469                                source,
11470                                &[],
11471                                &ParentIndex::unindexed(),
11472                            ),
11473                        })
11474                        .collect(),
11475                );
11476            }
11477            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
11478                node = node
11479                    .child_by_field_name("name")
11480                    .or_else(|| node.child_by_field_name("type"))?;
11481            }
11482            _ => return None,
11483        }
11484    }
11485}
11486
11487fn cpp_reconcile_primary_template_parameters(
11488    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
11489    preferred: &CodeUnit,
11490) -> Option<Vec<CppTemplateParameterMetadata>> {
11491    let canonical = candidates
11492        .iter()
11493        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
11494    let mut merged = canonical
11495        .parameters
11496        .iter()
11497        .map(|parameter| CppTemplateParameterMetadata {
11498            name: parameter.name.clone(),
11499            kind: parameter.kind,
11500            variadic: parameter.variadic,
11501            default: None,
11502        })
11503        .collect::<Vec<_>>();
11504
11505    for (_, metadata) in candidates {
11506        if metadata.parameters.len() != merged.len() {
11507            return None;
11508        }
11509        let rename_bindings = metadata
11510            .parameters
11511            .iter()
11512            .zip(&merged)
11513            .map(|(parameter, canonical)| {
11514                (
11515                    parameter.name.clone(),
11516                    CppTemplateTerm::Parameter(canonical.name.clone()),
11517                )
11518            })
11519            .collect::<HashMap<_, _>>();
11520        for ((parameter, canonical), merged_parameter) in metadata
11521            .parameters
11522            .iter()
11523            .zip(&canonical.parameters)
11524            .zip(&mut merged)
11525        {
11526            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
11527                return None;
11528            }
11529            let Some(default) = &parameter.default else {
11530                continue;
11531            };
11532            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
11533            if let Some(existing) = &merged_parameter.default {
11534                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
11535                    return None;
11536                }
11537            } else {
11538                merged_parameter.default = Some(CppTemplateExpression {
11539                    text: default.text.clone(),
11540                    term: normalized_term,
11541                });
11542            }
11543        }
11544    }
11545    Some(merged)
11546}
11547
11548pub fn cpp_bind_template_arguments(
11549    parameters: &[CppTemplateParameterMetadata],
11550    explicit_arguments: &[CppTemplateExpression],
11551) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
11552    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
11553    if variadic_index.is_some_and(|index| {
11554        index + 1 != parameters.len()
11555            || parameters[index + 1..]
11556                .iter()
11557                .any(|parameter| parameter.variadic)
11558    }) {
11559        return None;
11560    }
11561    let fixed_count = variadic_index.unwrap_or(parameters.len());
11562    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
11563        return None;
11564    }
11565    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
11566    let mut expanded = explicit_arguments[..explicit_fixed_count]
11567        .iter()
11568        .map(cpp_clone_template_expression_iterative)
11569        .collect::<Vec<_>>();
11570    let mut bindings = HashMap::default();
11571    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
11572        bindings.insert(
11573            parameter.name.clone(),
11574            cpp_clone_template_term_iterative(&argument.term),
11575        );
11576    }
11577    for parameter in &parameters[explicit_fixed_count..fixed_count] {
11578        let default = parameter.default.as_ref()?;
11579        let term = cpp_substitute_template_term(&default.term, &bindings)?;
11580        bindings.insert(parameter.name.clone(), term.clone());
11581        expanded.push(CppTemplateExpression {
11582            text: default.text.clone(),
11583            term,
11584        });
11585    }
11586    if let Some(index) = variadic_index {
11587        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
11588        expanded.extend(
11589            packed_arguments
11590                .iter()
11591                .map(cpp_clone_template_expression_iterative),
11592        );
11593        bindings.insert(
11594            parameters[index].name.clone(),
11595            CppTemplateTerm::Node {
11596                kind: "parameter_pack".to_string(),
11597                children: packed_arguments
11598                    .iter()
11599                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
11600                    .collect(),
11601            },
11602        );
11603    }
11604    Some((expanded, bindings))
11605}
11606
11607fn cpp_specialization_matches(
11608    metadata: &CppTemplateMetadata,
11609    arguments: &[CppTemplateExpression],
11610) -> bool {
11611    if metadata.specialization_arguments.len() != arguments.len() {
11612        return false;
11613    }
11614    let parameter_names = metadata
11615        .parameters
11616        .iter()
11617        .map(|parameter| parameter.name.as_str())
11618        .collect::<HashSet<_>>();
11619    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11620    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
11621        if !cpp_unify_template_term(
11622            &pattern.term,
11623            &argument.term,
11624            &parameter_names,
11625            &mut bindings,
11626        ) {
11627            return false;
11628        }
11629    }
11630    true
11631}
11632
11633fn cpp_specialization_more_specialized(
11634    candidate: &CppTemplateMetadata,
11635    other: &CppTemplateMetadata,
11636) -> bool {
11637    cpp_specialization_pattern_accepts(other, candidate)
11638        && !cpp_specialization_pattern_accepts(candidate, other)
11639}
11640
11641fn cpp_specialization_pattern_accepts(
11642    broader: &CppTemplateMetadata,
11643    narrower: &CppTemplateMetadata,
11644) -> bool {
11645    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
11646        return false;
11647    }
11648    let parameter_names = broader
11649        .parameters
11650        .iter()
11651        .map(|parameter| parameter.name.as_str())
11652        .collect::<HashSet<_>>();
11653    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11654    broader
11655        .specialization_arguments
11656        .iter()
11657        .zip(&narrower.specialization_arguments)
11658        .all(|(pattern, argument)| {
11659            cpp_unify_template_term(
11660                &pattern.term,
11661                &argument.term,
11662                &parameter_names,
11663                &mut bindings,
11664            )
11665        })
11666}
11667
11668pub fn cpp_substitute_template_term(
11669    term: &CppTemplateTerm,
11670    bindings: &HashMap<String, CppTemplateTerm>,
11671) -> Option<CppTemplateTerm> {
11672    enum Work<'a> {
11673        Visit(&'a CppTemplateTerm),
11674        Build { kind: String, child_count: usize },
11675    }
11676
11677    let mut work = vec![Work::Visit(term)];
11678    let mut substituted = Vec::new();
11679    while let Some(next) = work.pop() {
11680        match next {
11681            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11682                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
11683            }
11684            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11685                substituted.push(CppTemplateTerm::Atom {
11686                    kind: kind.clone(),
11687                    text: text.clone(),
11688                });
11689            }
11690            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11691                work.push(Work::Build {
11692                    kind: kind.clone(),
11693                    child_count: children.len(),
11694                });
11695                work.extend(children.iter().rev().map(Work::Visit));
11696            }
11697            Work::Build { kind, child_count } => {
11698                let children = substituted.split_off(substituted.len() - child_count);
11699                substituted.push(CppTemplateTerm::Node { kind, children });
11700            }
11701        }
11702    }
11703    substituted.pop()
11704}
11705
11706pub fn cpp_substitute_template_arguments(
11707    arguments: &[CppTemplateExpression],
11708    bindings: &HashMap<String, CppTemplateTerm>,
11709) -> Option<Vec<CppTemplateExpression>> {
11710    let mut substituted = Vec::new();
11711    for argument in arguments {
11712        let CppTemplateTerm::Node { kind, children } = &argument.term else {
11713            substituted.push(CppTemplateExpression {
11714                text: argument.text.clone(),
11715                term: cpp_substitute_template_term(&argument.term, bindings)?,
11716            });
11717            continue;
11718        };
11719        if kind != "parameter_pack_expansion" {
11720            substituted.push(CppTemplateExpression {
11721                text: argument.text.clone(),
11722                term: cpp_substitute_template_term(&argument.term, bindings)?,
11723            });
11724            continue;
11725        }
11726        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
11727            return None;
11728        };
11729        if ellipsis != "..." {
11730            return None;
11731        }
11732
11733        let mut pack_names = Vec::new();
11734        let mut work = vec![pattern];
11735        while let Some(term) = work.pop() {
11736            match term {
11737                CppTemplateTerm::Parameter(name)
11738                    if matches!(
11739                        bindings.get(name),
11740                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
11741                    ) =>
11742                {
11743                    if !pack_names.contains(name) {
11744                        pack_names.push(name.clone());
11745                    }
11746                }
11747                CppTemplateTerm::Node { children, .. } => work.extend(children),
11748                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
11749            }
11750        }
11751        let first_pack = pack_names.first()?;
11752        let CppTemplateTerm::Node {
11753            children: first_elements,
11754            ..
11755        } = bindings.get(first_pack)?
11756        else {
11757            return None;
11758        };
11759        let pack_len = first_elements.len();
11760        for pack_name in &pack_names {
11761            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11762                return None;
11763            };
11764            if children.len() != pack_len {
11765                return None;
11766            }
11767        }
11768        for index in 0..pack_len {
11769            let mut element_bindings = bindings.clone();
11770            for pack_name in &pack_names {
11771                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11772                    return None;
11773                };
11774                element_bindings.insert(
11775                    pack_name.clone(),
11776                    cpp_clone_template_term_iterative(&children[index]),
11777                );
11778            }
11779            substituted.push(CppTemplateExpression {
11780                text: argument.text.clone(),
11781                term: cpp_substitute_template_term(pattern, &element_bindings)?,
11782            });
11783        }
11784    }
11785    Some(substituted)
11786}
11787
11788fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
11789    enum Work<'a> {
11790        Visit(&'a CppTemplateTerm),
11791        Build { kind: String, child_count: usize },
11792    }
11793
11794    let mut work = vec![Work::Visit(term)];
11795    let mut cloned = Vec::new();
11796    while let Some(next) = work.pop() {
11797        match next {
11798            Work::Visit(CppTemplateTerm::Parameter(name)) => {
11799                cloned.push(CppTemplateTerm::Parameter(name.clone()));
11800            }
11801            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11802                cloned.push(CppTemplateTerm::Atom {
11803                    kind: kind.clone(),
11804                    text: text.clone(),
11805                });
11806            }
11807            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11808                work.push(Work::Build {
11809                    kind: kind.clone(),
11810                    child_count: children.len(),
11811                });
11812                work.extend(children.iter().rev().map(Work::Visit));
11813            }
11814            Work::Build { kind, child_count } => {
11815                let children = cloned.split_off(cloned.len() - child_count);
11816                cloned.push(CppTemplateTerm::Node { kind, children });
11817            }
11818        }
11819    }
11820    cloned
11821        .pop()
11822        .expect("template term traversal emits one root")
11823}
11824
11825fn cpp_clone_template_expression_iterative(
11826    expression: &CppTemplateExpression,
11827) -> CppTemplateExpression {
11828    CppTemplateExpression {
11829        text: expression.text.clone(),
11830        term: cpp_clone_template_term_iterative(&expression.term),
11831    }
11832}
11833
11834pub fn cpp_unify_template_term(
11835    pattern: &CppTemplateTerm,
11836    argument: &CppTemplateTerm,
11837    parameters: &HashSet<&str>,
11838    bindings: &mut HashMap<String, CppTemplateTerm>,
11839) -> bool {
11840    let mut work = vec![(pattern, argument)];
11841    while let Some((pattern, argument)) = work.pop() {
11842        match pattern {
11843            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
11844                if let Some(bound) = bindings.get(name) {
11845                    if !cpp_template_terms_equal(bound, argument) {
11846                        return false;
11847                    }
11848                } else {
11849                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
11850                }
11851            }
11852            CppTemplateTerm::Atom {
11853                kind: pattern_kind,
11854                text: pattern_text,
11855            } => {
11856                if !matches!(
11857                    argument,
11858                    CppTemplateTerm::Atom { kind, text }
11859                        if kind == pattern_kind && text == pattern_text
11860                ) {
11861                    return false;
11862                }
11863            }
11864            CppTemplateTerm::Node {
11865                kind: pattern_kind,
11866                children: pattern_children,
11867            } => {
11868                let CppTemplateTerm::Node { kind, children } = argument else {
11869                    return false;
11870                };
11871                if kind != pattern_kind || children.len() != pattern_children.len() {
11872                    return false;
11873                }
11874                work.extend(pattern_children.iter().zip(children).rev());
11875            }
11876            CppTemplateTerm::Parameter(_) => return false,
11877        }
11878    }
11879    true
11880}
11881
11882fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
11883    let mut work = vec![(left, right)];
11884    while let Some((left, right)) = work.pop() {
11885        match (left, right) {
11886            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
11887                if left != right {
11888                    return false;
11889                }
11890            }
11891            (
11892                CppTemplateTerm::Atom {
11893                    kind: left_kind,
11894                    text: left_text,
11895                },
11896                CppTemplateTerm::Atom {
11897                    kind: right_kind,
11898                    text: right_text,
11899                },
11900            ) => {
11901                if left_kind != right_kind || left_text != right_text {
11902                    return false;
11903                }
11904            }
11905            (
11906                CppTemplateTerm::Node {
11907                    kind: left_kind,
11908                    children: left_children,
11909                },
11910                CppTemplateTerm::Node {
11911                    kind: right_kind,
11912                    children: right_children,
11913                },
11914            ) => {
11915                if left_kind != right_kind || left_children.len() != right_children.len() {
11916                    return false;
11917                }
11918                work.extend(left_children.iter().zip(right_children).rev());
11919            }
11920            _ => return false,
11921        }
11922    }
11923    true
11924}
11925
11926pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
11927    let mut components = Vec::new();
11928    let mut stack = vec![node];
11929    while let Some(current) = stack.pop() {
11930        match current.kind() {
11931            "identifier"
11932            | "field_identifier"
11933            | "namespace_identifier"
11934            | "type_identifier"
11935            | "operator_name"
11936            | "destructor_name" => components.push(current),
11937            "template_type" | "template_function" => {
11938                stack.push(current.child_by_field_name("name")?);
11939            }
11940            "dependent_name" => stack.push(current.named_child(0)?),
11941            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11942                stack.push(current.child_by_field_name("name")?);
11943                if let Some(scope) = current.child_by_field_name("scope") {
11944                    stack.push(scope);
11945                }
11946            }
11947            "nested_namespace_specifier" => {
11948                for index in (0..current.named_child_count()).rev() {
11949                    stack.push(current.named_child(index)?);
11950                }
11951            }
11952            _ => return None,
11953        }
11954    }
11955    Some(components)
11956}
11957
11958pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
11959    node.child_by_field_name("scope").is_none()
11960        && node.child(0).is_some_and(|child| child.kind() == "::")
11961}
11962
11963fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11964    let mut namespaces = Vec::new();
11965    let mut current = node.parent();
11966    while let Some(parent) = current {
11967        if parent.kind() == "namespace_definition"
11968            && let Some(name) = parent.child_by_field_name("name")
11969        {
11970            let mut components = Vec::new();
11971            append_cpp_name_components(name, source, &mut components)?;
11972            namespaces.push(components);
11973        }
11974        current = parent.parent();
11975    }
11976    namespaces.reverse();
11977    Some(namespaces.into_iter().flatten().collect())
11978}
11979
11980/// Whether a parser-derived namespace path can be reconciled with an indexed
11981/// owner scope without inventing an unrelated short-name binding.
11982///
11983/// Macro namespace sentinels can make tree-sitter omit one or more namespace
11984/// definitions from the ancestor chain. Preserve the order of every namespace
11985/// that did survive parsing, but allow indexed components between them. An
11986/// empty path is accepted only when the declarator itself supplies a nested
11987/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
11988/// owner chain, that suffix is structural evidence that a namespace was lost.
11989/// A one-segment owner at the translation-unit root remains insufficient.
11990fn indexed_namespace_path_is_recoverable(
11991    lexical_scope: &[String],
11992    indexed_owner_scope: &[String],
11993    explicit_owner_component_count: usize,
11994) -> bool {
11995    if lexical_scope.is_empty() {
11996        return explicit_owner_component_count > 1;
11997    }
11998    if lexical_scope.len() >= indexed_owner_scope.len() {
11999        return false;
12000    }
12001    let mut indexed = indexed_owner_scope.iter();
12002    lexical_scope
12003        .iter()
12004        .all(|component| indexed.any(|candidate| candidate == component))
12005}
12006
12007pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
12008    let mut current = node.parent();
12009    while let Some(parent) = current {
12010        if parent.kind() == kind {
12011            return true;
12012        }
12013        current = parent.parent();
12014    }
12015    false
12016}
12017
12018/// Whether a declaration type is initialized with a pointer cast.
12019///
12020/// This structured shape has an independent qualified occurrence in addition
12021/// to the cast descriptor below it. Other declarations must keep their normal
12022/// full-range occurrence only.
12023pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
12024    let mut current = Some(node);
12025    while let Some(candidate) = current {
12026        if candidate.kind() == "declaration" {
12027            let Some(type_node) = candidate.child_by_field_name("type") else {
12028                return false;
12029            };
12030            if !(type_node.start_byte() <= node.start_byte()
12031                && node.end_byte() <= type_node.end_byte())
12032            {
12033                return false;
12034            }
12035            let mut cursor = candidate.walk();
12036            return candidate.named_children(&mut cursor).any(|child| {
12037                child.kind() == "init_declarator"
12038                    && child
12039                        .child_by_field_name("value")
12040                        .is_some_and(|value| value.kind() == "cast_expression")
12041            });
12042        }
12043        current = candidate.parent();
12044    }
12045    false
12046}
12047
12048#[derive(Clone, Copy, PartialEq, Eq)]
12049pub(crate) enum QualifiedAliasReferenceKind {
12050    Ordinary,
12051    ConstructorWithExpressionArgument,
12052    ExhaustiveTemplate,
12053}
12054
12055/// Whether a qualified alias reference preserves the requested target.
12056///
12057/// The complete qualified spelling and its terminal identifier are both valid
12058/// occurrences when the visible alias path is structurally proven to name the
12059/// target. Template aliases use their bound arguments; ordinary aliases use
12060/// their structured primary chain.
12061pub(crate) fn qualified_alias_reference_preserves_target(
12062    node: Node<'_>,
12063    target: &CodeUnit,
12064    analyzer: &CppGraphSource<'_>,
12065    visibility: &VisibilityIndex<'_>,
12066    file: &ProjectFile,
12067    source: &str,
12068) -> Option<QualifiedAliasReferenceKind> {
12069    if !matches!(
12070        node.kind(),
12071        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12072    ) {
12073        return None;
12074    }
12075    let components = cpp_type_name_components(node, source)?;
12076    let name = components.last()?;
12077    analyzer.type_alias_provider().and_then(|provider| {
12078        visibility
12079            .visible_identifier_candidates(file, name)
12080            .find_map(|candidate| {
12081                let proof = provider.is_type_alias(candidate)
12082                    && canonical_cpp_scope_components(candidate) == components
12083                    && visibility.external_type_candidate_visible_in_context(
12084                        analyzer, file, candidate, node,
12085                    )
12086                    && match cpp_template_reference_arguments(node, source) {
12087                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
12088                            analyzer, file, candidate, &arguments, target,
12089                        ),
12090                        None => visibility.structured_alias_primary_preserves_target(
12091                            analyzer, file, candidate, target,
12092                        ),
12093                    };
12094                proof.then(|| {
12095                    if cpp_template_reference_arguments(node, source).is_some()
12096                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
12097                            analyzer, file, candidate,
12098                        )
12099                    {
12100                        QualifiedAliasReferenceKind::ExhaustiveTemplate
12101                    } else if qualified_alias_constructor_has_expression_argument(node)
12102                        || qualified_alias_local_constructor_declaration(node)
12103                    {
12104                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12105                    } else {
12106                        QualifiedAliasReferenceKind::Ordinary
12107                    }
12108                })
12109            })
12110    })
12111}
12112
12113pub(crate) fn qualified_alias_reference_requires_terminal(
12114    reference: Option<QualifiedAliasReferenceKind>,
12115) -> bool {
12116    matches!(
12117        reference,
12118        Some(
12119            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12120                | QualifiedAliasReferenceKind::ExhaustiveTemplate
12121        )
12122    )
12123}
12124
12125fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
12126    let Some(declaration) = node.parent().filter(|parent| {
12127        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12128    }) else {
12129        return false;
12130    };
12131    let mut cursor = declaration.walk();
12132    declaration.named_children(&mut cursor).any(|child| {
12133        child.kind() == "init_declarator"
12134            && child
12135                .child_by_field_name("value")
12136                .filter(|value| value.kind() == "argument_list")
12137                .is_some_and(|arguments| {
12138                    let mut cursor = arguments.walk();
12139                    arguments.named_children(&mut cursor).any(|argument| {
12140                        let is_parameter = matches!(
12141                            argument.kind(),
12142                            "parameter_declaration" | "optional_parameter_declaration"
12143                        );
12144                        if is_parameter {
12145                            argument
12146                                .child_by_field_name("type")
12147                                .is_some_and(|type_node| {
12148                                    type_node.kind() == "type_identifier"
12149                                        && argument.child_by_field_name("declarator").is_none()
12150                                })
12151                        } else {
12152                            !argument.kind().ends_with("_literal")
12153                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
12154                        }
12155                    })
12156                })
12157    })
12158}
12159
12160/// Tree-sitter represents a local C++ direct construction such as
12161/// `Alias value(argument)` as a function declarator. Restrict that recovery to
12162/// declarations inside a compound statement so namespace-scope function
12163/// declarations with the same qualified return type stay full-range only.
12164fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
12165    let Some(declaration) = node.parent().filter(|parent| {
12166        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12167    }) else {
12168        return false;
12169    };
12170    if declaration
12171        .parent()
12172        .is_none_or(|parent| parent.kind() != "compound_statement")
12173    {
12174        return false;
12175    }
12176    let mut cursor = declaration.walk();
12177    declaration
12178        .named_children(&mut cursor)
12179        .any(|child| child.kind() == "function_declarator")
12180}
12181
12182/// Return the terminal identifier represented by a callable or type callee.
12183///
12184/// Qualified, scoped, template, and field wrappers are traversed through their
12185/// grammar fields so both function calls and type constructions emit the token
12186/// that names the referenced declaration.
12187pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
12188    loop {
12189        let next = match node.kind() {
12190            "qualified_identifier"
12191            | "scoped_identifier"
12192            | "template_method"
12193            | "template_function"
12194            | "template_type" => node.child_by_field_name("name"),
12195            "field_expression" => node.child_by_field_name("field"),
12196            _ => None,
12197        };
12198        let Some(next) = next else {
12199            return node;
12200        };
12201        node = next;
12202    }
12203}
12204
12205#[derive(Clone, Copy)]
12206pub struct RecoveredRelationalTemplateMemberCall<'tree> {
12207    pub receiver: Node<'tree>,
12208    pub member: Node<'tree>,
12209    pub arity: usize,
12210}
12211
12212/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
12213/// nested relational expressions instead of a `template_method` call.
12214///
12215/// The recovery uses only grammar fields: the selected field must be the left
12216/// side of `<`, that expression must be the left side of `>`, and the right
12217/// side of `>` must be the parenthesized call arguments. Semantic callers must
12218/// additionally prove the receiver owner and the member's template status.
12219pub fn recovered_relational_template_member_call(
12220    field: Node<'_>,
12221) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
12222    if field.kind() != "field_expression" {
12223        return None;
12224    }
12225    let receiver = field
12226        .child_by_field_name("argument")
12227        .or_else(|| field.child_by_field_name("object"))?;
12228    let member = field.child_by_field_name("field")?;
12229    let less = field.parent()?;
12230    if less.kind() != "binary_expression"
12231        || less.child_by_field_name("left") != Some(field)
12232        || less
12233            .child_by_field_name("operator")
12234            .is_none_or(|operator| operator.kind() != "<")
12235        || less.child_by_field_name("right").is_none()
12236    {
12237        return None;
12238    }
12239    let greater = less.parent()?;
12240    if greater.kind() != "binary_expression"
12241        || greater.child_by_field_name("left") != Some(less)
12242        || greater
12243            .child_by_field_name("operator")
12244            .is_none_or(|operator| operator.kind() != ">")
12245    {
12246        return None;
12247    }
12248    let arguments = greater.child_by_field_name("right")?;
12249    if arguments.kind() != "parenthesized_expression" {
12250        return None;
12251    }
12252    let arity = parenthesized_call_argument_arity(arguments)?;
12253    Some(RecoveredRelationalTemplateMemberCall {
12254        receiver,
12255        member,
12256        arity,
12257    })
12258}
12259
12260fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
12261    let expression = arguments.named_child(0)?;
12262    if expression.kind() != "comma_expression" {
12263        return Some(1);
12264    }
12265    let mut arity = 0usize;
12266    let mut stack = vec![expression];
12267    while let Some(node) = stack.pop() {
12268        if node.kind() == "comma_expression" {
12269            stack.push(node.child_by_field_name("right")?);
12270            stack.push(node.child_by_field_name("left")?);
12271        } else {
12272            arity += 1;
12273        }
12274    }
12275    Some(arity)
12276}
12277
12278/// Whether `node` is part of a call's callee expression, walking only through
12279/// the grammar wrappers that can structurally contain that callee.
12280pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
12281    while let Some(parent) = node.parent() {
12282        match parent.kind() {
12283            "call_expression" => {
12284                return parent
12285                    .child_by_field_name("function")
12286                    .or_else(|| parent.named_child(0))
12287                    == Some(node);
12288            }
12289            "qualified_identifier"
12290            | "scoped_identifier"
12291            | "template_function"
12292            | "template_type"
12293            | "field_expression" => node = parent,
12294            _ => return false,
12295        }
12296    }
12297    false
12298}
12299
12300pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
12301    if is_call_callee_node(node) {
12302        function_terminal_node(node)
12303    } else {
12304        node
12305    }
12306}
12307
12308pub fn normalize_type_text(value: &str) -> String {
12309    strip_tag_type_prefix(
12310        normalize_cpp_whitespace(value)
12311            .trim_start_matches("const ")
12312            .trim_end_matches('*')
12313            .trim_end_matches('&')
12314            .trim(),
12315    )
12316    .to_string()
12317}
12318
12319fn strip_tag_type_prefix(value: &str) -> &str {
12320    let value = value.trim_start_matches("const ");
12321    value
12322        .strip_prefix("struct ")
12323        .or_else(|| value.strip_prefix("class "))
12324        .or_else(|| value.strip_prefix("enum "))
12325        .unwrap_or(value)
12326        .trim()
12327}
12328
12329pub fn normalize_reference_name(value: &str) -> Option<String> {
12330    let normalized = normalize_cpp_reference_text(value);
12331    (!normalized.is_empty()).then_some(normalized)
12332}
12333
12334pub fn normalize_cpp_reference_text(value: &str) -> String {
12335    let mut text = normalize_cpp_whitespace(value)
12336        .trim_start_matches("new ")
12337        .trim()
12338        .to_string();
12339    if let Some(index) = text.find(['(', '{']) {
12340        text.truncate(index);
12341    }
12342    if let Some(index) = text.find('<') {
12343        text.truncate(index);
12344    }
12345    let normalized = text
12346        .trim()
12347        .trim_start_matches("const ")
12348        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
12349        .trim_matches(':')
12350        .trim();
12351    strip_tag_type_prefix(normalized).to_string()
12352}
12353
12354pub fn cpp_name_for(unit: &CodeUnit) -> String {
12355    let short = unit.short_name().replace(['.', '$'], "::");
12356    if unit.package_name().is_empty() {
12357        short
12358    } else {
12359        format!("{}::{}", unit.package_name(), short)
12360    }
12361}
12362
12363/// Render an indexed C++ qualified name from its authoritative FqName
12364/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
12365/// that belong to a template argument (for example `Args...`).
12366fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
12367    let fq = unit.fq();
12368    if fq.is_empty() {
12369        return None;
12370    }
12371    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12372    Some(
12373        fq.segments()
12374            .iter()
12375            .map(|&segment| interner.resolve(segment).0)
12376            .collect::<Vec<_>>()
12377            .join("::"),
12378    )
12379}
12380
12381fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
12382    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
12383        || unit.fq().is_empty() && cpp_name_for(unit) == expected
12384}
12385
12386/// Return the indexed C++ owner scope without reparsing its rendered name.
12387///
12388/// Template spellings are opaque within an indexed `FqName` segment.  In
12389/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
12390/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
12391/// through `parse_symbol_path` would mistake those dots for component
12392/// separators.  Cache-loaded/legacy units may still have an empty structured
12393/// name, so retain the parser only as that explicit fallback.
12394pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
12395    let fq = unit.fq();
12396    if !fq.is_empty() {
12397        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12398        let scope = fq
12399            .segments()
12400            .iter()
12401            .filter_map(|&segment| {
12402                let (text, kind) = interner.resolve(segment);
12403                matches!(
12404                    kind,
12405                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
12406                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
12407                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
12408                )
12409                .then(|| text.to_string())
12410            })
12411            .collect();
12412        return scope;
12413    }
12414    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12415        brokk_bifrost_core::analyzer::Language::Cpp,
12416        &cpp_name_for(unit),
12417    )
12418}
12419
12420// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
12421// (not the substring "->"), which deliberately reduces an `operator->`-style
12422// terminal segment to an empty tail rather than keeping it intact; the shared
12423// structured splitter's cpp operator-token merge would keep `operator->`
12424// whole instead, changing this function's result — `name_matches_callable`'s
12425// `expected.starts_with("operator")` fallback exists specifically to
12426// compensate for that reduction, and a pinned regression test
12427// (`operator-> must not be reduced with terminal_name-style punctuation
12428// splitting`) asserts today's char-class behavior. Not equivalence-provable;
12429// revisit alongside that pinned test if it is ever relaxed.
12430pub fn terminal_name(value: &str) -> &str {
12431    value
12432        .rsplit("::")
12433        .next()
12434        .unwrap_or(value)
12435        .rsplit(['.', '-', '>'])
12436        .next()
12437        .unwrap_or(value)
12438        .trim()
12439}
12440
12441pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
12442    terminal_name(&normalize_cpp_reference_text(value)) == expected
12443}
12444
12445pub fn name_matches_callable(value: &str, expected: &str) -> bool {
12446    name_matches_terminal(value, expected)
12447        || expected.starts_with("operator")
12448            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
12449}
12450
12451pub fn name_mentions(value: &str, expected: &str) -> bool {
12452    normalize_cpp_reference_text(value)
12453        .split("::")
12454        .any(|part| part == expected)
12455}
12456
12457pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
12458    let cpp_name = cpp_name_for(unit);
12459    if reference.contains("::") {
12460        return reference == cpp_name;
12461    }
12462    reference == cpp_name
12463        || terminal_name(reference) == unit.identifier()
12464            && (unit.package_name().is_empty() || reference == unit.identifier())
12465}
12466
12467pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
12468    match kind {
12469        TargetKind::Type
12470        | TargetKind::Constructor
12471        | TargetKind::Method
12472        | TargetKind::MemberField => true,
12473        TargetKind::FreeFunction => unit.is_function(),
12474        TargetKind::GlobalField => unit.is_field(),
12475        TargetKind::Macro => unit.is_macro(),
12476    }
12477}
12478
12479pub fn is_type_alias(unit: &CodeUnit) -> bool {
12480    unit.kind() == CodeUnitType::Field
12481        && unit.signature().is_some_and(|signature| {
12482            signature.starts_with("typedef ") || signature.starts_with("using ")
12483        })
12484}
12485
12486fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
12487    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12488    let target_name = cpp_name_for(target);
12489    if normalized.contains("::") {
12490        return normalized == target_name;
12491    }
12492    if let Some(namespace) = alias.namespace.as_deref() {
12493        return namespace_prefixes(namespace)
12494            .into_iter()
12495            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
12496    }
12497    target.package_name().is_empty() && normalized == target.identifier()
12498}
12499
12500fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
12501    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12502    if normalized.contains("::") {
12503        return vec![normalized];
12504    }
12505    alias
12506        .namespace
12507        .as_deref()
12508        .map(namespace_prefixes)
12509        .map(|prefixes| {
12510            prefixes
12511                .into_iter()
12512                .map(|prefix| format!("{prefix}::{normalized}"))
12513                .collect()
12514        })
12515        .unwrap_or_else(|| vec![normalized])
12516}
12517
12518/// The declared return type text of a C++ function unit, with leading declaration specifiers
12519/// stripped, e.g. `T*` for `T* operator->()`.
12520pub fn cpp_function_return_type_text(
12521    analyzer: &CppGraphSource<'_>,
12522    function: &CodeUnit,
12523) -> Option<String> {
12524    let metadata = analyzer.signature_metadata(function);
12525    if !metadata.is_empty() {
12526        let first = metadata.first()?.return_type_text()?;
12527        return metadata
12528            .iter()
12529            .all(|metadata| metadata.return_type_text() == Some(first))
12530            .then(|| first.to_string());
12531    }
12532    let signature = cpp_function_signature_text(analyzer, function)?;
12533    cpp_function_return_type_text_from_signature(&signature)
12534}
12535
12536fn cpp_function_signature_text(
12537    analyzer: &CppGraphSource<'_>,
12538    function: &CodeUnit,
12539) -> Option<String> {
12540    function
12541        .signature()
12542        .filter(|signature| signature.contains(function.identifier()))
12543        .map(str::to_string)
12544        .or_else(|| analyzer.signatures(function).first().cloned())
12545        .or_else(|| analyzer.get_source(function, false))
12546}
12547
12548fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
12549    let open = signature.find('(')?;
12550    let name_at = cpp_function_name_start(signature, open)?;
12551    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
12552        return Some(return_type);
12553    }
12554    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
12555        .split_whitespace()
12556        .filter(|token| {
12557            !matches!(
12558                *token,
12559                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
12560            )
12561        })
12562        .collect::<Vec<_>>()
12563        .join(" ");
12564    let type_text = type_text.trim();
12565    (!type_text.is_empty()).then(|| type_text.to_string())
12566}
12567
12568fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
12569    let before_parameters = &signature[..open];
12570    if let Some(operator_at) = before_parameters.rfind("operator") {
12571        let boundary = operator_at == 0
12572            || before_parameters[..operator_at]
12573                .chars()
12574                .next_back()
12575                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
12576        if boundary {
12577            return Some(operator_at);
12578        }
12579    }
12580    before_parameters
12581        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
12582        .map(|index| index + 1)
12583}
12584
12585fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
12586    let open = signature_from_name.find('(')?;
12587    let mut depth = 0i32;
12588    for (offset, ch) in signature_from_name[open..].char_indices() {
12589        match ch {
12590            '(' => depth += 1,
12591            ')' => {
12592                depth -= 1;
12593                if depth == 0 {
12594                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
12595                    let arrow = rest.find("->")?;
12596                    let return_type = rest[arrow + 2..].trim_start();
12597                    let return_type = return_type
12598                        .split(['{', ';'])
12599                        .next()
12600                        .unwrap_or(return_type)
12601                        .trim();
12602                    return (!return_type.is_empty()).then(|| return_type.to_string());
12603                }
12604            }
12605            _ => {}
12606        }
12607    }
12608    None
12609}
12610
12611/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
12612/// Returns the input unchanged when there is no such clause.
12613fn cpp_strip_leading_template_clause(text: &str) -> &str {
12614    let trimmed = text.trim_start();
12615    let Some(rest) = trimmed.strip_prefix("template") else {
12616        return text;
12617    };
12618    let rest = rest.trim_start();
12619    if !rest.starts_with('<') {
12620        return text;
12621    }
12622    let mut depth = 0i32;
12623    for (offset, ch) in rest.char_indices() {
12624        match ch {
12625            '<' => depth += 1,
12626            '>' => {
12627                depth -= 1;
12628                if depth == 0 {
12629                    return rest[offset + ch.len_utf8()..].trim_start();
12630                }
12631            }
12632            _ => {}
12633        }
12634    }
12635    text
12636}
12637
12638pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
12639    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
12640    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
12641    // the same string `default_parent_fq_name`/`fq().parent()` would render:
12642    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
12643    // `::`) between a trailing `Package` segment and a following `Type`
12644    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
12645    // popping the unit's own `fq()` segment would NOT reproduce this
12646    // fully-`::`-joined string. Left as a split on the locally-built
12647    // all-colon string rather than the unit's structured name.
12648    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
12649        namespace
12650            .strip_prefix("anonymous_namespace::")
12651            .unwrap_or(namespace)
12652            .to_string()
12653    })
12654}
12655
12656fn namespace_prefixes(namespace: &str) -> Vec<String> {
12657    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
12658    // non-`::` separator already converted to `::`, so re-tokenizing it with
12659    // the shared structured splitter and progressively popping the last
12660    // component reproduces the `rsplit_once("::")` outward walk exactly (same
12661    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
12662    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12663        brokk_bifrost_core::analyzer::Language::Cpp,
12664        namespace,
12665    );
12666    let mut prefixes = Vec::new();
12667    while !parts.is_empty() {
12668        prefixes.push(parts.join("::"));
12669        parts.pop();
12670    }
12671    prefixes
12672}
12673
12674fn nearest_namespace_candidates(
12675    candidates: Vec<CodeUnit>,
12676    normalized: &str,
12677    lexical_namespace: Option<&str>,
12678) -> Vec<CodeUnit> {
12679    if normalized.contains("::") {
12680        return candidates;
12681    }
12682    if let Some(namespace) = lexical_namespace {
12683        for prefix in namespace_prefixes(namespace) {
12684            let scoped = candidates
12685                .iter()
12686                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
12687                .cloned()
12688                .collect::<Vec<_>>();
12689            if !scoped.is_empty() {
12690                return scoped;
12691            }
12692        }
12693    }
12694    candidates
12695        .into_iter()
12696        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
12697        .collect()
12698}
12699
12700pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
12701    let mut namespaces = Vec::new();
12702    let mut current = node.parent();
12703    while let Some(parent) = current {
12704        if parent.kind() == "namespace_definition"
12705            && let Some(name) = parent.child_by_field_name("name")
12706        {
12707            let namespace = normalize_cpp_reference_text(node_text(name, source));
12708            if !namespace.is_empty() {
12709                namespaces.push(namespace);
12710            }
12711        }
12712        current = parent.parent();
12713    }
12714    if namespaces.is_empty() {
12715        None
12716    } else {
12717        namespaces.reverse();
12718        Some(namespaces.join("::"))
12719    }
12720}
12721
12722/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
12723/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
12724/// globals rather than members.
12725pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12726    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
12727}
12728
12729fn type_owner_resolution(
12730    analyzer: &CppGraphSource<'_>,
12731    code_unit: &CodeUnit,
12732) -> Option<ResolvedTypeOwner> {
12733    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
12734}
12735
12736fn target_type_owner_resolution(
12737    analyzer: &CppGraphSource<'_>,
12738    code_unit: &CodeUnit,
12739) -> Option<ResolvedTypeOwner> {
12740    match type_owner_resolution(analyzer, code_unit) {
12741        Some(owner) if !owner.is_forward_declaration => Some(owner),
12742        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
12743    }
12744}
12745
12746/// Recover method identity for an indexed out-of-line definition when the
12747/// analyzer has retained only its unique include-visible class forward
12748/// declaration. This is deliberately target-only: canonical declaration
12749/// resolution must continue to prefer the callable definition rather than
12750/// replacing it with the forward owner.
12751fn target_forward_owner_resolution(
12752    analyzer: &CppGraphSource<'_>,
12753    code_unit: &CodeUnit,
12754) -> Option<ResolvedTypeOwner> {
12755    if !code_unit.is_function() {
12756        return None;
12757    }
12758    let owner_fqn = brokk_bifrost_core::analyzer::default_parent_fq_name(code_unit)?;
12759    let cpp = analyzer.cpp?;
12760    let mut visible_files = HashSet::default();
12761    collect_include_closure(
12762        analyzer,
12763        cpp.include_target_index(),
12764        code_unit.source(),
12765        &mut visible_files,
12766        None,
12767    );
12768    let mut forward = None;
12769    for candidate in analyzer
12770        .global_usage_definition_index()
12771        .fqn(&owner_fqn)
12772        .into_iter()
12773        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
12774    {
12775        match cpp_class_declaration_strength(analyzer, candidate) {
12776            CppClassDeclarationStrength::Forward if forward.is_none() => {
12777                forward = Some(candidate.clone());
12778            }
12779            CppClassDeclarationStrength::Forward
12780            | CppClassDeclarationStrength::Full
12781            | CppClassDeclarationStrength::Unknown => return None,
12782        }
12783    }
12784    forward.map(|unit| ResolvedTypeOwner {
12785        unit,
12786        is_forward_declaration: true,
12787    })
12788}
12789
12790pub fn precise_parent_of(
12791    analyzer: &CppGraphSource<'_>,
12792    visibility: &VisibilityIndex<'_>,
12793    code_unit: &CodeUnit,
12794) -> Option<CodeUnit> {
12795    visibility.cached_precise_parent_of(analyzer, code_unit)
12796}
12797
12798fn precise_parent_resolution(
12799    analyzer: &CppGraphSource<'_>,
12800    code_unit: &CodeUnit,
12801) -> Option<ResolvedTypeOwner> {
12802    #[cfg(any(test, feature = "test-support"))]
12803    if let Some(cpp) = analyzer.cpp {
12804        cpp.record_cpp_parent_resolution_for_test();
12805    }
12806    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
12807        return Some(ResolvedTypeOwner {
12808            unit,
12809            is_forward_declaration: false,
12810        });
12811    }
12812    let fallback = analyzer.parent_of(code_unit);
12813    // fqname-M4: `owner_name` is used both bare (passed standalone to the
12814    // owner-resolution calls below) and manually recombined with
12815    // `package_name()` a few lines down, so this needs the package-less
12816    // `short_name` owner specifically; `default_parent_fq_name`/`fq.parent()`
12817    // would render the package-qualified owner instead, changing both uses.
12818    let Some(owner_name) = code_unit
12819        .short_name()
12820        .rsplit_once('.')
12821        .map(|(owner, _)| owner)
12822    else {
12823        return fallback.map(|unit| ResolvedTypeOwner {
12824            unit,
12825            is_forward_declaration: false,
12826        });
12827    };
12828    let owner_fqn = if code_unit.package_name().is_empty() {
12829        owner_name.to_string()
12830    } else {
12831        format!("{}.{}", code_unit.package_name(), owner_name)
12832    };
12833    match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12834        DirectOwnerResolution::UniqueFull(owner) => {
12835            return Some(ResolvedTypeOwner {
12836                unit: owner,
12837                is_forward_declaration: false,
12838            });
12839        }
12840        DirectOwnerResolution::Ambiguous => return None,
12841        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
12842    }
12843    match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12844        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
12845            unit: owner,
12846            is_forward_declaration: false,
12847        }),
12848        DirectOwnerResolution::Ambiguous => None,
12849        DirectOwnerResolution::ForwardsOnly(forwards) => {
12850            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12851                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12852                    unit: owner,
12853                    is_forward_declaration: false,
12854                }),
12855                FullOwnerResolution::None => {
12856                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
12857                        unit,
12858                        is_forward_declaration: true,
12859                    })
12860                }
12861                FullOwnerResolution::Ambiguous => None,
12862            }
12863        }
12864        DirectOwnerResolution::None => {
12865            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12866                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12867                    unit: owner,
12868                    is_forward_declaration: false,
12869                }),
12870                FullOwnerResolution::Ambiguous => None,
12871                FullOwnerResolution::None => fallback
12872                    .filter(|parent| {
12873                        parent.source() == code_unit.source()
12874                            && parent.short_name() == owner_name
12875                            && parent.package_name() == code_unit.package_name()
12876                            && (!parent.is_class()
12877                                || cpp_class_declaration_strength(analyzer, parent)
12878                                    == CppClassDeclarationStrength::Full)
12879                    })
12880                    .map(|unit| ResolvedTypeOwner {
12881                        unit,
12882                        is_forward_declaration: false,
12883                    }),
12884            }
12885        }
12886    }
12887}
12888
12889fn exact_structural_type_parent(
12890    analyzer: &CppGraphSource<'_>,
12891    code_unit: &CodeUnit,
12892) -> Option<CodeUnit> {
12893    if !code_unit.is_function() && !code_unit.is_field() {
12894        return None;
12895    }
12896    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
12897    let cpp = analyzer.cpp?;
12898    let parent = cpp.structural_parent_of(code_unit)?;
12899    (!parent.is_module()
12900        && parent.source() == code_unit.source()
12901        && parent.package_name() == code_unit.package_name()
12902        && parent.short_name() == encoded_owner)
12903        .then_some(parent)
12904}
12905
12906fn same_source_owner(
12907    analyzer: &CppGraphSource<'_>,
12908    code_unit: &CodeUnit,
12909    owner_fqn: &str,
12910    owner_name: &str,
12911) -> DirectOwnerResolution {
12912    let candidates = analyzer
12913        .global_usage_definition_index()
12914        .fqn(owner_fqn)
12915        .into_iter()
12916        .filter(|candidate| {
12917            candidate.is_class()
12918                && candidate.source() == code_unit.source()
12919                && candidate.short_name() == owner_name
12920                && candidate.package_name() == code_unit.package_name()
12921        })
12922        .collect::<Vec<_>>();
12923    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12924    classify_direct_owner_candidates(analyzer, candidates.into_iter())
12925}
12926
12927fn visible_full_cpp_owner(
12928    analyzer: &CppGraphSource<'_>,
12929    code_unit: &CodeUnit,
12930    owner_fqn: &str,
12931    owner_name: &str,
12932) -> FullOwnerResolution {
12933    let Some(cpp) = analyzer.cpp else {
12934        return FullOwnerResolution::None;
12935    };
12936    let mut visible_files = HashSet::default();
12937    collect_include_closure(
12938        analyzer,
12939        cpp.include_target_index(),
12940        code_unit.source(),
12941        &mut visible_files,
12942        None,
12943    );
12944    let candidates = analyzer
12945        .global_usage_definition_index()
12946        .fqn(owner_fqn)
12947        .into_iter()
12948        .filter(|candidate| {
12949            candidate.is_class()
12950                && candidate.short_name() == owner_name
12951                && candidate.package_name() == code_unit.package_name()
12952                && visible_files.contains(candidate.source())
12953        })
12954        .collect::<Vec<_>>();
12955    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12956    let mut full_definition = None;
12957    for candidate in candidates {
12958        match cpp_class_declaration_strength(analyzer, candidate) {
12959            CppClassDeclarationStrength::Full if full_definition.is_some() => {
12960                return FullOwnerResolution::Ambiguous;
12961            }
12962            CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
12963            CppClassDeclarationStrength::Forward => {}
12964            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
12965        }
12966    }
12967    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
12968}
12969
12970pub enum DirectOwnerResolution {
12971    None,
12972    ForwardsOnly(Vec<CodeUnit>),
12973    UniqueFull(CodeUnit),
12974    Ambiguous,
12975}
12976
12977enum FullOwnerResolution {
12978    None,
12979    Unique(CodeUnit),
12980    Ambiguous,
12981}
12982
12983#[derive(Clone, Copy, PartialEq, Eq)]
12984pub enum CppClassDeclarationStrength {
12985    Full,
12986    Forward,
12987    Unknown,
12988}
12989
12990fn directly_included_owner(
12991    analyzer: &CppGraphSource<'_>,
12992    code_unit: &CodeUnit,
12993    owner_fqn: &str,
12994    owner_name: &str,
12995) -> DirectOwnerResolution {
12996    let Some(cpp) = analyzer.cpp else {
12997        return DirectOwnerResolution::None;
12998    };
12999    let imports = analyzer.import_statements(code_unit.source());
13000    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
13001        .into_iter()
13002        .flat_map(|include| {
13003            resolve_include_targets_with_index(
13004                code_unit.source(),
13005                &include,
13006                cpp.include_target_index(),
13007            )
13008        })
13009        .collect();
13010    let candidates = analyzer
13011        .global_usage_definition_index()
13012        .fqn(owner_fqn)
13013        .into_iter()
13014        .filter(|candidate| {
13015            candidate.is_class()
13016                && candidate.short_name() == owner_name
13017                && candidate.package_name() == code_unit.package_name()
13018                && direct_includes.contains(candidate.source())
13019        })
13020        .collect::<Vec<_>>();
13021    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
13022    classify_direct_owner_candidates(analyzer, candidates.into_iter())
13023}
13024
13025fn prefer_member_declaring_owners<'a>(
13026    analyzer: &CppGraphSource<'_>,
13027    member: &CodeUnit,
13028    candidates: Vec<&'a CodeUnit>,
13029) -> Vec<&'a CodeUnit> {
13030    let matching = candidates
13031        .iter()
13032        .copied()
13033        .filter(|owner| owner_declares_member(analyzer, owner, member))
13034        .collect::<Vec<_>>();
13035    if matching.is_empty() {
13036        candidates
13037    } else {
13038        matching
13039    }
13040}
13041
13042fn owner_declares_member(
13043    analyzer: &CppGraphSource<'_>,
13044    owner: &CodeUnit,
13045    member: &CodeUnit,
13046) -> bool {
13047    analyzer.direct_children(owner).into_iter().any(|child| {
13048        child.kind() == member.kind()
13049            && child.identifier() == member.identifier()
13050            && child.signature() == member.signature()
13051    })
13052}
13053
13054fn classify_direct_owner_candidates<'a>(
13055    analyzer: &CppGraphSource<'_>,
13056    candidates: impl Iterator<Item = &'a CodeUnit>,
13057) -> DirectOwnerResolution {
13058    collapse_owner_candidates(candidates.map(|candidate| {
13059        (
13060            candidate.clone(),
13061            cpp_class_declaration_strength(analyzer, candidate),
13062        )
13063    }))
13064}
13065
13066pub fn collapse_owner_candidates(
13067    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
13068) -> DirectOwnerResolution {
13069    let mut full_definition = None;
13070    let mut forwards = Vec::new();
13071    for (candidate, strength) in candidates {
13072        match strength {
13073            CppClassDeclarationStrength::Full if full_definition.is_some() => {
13074                return DirectOwnerResolution::Ambiguous;
13075            }
13076            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
13077            CppClassDeclarationStrength::Forward => forwards.push(candidate),
13078            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
13079        }
13080    }
13081    if let Some(owner) = full_definition {
13082        DirectOwnerResolution::UniqueFull(owner)
13083    } else if !forwards.is_empty() {
13084        DirectOwnerResolution::ForwardsOnly(forwards)
13085    } else {
13086        DirectOwnerResolution::None
13087    }
13088}
13089
13090#[cfg(any(test, feature = "test-support"))]
13091pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13092    unique_logical_forward_owner(forwards)
13093}
13094
13095fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13096    let first = forwards.pop()?;
13097    forwards
13098        .iter()
13099        .all(|forward| same_logical_symbol(forward, &first))
13100        .then_some(first)
13101}
13102
13103pub fn cpp_class_declaration_strength(
13104    analyzer: &CppGraphSource<'_>,
13105    candidate: &CodeUnit,
13106) -> CppClassDeclarationStrength {
13107    if let Some(prepared) = analyzer
13108        .cpp
13109        .and_then(|cpp| cpp.prepared_syntax(analyzer.token, candidate.source()))
13110    {
13111        return cpp_class_declaration_strength_in_tree(
13112            analyzer,
13113            candidate,
13114            prepared.source(),
13115            prepared.tree().root_node(),
13116        );
13117    }
13118    let Some(source) = analyzer.indexed_source(candidate.source()) else {
13119        return CppClassDeclarationStrength::Unknown;
13120    };
13121    #[cfg(any(test, feature = "test-support"))]
13122    if let Some(cpp) = analyzer.cpp {
13123        cpp.record_cpp_class_strength_parse_for_test();
13124    }
13125    let mut parser = Parser::new();
13126    if parser
13127        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13128        .is_err()
13129    {
13130        return CppClassDeclarationStrength::Unknown;
13131    }
13132    let Some(tree) = parser.parse(&source, None) else {
13133        return CppClassDeclarationStrength::Unknown;
13134    };
13135    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
13136}
13137
13138fn cpp_class_declaration_strength_in_tree(
13139    analyzer: &CppGraphSource<'_>,
13140    candidate: &CodeUnit,
13141    source: &str,
13142    root: Node<'_>,
13143) -> CppClassDeclarationStrength {
13144    let ranges = analyzer.ranges(candidate);
13145    let mut saw_forward = false;
13146    for range in ranges {
13147        let mut stack = vec![root];
13148        while let Some(node) = stack.pop() {
13149            if node.start_byte() == range.start_byte
13150                && recovered_fragmented_plain_class_has_body(
13151                    node,
13152                    source,
13153                    candidate.identifier(),
13154                    &range,
13155                )
13156            {
13157                return CppClassDeclarationStrength::Full;
13158            }
13159            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
13160                continue;
13161            }
13162            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
13163                if matches!(
13164                    node.kind(),
13165                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13166                ) {
13167                    if cpp_class_node_has_body(node) {
13168                        return CppClassDeclarationStrength::Full;
13169                    }
13170                    saw_forward = true;
13171                } else if let Some(has_body) =
13172                    recovered_exported_class_has_body(node, source, candidate.identifier())
13173                {
13174                    if has_body {
13175                        return CppClassDeclarationStrength::Full;
13176                    }
13177                    saw_forward = true;
13178                }
13179            }
13180            let mut cursor = node.walk();
13181            stack.extend(node.named_children(&mut cursor));
13182        }
13183    }
13184    if saw_forward {
13185        CppClassDeclarationStrength::Forward
13186    } else {
13187        CppClassDeclarationStrength::Unknown
13188    }
13189}
13190
13191fn cpp_class_node_has_body(node: Node<'_>) -> bool {
13192    node.child_by_field_name("body").is_some() || {
13193        let mut cursor = node.walk();
13194        node.named_children(&mut cursor).any(|child| {
13195            matches!(
13196                child.kind(),
13197                "declaration_list" | "field_declaration_list" | "enumerator_list"
13198            )
13199        })
13200    }
13201}
13202
13203pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
13204    // fqname-M4: `owner_name` is used both bare and manually recombined with
13205    // `package_name()` below (same package-less short_name owner shape as
13206    // `precise_parent_resolution` above); `default_parent_fq_name` would
13207    // render the package-qualified owner instead, changing both uses.
13208    let owner_name = code_unit
13209        .short_name()
13210        .rsplit_once('.')
13211        .map(|(owner, _)| owner)?;
13212    let owner_fqn = if code_unit.package_name().is_empty() {
13213        owner_name.to_string()
13214    } else {
13215        format!("{}.{}", code_unit.package_name(), owner_name)
13216    };
13217    ctx.analyzer
13218        .global_usage_definition_index()
13219        .fqn(&owner_fqn)
13220        .into_iter()
13221        .find(|candidate| {
13222            candidate.is_class()
13223                && ctx.visibility.is_visible(ctx.file, candidate)
13224                && candidate.short_name() == owner_name
13225                && candidate.package_name() == code_unit.package_name()
13226        })
13227        .cloned()
13228}
13229
13230pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13231    left.kind() == right.kind()
13232        && left.fq_name() == right.fq_name()
13233        && left.signature() == right.signature()
13234        && left.source() == right.source()
13235}
13236
13237pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13238    same_symbol(left, right) || same_logical_symbol(left, right)
13239}
13240
13241pub fn same_visible_global_field_symbol(
13242    analyzer: &CppGraphSource<'_>,
13243    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
13244    left: &CodeUnit,
13245    right: &CodeUnit,
13246) -> bool {
13247    if same_symbol(left, right) {
13248        return true;
13249    }
13250    if !same_logical_symbol(left, right) {
13251        return false;
13252    }
13253    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
13254        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
13255    {
13256        left.source() == right.source()
13257    } else {
13258        true
13259    }
13260}
13261
13262fn cpp_global_field_has_internal_linkage_cached(
13263    analyzer: &CppGraphSource<'_>,
13264    cache: &mut HashMap<CodeUnit, bool>,
13265    candidate: &CodeUnit,
13266) -> bool {
13267    if let Some(internal) = cache.get(candidate) {
13268        return *internal;
13269    }
13270    #[cfg(any(test, feature = "test-support"))]
13271    note_cpp_global_field_internal_linkage_classification_for_test();
13272    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
13273    cache.insert(candidate.clone(), internal);
13274    internal
13275}
13276
13277#[cfg(any(test, feature = "test-support"))]
13278thread_local! {
13279    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13280}
13281
13282#[cfg(any(test, feature = "test-support"))]
13283fn note_cpp_global_field_internal_linkage_classification_for_test() {
13284    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13285        count.set(count.get() + 1);
13286    });
13287}
13288
13289#[cfg(any(test, feature = "test-support"))]
13290pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
13291    body: impl FnOnce() -> T,
13292) -> (T, usize) {
13293    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13294        count.set(0);
13295        let result = body();
13296        let observed = count.get();
13297        count.set(0);
13298        (result, observed)
13299    })
13300}
13301
13302pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13303    left.kind() == right.kind()
13304        && left.fq_name() == right.fq_name()
13305        && left.signature() == right.signature()
13306}
13307
13308pub fn cpp_global_field_has_internal_linkage(
13309    analyzer: &CppGraphSource<'_>,
13310    candidate: &CodeUnit,
13311) -> bool {
13312    if !candidate.is_field() || candidate.short_name().contains('.') {
13313        return false;
13314    }
13315    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
13316        return false;
13317    };
13318    match local_linkage {
13319        CppFieldLinkage::Internal => true,
13320        CppFieldLinkage::External => false,
13321        CppFieldLinkage::InternalUnlessExternalPeer => {
13322            !cpp_global_field_linkage_peers(analyzer, candidate)
13323                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
13324                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
13325        }
13326    }
13327}
13328
13329fn cpp_global_field_linkage_peers<'a>(
13330    analyzer: &CppGraphSource<'a>,
13331    candidate: &'a CodeUnit,
13332) -> impl Iterator<Item = &'a CodeUnit> + 'a {
13333    // These peers are returned to the caller, so they must borrow the analyzer
13334    // for `'a` rather than a handle that dies with this call. `fqn` reads the
13335    // shards directly for exactly that reason.
13336    let fq_name = candidate.fq_name();
13337    analyzer
13338        .global_usage_definition_index()
13339        .fqn(&fq_name)
13340        .into_iter()
13341        .filter(move |peer| {
13342            if *peer == candidate {
13343                return false;
13344            }
13345            #[cfg(any(test, feature = "test-support"))]
13346            note_cpp_global_field_linkage_peer_inspection_for_test();
13347            same_logical_symbol(peer, candidate)
13348        })
13349}
13350
13351#[cfg(any(test, feature = "test-support"))]
13352thread_local! {
13353    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13354}
13355
13356#[cfg(any(test, feature = "test-support"))]
13357fn note_cpp_global_field_linkage_peer_inspection_for_test() {
13358    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13359        count.set(count.get() + 1);
13360    });
13361}
13362
13363#[cfg(any(test, feature = "test-support"))]
13364pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
13365    body: impl FnOnce() -> T,
13366) -> (T, usize) {
13367    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13368        count.set(0);
13369        let result = body();
13370        let observed = count.get();
13371        count.set(0);
13372        (result, observed)
13373    })
13374}
13375
13376fn cpp_global_field_declaration_linkage(
13377    analyzer: &CppGraphSource<'_>,
13378    candidate: &CodeUnit,
13379) -> Option<CppFieldLinkage> {
13380    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
13381        return Some(linkage);
13382    }
13383    let cpp = analyzer.cpp?;
13384    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
13385        return cpp_global_field_declaration_linkage_in_tree(
13386            analyzer,
13387            candidate,
13388            prepared.source(),
13389            prepared.tree().root_node(),
13390        );
13391    }
13392    let source = analyzer.indexed_source(candidate.source())?;
13393    let mut parser = Parser::new();
13394    if parser
13395        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13396        .is_err()
13397    {
13398        return None;
13399    }
13400    let tree = parser.parse(&source, None)?;
13401    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
13402}
13403
13404fn cpp_global_field_declaration_linkage_in_tree(
13405    analyzer: &CppGraphSource<'_>,
13406    candidate: &CodeUnit,
13407    source: &str,
13408    root: Node<'_>,
13409) -> Option<CppFieldLinkage> {
13410    analyzer.ranges(candidate).iter().find_map(|range| {
13411        node_for_exact_range(root, range)
13412            .and_then(enclosing_cpp_field_declaration)
13413            .map(|declaration| {
13414                // One question about one declaration; see `ParentIndex::unindexed`.
13415                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
13416            })
13417    })
13418}
13419
13420fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
13421    loop {
13422        if matches!(node.kind(), "declaration" | "field_declaration") {
13423            return Some(node);
13424        }
13425        node = node.parent()?;
13426    }
13427}
13428
13429#[cfg(test)]
13430mod tests {
13431    use super::*;
13432
13433    #[test]
13434    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
13435        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
13436        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
13437        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
13438        assert!(indexed_namespace_path_is_recoverable(
13439            &["cache".to_string()],
13440            &indexed,
13441            1,
13442        ));
13443    }
13444
13445    #[test]
13446    fn sort_lookup_units_totally_orders_every_identity_field() {
13447        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
13448        let base = CodeUnit::with_signature(
13449            file.clone(),
13450            CodeUnitType::Function,
13451            "scope",
13452            "value",
13453            Some("()".to_string()),
13454            false,
13455        );
13456        let different_kind = CodeUnit::with_signature(
13457            file.clone(),
13458            CodeUnitType::Field,
13459            "scope",
13460            "value",
13461            Some("()".to_string()),
13462            false,
13463        );
13464        let synthetic = base.with_synthetic(true);
13465
13466        let interner = segment_interner();
13467        let mut member_fq = FqName::new();
13468        member_fq.push(interner.intern("scope", SegmentKind::Package));
13469        member_fq.push(interner.intern("value", SegmentKind::Member));
13470        let different_package_boundary = CodeUnit::from_fq(
13471            file.clone(),
13472            CodeUnitType::Function,
13473            member_fq,
13474            0,
13475            Some("()".to_string()),
13476            false,
13477        );
13478
13479        let mut unknown_fq = FqName::new();
13480        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
13481        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
13482        let different_segment_kind = CodeUnit::from_fq(
13483            file,
13484            CodeUnitType::Function,
13485            unknown_fq,
13486            1,
13487            Some("()".to_string()),
13488            false,
13489        );
13490
13491        let input = vec![
13492            base,
13493            different_kind,
13494            synthetic,
13495            different_package_boundary,
13496            different_segment_kind,
13497        ];
13498        let mut expected = input.clone();
13499        sort_lookup_units(&mut expected);
13500        assert!(expected.windows(2).all(|pair| {
13501            let mut ordered = pair.to_vec();
13502            sort_lookup_units(&mut ordered);
13503            ordered == pair && pair[0] != pair[1]
13504        }));
13505
13506        let mut reversed = input.clone();
13507        reversed.reverse();
13508        sort_lookup_units(&mut reversed);
13509        assert_eq!(reversed, expected);
13510
13511        let mut rotated = input;
13512        rotated.rotate_left(2);
13513        sort_lookup_units(&mut rotated);
13514        assert_eq!(rotated, expected);
13515    }
13516
13517    #[test]
13518    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
13519        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";
13520        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
13521        let parse = |source: &str| {
13522            let mut parser = Parser::new();
13523            parser
13524                .set_language(&tree_sitter_cpp::LANGUAGE.into())
13525                .expect("C++ grammar");
13526            parser.parse(source, None).expect("fixture tree")
13527        };
13528
13529        let tree = parse(damaged);
13530        let root = tree.root_node();
13531        let target = damaged.find("target").expect("target byte");
13532        let declaration = root
13533            .descendant_for_byte_range(target, target + "target".len())
13534            .and_then(|mut node| {
13535                loop {
13536                    if node.kind() == "declaration" {
13537                        break Some(node);
13538                    }
13539                    node = node.parent()?;
13540                }
13541            })
13542            .expect("declaration after the displaced terminator");
13543        let conditional = declaration
13544            .parent()
13545            .filter(|node| node.kind() == "preproc_ifdef")
13546            .expect("damaged inner conditional");
13547        let outer = conditional
13548            .parent()
13549            .filter(|node| node.kind() == "preproc_ifdef")
13550            .expect("ordinary outer include guard");
13551        let terminator = cpp_displaced_preprocessor_terminator(conditional)
13552            .expect("structured displaced #endif");
13553        assert_eq!(node_text(terminator, damaged), "#endif");
13554        assert!(terminator.end_byte() <= declaration.start_byte());
13555        assert!(!preprocessor_conditional_contains_descendant(
13556            conditional,
13557            declaration
13558        ));
13559        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
13560        assert!(preprocessor_conditional_contains_descendant(
13561            outer,
13562            declaration
13563        ));
13564
13565        let tree = parse(guarded);
13566        let conditional = tree
13567            .root_node()
13568            .named_child(0)
13569            .filter(|node| node.kind() == "preproc_ifdef")
13570            .expect("ordinary conditional");
13571        let declaration = conditional
13572            .named_children(&mut conditional.walk())
13573            .find(|node| node.kind() == "declaration")
13574            .expect("guarded declaration");
13575        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13576        assert!(preprocessor_conditional_contains_descendant(
13577            conditional,
13578            declaration
13579        ));
13580
13581        let damaged_alternative = format!(
13582            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
13583            "UNUSED(value)\n".repeat(64)
13584        );
13585        let tree = parse(&damaged_alternative);
13586        let conditional = tree
13587            .root_node()
13588            .named_child(0)
13589            .filter(|node| node.kind() == "preproc_ifdef")
13590            .expect("outer conditional with an alternative");
13591        assert!(conditional.has_error());
13592        assert!(conditional.child_by_field_name("alternative").is_some());
13593        assert!(
13594            conditional
13595                .child(conditional.child_count() - 1)
13596                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
13597        );
13598        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13599
13600        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";
13601        let tree = parse(split_declaration);
13602        let root = tree.root_node();
13603        let conditional = root
13604            .named_children(&mut root.walk())
13605            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
13606            .expect("split declaration conditional");
13607        let target = split_declaration
13608            .find("static int target")
13609            .expect("target byte");
13610        let boundary =
13611            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
13612        assert!(boundary.end_byte <= target, "{boundary:?}");
13613        assert_eq!(boundary.end_line, 9, "{boundary:?}");
13614        let target_node = root
13615            .descendant_for_byte_range(target, target + "static".len())
13616            .expect("target node");
13617        assert!(!preprocessor_conditional_contains_descendant(
13618            conditional,
13619            target_node
13620        ));
13621    }
13622
13623    #[test]
13624    fn fragmented_reference_guard_is_recovered() {
13625        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";
13626        let mut parser = Parser::new();
13627        parser
13628            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13629            .expect("C++ grammar");
13630        let tree = parser.parse(source, None).expect("fixture tree");
13631        let start = source.rfind("helper").expect("reference byte");
13632        let node = tree
13633            .root_node()
13634            .descendant_for_byte_range(start, start + "helper".len())
13635            .expect("reference node");
13636        let mut expected = HashSet::default();
13637        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
13638            vec![
13639                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
13640                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
13641            ],
13642        )));
13643        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
13644    }
13645
13646    #[test]
13647    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
13648        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";
13649        let mut parser = Parser::new();
13650        parser
13651            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13652            .expect("C++ grammar");
13653        let tree = parser.parse(source, None).expect("fixture tree");
13654        let root = tree.root_node();
13655        let definition_start = source.find("target(void)").expect("definition");
13656        let reference_start = source.rfind("target()").expect("reference");
13657        let definition = root
13658            .descendant_for_byte_range(definition_start, definition_start + "target".len())
13659            .expect("definition node");
13660        let reference = root
13661            .descendant_for_byte_range(reference_start, reference_start + "target".len())
13662            .expect("reference node");
13663        let required =
13664            preprocessor_guard_environment(definition, source).expect("definition guard");
13665        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
13666        assert!(guard_requirements_hold_at_reference(
13667            &required,
13668            Some(&active)
13669        ));
13670    }
13671
13672    #[test]
13673    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
13674        let source = "g_autoptr(FuChunkArray) self = make_array();";
13675        let mut parser = Parser::new();
13676        parser
13677            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13678            .expect("C++ grammar");
13679        let tree = parser.parse(source, None).expect("fixture tree");
13680        let statement = tree.root_node().named_child(0).expect("statement");
13681        let binding =
13682            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
13683        assert_eq!(binding.name, "self");
13684        assert_eq!(binding.type_name, "FuChunkArray");
13685        assert_eq!(binding.pointer_depth, 1);
13686
13687        let near_miss = "holder(FuChunkArray) self = make_array();";
13688        let tree = parser.parse(near_miss, None).expect("near-miss tree");
13689        let statement = tree.root_node().named_child(0).expect("statement");
13690        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
13691    }
13692
13693    #[test]
13694    fn boolean_guard_normalization_proves_equivalence_and_implication() {
13695        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
13696        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
13697        let negated_windows_branch =
13698            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
13699        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
13700        assert_eq!(negated_windows_branch, portable);
13701
13702        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
13703        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
13704        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
13705        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
13706        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
13707        assert!(fallback_branch.implies(&fallback_declaration));
13708        assert!(!fallback_declaration.implies(&fallback_branch));
13709    }
13710
13711    #[test]
13712    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
13713        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";
13714        let mut parser = Parser::new();
13715        parser
13716            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13717            .expect("C++ grammar");
13718        let tree = parser.parse(source, None).expect("fixture tree");
13719        let root = tree.root_node();
13720        let call = |marker: &str| {
13721            let start = source.find(marker).expect("call marker");
13722            let mut node = root
13723                .descendant_for_byte_range(start, start + "helper".len())
13724                .expect("call name node");
13725            loop {
13726                if node.kind() == "call_expression" {
13727                    break node;
13728                }
13729                node = node.parent().expect("call expression ancestor");
13730            }
13731        };
13732        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
13733        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
13734        let keyword_call = call("helper(NULL, template); /* bound */");
13735        let keyword_arguments = keyword_call
13736            .child_by_field_name("arguments")
13737            .expect("keyword argument list");
13738        assert_eq!(
13739            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
13740            1
13741        );
13742        assert_eq!(
13743            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
13744            0
13745        );
13746
13747        let unbound_call = call("helper(NULL, template); /* unbound */");
13748        let unbound_arguments = unbound_call
13749            .child_by_field_name("arguments")
13750            .expect("unbound argument list");
13751        assert_eq!(
13752            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
13753            0
13754        );
13755    }
13756
13757    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
13758        let mut parser = Parser::new();
13759        parser
13760            .set_language(&tree_sitter_cpp::LANGUAGE.into())
13761            .expect("C++ grammar");
13762        let tree = parser.parse(source, None).expect("C++ fixture tree");
13763        let mut stack = vec![tree.root_node()];
13764        while let Some(node) = stack.pop() {
13765            if node.kind() == "enum_specifier" {
13766                return flattened_macro_namespace_components(node, source);
13767            }
13768            let mut cursor = node.walk();
13769            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13770            stack.extend(children.into_iter().rev());
13771        }
13772        None
13773    }
13774
13775    #[test]
13776    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
13777        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13778namespace detail
13779{
13780enum class value_t { null };
13781}
13782NLOHMANN_JSON_NAMESPACE_END
13783NLOHMANN_JSON_NAMESPACE_BEGIN
13784namespace next
13785{
13786struct next_type {};
13787}
13788NLOHMANN_JSON_NAMESPACE_END
13789"#;
13790        assert_eq!(
13791            first_enum_flattened_namespace(complete),
13792            Some(vec!["detail".to_string()])
13793        );
13794
13795        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
13796        assert_eq!(
13797            first_enum_flattened_namespace(&stale_end),
13798            Some(vec!["detail".to_string()]),
13799            "a stale end marker before the begin marker must not replace the intended namespace"
13800        );
13801
13802        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13803namespace detail
13804{
13805enum class value_t { null };
13806}
13807struct next_type {};
13808"#;
13809        assert_eq!(first_enum_flattened_namespace(incomplete), None);
13810    }
13811}